Skip to content

feat(service,features,desktop): comment on lines in Files changed, and count and discard the pending review - #3646

Merged
luke-agent[bot] merged 16 commits into
mainfrom
feat/pull-request-line-comments
Oct 18, 2026
Merged

luke-agent[bot] merged 16 commits into
mainfrom
feat/pull-request-line-comments

Conversation

@dastratakos

@dastratakos dastratakos commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

B3b of the "Review and comment" stack, after #3313 (B3a, merged). The task: comment on a line or range from Files changed, offering "Add single comment" and "Start a review" as GitHub does. Started reviews go into the pending review that B1's Review panel submits. As the orchestrator decided, it also adds a small read of the member's own pending review and a Discard action for it.

Wire

Two new members on the action union in pull-request-write-wire.ts, both running through memberPullRequestWrite, so they act as the member pressing the button and never fall back to the App:

  • add-review-comment { mode: "single" | "review", path, line, side, startLine?, startSide?, body, headSha }. Sides are left or right, lines count from 1, the body can't be blank, and headSha is the commit the diff was read at.
  • discard-review.

Two answers gain an optional field:

Service

  • addLineComment:
    • single sends one addPullRequestReview(commitOID: headSha, event: COMMENT, threads: [ … ]). That publishes immediately, as GitHub's own "Add single comment" does.
    • review finds the member's own pending review (viewerDidAuthor, the same lookup B1's submit now shares through ownPendingReview). If there is none, it starts one at headSha. Then it sends addPullRequestReviewThread to that review.
  • discardPendingReview deletes only the member's own pending review on this PR. If they have none, it is refused with the words "You have no pending review on this pull request."
  • Pending read (viewerPendingReviewOf): runs in parallel with the conversation read, on the member's own token (memberReadToken, the same as feat(desktop,service,features): add and remove a reaction in the Pull request tab's Conversation #3619's, now merged) and never the App's. Any failure comes back as null, and the hosted answer stays private, no-store.
  • Changes read: reads the pull request alongside its files to get headSha. If that read doesn't land, headSha is left out.

