Skip to content

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

Open
dastratakos wants to merge 27 commits into
mainfrom
dean/pro-546-add-a-local-review-comments-model-to-the-cli
Open

feat(PRO-546): add a local review-comments model to the CLI#68
dastratakos wants to merge 27 commits into
mainfrom
dean/pro-546-add-a-local-review-comments-model-to-the-cli

Conversation

@dastratakos

@dastratakos dastratakos commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

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 "+", or gutter drag-select; hover highlights anchored lines; composer with markdown editor + formatting toolbar; supports multiple composers at once with per-anchor draft text via comment-drafts.
    • Tests: route integration tests (comments, viewer), text-selection helper unit tests, and comment-drafts unit tests.
    • 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.
    • Resolve Pierre shadow root lazily to fix select-to-comment when the diff renders late.
    • Comment bylines show the GitHub login, not the display name.
    • 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, deduped with a stable id.
    • Harden text-selection: fall back to end side when start side is unknown, capture mouseup on document for long drags, and clamp unified triple-clicks to the clicked side.
    • Re-dragging a range over an open draft updates its start line (use upsertDraft) so the created thread matches the new span.

Written for commit 7dbe216. Summary will update on new commits.

Review in cubic

@stage-review

stage-review Bot commented Jun 8, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 6 individual chapters for you:

Title
1 Define comment and viewer shared types
2 Implement database schema and scope key utility
3 Add viewer identity and comment API routes
4 Implement comment UI components and state
5 Integrate comments into the diff viewer
6 Other changes
Open in Stage

Chapters generated by Stage for commit 7dbe216 on Jun 9, 2026 2:32am UTC.

Comment thread packages/web/src/lib/use-text-selection.ts
Comment thread packages/web/src/lib/use-text-selection.ts Outdated
Comment thread packages/web/src/components/comments/comment-thread.tsx
Comment thread packages/web/src/components/comments/comment-thread.tsx
Comment thread packages/web/src/components/chapter/pierre-diff-viewer.tsx Outdated
Comment thread packages/web/src/components/chapter/pierre-diff-viewer.tsx Outdated
@dastratakos dastratakos marked this pull request as ready for review June 8, 2026 05:54

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/web/src/lib/use-comment-threads.ts
Comment thread packages/web/src/components/comments/comment-thread.tsx
Comment thread packages/web/src/lib/use-viewer.ts
Comment thread packages/cli/src/routes/viewer.ts Outdated
Comment thread packages/cli/src/runs/scope-key.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/cli/src/runs/scope-key.ts
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.
@dastratakos dastratakos force-pushed the dean/pro-546-add-a-local-review-comments-model-to-the-cli branch from bdedb69 to 8889afc Compare June 8, 2026 18:04

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/web/src/lib/use-text-selection.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/web/src/components/chapter/pierre-diff-viewer.tsx Outdated
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.
Comment thread packages/web/src/lib/use-comment-threads.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/web/src/lib/use-text-selection.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/web/src/lib/use-text-selection.ts Outdated
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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/web/src/lib/comment-threads-context.tsx

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/web/src/lib/use-text-selection.ts Outdated
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.
Comment thread packages/web/src/components/chapter/pierre-diff-viewer.tsx Outdated
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.
Comment thread packages/web/src/components/chapter/pierre-diff-viewer.tsx Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/web/src/components/chapter/pierre-diff-viewer.tsx Outdated
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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7dbe216. Configure here.

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.

1 participant