feat(service,features,desktop): submit a review on an agent's pull request as the member who presses
B1 of the "Review and comment" stack, stacked on #3272 (feat/pull-request-member-writes). The task: port stage-cli's review panel to the Pull request tab. You choose Approve, Request changes or Comment, with an optional Markdown body. GitHub's refusals show in GitHub's own words, the conversation is read again after submitting, and if the member already has a pending review, that review is the one submitted.
Wire
The action union in pull-request-write-wire.ts gets one new member, and there is no parallel path:
{ action: "submit-review", event: "comment" | "approve" | "request-changes", body }- The body is capped at
MAX_BODY_CHARS, 65,536 characters, which is GitHub's own limit for a comment. - The endpoint's body limit grows to match: up to 6 bytes per character as JSON, plus 4 KiB.
Service
submitPullRequestReview in github/pull-request-writes.ts runs on the member's token through githubWrite, behind memberPullRequestWrite, so the rule from #3272 still applies: it acts as the member and never falls back to the App.
- It reads the PR's pending reviews and picks the one where
viewerDidAuthoris true. - If there is one, it sends
submitPullRequestReviewon that review, so any comments the member started it with get published. - If not, it sends
addPullRequestReviewwith the event, which creates and submits the review in one step. - An empty body is left unsent, so a pending review keeps whatever text it already has.
Nothing is refused on Luke's side that GitHub might allow. For example, approving your own pull request comes back as GitHub's refusal, in its words.
UI
While the pull request is open or a draft, the bar gets a Review button that opens pull-request-review.tsx, a popover like stage-cli's:
- It has a Write / Preview Markdown field, the three choices with descriptions, Cancel, and Submit review. ⌘↩ submits and Escape closes.
- The review gets its own
usePullRequestWritesinstance, separate from the pill's. That lets GitHub's answer show inside the panel instead of under the bar, and offer Sign in with GitHub when needed. - When GitHub accepts the review, the panel closes and clears its text, and the tab reads again.
- If the panel is closed by a stray click, the text you wrote is still there when you reopen it.
- Small shared changes:
usePullRequestWrites.writetakes an optionalonDone,PullRequestWriteNotetakes aclassNamefor where it is drawn, andAnchoredPopovertakes awidth.



Tests
- Service (
coding-agents-app.test.ts, "changing an agent's pull request"):- with no pending review, the service sends
addPullRequestReviewwith the event and body on the member's token, and GitHub's "Can not approve your own pull request" comes back asrefusedin its words; - the member's own pending review is the one submitted and nobody else's is touched, and a blank body is left unsent;
- an unknown event, or a body over GitHub's limit, gets invalid-request and never reaches GitHub.
- with no pending review, the service sends
- Renderer (
agent-pull-request-tab.test.ts):- Review submits the choice and the body for the agent, the panel closes and clears, and the conversation is read again;
- a refusal shows inside the panel, not under the bar, and the text stays;
- a merged pull request has no Review button.
- Goldens: two re-recorded with
LUKE_UPDATE_FIXTURES=1, the request schema and the view's params, each gaining thesubmit-reviewmember. Nothing else changed.
Evidence
pnpm check:affected ran out of memory in the sandbox (exit 137), so I ran the parts separately and all passed:
tscfor service and desktop- vitest: the service suite plus four affected files, 368 tests
./scripts/oxlint.sh,biome,pnpm knip,./scripts/repository-checks.shandpnpm discarded-effect- the desktop build, within its bundle budget
The screenshots come from a throwaway esbuild harness with a fake window.luke, captured with Playwright. The harness has been deleted.
Made with Luke
Summary by cubic
Adds a Review button to the Pull request tab so a member can submit a review on an agent's pull request as themselves, choosing Comment, Approve, or Request changes with an optional Markdown body.
- New
submit-reviewaction on the member-writes seam sends the review on the member's own GitHub sign-in token, never the Luke App's. - The member's own pending review is the one submitted; otherwise a review is created and submitted in one step, and a blank body is left unsent.
- GitHub's refusals appear inside the panel in GitHub's words, and the conversation is read again after GitHub answers.
- A press outside closes the panel, keeping its draft; it stays open while a review is in flight and scrolls within its fullest side in short windows. A merged pull request shows no Review button.