UI

  • Picking lines. Files changed's CodeView list now uses Pierre's own line picking (enableLineSelection, enableGutterUtility, onGutterUtilityClick). You can press the gutter "+" on one line, drag it across lines, or pick lines by their numbers and then press "+". Files with no lines of their own (binary, too large, or images feat(desktop,service,features): draw changed images before and after in Files changed #3638 draws as pictures) never offer a comment. Ranges GitHub won't take, such as one spanning two hunks or a line with no diff row, open nothing (commentLines in changes-model.ts).
  • The comment (line-comment.tsx) is drawn as a note under the last picked line, with those lines lit:
    • It has the shared Markdown field and offers Add single comment and Start a review. If you already have a pending review, it offers only Add review comment, as GitHub does.
    • ⌘↩ sends it; Escape and Cancel close it.
    • When GitHub accepts it, the comment closes and clears its text, and the conversation is read again. That draws the new thread on its line and updates the pending count.
    • GitHub's refusal shows in its words beside Refresh changes, and the text stays.
    • Text is kept per file, so it survives the comment scrolling out of the virtual list.
  • Review button: shows a badge with the pending comment count, and its panel says "N pending comments will be published." Discard appears only while you have a pending review. It asks first in the destructive tone, then sends discard-review.

Screenshots from the real diff bundle (diff-view/index.ts), driven with Playwright:
A range picked with the gutter's +, its comment open
GitHub's refusal under the comment, beside Refresh changes
Review counting the pending review, with Discard
Discard asking first

Tests

  • Service (coding-agents-app.test.ts):
    • a single comment is one addPullRequestReview with the thread, at headSha, on the member's token;
    • review starts a pending review when there is none, or joins the member's own and nobody else's;
    • Discard deletes the member's own pending review only, and is refused in words when there is none;
    • an invalid line, side, head or mode never reaches GitHub;
    • the pending read with two members: each sees their own, a member with no sign-in or a 502 gets null, and the App's token is never used;
    • the changes answer names headSha.
  • Renderer:
    • the "+" opens the comment under the last line, normalising a range dragged upward;
    • Add single comment sends the exact request, closes, and clears the text;
    • with a pending review, only Add review comment is offered, and Review shows the count and Discard (with confirm);
    • a refusal keeps the text and Refresh changes reads the files again;
    • commentLines gets the right sides, a one-line pick, a cross-hunk pick, a line with no row, and a binary file.
  • Goldens: six re-recorded with LUKE_UPDATE_FIXTURES=1:
    • the action request and the view's params, each gaining the two actions;
    • the changes answer, wire and view, gaining headSha;
    • the conversation answer, wire and view, gaining pendingReview.

Evidence

pnpm check:affected runs out of memory in this sandbox, so I ran the parts separately and all passed:

  • tsc for desktop and service
  • vitest: the renderer task suites, the main act row, the host composer, the wire goldens, coding-agents-app and github/ (1,470 tests)
  • ./scripts/oxlint.sh, biome, pnpm knip and ./scripts/repository-checks.sh
  • the desktop build

The desktop build fits the bundle budget #3305 re-recorded.

Made with Luke


Devin Review


Summary by cubic

Adds line commenting to Files changed, as GitHub's diff offers it, with comments published at once or held in the member's pending review, which Review now counts and can discard.

New Features

  • The gutter "+" picks one line or a range; binary and oversized files, and ranges GitHub won't take, open nothing.
  • The comment box offers Add single comment and Start a review (or Add review comment when a pending review exists), keeps text per file, and shows GitHub's refusal in its own words.
  • Review shows the pending comment count and Discard, which confirms first and deletes only the member's own pending review.

Service

  • add-review-comment and discard-review run as the member on their own GitHub token: single publishes at the head sha the diff was read at, review joins the member's pending review and starts one, with its first comment, if none.
  • A comment for a pending review held on another commit is refused in words, and a refused first comment leaves no empty review behind.
  • discard-review refuses in words when the member holds no pending review.
  • The conversation answer gains pendingReview and the changes answer gains headSha.

Written for commit ca69a7d. Summary will update on new commits.

View guided diff Turn on auto-fix

dastratakos and others added 5 commits October 18, 2026 17:31
…om the foot of its Conversation

The Conversation ends in a comment box, as GitHub's own does: Markdown
with Write and Preview, sent as a new `comment` action on the
member-writes seam (`addComment` on the pull request), so GitHub hears it
as the member on their own sign-in, never the Luke GitHub App. Once
GitHub takes it the box lets its words go and the conversation is read
again; a refusal stands under the box in GitHub's words, the words kept.

The review panel's Write and Preview move into `markdown-field.tsx`,
which both now draw.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
…ails

The comment box is drawn only while the conversation stands read, so a
refresh that failed took it away and Retry brought back an empty one.
The tab now holds the box's words (`usePullRequestComment`), so they
stand again once the conversation is read.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
…e retired aliases

#3524 retired `--space-*` and `--text-*`, so the comment box's gap and
its refusal's colour fell back to nothing on main. They now read
`--luke-space-8`, `--luke-color-text-muted`, and
`--luke-type-caption-size`, as the thread's own note does.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
… as the member, and read their pending review beside the conversation

Two actions join the member-writes union. `add-review-comment` comments
on lines of a changed file at the head the diff was read at: `single`
publishes it at once as a review of its own (`addPullRequestReview` with
one thread), and `review` adds it to the member's own pending review,
starting one where they hold none, which is the review the Review panel
submits. `discard-review` deletes the member's own pending review, found
by the pull request and `viewerDidAuthor`, and is refused in words where
they hold none.

The conversation's read carries the member's pending review beside it,
read in parallel on their own token (`memberReadToken`, as the reactions
read does) and never on the App's, null where they hold none, have no
sign-in, or GitHub would not answer. The changes answer names the head
its files were read at (`headSha`), read from the pull request beside
them, as #3638 names it.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
…rd the pending review in Review

Files changed's list turns on Pierre's own line picking: the gutter's +
on a line, or dragged across lines, or on lines picked by their numbers,
opens a comment under the last of them, its lines lit, as GitHub's own
diff does. The comment offers Add single comment and Start a review, or
Add review comment where the member holds a pending review, and sends
`add-review-comment` at the head the files were read at. Lines GitHub
would take no comment on (a file drawn without lines of its own, a range
across hunks) open nothing (`commentLines`). GitHub's refusal stands
under the comment in its words beside Refresh changes, its words kept;
a file's words outlive the comment scrolling away.

The Review press counts the pending review's comments, its panel says
they will be published, and Discard, offered only while one is held,
asks first in the destructive tone, then sends `discard-review`. The
review panel's and the comment's fields are the shared `MarkdownField`.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
@vercel

vercel Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
luke-web Ready Ready Preview Oct 18, 2026 8:11pm UTC

Request Review

@luke-agent
luke-agent Bot changed the base branch from main to feat/pull-request-comment October 18, 2026 17:56
luke-agent Bot added a commit that referenced this pull request Oct 18, 2026
Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

dastratakos and others added 2 commits October 18, 2026 18:05
…ne held on another commit, and let Discard wait on GitHub

A review a line comment starts is now made with that comment in one
`addPullRequestReview`, so a comment GitHub refuses leaves no empty
pending review behind. A comment for a review held pending on a commit
other than the one the diff was read at is refused in words saying so,
rather than given lines counted on another commit. Discard's question
stands busy until GitHub answers and says its refusal in GitHub's words,
`usePullRequestWrites.write` answering how the change went. The Refresh
changes test asserts the files as drawn again, not how often they were
read.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
dastratakos and others added 2 commits October 18, 2026 18:29
Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
dastratakos and others added 3 commits October 18, 2026 19:09
#3568 draws a write's refusal as `ErrorState`, so the comment box's own
`.failure-note-words` rule draws nothing and its test read no words. The
rule goes, and the test reads the refusal by its alert, as the thread's
does.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
The line comment's refusal is drawn by main's `ErrorState` now, so its own
`.failure-note-words` rule and the Review panel's note rules go, and its
test reads the refusal by its alert.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
dastratakos and others added 2 commits October 18, 2026 19:32
Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
Base automatically changed from feat/pull-request-comment to main October 18, 2026 19:56
Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
Merged via the queue into main with commit 0b1e0a0 Oct 18, 2026
11 checks passed
@luke-agent
luke-agent Bot deleted the feat/pull-request-line-comments branch October 18, 2026 20:24

This branch was successfully deployed

1 active deployment
Preview — 60c3c04e Deployed Oct 18, 2026 by vercel[bot]
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