Uh oh!
There was an error while loading. Please reload this page.
feat(web): add a file tree to the diff panel and pull request code tab - #9330
Conversation
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR adds a new persisted file-tree workflow across two production diff surfaces, including dynamic folder updates, selection synchronization, scrolling, and pull-request paging controls. The supplied unresolved comments identify medium-severity risks in incremental folder expansion and flattened-directory handling, while the new product defaults require human review. You can add or adjust custom eligibility rules. Learn more. |
Uh oh!
There was an error while loading. Please reload this page.
51359a7 to
ea55cf8CompareNavigating a diff with many files meant scrolling through all of them. Both the thread diff panel and the pull request Code tab now have a toolbar toggle that opens a Pierre file tree of the changed files with git status marks. Picking a file expands it if it was folded and scrolls the diff to it. The tree keeps the reader's expanded folders across refreshes and later diff slices by diffing the path list instead of rebuilding, and a "Load more files" footer in the pull request tree pulls in the next slice without scrolling for the sentinel. One shared DiffFileTree component backs both views; the Pierre tree theme it shares with the Files tab moves to pierre-tree-theme.ts. Based on the approach in #6373. Co-authored-by: Shpetim <32248437+ShpetimA@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every other subheader under PreviewPanelShell compacts to h-7 with a transparent border in the inline right panel; the tree header stayed at 40px with a solid border beside the compacted diff toolbar. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| if (updates.length > 0) model.batch(updates); | ||
| } | ||
| model.setGitStatus(gitStatus); | ||
| }, [gitStatus, model, paths]); |
There was a problem hiding this comment.
New folders stay collapsed after updates
Medium Severity
initialExpansion: "open" only applies to the first resetPaths. Later path changes use batch adds and never expand the new directories. A scope switch, agent refresh, or PR slice that introduces a new folder can hide those files behind collapsed rows, even though directories are supposed to start open.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit ea55cf8. Configure here.
There was a problem hiding this comment.
Checked against the Pierre source and this does not happen. initialExpansion: "open" sets state.defaultExpansion = "open", and isDirectoryExpanded falls through to isDirectoryExpandedByDefault, which returns true for any directory not explicitly collapsed — including ones added later via batch. The only path that marks new directories collapsed on add is collapseNewDirectoriesByDefault, which is set only when initialExpansion is "closed" and every directory was listed in initialExpandedPaths (the approach #6373 used, which is exactly why it needed a reset-and-recompute effect). Verified in the recording: switching the scope to Branch changes swaps in a whole new set of folders and they all arrive open.
| ancestor += `${segment}/`; | ||
| const directory = model.getItem(ancestor); | ||
| if (directory !== null && "expand" in directory) directory.expand(); | ||
| } |
There was a problem hiding this comment.
Ancestor expand misses unsuffixed folders
Low Severity
The reveal effect expands ancestors only via model.getItem with a trailing slash. The Files tab looks up both the slashed and unsuffixed ids because Pierre can register a flattened directory under either form. If the slashed lookup misses, a collapsed parent stays closed and the selected file is not revealed.
Reviewed by Cursor Bugbot for commit ea55cf8. Configure here.
There was a problem hiding this comment.
FileTreeController.getItem documents that it "accepts both canonical directory paths (src/) and bare directory lookup paths (src)", so the trailing-slash form already resolves flattened directories; the double lookup in FileBrowserPanel is belt-and-braces, not a requirement. Leaving as is.
ea55cf8 to
de21796CompareUh oh!
There was an error while loading. Please reload this page.
The handled-reveal guard stayed set while the selected file was absent from the tree, so a file dropped by a refresh and restored by the next one was never reselected under the same reveal request. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
Bugbot Autofix is ON, but a cloud agent failed to start.
Reviewed by Cursor Bugbot for commit 5ea691d. Configure here.
| syncingSelectionRef.current = false; | ||
| }); | ||
| // `paths` is a dependency so a file that arrives after it was asked for is still revealed. | ||
| }, [model, paths, revealRequestId, selectedPath]); |
There was a problem hiding this comment.
Flattened folders break expand-all
Medium Severity
DiffFileTree turns on flattenEmptyDirectories and seeds Pierre with file paths only, then treats every ancestor from collectDirectoryPaths as a real item. Flattened single-child chains often have no apps/ or apps/web/ node, so areAllDirectoriesExpanded stays false and the header control keeps offering Expand instead of Collapse. The same lookups drive chat reveal, so collapsed ancestors may never reopen.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 5ea691d. Configure here.
There was a problem hiding this comment.
Tested this against @pierre/trees directly rather than assume: a FileTree with flattenEmptyDirectories: true, initialExpansion: "open", seeded only with apps/mobile/src/a.ts, apps/mobile/src/b.ts, docs/x.md. getItem resolves every ancestor from collectDirectoryPaths (apps/, apps/mobile/, apps/mobile/src/) — flattening is a render-time projection over the canonical path store, the nodes still exist. areAllDirectoriesExpanded is true on load and flips to false after collapsing apps/mobile/src/, so the header control toggles correctly. This is also the same helper and the same flattenEmptyDirectories setting the Files tab has shipped with. The recordings in the description show both the collapse/expand-all toggle and chat-driven reveal working over flattened chains (mobile / src, server / src / environment).
Uh oh!
There was an error while loading. Please reload this page.
## What's Changed * chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298 * fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306 * fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294 * feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299 * feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313 * fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314 * refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253 * fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316 * chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300 * chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321 * refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322 * fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324 * fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255 * fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326 * chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331 * chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327 * feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330 * feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315 * fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334 * fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343 * feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339 * fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349 * feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137 * fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347 * fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351 * feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093 ## New Contributors * @tristanmanchester made their first contribution in pingdotgg/t3code#9255 **Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265


