feat(PRO-546): add a local review-comments model to the CLI

Merged
DA
5mo ago
+4139-85

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 + comment tables (migration 0006); threads keyed by a deterministic scopeKey (extracted shared deriveScopeKey) 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 test all 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.

Open in Stage

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_thread and comment tables with deterministic scopeKey via extracted deriveScopeKey (migration 0006).
    • API: routes/comments.ts to list/create/reply/edit/delete and resolve/reopen threads; wire types in @stagereview/types/comments.
    • Viewer: /api/viewer resolves identity via gh api user (fallback to git user.name or "You"); web useViewer renders 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-drafts unit tests, and viewer fallback regressions (missing avatar_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 ./comments and ./viewer from @stagereview/types.
  • Bug Fixes

    • Viewer route degrades to the local fallback when readRepoRoot fails and keeps identity cached for the session with gcTime; 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 endSide when startSide is unknown, capture mouseup on document for 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 enforceSameOrigin on all mutation routes (403 on violation).
    • Keep an active thread form mounted when resolving a thread so unsaved text isn’t lost.