feat(desktop): draw Files changed with Pierre's diffs, unified or split

Merged
DA
3d ago
+1268-275

Summary

This rebuilds the Files changed view of an agent's Pull request tab on @pierre/diffs 1.5.2, the library stage-cli uses. It replaces #3121's hand-rolled split diff. It is the first of a stack: the review-thread previews (#3221) and the full stage-cli diff settings follow in stacked PRs.

  • Diffs. Each changed file's patch, headed as git writes one (filePatch), is drawn by Pierre's vanilla FileDiff. It is unified by default, split as an option, and changed words are marked (word-alt, maxLineDiffLength 1000). Shiki colours it in the File tab's own themes (github-dark-default / github-light-default) on its JavaScript engine, because the CSP allows no WASM. Between hunks it shows Pierre's "N unmodified lines" (line-info-basic), with no expand action, because only the patch is at hand.
  • Look. A constructed sheet adopted into Pierre's shadow root maps its --diffs-* variables onto Luke's tokens: code ground and ink, line numbers, --font-mono at 11.5px / 1.6, and the separator in --code-pointed. New semantic tokens in base.css (--diff-added-ground, --diff-removed-ground, --diff-added-word, --diff-removed-word, --diff-added-mark, --diff-removed-mark) replace the color-mix literals.
  • CSP adaptor. The panel's style-src 'self' refuses the inline style attributes and the <style> Pierre writes as HTML. The gutter/code grid-row spans and the token colours depend on them. The diff bundle answers Pierre's import of hast-util-to-html with its own writer (diff-view/inline-styles.ts). That writer puts each style in data-inline-style, and a watcher on the shadow root applies it through the CSSOM. Pierre's theme <style> is adopted as a constructed sheet, the same way the terminal handles its <style>. Result: no per-token CSP violations, just one for the theme <style> element.
  • Settings. The ⋯ keeps Line numbers and Word wrap, and adds Split view and Highlight changed words (LinesMenu now takes the bar's own looks). file-view-settings.ts now decodes each stored field on its own (catchDecoding plus withDecodingDefault), so one unknown value no longer resets the rest.
  • Narrow panels. Below 640px of diff width, the diff draws unified whatever the setting says. Split view shows dimmed with "Needs a wider panel", and the stored choice is kept for a wider panel.
  • Find. find-matches.ts reads the open shadow root of a host marked data-find-shadow in the host's place, leaving out what the attribute's selector names ([data-gutter], the line numbers). Ranges are ordered across the shadow edge, and scrollers are walked across it. watchText also watches marked shadow roots. The shadow root paints ::highlight(find-*) itself, because document rules don't reach into it. Unmarked shadow roots, such as the file tree, are still not searched.
  • Lazy loading. Pierre and Shiki ship as diff-view.js, an ES module split into chunks like diagram.js, loaded the first time a diff is shown. Each Shiki grammar is its own chunk.
  • Removed: the hand-rolled diff rows (patchHunks, DIFF_ROW, the .code-line[data-diff] CSS) and the side-panel tests' diff-row numbering assertions, since Files changed no longer draws them.

Evidence

  • pnpm check:affected: everything passes except src/main/app-menu.test.ts, which fails in the sandbox before running because the Electron binary isn't installed ("Electron failed to install correctly"). That's the environment, not this change; CI installs Electron.
  • Bundle sizes (gzip, loaded up front):
    • renderer/renderer.js: main 778,064 → branch 777,272 (−792 B).
    • New renderer/diff-view.js: 135,766 B, recorded in bundle-budget.json. It loads only when a diff is first shown.
    • Lazy chunks on disk: about 11 MB raw / 1.9 MB gzip, mostly Shiki's grammars, fetched one language at a time.
  • A throwaway harness under the real panel CSP (Playwright/Chromium) showed the diff in light and dark, unified and split, ⌘F matches painted inside the shadow root, and the narrow fallback with Split view dimmed.

Invariants

  • ./scripts/check.sh green. (CI)
  • JSON Schema goldens unchanged.
  • Every behavior change has, in this PR, the test that fails without it. agent-changes-tab.test.ts covers the patch handed over whole, the default look, Split view, Highlight changed words, the narrow fallback, and per-field decode. find-matches.test.ts covers shadow-root find, ordering across the edge, and watching. inline-styles.test.ts covers styles carried in the data attribute without changing Pierre's tree.
  • Gateway envelope goldens unchanged.
  • No new as type assertion.
  • No Effect.run* outside the edges.
  • No new raw async primitives.
  • No discarded Effect.
  • Test count: +10 (5 + 3 + 2 new), −1 (narrow "numbers its diff on the new side alone"; Pierre now draws the numbers, and the narrow behaviour is covered by the new fallback test). Net +9.
  • The hand-rolled diff rows this replaces are deleted. diff-view/inline-styles.ts is a CSP adaptor of the same kind as terminal/adopted-styles.ts.
  • No CLAUDE.md/AGENTS.md sentence names a changed part.
  • PRIVACY.md unchanged. The diff stays inside the tab's ph-no-capture region.
  • Deps match imports: @pierre/diffs 1.5.2, plus hast-util-to-html 9.0.5 and @types/hast (the exact version Pierre pins, for the writer).
  • Barrel/door rule respected.

Notes

  • Pierre's worker pool is not used: one file is drawn at a time, and the CSP only admits blob: workers.
  • Follow-ups, stacked: (1) the review-thread code previews from #3221 move onto the same PierreDiff wrapper, and the rows go for good; (2) stage-cli's full diff settings (indicators, inline diff mode, backgrounds, syntax theme per appearance, font, size, line height, ligatures, minimize inline comments) in a settings popover.
  • If #3221 lands first, this rebases and keeps its diff-rows.tsx until follow-up (1).

Made with Luke


Devin Review


Summary by cubic

Rebuilds the Files changed view of an agent's Pull Request tab on @pierre/diffs 1.5.2, replacing the hand-rolled diff rows. Diffs are now drawn by Pierre's library with Shiki syntax coloring, loaded lazily the first time a diff is shown, with unified and split layouts.

Changes

  • Adds Split view and Highlight changed words to the ⋯ menu; below 640px of diff width a split is drawn unified and the option is dimmed.
  • Draws each patch as a lazily loaded diff-view.js bundle, coloured in the File tab's themes with per-language grammar chunks.
  • Extends find-in-tab to search a marked host's shadow root, leaving the diff gutter out of matches and ordering ranges across the shadow edge.
  • Applies Pierre's inline styles through the CSSOM under the panel's CSP, and introduces --diff-* semantic tokens for added/removed lines and words.
  • Decodes each stored diff setting independently, so one unrecognized value leaves the others intact.