feat(web,desktop): show an agent's pull request changes in a Changes tab
Merged
LU
7d ago+1864+1864+0123456789012345678901234567890123456789-52-52-01234567890123456789
Summary
Once an agent has opened a pull request, a Changes chip shows at the foot of its tab. The chip opens a side-panel Changes tab for that agent: the selected file's unified diff (hunk headers, old and new line numbers, added and removed rows) next to the existing Pierre file tree, narrowed to the changed files. Choosing a file in the tree shows its diff. The bar shows the file count and +N −M, a button that opens the PR's files on GitHub, and Refresh.
- Source: the GitHub PR only. The new
GET /api/agents/{id}/changesreadspulls/{n}/fileson the developer's own token, 100 files per page up to 300 files, and drops any patch over 100k chars. It goes to the window through the hosted client, the gateway'scodingAgents.changes, the host, and theCODING_AGENTS_CHANGESact (panel only; the voice window is refused). Nothing is cached or stored. The sandbox is not a source here: only eve's hooks reach it, and it is stopped between turns. - Answer:
{ pullRequest: { url, files, truncated } | null }.nullmeans no PR yet, which is different from a PR with no files. Each file has its path, previous path, status,+/−counts, and patch. When there is no patch, it says why:binaryortoo-large. A file that was only renamed carries an empty patch and reads "renamed without changes". - States: loading, failed with Retry, no PR yet, no files, list cut short, binary, too large, renamed only, and patch unreadable. A file is never drawn as unchanged when it has no patch.
- Tab model: one tab per agent, held like a delegated worker's tab. It shows while its agent's tab is open, is not kept across a launch, and is not pinned. A read happens when the tab first shows, on Refresh or Retry, and when the agent ends.
Simplified before landing
The bot's first draft was +2,325. It is now about +1,860, with the same behaviour:
- The server reads the files inside the endpoint;
coder-host/changes.tsis gone. The answer drops the unusedreadAt, a one-member "unavailable" enum, and two exported inner schemas along with their JSON goldens. - The tab no longer makes a second "published" read just for its GitHub button, because the answer now carries the PR's URL.
changes-view+use-agent-changes+agent-changes-tabbecame oneagent-changes-tab.tsx. The panel keys the tab by agent, so the hook's agent-switch state went away, and the answer view replaces a read-state enum.- Hunks come from jsdiff's
parsePatch, which is already in the bundle for plan merges, so no new dependency. It replaces the hand-written parser. A patch that doesn't parse says so. +N −Muses one sharedLinesChanged, also used by the PR row.
Evidence
./scripts/check.sh: exit 0 (knip, lint, discarded-effect, typecheck, all vitest shards, build).- macOS Electron verification (
./scripts/verify.sh): not run. This is a UI change and CI could not verify the Mac UI: CI is Linux only and builds nothing for the Mac. LUKE-159 is the manual Mac pass. - Tests:
apps/web/tests/coding-agents-app.test.ts: files and patches; binary, too-large, rename and no-PR cases; paging and the 300 cut; ownership and method refusals.packages/host/src/compose-coding-agents.test.ts: the gateway method.apps/desktop/src/main/ipc/coding-agent-acts.test.ts: the act reaches the panel and is refused to the voice window.apps/desktop/src/renderer/planning/agent-changes-tab.test.ts: drivesAgentChangesTabthrough loading, the diff with row numbers (including a+--line and a "\ No newline" note), the tree's narrowing and reveal, binary, too-large, rename-only, unparseable, truncated, failed→Retry, no PR, empty, re-read on agent end, Refresh, and the GitHub button.use-side-panel.test.ts: the tab's lifecycle.agent-tab.test.ts: the chip.
- JSON Schema goldens: new files only, for
codingAgentChangesAnswerSchemaandcodingAgentChangesAnswerViewSchema, written underLUKE_UPDATE_FIXTURES=1. No existing golden changed. - No migration.
Visual evidence
Rendered in headless Chrome on Linux from the built styles.css and file-tree.js, with the real AgentChangesTab over a fixture answer. This is not an Electron capture.
| Light | Dark |
|---|---|
![]() | ![]() |
🤖 Generated with Claude Code

