Changed files ({changedFileCountLabel})
- {(summaryStat.additions > 0 || summaryStat.deletions > 0) && (
+ {hasNonZeroStat(summaryStat) && (
<>
•
- +{summaryStat.additions}
- /
- -{summaryStat.deletions}
+
>
)}
@@ -3572,40 +3685,12 @@ const MessagesTimeline = memo(function MessagesTimeline({
View diff
-
- {checkpointFiles.map((file) => (
-
- ))}
-
+
);
})()}
diff --git a/apps/web/src/lib/turnDiffTree.test.ts b/apps/web/src/lib/turnDiffTree.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f852e40c61f8fea829f06ab337d8cee0466c6f5f
--- /dev/null
+++ b/apps/web/src/lib/turnDiffTree.test.ts
@@ -0,0 +1,129 @@
+import { describe, expect, it } from "vitest";
+
+import { buildTurnDiffTree, summarizeTurnDiffStats } from "./turnDiffTree";
+
+describe("summarizeTurnDiffStats", () => {
+ it("sums only files with numeric additions/deletions", () => {
+ const stat = summarizeTurnDiffStats([
+ { path: "README.md", additions: 3, deletions: 1 },
+ { path: "docs/notes.md" },
+ { path: "src/index.ts", additions: 5, deletions: 2 },
+ ]);
+
+ expect(stat).toEqual({ additions: 8, deletions: 3 });
+ });
+});
+
+describe("buildTurnDiffTree", () => {
+ it("builds nested directory nodes with aggregated stats", () => {
+ const tree = buildTurnDiffTree([
+ { path: "src/index.ts", additions: 2, deletions: 1 },
+ { path: "src/components/Button.tsx", additions: 4, deletions: 2 },
+ { path: "README.md", additions: 1, deletions: 0 },
+ ]);
+
+ expect(tree).toEqual([
+ {
+ kind: "directory",
+ name: "src",
+ path: "src",
+ stat: { additions: 6, deletions: 3 },
+ children: [
+ {
+ kind: "directory",
+ name: "components",
+ path: "src/components",
+ stat: { additions: 4, deletions: 2 },
+ children: [
+ {
+ kind: "file",
+ name: "Button.tsx",
+ path: "src/components/Button.tsx",
+ stat: { additions: 4, deletions: 2 },
+ },
+ ],
+ },
+ {
+ kind: "file",
+ name: "index.ts",
+ path: "src/index.ts",
+ stat: { additions: 2, deletions: 1 },
+ },
+ ],
+ },
+ {
+ kind: "file",
+ name: "README.md",
+ path: "README.md",
+ stat: { additions: 1, deletions: 0 },
+ },
+ ]);
+ });
+
+ it("keeps files without stat values and excludes them from directory totals", () => {
+ const tree = buildTurnDiffTree([
+ { path: "docs/notes.md" },
+ { path: "docs/todo.md", additions: 1, deletions: 1 },
+ ]);
+
+ expect(tree).toEqual([
+ {
+ kind: "directory",
+ name: "docs",
+ path: "docs",
+ stat: { additions: 1, deletions: 1 },
+ children: [
+ {
+ kind: "file",
+ name: "notes.md",
+ path: "docs/notes.md",
+ stat: null,
+ },
+ {
+ kind: "file",
+ name: "todo.md",
+ path: "docs/todo.md",
+ stat: { additions: 1, deletions: 1 },
+ },
+ ],
+ },
+ ]);
+ });
+
+ it("normalizes file paths with windows separators", () => {
+ const tree = buildTurnDiffTree([{ path: "apps\\web\\src\\index.ts", additions: 2, deletions: 1 }]);
+
+ expect(tree).toEqual([
+ {
+ kind: "directory",
+ name: "apps",
+ path: "apps",
+ stat: { additions: 2, deletions: 1 },
+ children: [
+ {
+ kind: "directory",
+ name: "web",
+ path: "apps/web",
+ stat: { additions: 2, deletions: 1 },
+ children: [
+ {
+ kind: "directory",
+ name: "src",
+ path: "apps/web/src",
+ stat: { additions: 2, deletions: 1 },
+ children: [
+ {
+ kind: "file",
+ name: "index.ts",
+ path: "apps/web/src/index.ts",
+ stat: { additions: 2, deletions: 1 },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ]);
+ });
+});
diff --git a/apps/web/src/lib/turnDiffTree.ts b/apps/web/src/lib/turnDiffTree.ts
new file mode 100644
index 0000000000000000000000000000000000000000..126f96783af4aa8214617e6120b4c83109782c78
--- /dev/null
+++ b/apps/web/src/lib/turnDiffTree.ts
@@ -0,0 +1,148 @@
+import type { TurnDiffFileChange } from "../types";
+
+export interface TurnDiffStat {
+ additions: number;
+ deletions: number;
+}
+
+export interface TurnDiffTreeDirectoryNode {
+ kind: "directory";
+ name: string;
+ path: string;
+ stat: TurnDiffStat;
+ children: TurnDiffTreeNode[];
+}
+
+export interface TurnDiffTreeFileNode {
+ kind: "file";
+ name: string;
+ path: string;
+ stat: TurnDiffStat | null;
+}
+
+export type TurnDiffTreeNode = TurnDiffTreeDirectoryNode | TurnDiffTreeFileNode;
+
+interface MutableDirectoryNode {
+ name: string;
+ path: string;
+ stat: TurnDiffStat;
+ directories: Map