Skip to content

Commit d2095f2

Browse files
committed
feat(web): group live tool activity
1 parent 68825d8 commit d2095f2

9 files changed

Lines changed: 1634 additions & 281 deletions

apps/web/src/components/chat/ChangedFilesTree.test.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,22 @@ describe("ChangedFilesCard", () => {
2323
expect(markup).toContain('data-changed-files-state="expanded"');
2424
expect(markup).toContain('aria-expanded="true"');
2525
expect(markup).toContain("whitespace-nowrap");
26-
expect(markup).toContain('class="group flex min-w-0 flex-1 items-center rounded-xl');
27-
expect(markup).not.toMatch(/class="group flex min-w-0 flex-1 items-center[^"]*overflow-hidden/);
28-
expect(markup).toContain('class="flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden"');
26+
expect(markup).toContain('class="flex min-w-0 items-center gap-1.5 rounded-md px-1 py-1');
2927
expect(markup).toContain('class="flex shrink-0 items-center gap-1 whitespace-nowrap');
30-
expect(markup).toContain('class="ml-1 hidden min-w-0 flex-1 truncate');
31-
expect(markup).toContain("@[24rem]/changed-files:inline");
32-
expect(markup).not.toContain("sm:inline");
33-
expect(markup).toContain('class="flex shrink-0 items-center gap-1.5 pr-1"');
28+
expect(markup).toContain('class="hidden @[24rem]/changed-files:inline">Open diff</span>');
29+
expect(markup).toContain('class="flex shrink-0 items-center gap-1.5"');
3430
expect(markup).toContain("!size-[22px]");
3531
expect(markup).toContain("size-3");
3632
expect(markup).toContain('aria-label="Collapse all folders"');
3733
expect(markup).toContain('aria-label="Open diff"');
3834
expect(markup).toContain('role="group" aria-label="2 additions, 1 deletions"');
3935
expect(markup).toContain("1 changed file");
4036
expect(markup).not.toContain("1 changed files");
37+
expect(markup).not.toContain("Hide files");
38+
expect(markup).not.toContain("ml-auto");
4139
});
4240