Problem
Reading a diff with many files means scrolling through all of them to find the one you want. Neither the thread Diff panel nor the pull request Code tab had any way to see the changed files as a structure or jump straight to one.
Fix
One shared
DiffFileTreecomponent (apps/web/src/components/diffs/), mounted in both places behind a folder-tree toggle at the end of each toolbar (same spot and icon as the Files tab). The toggle persists per view inlocalStorage, off by default.loadNextSlice.pierre-tree-theme.tsso all three trees share it.Based on the approach in #6373 by @ShpetimA; that PR predates #9174 (expanded-by-default) and had drifted, so this is a fresh cut with him as co-author. Deliberately not coupling "expand all files" to "expand all folders" — the tree has its own button, matching the Files tab.
Surfaces
apps/web). Mobile has its own diff UI and is unchanged.docs/user/source-control.md.Verification
vp test run apps/web/src/components/diffs/diffFileTree.logic.test.ts(6 tests),tsgo --noEmitinapps/web, targetedvp linton the touched files. Manually verified in a real browser against a snapshot of real data.Thread diff panel
Switching scope to Branch changes rebuilds the tree for that scope:
Walkthrough (toggle on, jump to two files, collapse/expand folders, switch scope, jump again, toggle off):
https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/ec08e7f59a261fe0/diff-panel.mp4
Pull request Code tab (PR #9253, 52 files)
Walkthrough (toggle on, jump, collapse all folders, drill into
apps/web/src, collapse all files from the toolbar, then pick a file in the tree so it expands just that one and scrolls to it):https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/28416528c557fe77/pr-code-tab.mp4
Not exercised: the Load more files footer, since every open PR I tried fits in GitHub's 100-file page so
nextCursoris never set. It drives the same code path as the existing scroll sentinel.Claude Fable 5 via Claude Code.
🤖 Generated with Claude Code
Note
Low Risk
UI-only navigation and layout in shared web diff views; no auth, API, or data-model changes.
Overview
Adds a folder-tree sidebar to the thread Diff panel and pull request Code tab so reviewers can browse changed files by path and jump to any file without scrolling the full patch.
A shared
DiffFileTree(Pierre file tree + git status badges) sits beside the diff viewer when a toolbar Show file tree toggle is on. Visibility is persisted inlocalStorageper surface (off by default). Picking a file unfolds a collapsed diff and scrolls the code view to it; the thread panel also syncs selection from chat changed-files links into the tree.Tree paths update incrementally via
buildDiffFileTreeUpdatesso refreshes and paged PR slices keep folder expand/collapse state. On large PRs, the tree footer exposes Load more files / Retry, sharing the sameloadNextSlicepath as the scroll sentinel. Pierre tree styling is centralized inpierre-tree-theme.ts(also used by the Files tab). User docs insource-control.mddescribe the feature.Reviewed by Cursor Bugbot for commit 5ea691d. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add file tree toggle and navigation to
DiffPanelDiffFileTreelisting all changed filesMacroscope summarized 5ea691d.