Skip to content

Render changed files as expandable tree with aggregated diff stats#172

Merged
juliusmarminge merged 3 commits intomainfrom
t3code/changed-files-tree-view
Mar 5, 2026
Merged

Render changed files as expandable tree with aggregated diff stats#172
juliusmarminge merged 3 commits intomainfrom
t3code/changed-files-tree-view

Conversation

@juliusmarminge
Copy link
Copy Markdown
Member

@juliusmarminge juliusmarminge commented Mar 5, 2026

Summary

  • Replace flat changed-file chips in ChatView with an expandable directory/file tree for each turn.
  • Add shared diff-stat helpers (summarizeTurnDiffStats, DiffStatLabel) so totals and per-node stats render consistently.
  • Introduce buildTurnDiffTree to normalize paths, build nested directory nodes, and aggregate additions/deletions up the tree.
  • Support file-level diff navigation from tree leaves while keeping directory expand/collapse state in the UI.
  • Add unit coverage for diff stat summarization, nested tree construction, missing stat handling, and Windows path normalization.

Testing

  • apps/web/src/lib/turnDiffTree.test.ts validates aggregated stats and tree structure for nested paths.
  • apps/web/src/lib/turnDiffTree.test.ts verifies files without numeric stats are preserved and excluded from totals.
  • apps/web/src/lib/turnDiffTree.test.ts verifies \\ path normalization into POSIX-style segments.
  • bun lint — Not run
  • bun typecheck — Not run

Note

Medium Risk
Moderate UI/UX change in ChatView that 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 ChatView is 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.ts to normalize paths (including Windows separators), build/sort/compact directory nodes, and aggregate additions/deletions (with summarizeTurnDiffStats), plus unit tests covering stats summarization, nesting/aggregation, missing stats handling, path normalization, and compaction behavior. Also updates VscodeEntryIcon to accept an optional className for 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 ChangedFilesTree with per-directory aggregation and expand/collapse controls, pass resolvedTheme for icon rendering, and provide summarizeTurnDiffStats and buildTurnDiffTree utilities in turnDiffTree.ts, with tests in turnDiffTree.test.ts.

📍Where to Start

Start with buildTurnDiffTree and summarizeTurnDiffStats in turnDiffTree.ts, then review ChangedFilesTree integration in ChatView.tsx.

Macroscope summarized c45ea6d.

- 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
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 5, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3b27b19c-6d02-4bf6-972b-6b999b491f03

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch t3code/changed-files-tree-view

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread apps/web/src/components/ChatView.tsx Outdated
Comment thread apps/web/src/lib/turnDiffTree.ts
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Create PR

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">

Comment thread apps/web/src/components/ChatView.tsx
Comment thread apps/web/src/components/ChatView.tsx Outdated
- 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
Comment thread apps/server/src/main.ts
Comment thread apps/server/src/attachmentPaths.ts Outdated
Comment thread apps/server/src/imageMime.ts Outdated
- 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>
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 showParentheses prop is dead code
    • Removed the unused showParentheses prop and its associated rendering logic from DiffStatLabel since no caller ever passes it.

Create PR

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)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Fix in Cursor Fix in Web

additions: number;
deletions: number;
showParentheses?: boolean;
}) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

@juliusmarminge juliusmarminge merged commit 6841fa1 into main Mar 5, 2026
5 checks passed
@juliusmarminge juliusmarminge deleted the t3code/changed-files-tree-view branch March 5, 2026 23:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant