@tangle-network/ui 11.6.0 → 11.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +43 -0
  3. package/dist/chat.d.ts +18 -8
  4. package/dist/chat.js +6 -5
  5. package/dist/{chunk-XROQW5J7.js → chunk-4ENFLPWQ.js} +1 -1
  6. package/dist/{chunk-XPYPZMYT.js → chunk-6RIX7M27.js} +1 -1
  7. package/dist/{chunk-R5GVQDL3.js → chunk-CVX53NCC.js} +7 -5
  8. package/dist/{chunk-ULDNFLIM.js → chunk-EIUE5GV2.js} +10 -14
  9. package/dist/chunk-L57WRIND.js +59 -0
  10. package/dist/chunk-LFVF2HFB.js +19 -0
  11. package/dist/{chunk-AZWDI2JG.js → chunk-UYTRNR7Z.js} +1 -1
  12. package/dist/{chunk-RKC3GXHE.js → chunk-VDCZHOOO.js} +99 -104
  13. package/dist/{chunk-J2G7KCYH.js → chunk-XT4JMYVB.js} +109 -67
  14. package/dist/hooks.js +6 -5
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +15 -10
  17. package/dist/primitives.d.ts +15 -1
  18. package/dist/primitives.js +5 -1
  19. package/dist/run.d.ts +22 -17
  20. package/dist/run.js +8 -6
  21. package/dist/sdk-hooks.js +6 -5
  22. package/dist/utils.d.ts +6 -0
  23. package/dist/utils.js +1 -1
  24. package/package.json +1 -1
  25. package/src/chat/agent-timeline.stories.tsx +163 -10
  26. package/src/chat/agent-timeline.test.tsx +114 -7
  27. package/src/chat/agent-timeline.tsx +120 -158
  28. package/src/chat/chat-container.test.tsx +59 -0
  29. package/src/chat/user-message.test.tsx +33 -0
  30. package/src/chat/user-message.tsx +13 -8
  31. package/src/primitives/index.ts +3 -0
  32. package/src/primitives/text-shimmer.test.tsx +34 -0
  33. package/src/primitives/text-shimmer.tsx +82 -0
  34. package/src/run/inline-thinking-item.stories.tsx +2 -2
  35. package/src/run/inline-thinking-item.tsx +2 -2
  36. package/src/run/inline-tool-item.test.tsx +76 -0
  37. package/src/run/inline-tool-item.tsx +9 -7
  38. package/src/run/run-group.tsx +4 -3
  39. package/src/run/run-row-shell.test.tsx +74 -4
  40. package/src/run/run-row-shell.tsx +81 -53
  41. package/src/run/tool-call-step.tsx +9 -7
  42. package/src/utils/tool-display.test.ts +53 -0
  43. package/src/utils/tool-display.ts +16 -15
  44. package/dist/chunk-LQS34IGP.js +0 -0
  45. /package/dist/{chunk-EJ5NBW7V.js → chunk-LDLOJKPC.js} +0 -0
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  useSSEStream,
21
21
  useSdkSession,
22
22
  useToolCallStream
