Skip to content

feat: batch review comments into one prompt for the session - #129

Merged
omartelo merged 2 commits into
mainfrom
brave-willow
Jul 30, 2026
Merged

omartelo merged 2 commits into
mainfrom
brave-willow

Conversation

@omartelo

Copy link
Copy Markdown
Collaborator

Closes #119.

A diff could hand the session a reference — a file, a line range — but never an instruction, so anything that actually had to change was typed out by hand while looking straight at it. This adds the missing half: read the diff, leave line comments, hand them over as one turn.

What it does

Right-clicking a selection now also offers Comment on lines 12-30…, in both review surfaces — the Review dock's working diff and a pull request's Files changed. A box opens between the lines themselves, carrying the anchor as its label; ⏎ files the note, ⇧⏎ breaks a line, Esc discards it.

Comments collect at the foot of the panel in the order the diff was read, each removable on its own, and one Send hands the batch to the session as a single prompt:

Review comments:

- frontend/src/lib/use-inject.ts:12-18 — a session-less write should say so
- frontend/src/components/diff/FileDiff.tsx:52 — name this prop onReview

Sending clears them.

Decisions

  • Bracketed paste, not a raw write. Newlines are what make the block readable and also what a TUI reads as send — one per line — so the batch goes in wrapped the way xterm wraps text pasted by hand. It lands in the prompt as one paste, unsent: Enter stays the user's. Verified against a real Claude Code, which takes it as [Pasted text #1 +N lines].
  • The composer is a CodeMirror block widget, plain DOM. The editor owns that node; eq() keeps the widget alive across unrelated state changes so what was typed survives, and the root is contenteditable=false with every keydown stopped there, or the note would run through CodeMirror's own input handling.
  • Anchors are path:12-30 — the same form the line inject already writes. No hunk is pasted inline: the provider reads the file.
  • In memory, keyed by what is under review (the checkout's path, or the pull request's URL). Comments outlive leaving the tab but not a restart: an anchor is a line number of the diff as it was rendered, and outliving a restart would mean outliving every edit made in between.
  • useInject now answers whether the write landed, so a Send with no session to take it says so and keeps the comments instead of dropping them.
  • Not in scope: sending a comment to GitHub instead of to the session — Review: batch line comments into one prompt for the session #119 asks for that to be decided together with the review-threads work (Pulls: read and reply to review threads #122).

Ceilings

  • A comment on a PR whose branch is not the session's checkout points at the session's own copy of those lines. Open in Session is the way to line them up.
  • One composer per file's diff at a time; a file with only deletions has no new-file line to anchor to, so the action is disabled there.

Test plan

  • frontend/src/lib/review-comments.test.ts covers the store (per-review keying, blank text, removal, notification) and the composed payload (bullets, anchors, multi-line continuation, paste markers).
  • Local gate green: biome check, tsc, 516 frontend tests, vite build, go test ./... (576), go vet, gofmt.
  • The render path is node-untestable, so the whole flow was driven by hand in an isolated dev instance: select → right-click → composer between the lines → ⏎ files it → bar shows the batch → Send → the session's PTY receives [Pasted text #1 +3 lines], unsent. Esc discards without touching the batch.

omartelo and others added 2 commits July 30, 2026 12:21
A diff could hand the session a reference — a file, a line range — but
never an instruction, so anything that actually had to change was typed
out by hand while looking straight at it.

Right-clicking a selection now also offers "Comment on lines", in both
review surfaces: the Review dock's working diff and a pull request's
Files changed. The note is held against those lines rather than written
out. Comments collect at the foot of the panel in the order the diff was
read, each removable on its own, and one Send hands the batch over as a
single prompt.

The batch goes in as a bracketed paste. Newlines are what make the block
readable and also what a TUI reads as send — one per line — so the
envelope xterm puts around text pasted by hand is what keeps the batch
one paste, and unsent: Enter stays the user's. Verified against a real
Claude Code, which takes it as "[Pasted text #1 +N lines]".

Comments live in a module-level store keyed by what is under review — the
checkout's path, or the pull request's URL — in memory only: an anchor is
a line number of the diff as it was rendered, and outliving a restart
would mean outliving every edit made in between.

useInject now answers whether the write landed, so a Send with no session
to take it says so and keeps the comments instead of dropping them.

Refs #119

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The composer was a modal in the middle of the window: it covered the diff
being read and put the note as far from the lines it was about as the
screen allows. It is now a CodeMirror block widget that opens under the
selection — the anchor as its label, the field, and the two keys that
close it.

Plain DOM rather than a React portal: the editor owns this node, and eq()
keeps the widget alive across unrelated state changes, so what was typed
survives. The root is contenteditable=false and every keydown stops
there — the editor's content is contenteditable, and without that the
note would run through CodeMirror's own input handling.

useCodeMirrorView gains dispatchEffects, the seam an extension is driven
through from outside, and useDiffEditor holds the submit handler in a ref
so the extension set stays built once per document.

Refs #119

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@omartelo
omartelo merged commit c1cb9aa into main Jul 30, 2026
3 checks passed
@omartelo
omartelo deleted the brave-willow branch July 30, 2026 15:58
omartelo added a commit that referenced this pull request Jul 30, 2026
#119 landed while this was open, and it had built the same thing twice
over: a CodeMirror block widget that opens a gap between two lines of a
diff, so a comment is written where it is aimed. Two implementations of
one idea is one too many, so the merge keeps this branch's and moves
#129's feature onto it.

What was kept from each:

- The gap is codemirror-threads.ts. It holds any number of slots per
  editor, keyed, and React fills each through a portal — which is what a
  thread needs, since it renders replies, a reply box and a resolve
  action. codemirror-comment.ts held exactly one composer per editor and
  built its DOM by hand; deleted.
- The composer is now a slot like the rest, and the shared CommentBox
  renders it. It keeps #129's Enter-to-file, behind submitOnEnter: a
  note taken while reading is one line and reaching for a modifier is
  the slower half of writing it, while a review comment is prose that a
  stray Enter would file half of.
- The batch bar, the review-comments store and the bracketed-paste
  compose are #129's, untouched.

The two destinations now sit side by side in the diff's context menu,
and this is where #119's open question gets its answer: they are named
for where the note goes — "Comment for the session" and "Comment on the
pull request" — because once a box is open the two look identical, and
picking the wrong one is a note that lands where nobody looks for it.
The dock's working diff has no pull request behind it and so offers only
the first.

Consequences worth naming:

- The composer hangs off the *document* line the selection ended on
  rather than a resolved new-file line, which is #129's anchoring and
  the better one: a selection ending on a deleted line still has a place
  to open. reviewSlots no longer resolves it at all.
- FileDiff's onComment became onSessionComment, and its review prop
  carries the GitHub half. ReviewPanel follows.
- useCodeMirrorView exposes the live view; dispatchEffects went with the
  widget that needed it.

Verified over CDP against a real pull request: the menu offers both
items on one selection, the GitHub box files a pending comment (Submit
review 2), and the session box files its note on a bare Enter into the
batch bar. No new console errors.
@omartelo omartelo mentioned this pull request Jul 30, 2026
3 of 4 tasks
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.

Review: batch line comments into one prompt for the session

1 participant