Skip to content

feat: add collaborative review sessions#316

Open
eprj453 wants to merge 1 commit intobacknotprop:mainfrom
eprj453:main
Open

feat: add collaborative review sessions#316
eprj453 wants to merge 1 commit intobacknotprop:mainfrom
eprj453:main

Conversation

@eprj453
Copy link
Copy Markdown

@eprj453 eprj453 commented Mar 16, 2026

Add server-side collaborative review sessions allowing multiple team members to review and annotate a plan using a single shared URL.

Problem Solved:
Previously, to get feedback from N reviewers, you needed N separate share URLs (each reviewer creates their own annotated version and sends it back). Now, create one session URL and all reviewers add annotations to the same session.

Backend Changes:

  • Add ReviewSession, CreateReviewSessionRequest, AddAnnotationsRequest types
  • Extend PasteStore interface with session methods
  • Implement session storage for filesystem and Cloudflare KV
  • Add 3 new API endpoints:
    • POST /api/review-session (create new session)
    • GET /api/review-session/:id (fetch session state)
    • PATCH /api/review-session/:id/annotations (add annotations with optimistic locking)

Frontend Changes:

  • Add useCollaborativeSession hook for session management
  • Add CollaborativeSessionButton UI component
  • Integrate collaborative session button in plan editor toolbar

Key Features:

  • One fixed URL for all reviewers (no N-way URL ping-pong)
  • Server merges annotations automatically (deduplicates)
  • Optimistic locking prevents version conflicts
  • Auto-expires after 7 days (same as paste service)
  • Works offline (local filesystem) or cloud (Cloudflare KV)

Files Changed:

  • packages/ui/types.ts (+50 lines)
  • apps/paste-service/core/storage.ts (+10 lines)
  • apps/paste-service/stores/fs.ts (+50 lines)
  • apps/paste-service/stores/kv.ts (+30 lines)
  • apps/paste-service/core/handler.ts (+210 lines)
  • packages/editor/App.tsx (+10 lines)
  • CLAUDE.md (+40 lines)

Files Created:

  • packages/ui/hooks/useCollaborativeSession.ts (260 lines)
  • packages/ui/components/CollaborativeSessionButton.tsx (170 lines)
  • COLLABORATIVE_REVIEW_GUIDE.md (comprehensive testing guide)

Tested:

  • ✅ Session creation
  • ✅ Session retrieval
  • ✅ Annotation merging from multiple reviewers
  • ✅ Optimistic locking (version conflict handling)
  • ✅ Deduplication
  • ✅ Filesystem storage persistence

Add server-side collaborative review sessions allowing multiple
team members to review and annotate a plan using a single shared URL.

**Problem Solved:**
Previously, to get feedback from N reviewers, you needed N separate
share URLs (each reviewer creates their own annotated version and
sends it back). Now, create one session URL and all reviewers add
annotations to the same session.

**Backend Changes:**
- Add ReviewSession, CreateReviewSessionRequest, AddAnnotationsRequest types
- Extend PasteStore interface with session methods
- Implement session storage for filesystem and Cloudflare KV
- Add 3 new API endpoints:
  - POST /api/review-session (create new session)
  - GET /api/review-session/:id (fetch session state)
  - PATCH /api/review-session/:id/annotations (add annotations with optimistic locking)

**Frontend Changes:**
- Add useCollaborativeSession hook for session management
- Add CollaborativeSessionButton UI component
- Integrate collaborative session button in plan editor toolbar

**Key Features:**
- One fixed URL for all reviewers (no N-way URL ping-pong)
- Server merges annotations automatically (deduplicates)
- Optimistic locking prevents version conflicts
- Auto-expires after 7 days (same as paste service)
- Works offline (local filesystem) or cloud (Cloudflare KV)

**Files Changed:**
- packages/ui/types.ts (+50 lines)
- apps/paste-service/core/storage.ts (+10 lines)
- apps/paste-service/stores/fs.ts (+50 lines)
- apps/paste-service/stores/kv.ts (+30 lines)
- apps/paste-service/core/handler.ts (+210 lines)
- packages/editor/App.tsx (+10 lines)
- CLAUDE.md (+40 lines)

**Files Created:**
- packages/ui/hooks/useCollaborativeSession.ts (260 lines)
- packages/ui/components/CollaborativeSessionButton.tsx (170 lines)
- COLLABORATIVE_REVIEW_GUIDE.md (comprehensive testing guide)