23
- } from "./chunk-XROQW5J7.js";
23
+ } from "./chunk-4ENFLPWQ.js";
24
24
  import {
25
25
  addMessage,
26
26
  addParts,
@@ -118,7 +118,7 @@ import {
118
118
  UploadProgress,
119
119
  useTheme,
120
120
  useToast
121
- } from "./chunk-EJ5NBW7V.js";
121
+ } from "./chunk-LDLOJKPC.js";
122
122
  import {
123
123
  Avatar,
124
124
  AvatarFallback,
@@ -146,28 +146,29 @@ import {
146
146
  MessageList,
147
147
  ThinkingIndicator,
148
148
  UserMessage
149
- } from "./chunk-RKC3GXHE.js";
149
+ } from "./chunk-VDCZHOOO.js";
150
150
  import {
151
151
  useAutoScroll,
152
152
  useRunCollapseState,
153
153
  useRunGroups
154
- } from "./chunk-AZWDI2JG.js";
155
- import "./chunk-LQS34IGP.js";
154
+ } from "./chunk-UYTRNR7Z.js";
155
+ import {
156
+ LiveDuration
157
+ } from "./chunk-LFVF2HFB.js";
156
158
  import {
157
159
  ToolCallFeed,
158
160
  parseToolEvent
159
- } from "./chunk-XPYPZMYT.js";
161
+ } from "./chunk-6RIX7M27.js";
160
162
  import {
161
163
  InlineThinkingItem,
162
164
  RunGroup
163
- } from "./chunk-R5GVQDL3.js";
165
+ } from "./chunk-CVX53NCC.js";
164
166
  import {
165
167
  ExpandedToolDetail,
166
168
  InlineToolItem,
167
- LiveDuration,
168
169
  RunRowShell,
169
170
  RunRowStatusDot
170
- } from "./chunk-J2G7KCYH.js";
171
+ } from "./chunk-XT4JMYVB.js";
171
172
  import {
172
173
  TOOL_CATEGORY_ICONS,
173
174
  formatBytes,
@@ -177,7 +178,7 @@ import {
177
178
  getToolDisplayMetadata,
178
179
  getToolErrorText,
179
180
  truncateText
180
- } from "./chunk-ULDNFLIM.js";
181
+ } from "./chunk-EIUE5GV2.js";
181
182
  import {
182
183
  CommandPreview,
183
184
  DiffPreview,
@@ -187,6 +188,9 @@ import {
187
188
  WebSearchPreview,
188
189
  WriteFilePreview
189
190
  } from "./chunk-AAUNOHVL.js";
191
+ import {
192
+ TextShimmer
193
+ } from "./chunk-L57WRIND.js";
190
194
  import "./chunk-RQGKSCEZ.js";
191
195
  import {
192
196
  OpenUIArtifactRenderer
@@ -364,6 +368,7 @@ export {
364
368
  TerminalDisplay,
365
369
  TerminalInput,
366
370
  TerminalLine,
371
+ TextShimmer,
367
372
  Textarea,
368
373
  ThemeToggle,
369
374
  ThinkingIndicator,
@@ -192,6 +192,20 @@ declare function SkeletonTable({ rows }: {
192
192
  rows?: number;
193
193
  }): react_jsx_runtime.JSX.Element;
194
194
 
195
+ interface TextShimmerProps {
196
+ children: ReactNode;
197
+ className?: string;
198
+ /** One sweep across the text, in milliseconds. */
199
+ durationMs?: number;
200
+ }
201
+ /**
202
+ * Inline text with a gradient-mask sweep — the in-flight signal for a run row
203
+ * label ("Thinking…", a tool verb while its call is running). Carries the
204
+ * styles it needs, so a consumer that only scans this package for Tailwind
205
+ * classes still gets the animation.
206
+ */
207
+ declare function TextShimmer({ children, className, durationMs, }: TextShimmerProps): react_jsx_runtime.JSX.Element;
208
+
195
209
  interface Toast {
196
210
  id: string;
197
211
  title: string;
@@ -506,4 +520,4 @@ interface FilterFieldProps extends React$1.HTMLAttributes<HTMLDivElement> {
506
520
  }
507
521
  declare const FilterField: React$1.ForwardRefExoticComponent<FilterFieldProps & React$1.RefAttributes<HTMLDivElement>>;
508
522
 
509
- export { Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, FilterField, type FilterFieldProps, InlineCode, type InlineCodeProps, Input, type InputProps, Label, Metric, type MetricProps, MetricStrip, type MetricStripProps, PageHeader, type PageHeaderProps, Progress, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, Textarea, type TextareaProps, ThemeToggle, type Toast, ToastContainer, ToastProvider, Toolbar, type ToolbarProps, type UploadFile, UploadProgress, type UploadProgressProps, badgeVariants, useTheme, useToast };
523
+ export { Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, FilterField, type FilterFieldProps, InlineCode, type InlineCodeProps, Input, type InputProps, Label, Metric, type MetricProps, MetricStrip, type MetricStripProps, PageHeader, type PageHeaderProps, Progress, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, type Toast, ToastContainer, ToastProvider, Toolbar, type ToolbarProps, type UploadFile, UploadProgress, type UploadProgressProps, badgeVariants, useTheme, useToast };
@@ -50,7 +50,7 @@ import {
50
50
  UploadProgress,
51
51
  useTheme,
52
52
  useToast
53
- } from "./chunk-EJ5NBW7V.js";
53
+ } from "./chunk-LDLOJKPC.js";
54
54
  import {
55
55
  Avatar,
56
56
  AvatarFallback,
@@ -71,6 +71,9 @@ import {
71
71
  DropdownMenuSubTrigger,
72
72
  DropdownMenuTrigger
73
73
  } from "./chunk-34A66VBG.js";
74
+ import {
75
+ TextShimmer
76
+ } from "./chunk-L57WRIND.js";
74
77
  import {
75
78
  Badge,
76
79
  Card,
@@ -194,6 +197,7 @@ export {
194
197
  TerminalDisplay,
195
198
  TerminalInput,
196
199
  TerminalLine,
200
+ TextShimmer,
197
201
  Textarea,
198
202
  ThemeToggle,
199
203
  ToastContainer,
package/dist/run.d.ts CHANGED
@@ -30,28 +30,31 @@ declare const RunGroup: React.MemoExoticComponent<({ run, partMap, collapsed, on
30
30
 
31
31
  type RunRowStatus = "running" | "success" | "error" | "idle";
32
32
  /**
33
- * Trailing status indicator shared by every run row: a spinner while running,
34
- * a green/red dot on terminal states, nothing when idle. Status is shown here
35
- * — it never replaces the row's semantic lead icon.
33
+ * Trailing status indicator shared by every run row. Only `error` draws a
34
+ * mark (a red dot). Success is silence, and a running row carries its state in
35
+ * the title — a `TextShimmer` sweep — so neither renders anything here.
36
36
  */
37
37
  declare function RunRowStatusDot({ status }: {
38
38
  status: RunRowStatus;
39
39
  }): react_jsx_runtime.JSX.Element | null;
40
40
  interface RunRowShellProps {
41
- /** Semantic lead glyph — always visible; never replaced by status. */
41
+ /** Semantic lead glyph (14px). At rest it is the row's only ornament; on
42
+ * hover and while open the expand chevron takes its slot. */
42
43
  icon: ReactNode;
43
44
  /** Row title. A string in the common case; a node when the title carries its
44
- * own treatment (e.g. an active-state shimmer sweep) — it renders inline in
45
- * the shell's title slot either way. */
45
+ * own treatment — a running row passes `<TextShimmer>` here, which is the
46
+ * whole in-flight signal (the shell draws no spinner). */
46
47
  title: ReactNode;
47
48
  /** Secondary inline text (tool path/command, or a reasoning preview). */
48
49
  description?: string;
49
50
  /** Render the description in mono (tools) vs prose (reasoning). */
50
51
  descriptionMono?: boolean;
51
52
  status?: RunRowStatus;
52
- /** Live-ticks a duration while running. */
53
+ /** @deprecated The shell no longer ticks a live duration while running; the
54
+ * running state lives in the title (`TextShimmer`). Accepted and ignored so
55
+ * existing callers keep compiling. */
53
56
  startTime?: number;
54
- /** Static duration shown once the row is no longer running. */
57
+ /** Static duration, revealed on hover once the row is no longer running. */
55
58
  durationMs?: number;
56
59
  /** Radius shaping when rows are stacked into a group. */
57
60
  groupPosition?: "single" | "first" | "middle" | "last";
@@ -68,12 +71,13 @@ interface RunRowShellProps {
68
71
  children?: ReactNode;
69
72
  }
70
73
  /**
71
- * Shared shell for the agent activity rows (reasoning + tool). Owns the
72
- * bordered container, the lead icon badge, title/description, the trailing
73
- * meta cluster (duration + status + chevron) and the collapsible body, so
74
- * both row kinds read as one family instead of two bespoke layouts.
74
+ * Shared shell for the agent activity rows (reasoning + tool). A quiet
75
+ * one-liner at rest — bare glyph, muted title and description, no fill, no
76
+ * border — that becomes a bordered card only while open. The border is always
77
+ * present (transparent at rest) so hover and expand never shift layout.
78
+ * Both row kinds render through it so they read as one family.
75
79
  */
76
- declare function RunRowShell({ icon, title, description, descriptionMono, status, startTime, durationMs, groupPosition, collapsedError, actions, open: openProp, onOpenChange, defaultOpen, className, contentClassName, children, }: RunRowShellProps): react_jsx_runtime.JSX.Element;
80
+ declare function RunRowShell({ icon, title, description, descriptionMono, status, durationMs, groupPosition, collapsedError, actions, open: openProp, onOpenChange, defaultOpen, className, contentClassName, children, }: RunRowShellProps): react_jsx_runtime.JSX.Element;
77
81
 
78
82
  interface InlineToolItemProps {
79
83
  part: ToolPart;
@@ -88,10 +92,11 @@ interface InlineToolItemProps {
88
92
  description?: string;
89
93
  }
90
94
  /**
91
- * Compact single-line tool call display. Shows a category icon, title,
92
- * description, duration and status, and expands on click to show
93
- * ExpandedToolDetail. Rendered through the shared RunRowShell so it stays
94
- * consistent with the reasoning row.
95
+ * Compact single-line tool call display: a category glyph, the tool verb, and
96
+ * the path or command it acted on. While the call runs the verb shimmers;
97
+ * once it completes the row goes quiet (duration on hover, a red dot only on
98
+ * error). Expands on click to show ExpandedToolDetail. Rendered through the
99
+ * shared RunRowShell so it stays consistent with the reasoning row.
95
100
  */
96
101
  declare const InlineToolItem: React.MemoExoticComponent<({ part, renderToolDetail, groupPosition, className, contentClassName, actions, title: titleOverride, description: descriptionOverride, }: InlineToolItemProps) => react_jsx_runtime.JSX.Element>;
97
102
 
package/dist/run.js CHANGED
@@ -1,21 +1,23 @@
1
- import "./chunk-LQS34IGP.js";
1
+ import {
2
+ LiveDuration
3
+ } from "./chunk-LFVF2HFB.js";
2
4
  import {
3
5
  ToolCallFeed,
4
6
  parseToolEvent
5
- } from "./chunk-XPYPZMYT.js";
7
+ } from "./chunk-6RIX7M27.js";
6
8
  import {
7
9
  InlineThinkingItem,
8
10
  RunGroup
9
- } from "./chunk-R5GVQDL3.js";
11
+ } from "./chunk-CVX53NCC.js";
10
12
  import {
11
13
  ExpandedToolDetail,
12
14
  InlineToolItem,
13
- LiveDuration,
14
15
  RunRowShell,
15
16
  RunRowStatusDot
16
- } from "./chunk-J2G7KCYH.js";
17
- import "./chunk-ULDNFLIM.js";
17
+ } from "./chunk-XT4JMYVB.js";
18
+ import "./chunk-EIUE5GV2.js";
18
19
  import "./chunk-AAUNOHVL.js";
20
+ import "./chunk-L57WRIND.js";
19
21
  import "./chunk-3M7BPSSW.js";
20
22
  import "./chunk-GYPQXTJU.js";
21
23
  import "./chunk-MKTSMWVD.js";
package/dist/sdk-hooks.js CHANGED
@@ -5,17 +5,18 @@ import {
5
5
  useSSEStream,
6
6
  useSdkSession,
7
7
  useToolCallStream
8
- } from "./chunk-XROQW5J7.js";
8
+ } from "./chunk-4ENFLPWQ.js";
9
9
  import "./chunk-OEX7NZE3.js";
10
10
  import {
11
11
  useAutoScroll,
12
12
  useRunCollapseState,
13
13
  useRunGroups
14
- } from "./chunk-AZWDI2JG.js";
15
- import "./chunk-XPYPZMYT.js";
16
- import "./chunk-J2G7KCYH.js";
17
- import "./chunk-ULDNFLIM.js";
14
+ } from "./chunk-UYTRNR7Z.js";
15
+ import "./chunk-6RIX7M27.js";
16
+ import "./chunk-XT4JMYVB.js";
17
+ import "./chunk-EIUE5GV2.js";
18
18
  import "./chunk-AAUNOHVL.js";
19
+ import "./chunk-L57WRIND.js";
19
20
  import "./chunk-ZRVH3WCA.js";
20
21
  import "./chunk-P5F43A65.js";
21
22
  import "./chunk-WUQDUBJG.js";
package/dist/utils.d.ts CHANGED
@@ -36,6 +36,12 @@ declare function timeAgo(ts: number): string;
36
36
 
37
37
  declare const TOOL_CATEGORY_ICONS: Record<ToolCategory, string>;
38
38
  declare function getToolCategory(toolName: string): ToolCategory;
39
+ /**
40
+ * Title is the bare verb ("Read", "Edit", "Shell"); the path, command, pattern
41
+ * or query lives in `description` only, so a row prints its subject once.
42
+ * `targetPath` / `commandSnippet` carry the untruncated value for consumers
43
+ * that need it (diff headers, expanded detail).
44
+ */
39
45
  declare function getToolDisplayMetadata(part: ToolPart): ToolDisplayMetadata;
40
46
  /** Extract error text from a tool part, if any. */
41
47
  declare function getToolErrorText(part: ToolPart, fallback?: string): string | undefined;
package/dist/utils.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  getToolDisplayMetadata,
12
12
  getToolErrorText,
13
13
  truncateText
14
- } from "./chunk-ULDNFLIM.js";
14
+ } from "./chunk-EIUE5GV2.js";
15
15
  import {
16
16
  cn
17
17
  } from "./chunk-RQHJBTEU.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.6.0",
3
+ "version": "11.7.0",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,6 +3,9 @@ import { useState } from 'react'
3
3
  import { Code, FileText } from 'lucide-react'
4
4
  import { AgentTimeline } from './agent-timeline'
5
5
  import type { AgentTimelineItem } from './agent-timeline'
6
+ import { ChatContainer } from './chat-container'
7
+ import type { SessionMessage } from '../types/message'
8
+ import type { SessionPart } from '../types/parts'
6
9
 
7
10
  const meta: Meta<typeof AgentTimeline> = {
8
11
  title: 'Chat/AgentTimeline',
@@ -52,8 +55,9 @@ const codingSession: AgentTimelineItem[] = [
52
55
  call: {
53
56
  id: 'tc-1',
54
57
  type: 'glob',
55
- label: 'Find **/*.ts in src/',
58
+ label: 'Find',
56
59
  status: 'success',
60
+ detail: 'src/**/*.ts',
57
61
  output:
58
62
  'src/index.ts\nsrc/types.ts\nsrc/utils/format.ts\nsrc/utils/parse.ts',
59
63
  duration: 18,
@@ -65,8 +69,9 @@ const codingSession: AgentTimelineItem[] = [
65
69
  call: {
66
70
  id: 'tc-2',
67
71
  type: 'read',
68
- label: 'Read src/types.ts',
72
+ label: 'Read',
69
73
  status: 'success',
74
+ detail: 'src/types.ts',
70
75
  output:
71
76
  '// Existing types\nexport type Maybe<T> = T | null | undefined\nexport type Nullable<T> = T | null',
72
77
  duration: 31,
@@ -86,8 +91,9 @@ const codingSession: AgentTimelineItem[] = [
86
91
  call: {
87
92
  id: 'tc-3',
88
93
  type: 'write',
89
- label: 'Write src/types.ts',
94
+ label: 'Write',
90
95
  status: 'success',
96
+ detail: 'src/types.ts',
91
97
  output: 'File written — 47 lines added.',
92
98
  duration: 22,
93
99
  },
@@ -98,7 +104,7 @@ const codingSession: AgentTimelineItem[] = [
98
104
  call: {
99
105
  id: 'tc-4',
100
106
  type: 'bash',
101
- label: 'npx tsc --noEmit',
107
+ label: 'Shell',
102
108
  status: 'success',
103
109
  detail: 'npx tsc --noEmit --strict',
104
110
  output: '✓ No type errors',
@@ -201,8 +207,9 @@ export const WhileThinking: Story = {
201
207
  call: {
202
208
  id: 'tc-1',
203
209
  type: 'glob',
204
- label: 'Find **/*.ts in src/',
210
+ label: 'Find',
205
211
  status: 'success',
212
+ detail: 'src/**/*.ts',
206
213
  duration: 14,
207
214
  },
208
215
  },
@@ -234,24 +241,27 @@ export const WithToolGroup: Story = {
234
241
  {
235
242
  id: 'tc-1',
236
243
  type: 'read',
237
- label: 'Read src/routes/users.ts',
244
+ label: 'Read',
238
245
  status: 'success',
246
+ detail: 'src/routes/users.ts',
239
247
  output: "router.get('/', authMiddleware, listUsers)\nrouter.post('/', authMiddleware, createUser)",
240
248
  duration: 24,
241
249
  },
242
250
  {
243
251
  id: 'tc-2',
244
252
  type: 'read',
245
- label: 'Read src/routes/sessions.ts',
253
+ label: 'Read',
246
254
  status: 'success',
255
+ detail: 'src/routes/sessions.ts',
247
256
  output: "router.get('/', listSessions) // missing auth!\nrouter.delete('/:id', authMiddleware, deleteSession)",
248
257
  duration: 19,
249
258
  },
250
259
  {
251
260
  id: 'tc-3',
252
261
  type: 'read',
253
- label: 'Read src/routes/health.ts',
262
+ label: 'Read',
254
263
  status: 'success',
264
+ detail: 'src/routes/health.ts',
255
265
  output: "router.get('/', healthCheck) // intentionally public",
256
266
  duration: 11,
257
267
  },
@@ -305,7 +315,7 @@ export const WithError: Story = {
305
315
  call: {
306
316
  id: 'tc-1',
307
317
  type: 'bash',
308
- label: 'pnpm test',
318
+ label: 'Shell',
309
319
  status: 'error',
310
320
  detail: 'pnpm test --run',
311
321
  output:
@@ -351,8 +361,9 @@ export const WithArtifact: Story = {
351
361
  call: {
352
362
  id: 'tc-1',
353
363
  type: 'write',
354
- label: 'Write openapi.yaml',
364
+ label: 'Write',
355
365
  status: 'success',
366
+ detail: 'openapi.yaml',
356
367
  output: '187 lines written.',
357
368
  duration: 44,
358
369
  },
@@ -438,3 +449,145 @@ export const StatusTones: Story = {
438
449
  ],
439
450
  },
440
451
  }
452
+
453
+ // ---------------------------------------------------------------------------
454
+ // Full transcript through ChatContainer — the real consumer path. Tool titles
455
+ // and descriptions come from `getToolDisplayMetadata`, so this story shows the
456
+ // row grammar a product sees (not hand-written labels): prose, reasoning, a
457
+ // tool sequence, a running row, an error row, and a streaming reply.
458
+ // ---------------------------------------------------------------------------
459
+
460
+ const transcriptMessages: SessionMessage[] = [
461
+ { id: 'u-1', role: 'user', time: { created: NOW - 240_000 } },
462
+ { id: 'a-1', role: 'assistant', time: { created: NOW - 230_000 } },
463
+ { id: 'u-2', role: 'user', time: { created: NOW - 60_000 } },
464
+ { id: 'a-2', role: 'assistant', time: { created: NOW - 50_000 } },
465
+ ]
466
+
467
+ const transcriptParts: Record<string, SessionPart[]> = {
468
+ 'u-1': [
469
+ {
470
+ type: 'text',
471
+ text: 'The retry loop in `src/batch-writer.ts` hammers the API when the token expires. Add jittered backoff and make sure the tests cover it.',
472
+ },
473
+ ],
474
+ 'a-1': [
475
+ {
476
+ type: 'reasoning',
477
+ text: 'The loop retries immediately on 401. I should read the writer, find where the delay is computed, add exponential backoff with jitter, and extend the existing test.',
478
+ time: { start: NOW - 229_000, end: NOW - 226_000 },
479
+ },
480
+ {
481
+ type: 'tool',
482
+ id: 't-1',
483
+ tool: 'read',
484
+ state: {
485
+ status: 'completed',
486
+ input: { file_path: 'src/batch-writer.ts' },
487
+ output: 'export async function flush(batch: Row[]) {\n for (let attempt = 0; attempt < 5; attempt++) {\n try { return await post(batch) } catch (err) { /* retry */ }\n }\n}',
488
+ time: { start: NOW - 225_000, end: NOW - 224_960 },
489
+ },
490
+ },
491
+ {
492
+ type: 'tool',
493
+ id: 't-2',
494
+ tool: 'grep',
495
+ state: {
496
+ status: 'completed',
497
+ input: { pattern: 'flush\\(', path: 'src/' },
498
+ output: 'src/batch-writer.ts:12\nsrc/batch-writer.test.ts:8\nsrc/batch-writer.test.ts:31',
499
+ time: { start: NOW - 224_000, end: NOW - 223_970 },
500
+ },
501
+ },
502
+ {
503
+ type: 'tool',
504
+ id: 't-3',
505
+ tool: 'edit',
506
+ state: {
507
+ status: 'completed',
508
+ input: {
509
+ file_path: 'src/batch-writer.ts',
510
+ old_string: '} catch (err) { /* retry */ }',
511
+ new_string: '} catch (err) {\n await sleep(backoff(attempt))\n }',
512
+ },
513
+ output: 'Applied 1 edit',
514
+ time: { start: NOW - 223_000, end: NOW - 222_980 },
515
+ },
516
+ },
517
+ {
518
+ type: 'tool',
519
+ id: 't-4',
520
+ tool: 'bash',
521
+ state: {
522
+ status: 'completed',
523
+ input: { command: 'pnpm vitest run src/batch-writer.test.ts' },
524
+ output: ' ✓ src/batch-writer.test.ts (3)\n\nTest Files 1 passed (1)\n Tests 3 passed (3)',
525
+ time: { start: NOW - 222_000, end: NOW - 219_600 },
526
+ },
527
+ },
528
+ {
529
+ type: 'text',
530
+ text: 'Added `backoff(attempt)` — exponential from 200ms with ±25% jitter, capped at 5s — and the writer now waits before each retry. The existing three tests still pass.',
531
+ },
532
+ ],
533
+ 'u-2': [
534
+ {
535
+ type: 'text',
536
+ text: 'Now add a test that proves the jitter actually spreads the retries.',
537
+ },
538
+ ],
539
+ 'a-2': [
540
+ {
541
+ type: 'tool',
542
+ id: 't-5',
543
+ tool: 'write',
544
+ state: {
545
+ status: 'completed',
546
+ input: {
547
+ file_path: 'src/batch-writer.jitter.test.ts',
548
+ content: "import { backoff } from './batch-writer'\n\ntest('jitter spreads retries', () => {\n const delays = new Set(Array.from({ length: 50 }, () => backoff(2)))\n expect(delays.size).toBeGreaterThan(10)\n})",
549
+ },
550
+ output: 'File written',
551
+ time: { start: NOW - 49_000, end: NOW - 48_970 },
552
+ },
553
+ },
554
+ {
555
+ type: 'tool',
556
+ id: 't-6',
557
+ tool: 'bash',
558
+ state: {
559
+ status: 'error',
560
+ input: { command: 'pnpm vitest run src/batch-writer.jitter.test.ts' },
561
+ error: "Cannot find module './batch-writer' — did you mean './batch-writer.ts'? (resolve.extensions does not include .ts in this config)",
562
+ time: { start: NOW - 48_000, end: NOW - 46_800 },
563
+ },
564
+ },
565
+ {
566
+ type: 'tool',
567
+ id: 't-7',
568
+ tool: 'bash',
569
+ state: {
570
+ status: 'running',
571
+ input: { command: 'pnpm vitest run src/batch-writer.jitter.test.ts --config vitest.config.ts' },
572
+ time: { start: NOW - 3_000 },
573
+ },
574
+ },
575
+ {
576
+ type: 'text',
577
+ text: 'The first run failed on module resolution — the test runner was picking up the root config. Re-running with the package config',
578
+ },
579
+ ],
580
+ }
581
+
582
+ export const FullTranscript: Story = {
583
+ render: () => (
584
+ <div className="h-screen">
585
+ <ChatContainer
586
+ presentation="timeline"
587
+ messages={transcriptMessages}
588
+ partMap={transcriptParts}
589
+ isStreaming
590
+ />
591
+ </div>
592
+ ),
593
+ }