The v2 workspace Changes pane now renders like the PR view's Code tab — card-per-file diffs, a resizable file tree, a real toolbar — and links straight to the in-app PR view when the branch has one. Built by extracting the PR tab into shared components, not by copying it.
Bordered, rounded card per file with a 16px gap; filename-first headers with the directory trailing off muted; emerald/red ± counts; always-visible collapse chevrons; a resizable in-pane file tree with ± row decorations and click-to-navigate; a toolbar with the Files pill, collapse/expand-all, prev/next comment navigation, and the unified/split toggles. Everything workspace-specific survived: section bar, inline editing, per-file actions, viewed-files, ⌘F search, scroll persistence.
When the workspace's branch has a linked pull request, the pane header shows a
state-tinted PR icon and #number. Clicking it opens
/pull-requests/$prNumber inside the app — until now, every PR affordance
in the product linked out to github.com. Hidden when there is no PR, and for session
workspaces that have no project scope.
The PR Code tab was the styling source, so its pieces were promoted into shared
code and both surfaces now consume them. PullRequestCodeTab.tsx dropped ~300 lines
in the process. When main's i18n sweep landed mid-branch, the components adopted the exact
message ids of the strings they replaced, so every existing translation carried over.
Verification ran against the real app over CDP — real clicks, screenshots, and DOM measurement, using this very worktree's own 39-file diff as the test changeset. Four real bugs surfaced that code review alone would have missed; zero console errors across the whole session.
Pierre slots the filename suffix through an unstyled light-DOM wrapper div. As a flex item it
defaults to min-width: auto and refuses to shrink — measured 893px inside a 513px
header — so the directory text overflowed beneath the diff-stat counts. Latent in the PR tab
too; its pane was just always wide enough to hide it.
slot[name='header-filename-suffix']::slotted(*) { min-width: 0 } in the shared card CSS, letting the truncate chain ellipsize.
Padding-top on the scroll container let file content show through an 8px band above every pinned card header.
Horizontal inset only on the scroller; sticky headers pin flush.
The scroll hook keeps its navigation target expanded while sticky tracking is armed, so collapse-all left the last-clicked file open and the toggle never flipped.
Collapsing the sticky target now releases the target in the same pane-data write.
Same-kind panes render through one unkeyed component instance, so a recreated Changes pane inherited the previous pane's manual tree override — a closed pane's "show tree" choice kept the tree open in a fresh 330px pane where it should have auto-hidden.
Render-phase state reset keyed on the pane id (and biome-proof, after the linter quietly gutted the effect version).
Merging origin/main brought a repo-wide Lingui i18n sweep and a
deferred-diff-loading feature straight through the restructured files.
DeferredDiffPlaceholder and i18n live alongside the tree and comment navigation.bun.lock was taken from main verbatim — the text-merged lockfile
had resolved two conflicting CodeMirror versions.db:migrate brought the workspace's Neon branch
current.| Check | Result |
|---|---|
| unit tests (touched areas) | 49 pass — 5 new co-located test files: tree data dedupe, thread ordering, path splitting, diff-stat formatting, collision-safe paths |
| tsc --noEmit (desktop) | pass — verified on the exact merged tree in a fresh worktree; this workspace's local env has a phantom-type quirk that misreports, the code is clean |
| lingui compile --strict | pass — every locale complete |
| lint / sherif | pass |
| CDP end-to-end | pass — tree click-to-navigate, collapse/expand-all, tree toggle + 700px auto-hide with manual override, unified/split sync across surfaces, comment nav force-expanding threads, PR chip → in-app /pull-requests/6882, PR Code tab pixel-unchanged |