feat(web,desktop): show an agent's pull request changes in a Changes tab

Merged
LU
7d ago
+1864-52

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}/changes reads pulls/{n}/files on 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's codingAgents.changes, the host, and the CODING_AGENTS_CHANGES act (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 }. null means 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: binary or too-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.ts is gone. The answer drops the unused readAt, 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-tab became one agent-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 −M uses one shared LinesChanged, 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: drives AgentChangesTab through 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 codingAgentChangesAnswerSchema and codingAgentChangesAnswerViewSchema, written under LUKE_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.

LightDark
Changes tab, lightChanges tab, dark

🤖 Generated with Claude Code


Open workspace in Conductor