feat(desktop): draw Files changed with Pierre's diffs, unified or split
Merged
DA
3d ago+1268+1268+0123456789012345678901234567890123456789-275-275-012345678901234567890123456789
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 vanillaFileDiff. It is unified by default, split as an option, and changed words are marked (word-alt,maxLineDiffLength1000). 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-monoat 11.5px / 1.6, and the separator in--code-pointed. New semantic tokens inbase.css(--diff-added-ground,--diff-removed-ground,--diff-added-word,--diff-removed-word,--diff-added-mark,--diff-removed-mark) replace thecolor-mixliterals. - CSP adaptor. The panel's
style-src 'self'refuses the inlinestyleattributes and the<style>Pierre writes as HTML. The gutter/codegrid-rowspans and the token colours depend on them. The diff bundle answers Pierre's import ofhast-util-to-htmlwith its own writer (diff-view/inline-styles.ts). That writer puts each style indata-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 (
LinesMenunow takes the bar's ownlooks).file-view-settings.tsnow decodes each stored field on its own (catchDecodingpluswithDecodingDefault), 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.tsreads the open shadow root of a host markeddata-find-shadowin 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.watchTextalso 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 likediagram.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 exceptsrc/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 inbundle-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.shgreen. (CI) - JSON Schema goldens unchanged.
- Every behavior change has, in this PR, the test that fails without it.
agent-changes-tab.test.tscovers the patch handed over whole, the default look, Split view, Highlight changed words, the narrow fallback, and per-field decode.find-matches.test.tscovers shadow-root find, ordering across the edge, and watching.inline-styles.test.tscovers styles carried in the data attribute without changing Pierre's tree. - Gateway envelope goldens unchanged.
- No new
astype 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.tsis a CSP adaptor of the same kind asterminal/adopted-styles.ts. - No
CLAUDE.md/AGENTS.mdsentence names a changed part. -
PRIVACY.mdunchanged. The diff stays inside the tab'sph-no-captureregion. - Deps match imports:
@pierre/diffs1.5.2, plushast-util-to-html9.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
PierreDiffwrapper, 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.tsxuntil follow-up (1).
Made with Luke
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.jsbundle, 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.