Render changed files as expandable tree with aggregated diff stats#172
Render changed files as expandable tree with aggregated diff stats#172juliusmarminge merged 3 commits intomainfrom
Conversation
- Replace flat changed-files chips in ChatView with a nested, expandable tree - Extract diff tree/stat logic into turnDiffTree utilities - Add unit tests for stat aggregation, nesting, and Windows path normalization
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: First toggle click fails for unregistered top-level directories
- Changed toggleDirectory to accept the effective expanded state (which accounts for the fallback default) instead of toggling the raw undefined state value.
- ✅ Fixed: Directory always shows +0/-0 when children lack stats
- Wrapped the directory DiffStatLabel render with a hasNonZeroStat guard, consistent with file nodes and the summary header.
Or push these changes by commenting:
@cursor push 7346e62eab
Preview (7346e62eab)
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -3176,10 +3176,10 @@
() => buildInitiallyExpandedDirectoryState(treeNodes),
);
- const toggleDirectory = useCallback((pathValue: string) => {
+ const toggleDirectory = useCallback((pathValue: string, currentlyExpanded: boolean) => {
setExpandedDirectories((current) => ({
...current,
- [pathValue]: !current[pathValue],
+ [pathValue]: !currentlyExpanded,
}));
}, []);
@@ -3193,7 +3193,7 @@
type="button"
className="group flex w-full items-center gap-1.5 rounded-md py-1 pr-2 text-left hover:bg-background/80"
style={{ paddingLeft: `${leftPadding}px` }}
- onClick={() => toggleDirectory(node.path)}
+ onClick={() => toggleDirectory(node.path, isExpanded)}
>
<ChevronRightIcon
aria-hidden="true"
@@ -3210,9 +3210,11 @@
<span className="truncate font-mono text-[11px] text-muted-foreground/90 group-hover:text-foreground/90">
{node.name}
</span>
- <span className="ml-auto shrink-0 font-mono text-[10px] tabular-nums">
- <DiffStatLabel additions={node.stat.additions} deletions={node.stat.deletions} />
- </span>
+ {hasNonZeroStat(node.stat) && (
+ <span className="ml-auto shrink-0 font-mono text-[10px] tabular-nums">
+ <DiffStatLabel additions={node.stat.additions} deletions={node.stat.deletions} />
+ </span>
+ )}
</button>
{isExpanded && (
<div className="space-y-0.5">- Compact single-directory chains in turn diff trees and preserve branch points - Add per-turn expand/collapse-all controls and themed VS Code file icons in ChatView - Centralize attachment route prefix stripping and tighten image/extension normalization helpers
- revert unrelated server-side attachment/image changes from this branch\n- keep changed-files tree UX fixes (icons, directory behavior, whitespace-safe paths, stat visibility)\n\nCo-authored-by: codex <codex@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Inconsistent path format passed to diff handler
- Added normalizeFilePath() export to turnDiffTree.ts and applied it to the 'View diff' button's checkpointFiles[0]?.path so it matches the normalized paths used by tree node click handlers.
- ✅ Fixed: Unused
showParenthesesprop is dead code- Removed the unused showParentheses prop and its associated rendering logic from DiffStatLabel since no caller ever passes it.
Or push these changes by commenting:
@cursor push 74d445af4d
| type="button" | ||
| className="group flex w-full items-center gap-1.5 rounded-md py-1 pr-2 text-left hover:bg-background/80" | ||
| style={{ paddingLeft: `${leftPadding}px` }} | ||
| onClick={() => onOpenTurnDiff(turnId, node.path)} |
There was a problem hiding this comment.
Inconsistent path format passed to diff handler
Medium Severity
When a tree file node is clicked, onOpenTurnDiff receives the normalized node.path (backslashes converted to forward slashes, consecutive separators collapsed via normalizePathSegments). However, the "View diff" button still passes the original unnormalized checkpointFiles[0]?.path directly from server data. If paths contain Windows-style backslash separators, the two call sites pass different string representations of the same file to the same handler, which could cause a mismatch in the downstream diff viewer.
Additional Locations (1)
| additions: number; | ||
| deletions: number; | ||
| showParentheses?: boolean; | ||
| }) { |
There was a problem hiding this comment.
Unused showParentheses prop is dead code
Low Severity
The showParentheses prop on DiffStatLabel is defined with a default of false, but no caller ever passes true. This appears to be leftover scaffolding from the old inline-chip layout that rendered parentheses around per-file stats — a format that no longer exists after this refactor.



Summary
ChatViewwith an expandable directory/file tree for each turn.summarizeTurnDiffStats,DiffStatLabel) so totals and per-node stats render consistently.buildTurnDiffTreeto normalize paths, build nested directory nodes, and aggregate additions/deletions up the tree.Testing
apps/web/src/lib/turnDiffTree.test.tsvalidates aggregated stats and tree structure for nested paths.apps/web/src/lib/turnDiffTree.test.tsverifies files without numeric stats are preserved and excluded from totals.apps/web/src/lib/turnDiffTree.test.tsverifies\\path normalization into POSIX-style segments.bun lint— Not runbun typecheck— Not runNote
Medium Risk
Moderate UI/UX change in
ChatViewthat introduces new tree-building/state logic and could affect diff navigation or rendering performance, but does not touch auth/security or persistence.Overview
Changed files rendering in
ChatViewis now a hierarchical tree. The per-turn “Changed files” section switches from flat file chips to an expandable directory/file tree with per-directory aggregated+/-stats, per-file stats, and a per-turn Expand all / Collapse all control.Adds
lib/turnDiffTree.tsto normalize paths (including Windows separators), build/sort/compact directory nodes, and aggregate additions/deletions (withsummarizeTurnDiffStats), plus unit tests covering stats summarization, nesting/aggregation, missing stats handling, path normalization, and compaction behavior. Also updatesVscodeEntryIconto accept an optionalclassNamefor size/styling in the new tree UI.Written by Cursor Bugbot for commit c45ea6d. This will update automatically on new commits. Configure here.
Note
Render the Assistant message Changed files section as an expandable directory/file tree with aggregated diff stats and theme-correct icons in ChatView.tsx and MessagesTimeline.tsx
Add
ChangedFilesTreewith per-directory aggregation and expand/collapse controls, passresolvedThemefor icon rendering, and providesummarizeTurnDiffStatsandbuildTurnDiffTreeutilities in turnDiffTree.ts, with tests in turnDiffTree.test.ts.📍Where to Start
Start with
buildTurnDiffTreeandsummarizeTurnDiffStatsin turnDiffTree.ts, then reviewChangedFilesTreeintegration in ChatView.tsx.Macroscope summarized c45ea6d.