**Tested:**
- ✅ Session creation
- ✅ Session retrieval
- ✅ Annotation merging from multiple reviewers
- ✅ Optimistic locking (version conflict handling)
- ✅ Deduplication
- ✅ Filesystem storage persistence

Co-authored-by: Claude <noreply@anthropic.com>
@backnotprop
Copy link
Copy Markdown
Owner

Hey @eprj453 what was the motivation behind this, what type of testing have you done

@osujin
Copy link
Copy Markdown

osujin commented Mar 17, 2026

We're running plannotator on a self-hosted EKS deployment and this is the feature that would make it genuinely useful for team review. The paste service extension approach is clean — it builds on existing infrastructure without requiring a separate real-time backend.

@grubmanItay
Copy link
Copy Markdown
Contributor

Related to #313.

Great work @eprj453 — this covers a lot of ground and tackles the hardest part: the full storage layer, API endpoints, both FS and KV backends, the hook, and the UI integration. The overall architecture (extending the paste
service rather than adding a separate real-time backend) is exactly the right call. Really nice to see this taking shape.

A few things I ran into while prototyping the same feature locally that might save you some debugging. Tagging @backnotprop for his take on these.

Encryption

The existing share flow uses AES-256-GCM with the key in the URL fragment so the server never sees plan content. This PR stores plans and annotations as plaintext JSON on the paste service. That's a regression from
plannotator's current security model — anyone with server/KV access can read everything. I'd expect this to use the same @plannotator/shared/crypto encrypt/decrypt flow with a #key=... fragment in the session URL.

No real-time sync

Reviewers have to manually click "Refresh" to see others' annotations and "Submit" to push their own. For a collaborative session this is a dealbreaker in practice — you end up with version conflicts and stale state. A polling
loop (even a simple 2-3s setInterval) would make this actually usable.

Remote annotations won't render as highlights

refreshSession() merges server annotations via setAnnotations() directly, but annotations from fromShareable() come back with blockId: '', startOffset: 0, and no startMeta/endMeta. They need to go through
viewerRef.current.applySharedAnnotations() to get anchored to the DOM — otherwise they exist in state but are invisible in the document. I hit this exact bug when prototyping this.

Optimistic locking on KV is racy

The KV updateSession does get-then-put (non-atomic) which the code acknowledges. With 3+ reviewers submitting around the same time, annotations get silently dropped. An append-only log (one key per annotation batch, e.g.
session:{id}:ann:{timestamp}) avoids the read-modify-write race entirely and scales better since you're not rewriting the full session on every update.

No session lifecycle

There's no concept of a session owner, closing a session, or approve/deny. In practice the person who created the session needs to be able to lock it when review is done.

Theme

Minor: CollaborativeSessionButton uses hard-coded colors (bg-purple-600 etc.) — should use the semantic theme tokens (bg-primary, bg-accent, etc.) so it works with both dark and light themes.

@backnotprop what's your thinking on this? Happy to share more detail from my prototype if it helps.

@backnotprop
Copy link
Copy Markdown
Owner

Yes all these things matter @grubmanItay - also willing to meet over call for this work

@eprj453
Copy link
Copy Markdown
Author

eprj453 commented Mar 20, 2026

Thanks everyone for the thoughtful feedback — this is very helpful.

You’re right that many of these points should have been discussed earlier in an issue/discussion before going this far in implementation. I’ll make sure to do that for design-heavy changes going forward.

I still believe that enabling shared review workflows for even small teams can make plannotator much more broadly useful and drive wider adoption of the project. If follow-up issues or PRs are opened around this direction, I’d be happy to actively support and contribute.

@backnotprop
Copy link
Copy Markdown
Owner

Diving deeper into the PR this weekend

orius123 added a commit to orius123/plannotator that referenced this pull request Mar 26, 2026
Adds from PR backnotprop#316:
- ReviewSession type with collaborative session fields
- CollaborativeSessionButton component
- useCollaborativeSession hook

Extensions for Shuni integration:
- AnnotationAuthor type (human/bot distinction)
- Session status lifecycle (reviewing/approved/rejected/expired)
- closedBy/closedAt fields on ReviewSession
- author field on Annotation type uses AnnotationAuthor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants