feat(PRO-546): add a local review-comments model to the CLI#68
feat(PRO-546): add a local review-comments model to the CLI#68dastratakos wants to merge 27 commits into
Conversation
|
Ready to review this PR? Stage has broken it down into 6 individual chapters for you: Chapters generated by Stage for commit 7dbe216 on Jun 9, 2026 2:32am UTC. |
There was a problem hiding this comment.
cubic analysis
5 issues found across 35 files
Linked issue analysis
Linked issue: PRO-546: Add a local review-comments model to the CLI
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Add database schema for comments and threads (comment and comment_thread tables), with a scopeKey anchor and appropriate indexes | Migration and schema files add comment and comment_thread tables, scopeKey column, and indexes; snapshot and migration files present. |
| ✅ | Expose API routes to list/create threads, reply, resolve/reopen, edit, and delete comments/threads | New routes implement CRUD and resolve actions and are wired into the server routes. |
| ✅ | Provide a web UI for inline threads (render in diff), a composer with markdown toolbar, and text-selection + gutter affordances to start comments | Web components and plumbing added: inline rendering integrated into the diff viewer, composer/editor and toolbar, selection popup, and gutter affordances, plus provider/context wiring. |
| ✅ | Anchor threads to a deterministic diff scope so threads persist across re-imports (scope-key survival) | deriveScopeKey was added and comment_thread stores scopeKey; server routes resolve and query by scope key; tests exercise scope-key survival. |
| ✅ | Operate locally without GitHub sync and provide viewer identity fallbacks | PR is explicit that comments are local-only; viewer resolution uses gh when available but falls back to git config or a generic label; local author default is used in DB schema. |
| ✅ | Include tests covering routes and selection helpers (CRUD, cascade, scope-key survival, and text-selection unit tests) | New integration and unit tests were added and test suite reported passing in PR testing notes. |
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2e785edd3d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…on't reach Pierre's slot)
Pierre creates its <diffs-container> shadow root asynchronously, so caching it once on mount left getSelection() reading a null root when the diff rendered late, silently breaking select-to-comment. Resolve it per-event and bind shadow listeners as soon as the root appears (bounded poll), with the container capture listeners as fallback. Addresses Cursor Bugbot finding.
… on hover Wire Pierre's onLineSelected so dragging across the line-number gutter opens the composer for the whole range (multi-line comments). Hovering a thread now highlights its anchored lines via selectedLines; an isHoveringRef guard keeps that highlight from triggering onLineSelected and opening a composer.
Resolve the local reviewer's identity via `gh api user` (falling back to git config user.name, then a generic 'You') and render their name + avatar in the comment byline instead of a hardcoded placeholder. Adds a /api/viewer route, a useViewer hook, and the Viewer wire type. Degrades gracefully when gh is unauthenticated or the repo isn't on GitHub.
bdedb69 to
8889afc
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8889afc48b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7f64cf323e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A split-view gutter drag that crosses from one column to the other emits a SelectedLineRange whose side and endSide differ. handleLineSelected opened a draft using only norm.side, so the thread would cover unrelated old/new line numbers on a single side. Extract toSingleSideSelection, which mirrors buildSelectedLineRange's null-on-cross-side contract, and use it for the gutter path too.
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/web/src/lib/use-text-selection.ts">
<violation number="1" location="packages/web/src/lib/use-text-selection.ts:66">
P2: `toSingleSideSelection` can anchor to the wrong diff side when only `endSide` is present, because it defaults directly to additions instead of falling back to `endSide`.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db4b45acd3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The threads query exposed error/isLoading, but no consumer read them, so a load failure rendered identically to having no comments — the diff still shows, the overlay is just silently empty. Toast the error from the provider (one query instance → one toast); React Query only sets error once its retries exhaust, so transient blips stay quiet. Add a regression test covering the error-toast and the no-toast-on-empty-success paths.
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: debad2ab73
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Three review-flagged gaps in the comment text-selection path: - toSingleSideSelection fell straight through to additions when only endSide was set; fall back to endSide first (mirrors normalizeSelected- LineRange), so a side-less range anchors to the side actually known. - mouseup was bound on the diff container, so releasing a multi-line drag past the last visible row (outside the diff) never fired — the drag stuck open and no popup appeared. Capture mouseup on the document instead. - A unified-view triple-click on a replacement line extends into the addition row that shares the deletion's number; the endLine>startLine guard missed it, so the cross-side range was dropped. Detect the trailing row by element identity and clamp to the clicked side. Add toSingleSideSelection unit cases for the endSide fallback.
Give the failed-fetch toast a stable id so a re-fire (StrictMode double- mount, remount with a cached error, or refetch failing with a new error reference) updates one toast instead of stacking duplicates.
The text-selection popup is suppressed while a composer is open (draft !== null), but the gutter '+' and gutter drag-select both replaced the draft anchor unconditionally. Since the composer's text lives in CommentForm's own state, moving the anchor unmounts the form and drops in-progress text. Guard both gutter paths on a draftRef (a render-synced mirror, so the Pierre callbacks keep stable identity and don't re-init the diff).
Replace the single moving draft with a collection of independent composers: opening a comment on another line (gutter +, gutter drag, or text selection) now adds a composer instead of relocating the existing one. Each composer submits, cancels, and shows errors independently. This supersedes the earlier single-draft guard (7117d9f): rather than block a second comment to avoid discarding the first, both stay open, so nothing is ever discarded. Implementation notes: - Draft state is a keyed collection; one composer per (side, endLine) row. - Composer text lives in a parent draftBodies ref keyed by anchor, so a form keeps its text if it remounts when another draft opens or closes. - Pierre keys its annotation rows by array index, so a row can be reused for a different anchor when a draft is added/removed; the composer carries a stable per-anchor key to force a clean remount (re-reading its own text) rather than inheriting another composer's in-progress state. - Extract the draft/annotation logic into lib/comment-drafts.ts with unit tests. Known trade-off: opening/closing a composer briefly flashes the other annotation rows (Pierre re-applies the annotation layer); accepted as-is for now.
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
openDraft treated an existing composer on the same (side, endLine) row as a no-op, so re-dragging a range that shares the end line but changes the start (e.g. 3-10 -> 7-10) left the draft's startLine stale and created the thread with the wrong span. Upsert instead: re-opening the row adopts the new startLine (and clears any stale submit error). Extracted to upsertDraft with unit tests.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7dbe216. Configure here.
| id: "comment-threads-error", | ||
| description: value.error instanceof Error ? value.error.message : undefined, | ||
| }); | ||
| }, [value.error]); |
There was a problem hiding this comment.
Error toast stays after recovery
Low Severity
When comment threads fail to load, a toast is shown with a stable id. If a later refetch or navigation succeeds and error clears, nothing dismisses that toast, so users can still see “Couldn’t load comments” while comments are actually visible.
Reviewed by Cursor Bugbot for commit 7dbe216. Configure here.


Summary
Adds a local, line-anchored review-comments model to the
stagereviewCLI 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-runningshowon 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
comment_thread+commenttables (migration0006); threads keyed by a deterministicscopeKey(extracted sharedderiveScopeKey) so comments persist across re-imports of the same diff.routes/comments.ts— list/create threads, reply, resolve (PATCH), edit & delete comments/threads; wire types in@stagereview/types/comments.useCommentThreads(React Query) + a run-level context provider; threads render inline via Pierre line annotations; create via a text-selection popup and a gutter "+".Testing
pnpm typecheck && pnpm lint && pnpm testall pass (347 tests, incl. 11 new comment-route tests).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
comment_threadandcommenttables with deterministicscopeKeyvia extractedderiveScopeKey(migration0006).routes/comments.tsto list/create/reply/edit/delete and resolve/reopen threads; wire types in@stagereview/types/comments./api/viewerresolves identity viagh api user(fallback to gituser.nameor "You"); webuseViewerrenders GitHub login + avatar; types in@stagereview/types/viewer.comment-drafts.comment-draftsunit tests.react-textarea-autosize; export./commentsand./viewerfrom@stagereview/types.Bug Fixes
readRepoRootfails and keeps identity cached for the session withgcTime.mouseupondocumentfor long drags, and clamp unified triple-clicks to the clicked side.upsertDraft) so the created thread matches the new span.Written for commit 7dbe216. Summary will update on new commits.