43-
it("renders a scope and representative-file preview for a large latest change", () => {
41+
it("renders a clean representative-file preview for a large latest change", () => {
4442
const markup = renderToStaticMarkup(
4543
<ChangedFilesCard
4644
turnId={TurnId.make("turn-1")}
@@ -67,14 +65,16 @@ describe("ChangedFilesCard", () => {
6765

6866
expect(markup).toContain('data-changed-files-state="preview"');
6967
expect(markup).toContain('aria-expanded="false"');
70-
expect(markup).toContain("apps");
71-
expect(markup).toContain("2 files");
72-
expect(markup).toContain("packages");
73-
expect(markup).toContain("root");
68+
expect(markup).toContain("apps/web/src/");
69+
expect(markup).toContain("packages/shared/src/");
7470
expect(markup).toContain("App.tsx");
7571
expect(markup).toContain("git.ts");
7672
expect(markup).toContain("README.md");
77-
expect(markup).toContain("Show all 4 files");
73+
expect(markup).not.toContain("basis-0");
74+
expect(markup).not.toContain("+1 more");
75+
expect(markup).not.toContain("Show files");
76+
expect(markup).toContain('aria-label="120 additions, 20 deletions"');
77+
expect(markup).toContain("focus-visible:ring-inset");
7878
expect(markup).not.toContain("App.test.tsx");
7979
});
8080

apps/web/src/components/chat/ChangedFilesTree.tsx

Lines changed: 42 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ import { DiffStatLabel, hasNonZeroStat } from "./DiffStatLabel";
1919
import { PierreEntryIcon } from "./PierreEntryIcon";
2020
import { Button } from "../ui/button";
2121
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
22-
import {
23-
changedFileName,
24-
selectChangedFilePreview,
25-
summarizeChangedFileScopes,
26-
} from "./changedFilesPresentation";
22+
import { changedFileName, selectChangedFilePreview } from "./changedFilesPresentation";
2723

2824
const EMPTY_DIRECTORY_OVERRIDES: Record<string, boolean> = {};
2925

@@ -50,21 +46,20 @@ export const ChangedFilesCard = memo(function ChangedFilesCard(props: {
5046
onOpenTurnDiff,
5147
} = props;
5248
const summaryStat = useMemo(() => summarizeTurnDiffStats(files), [files]);
53-
const scopeSummary = useMemo(() => summarizeChangedFileScopes(files), [files]);
5449
const previewFiles = useMemo(() => selectChangedFilePreview(files), [files]);
5550
const compactPreviewVisible = showCompactPreview && !expanded;
5651

5752
return (
5853
<div
59-
className="@container/changed-files mt-4 rounded-2xl border border-border/70 bg-secondary p-2 dark:border-transparent dark:bg-input/32"
54+
className="@container/changed-files mt-4 rounded-xl border border-border/70 bg-secondary p-1.5 dark:border-transparent dark:bg-input/32"
6055
data-changed-files-state={
6156
expanded ? "expanded" : compactPreviewVisible ? "preview" : "collapsed"
6257
}
6358
>
6459
<div
6560
data-changed-files-header=""
6661
className={cn(
67-
"flex items-center justify-between gap-2 rounded-xl",
62+
"flex items-center justify-between gap-2 rounded-lg px-1",
6863
expanded &&
6964
"sticky top-2 z-10 mb-2 bg-secondary dark:bg-[color-mix(in_srgb,var(--foreground)_2.5%,var(--background))]",
7065
)}
@@ -73,36 +68,31 @@ export const ChangedFilesCard = memo(function ChangedFilesCard(props: {
7368
type="button"
7469
aria-expanded={expanded}
7570
data-scroll-anchor-ignore
76-
className="group flex min-w-0 flex-1 items-center rounded-xl px-2 py-1.5 text-left transition-colors hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
71+
className="flex min-w-0 items-center gap-1.5 rounded-md px-1 py-1 text-left transition-colors hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
7772
onClick={() => onExpandedChange(!expanded)}
7873
>
79-
<span className="flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden">
80-
<ChevronRightIcon
81-
aria-hidden="true"
82-
className={cn(
83-
"size-3.5 shrink-0 text-muted-foreground transition-transform",
84-
expanded && "rotate-90",
85-
)}
86-
/>
87-
<span className="flex shrink-0 items-center gap-1 whitespace-nowrap font-medium text-foreground text-xs leading-4">
88-
<span>
89-
{files.length} changed file{files.length === 1 ? "" : "s"}
90-
</span>
91-
{hasNonZeroStat(summaryStat) && (
92-
<DiffStatLabel
93-
additions={summaryStat.additions}
94-
className="text-xs leading-4"
95-
deletions={summaryStat.deletions}
96-
layout="inline"
97-
/>
98-
)}
99-
</span>
100-
<span className="ml-1 hidden min-w-0 flex-1 truncate text-[11px] text-muted-foreground group-hover:text-foreground/80 @[24rem]/changed-files:inline">
101-
{expanded ? "Hide files" : "Show files"}
74+
<ChevronRightIcon
75+
aria-hidden="true"
76+
className={cn(
77+
"size-3.5 shrink-0 text-muted-foreground transition-transform",
78+
expanded && "rotate-90",
79+
)}
80+
/>
81+
<span className="flex shrink-0 items-center gap-1 whitespace-nowrap font-medium text-foreground text-xs leading-4">
82+
<span>
83+
{files.length} changed file{files.length === 1 ? "" : "s"}
10284
</span>
85+
{hasNonZeroStat(summaryStat) && (
86+
<DiffStatLabel
87+
additions={summaryStat.additions}
88+
className="text-xs leading-4"
89+
deletions={summaryStat.deletions}
90+
layout="inline"
91+
/>
92+
)}
10393
</span>
10494
</button>
105-
<div className="flex shrink-0 items-center gap-1.5 pr-1">
95+
<div className="flex shrink-0 items-center gap-1.5">
10696
{expanded ? (
10797
<Tooltip>
10898
<TooltipTrigger
@@ -160,43 +150,35 @@ export const ChangedFilesCard = memo(function ChangedFilesCard(props: {
160150
onOpenTurnDiff={onOpenTurnDiff}
161151
/>
162152
) : compactPreviewVisible ? (
163-
<div className="px-2 pb-1.5 pt-1">
164-
<p className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-[11px] text-muted-foreground">
165-
{scopeSummary.map((scope, index) => (
166-
<span key={scope.label} className="inline-flex items-center gap-1">
167-
{index > 0 ? <span aria-hidden="true">·</span> : null}
168-
<span className="font-mono text-foreground/75">{scope.label}</span>
169-
<span>
170-
{scope.fileCount} file{scope.fileCount === 1 ? "" : "s"}
171-
</span>
172-
</span>
173-
))}
174-
</p>
175-
<div className="mt-2 flex flex-wrap items-center gap-1.5">
153+
<div className="px-1.5 pb-1 pt-0.5">
154+
<div className="flex min-w-0 items-center gap-x-3 overflow-hidden">
176155
{previewFiles.map((file) => (
177156
<button
178157
key={file.path}
179158
type="button"
180159
title={file.path}
181-
className="inline-flex max-w-48 items-center gap-1 rounded-md border border-border/70 bg-background/45 px-1.5 py-1 font-mono text-[10px] text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
160+
className="group flex min-h-6 min-w-0 max-w-72 shrink items-center gap-1.5 rounded-md px-1 text-left transition-colors hover:bg-accent/45 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
182161
onClick={() => onOpenTurnDiff(turnId, file.path)}
183162
>
184163
<PierreEntryIcon
185164
pathValue={file.path}
186165
kind="file"
187166
theme={resolvedTheme}
188-
className="size-3 shrink-0 text-muted-foreground/70"
167+
className="size-3.5 shrink-0 text-muted-foreground/70"
189168
/>
190-
<span className="truncate">{changedFileName(file.path)}</span>
169+
<span className="min-w-0 truncate font-mono text-xs text-foreground/75 group-hover:text-foreground">
170+
{changedFileName(file.path)}
171+
</span>
172+
{hasNonZeroStat(file) ? (
173+
<DiffStatLabel
174+
additions={file.additions}
175+
deletions={file.deletions}
176+
layout="inline"
177+
className="shrink-0 text-[10px]"
178+
/>
179+
) : null}
191180
</button>
192181
))}
193-
<button
194-
type="button"
195-
className="rounded-md px-1.5 py-1 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
196-
onClick={() => onExpandedChange(true)}
197-
>
198-
Show all {files.length} files
199-
</button>
200182
</div>
201183
</div>
202184
) : null}
@@ -272,11 +254,11 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {
272254
) : (
273255
<FolderClosedIcon className="size-3.5 shrink-0 text-muted-foreground/75" />
274256
)}
275-
<span className="truncate font-mono text-[11px] text-muted-foreground/90 group-hover:text-foreground/90">
257+
<span className="min-w-0 truncate font-mono text-[11px] text-muted-foreground/90 group-hover:text-foreground/90">
276258
{node.name}
277259
</span>
278260
{hasNonZeroStat(node.stat) && (
279-
<span className="ml-auto shrink-0 font-mono text-[10px] tabular-nums">
261+
<span className="ml-1 shrink-0 font-mono text-[10px] tabular-nums">
280262
<DiffStatLabel additions={node.stat.additions} deletions={node.stat.deletions} />
281263
</span>
282264
)}
@@ -307,11 +289,11 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {
307289
theme={resolvedTheme}
308290
className="size-3.5 text-muted-foreground/70"
309291
/>
310-
<span className="truncate font-mono text-[11px] text-muted-foreground/80 group-hover:text-foreground/90">
292+
<span className="min-w-0 truncate font-mono text-[11px] text-muted-foreground/80 group-hover:text-foreground/90">
311293
{node.name}
312294
</span>
313295
{node.stat && (
314-
<span className="ml-auto shrink-0 font-mono text-[10px] tabular-nums">
296+
<span className="ml-1 shrink-0 font-mono text-[10px] tabular-nums">
315297
<DiffStatLabel additions={node.stat.additions} deletions={node.stat.deletions} />
316298
</span>
317299
)}

0 commit comments

Comments
 (0)