feat(PRO-546): add a local review-comments model to the CLI
Summary
Adds a local, line-anchored review-comments model to the stagereview CLI so reviewers can write comment threads — reply, resolve/reopen, edit, delete — alongside the existing checkmark review state, entirely on-machine with no GitHub sync yet (PRO-546). Threads anchor to the diff scope rather than a run, so they survive re-running show on the same diff, and the shape maps cleanly onto GitHub's review-comment model for the planned sync work. The comment UI is vendored to match the hosted review experience: inline threads in the diff, a composer with a markdown formatting toolbar, and text-selection + gutter affordances for starting a comment.
Changes
- DB: new
comment_thread+commenttables (migration0006); threads keyed by a deterministicscopeKey(extracted sharedderiveScopeKey) so comments persist across re-imports of the same diff. - API:
routes/comments.ts— list/create threads, reply, resolve (PATCH), edit & delete comments/threads; wire types in@stagereview/types/comments. - Web:
useCommentThreads(React Query) + a run-level context provider; threads render inline via Pierre line annotations; create via a text-selection popup and a gutter "+". - UI components: vendored comment thread, composer, markdown editor + formatting toolbar, and actions — stripped of GitHub specifics (local author, client-side markdown rendering).
- Tests: route-level integration tests (CRUD, cascade, scope-key survival across re-imports) and text-selection helper unit tests.
Testing
pnpm typecheck && pnpm lint && pnpm testall pass (347 tests, incl. 11 new comment-route tests).- Manually verified end-to-end against a live
stagereview show: create/reply/resolve/edit/delete, inline rendering, persistence across re-runs, and the selection/gutter/toolbar affordances.
Summary by cubic
Adds local, line-anchored review comments to the CLI and diff UI so reviewers can create, reply, edit, delete, and resolve threads on-device. Also shows the gh-authenticated user's GitHub login and avatar in comment bylines with graceful fallbacks, and supports multiple comment composers open at once (PRO-546).
-
New Features
- DB:
comment_threadandcommenttables with deterministicscopeKeyvia extractedderiveScopeKey(migration0006). - API:
routes/comments.tsto list/create/reply/edit/delete and resolve/reopen threads; wire types in@stagereview/types/comments. - Viewer:
/api/viewerresolves identity viagh api user(fallback to gituser.nameor "You"); webuseViewerrenders GitHub login + avatar; types in@stagereview/types/viewer. - Web UI: inline threads in the diff; start from text selection, gutter "+", and gutter drag-select; hover highlights anchored lines; composer with markdown editor + formatting toolbar; multiple composers with per-anchor draft text via
comment-drafts. - Tests: route integration tests (comments, viewer), text-selection helper unit tests,
comment-draftsunit tests, and viewer fallback regressions (missingavatar_url, non-git repo). - Infra: shared
parseJsonBody<T>for request validation used by comments, view-state, and pull-request mutation routes. - Deps: add
react-textarea-autosize; export./commentsand./viewerfrom@stagereview/types.
- DB:
-
Bug Fixes
- Viewer route degrades to the local fallback when
readRepoRootfails and keeps identity cached for the session withgcTime; bylines show the GitHub login, not the display name. - Resolve Pierre shadow root lazily to fix select-to-comment when the diff renders late.
- Unified diff: treat change–deletion rows as the deletions side for correct anchoring and selection.
- Reject cross-side gutter drag selections in split view to avoid mismatched line anchoring.
- Comment threads fetch failures show a toast (stable id, deduped) and the toast dismisses on recovery.
- Harden text selection: fall back to
endSidewhenstartSideis unknown, capturemouseupondocumentfor long drags, and clamp triple-click selections to the previous rendered line so drafts anchor to visible rows (covers unified replacement lines and hunk gaps). - Re-dragging a range over an open draft updates its start line (
upsertDraft) so the created thread matches the new span. - Gate reply actions while another thread form is active to avoid dropping unsaved edits.
- Block cross-origin comment writes with
enforceSameOriginon all mutation routes (403 on violation). - Keep an active thread form mounted when resolving a thread so unsaved text isn’t lost.
- Viewer route degrades to the local fallback when