@tangle-network/ui 6.0.0 → 7.0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @tangle-network/ui
2
2
 
3
+ ## 7.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 46592b3: Calmer chat/run design + named multi-theme system.
8
+
9
+ - `ChatMessage`/`RunGroup`: role labels move above the bubble (plain text-xs), avatar circles removed (`avatar`/`hideAvatar` are deprecated no-ops), `InlineToolItem` rows are taller with quiet inline failed/running text instead of uppercase pills. `ToolCallStep`/`ToolCallFeed` stories leave Storybook (source adapters remain).
10
+ - `@tangle-network/brand` adds `themes.css`: `[data-theme]` scopes (`aubergine`, `aubergine-light`, `arena`, `arena-light`, `tangle-light`) that re-skin every component through the `@theme` semantic mappings, plus a `Foundations/Theme Showcase` story.
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [46592b3]
15
+ - @tangle-network/brand@0.8.0
16
+
3
17
  ## 6.0.0
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Generic React UI components for Tangle products. Imports tokens from `@tangle-network/brand` (peer dep). Tailwind v4-native.
4
4
 
5
+ Component visual precedent is governed by the repo-level [brand guidelines](../../docs/brand-guidelines.md) and [component audit](../../docs/component-audit.md). Storybook examples are test surfaces; they are not automatically approved brand patterns.
6
+
5
7
  ## Install
6
8
 
7
9
  ```sh
package/dist/chat.d.ts CHANGED
@@ -158,12 +158,12 @@ interface ChatMessageProps {
158
158
  assistantLabel?: string;
159
159
  /** Hide the role label row entirely */
160
160
  hideRoleLabel?: boolean;
161
- /** Hide the avatar icon */
161
+ /** @deprecated Avatars were removed from the bubble design; this prop is ignored. */
162
162
  hideAvatar?: boolean;
163
- /** Custom avatar element (replaces default User/Bot icon) */
163
+ /** @deprecated Avatars were removed from the bubble design; this prop is ignored. */
164
164
  avatar?: ReactNode;
165
165
  }
166
- declare function ChatMessage({ role, content, toolCalls, isStreaming, timestamp, className, userLabel, assistantLabel, hideRoleLabel, hideAvatar, avatar, }: ChatMessageProps): react_jsx_runtime.JSX.Element;
166
+ declare function ChatMessage({ role, content, toolCalls, isStreaming, timestamp, className, userLabel, assistantLabel, hideRoleLabel, }: ChatMessageProps): react_jsx_runtime.JSX.Element;
167
167
 
168
168
  interface ThinkingIndicatorProps {
169
169
  className?: string;
package/dist/chat.js CHANGED
@@ -6,10 +6,10 @@ import {
6
6
  MessageList,
7
7
  ThinkingIndicator,
8
8
  UserMessage
9
- } from "./chunk-SJ6IL4HI.js";
9
+ } from "./chunk-5CS3I7Y3.js";
10
10
  import "./chunk-AZWDI2JG.js";
11
- import "./chunk-LASW7CYH.js";
12
- import "./chunk-EOGJX2TU.js";
11
+ import "./chunk-QIRVZMQY.js";
12
+ import "./chunk-RKQDBRTC.js";
13
13
  import "./chunk-ULDNFLIM.js";
14
14
  import "./chunk-AAUNOHVL.js";
15
15
  import "./chunk-52Y3FMFI.js";
@@ -6,11 +6,11 @@ import {
6
6
  import {
7
7
  InlineThinkingItem,
8
8
  RunGroup
9
- } from "./chunk-LASW7CYH.js";
9
+ } from "./chunk-QIRVZMQY.js";
10
10
  import {
11
11
  ToolCallGroup,
12
12
  ToolCallStep
13
- } from "./chunk-EOGJX2TU.js";
13
+ } from "./chunk-RKQDBRTC.js";
14
14
  import {
15
15
  getToolDisplayMetadata
16
16
  } from "./chunk-ULDNFLIM.js";
@@ -930,7 +930,6 @@ var ChatContainer = memo3(
930
930
  ChatContainer.displayName = "ChatContainer";
931
931
 
932
932
  // src/chat/chat-message.tsx
933
- import { User, Bot } from "lucide-react";
934
933
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
935
934
  function ChatMessage({
936
935
  role,
@@ -941,31 +940,22 @@ function ChatMessage({
941
940
  className,
942
941
  userLabel = "You",
943
942
  assistantLabel = "Agent",
944
- hideRoleLabel,
945
- hideAvatar,
946
- avatar
943
+ hideRoleLabel
947
944
  }) {
948
945
  const isUser = role === "user";
949
946
  return /* @__PURE__ */ jsxs6(
950
947
  "div",
951
948
  {
952
949
  className: cn(
953
- "flex gap-3",
954
- isUser ? "flex-row-reverse" : "flex-row",
950
+ "flex flex-col gap-1",
951
+ isUser ? "items-end" : "items-start",
955
952
  className
956
953
  ),
957
954
  children: [
958
- !hideAvatar && (avatar ? /* @__PURE__ */ jsx7("div", { className: "mt-0.5 shrink-0", children: avatar }) : /* @__PURE__ */ jsx7(
959
- "div",
960
- {
961
- className: cn(
962
- "mt-0.5 flex shrink-0 items-center justify-center rounded-[calc(var(--radius-md)+2px)] border",
963
- "h-[var(--avatar-size)] w-[var(--avatar-size)]",
964
- isUser ? "border-border bg-[var(--accent-surface-soft)] text-[var(--accent-text)]" : "border-border bg-muted text-[var(--brand-cool)]"
965
- ),
966
- children: isUser ? /* @__PURE__ */ jsx7(User, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx7(Bot, { className: "h-3.5 w-3.5" })
967
- }
968
- )),
955
+ !hideRoleLabel && /* @__PURE__ */ jsxs6("div", { className: cn("flex items-center gap-2 px-1", isUser && "flex-row-reverse"), children: [
956
+ /* @__PURE__ */ jsx7("span", { className: "font-medium text-foreground text-xs", children: isUser ? userLabel : assistantLabel }),
957
+ timestamp && /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground text-xs", children: formatTime2(timestamp) })
958
+ ] }),
969
959
  /* @__PURE__ */ jsxs6(
970
960
  "div",
971
961
  {
@@ -975,10 +965,6 @@ function ChatMessage({
975
965
  isUser ? "border-border bg-muted/50" : "border-border bg-card"
976
966
  ),
977
967
  children: [
978
- !hideRoleLabel && /* @__PURE__ */ jsxs6("div", { className: cn("flex items-center gap-2", isUser && "flex-row-reverse"), children: [
979
- /* @__PURE__ */ jsx7("span", { className: "text-[var(--font-size-xs)] font-[var(--chat-label-weight,600)] uppercase tracking-[var(--chat-label-tracking,0.14em)] text-foreground", children: isUser ? userLabel : assistantLabel }),
980
- timestamp && /* @__PURE__ */ jsx7("span", { className: "text-[var(--font-size-xs)] text-muted-foreground", children: formatTime2(timestamp) })
981
- ] }),
982
968
  isUser ? /* @__PURE__ */ jsx7("div", { className: "whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)] text-foreground", children: content }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
983
969
  content && /* @__PURE__ */ jsx7(Markdown, { className: "tangle-prose text-[var(--font-size-base)] leading-[var(--line-height-base)]", children: content }),
984
970
  isStreaming && /* @__PURE__ */ jsx7("span", { className: "ml-0.5 inline-block h-4 w-2 animate-pulse rounded-sm bg-[var(--brand-cool)] align-text-bottom" })
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-OEX7NZE3.js";
12
12
  import {
13
13
  parseToolEvent
14
- } from "./chunk-O6NUUCT2.js";
14
+ } from "./chunk-IWQZXL6A.js";
15
15
 
16
16
  // src/hooks/use-dropdown-menu.ts
17
17
  import { useEffect, useRef, useState } from "react";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ToolCallGroup,
3
3
  ToolCallStep
4
- } from "./chunk-EOGJX2TU.js";
4
+ } from "./chunk-RKQDBRTC.js";
5
5
  import {
6
6
  Markdown
7
7
  } from "./chunk-FJBTCTZM.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  InlineToolItem,
3
3
  LiveDuration
4
- } from "./chunk-EOGJX2TU.js";
4
+ } from "./chunk-RKQDBRTC.js";
5
5
  import {
6
6
  formatDuration,
7
7
  truncateText
@@ -142,18 +142,15 @@ function AssistantShell({
142
142
  isStreaming,
143
143
  children
144
144
  }) {
145
- return /* @__PURE__ */ jsxs2("div", { className: "flex gap-2.5", children: [
146
- /* @__PURE__ */ jsx2("div", { className: "mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--brand-primary)] text-white", children: /* @__PURE__ */ jsx2(Bot, { className: "h-3.5 w-3.5" }) }),
147
- /* @__PURE__ */ jsxs2("div", { className: ASSISTANT_SHELL, children: [
148
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-[var(--text-muted)]", children: [
149
- /* @__PURE__ */ jsx2("span", { children: branding.label }),
150
- isStreaming ? /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-1.5 text-[var(--text-muted)]", children: [
151
- /* @__PURE__ */ jsx2(Loader2, { className: "h-3 w-3 animate-spin" }),
152
- "Thinking"
153
- ] }) : null
154
- ] }),
155
- children
156
- ] })
145
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1", children: [
146
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 px-1 font-medium text-muted-foreground text-xs", children: [
147
+ /* @__PURE__ */ jsx2("span", { children: branding.label }),
148
+ isStreaming ? /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-1.5", children: [
149
+ /* @__PURE__ */ jsx2(Loader2, { className: "h-3 w-3 animate-spin" }),
150
+ "Thinking"
151
+ ] }) : null
152
+ ] }),
153
+ /* @__PURE__ */ jsx2("div", { className: ASSISTANT_SHELL, children })
157
154
  ] });
158
155
  }
159
156
  var CATEGORY_ICON_MAP = {
@@ -378,16 +375,7 @@ var RunGroup = memo2(
378
375
  "bg-transparent hover:bg-transparent"
379
376
  ),
380
377
  children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
381
- /* @__PURE__ */ jsx2(
382
- "div",
383
- {
384
- className: cn(
385
- "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--brand-primary)] text-white"
386
- ),
387
- children: /* @__PURE__ */ jsx2(Bot, { className: "h-3.5 w-3.5" })
388
- }
389
- ),
390
- /* @__PURE__ */ jsx2("span", { className: cn("text-sm font-semibold", branding.textClass), children: branding.label }),
378
+ /* @__PURE__ */ jsx2("span", { className: cn("font-semibold text-sm", branding.textClass), children: branding.label }),
391
379
  renderSummary(run) ? /* @__PURE__ */ jsx2("span", { className: "text-[11px] text-muted-foreground", children: renderSummary(run) }) : null,
392
380
  collapsed && run.summaryText ? /* @__PURE__ */ jsx2("span", { className: "min-w-0 truncate text-[11px] text-foreground/70", children: run.summaryText }) : null,
393
381
  /* @__PURE__ */ jsxs2("div", { className: "ml-auto flex shrink-0 items-center gap-1.5", children: [
@@ -233,7 +233,7 @@ var InlineToolItem = memo2(
233
233
  className
234
234
  ),
235
235
  children: [
236
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 px-2.5 py-1.5", children: [
236
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2.5 px-3 py-2.5", children: [
237
237
  /* @__PURE__ */ jsx3("div", { className: cn(
238
238
  "shrink-0",
239
239
  isRunning && "text-primary",
@@ -246,8 +246,8 @@ var InlineToolItem = memo2(
246
246
  /* @__PURE__ */ jsxs2("div", { className: "ml-auto flex shrink-0 items-center gap-1.5", children: [
247
247
  isRunning && startTime ? /* @__PURE__ */ jsx3(LiveDuration, { startTime }) : null,
248
248
  !isRunning && durationMs != null ? /* @__PURE__ */ jsx3("span", { className: "text-[10px] font-mono tabular-nums text-muted-foreground", children: formatDuration(durationMs) }) : null,
249
- isError ? /* @__PURE__ */ jsx3("span", { className: "rounded-full border border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] px-1.5 py-px text-[10px] font-semibold uppercase text-[var(--surface-danger-text)]", children: "Failed" }) : null,
250
- isRunning ? /* @__PURE__ */ jsx3("span", { className: "rounded-full border border-[var(--border-accent)] bg-primary/10 px-1.5 py-px text-[10px] font-semibold uppercase text-primary", children: "Running" }) : null,
249
+ isError ? /* @__PURE__ */ jsx3("span", { className: "text-[10px] font-medium text-[var(--surface-danger-text)]", children: "failed" }) : null,
250
+ isRunning ? /* @__PURE__ */ jsx3("span", { className: "text-[10px] font-medium text-primary", children: "running" }) : null,
251
251
  open ? /* @__PURE__ */ jsx3(ChevronDown, { className: "h-3 w-3 text-muted-foreground" }) : /* @__PURE__ */ jsx3(ChevronRight, { className: "h-3 w-3 text-muted-foreground" })
252
252
  ] })
253
253
  ] }),
package/dist/hooks.js CHANGED
@@ -11,15 +11,15 @@ import {
11
11
  useSSEStream,
12
12
  useSdkSession,
13
13
  useToolCallStream
14
- } from "./chunk-PN3S2MTV.js";
14
+ } from "./chunk-DLSGUNRD.js";
15
15
  import "./chunk-OEX7NZE3.js";
16
16
  import {
17
17
  useAutoScroll,
18
18
  useRunCollapseState,
19
19
  useRunGroups
20
20
  } from "./chunk-AZWDI2JG.js";
21
- import "./chunk-O6NUUCT2.js";
22
- import "./chunk-EOGJX2TU.js";
21
+ import "./chunk-IWQZXL6A.js";
22
+ import "./chunk-RKQDBRTC.js";
23
23
  import "./chunk-ULDNFLIM.js";
24
24
  import "./chunk-AAUNOHVL.js";
25
25
  import "./chunk-ZRVH3WCA.js";
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  useSSEStream,
18
18
  useSdkSession,
19
19
  useToolCallStream
20
- } from "./chunk-PN3S2MTV.js";
20
+ } from "./chunk-DLSGUNRD.js";
21
21
  import {
22
22
  addMessage,
23
23
  addParts,
@@ -138,7 +138,7 @@ import {
138
138
  MessageList,
139
139
  ThinkingIndicator,
140
140
  UserMessage
141
- } from "./chunk-SJ6IL4HI.js";
141
+ } from "./chunk-5CS3I7Y3.js";
142
142
  import {
143
143
  useAutoScroll,
144
144
  useRunCollapseState,
@@ -148,18 +148,18 @@ import "./chunk-LQS34IGP.js";
148
148
  import {
149
149
  ToolCallFeed,
150
150
  parseToolEvent
151
- } from "./chunk-O6NUUCT2.js";
151
+ } from "./chunk-IWQZXL6A.js";
152
152
  import {
153
153
  InlineThinkingItem,
154
154
  RunGroup
155
- } from "./chunk-LASW7CYH.js";
155
+ } from "./chunk-QIRVZMQY.js";
156
156
  import {
157
157
  ExpandedToolDetail,
158
158
  InlineToolItem,
159
159
  LiveDuration,
160
160
  ToolCallGroup,
161
161
  ToolCallStep
162
- } from "./chunk-EOGJX2TU.js";
162
+ } from "./chunk-RKQDBRTC.js";
163
163
  import {
164
164
  TOOL_CATEGORY_ICONS,
165
165
  formatBytes,
package/dist/run.js CHANGED
@@ -2,18 +2,18 @@ import "./chunk-LQS34IGP.js";
2
2
  import {
3
3
  ToolCallFeed,
4
4
  parseToolEvent
5
- } from "./chunk-O6NUUCT2.js";
5
+ } from "./chunk-IWQZXL6A.js";
6
6
  import {
7
7
  InlineThinkingItem,
8
8
  RunGroup
9
- } from "./chunk-LASW7CYH.js";
9
+ } from "./chunk-QIRVZMQY.js";
10
10
  import {
11
11
  ExpandedToolDetail,
12
12
  InlineToolItem,
13
13
  LiveDuration,
14
14
  ToolCallGroup,
15
15
  ToolCallStep
16
- } from "./chunk-EOGJX2TU.js";
16
+ } from "./chunk-RKQDBRTC.js";
17
17
  import "./chunk-ULDNFLIM.js";
18
18
  import "./chunk-AAUNOHVL.js";
19
19
  import "./chunk-52Y3FMFI.js";
package/dist/sdk-hooks.js CHANGED
@@ -5,15 +5,15 @@ import {
5
5
  useSSEStream,
6
6
  useSdkSession,
7
7
  useToolCallStream
8
- } from "./chunk-PN3S2MTV.js";
8
+ } from "./chunk-DLSGUNRD.js";
9
9
  import "./chunk-OEX7NZE3.js";
10
10
  import {
11
11
  useAutoScroll,
12
12
  useRunCollapseState,
13
13
  useRunGroups
14
14
  } from "./chunk-AZWDI2JG.js";
15
- import "./chunk-O6NUUCT2.js";
16
- import "./chunk-EOGJX2TU.js";
15
+ import "./chunk-IWQZXL6A.js";
16
+ import "./chunk-RKQDBRTC.js";
17
17
  import "./chunk-ULDNFLIM.js";
18
18
  import "./chunk-AAUNOHVL.js";
19
19
  import "./chunk-ZRVH3WCA.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "6.0.0",
3
+ "version": "7.0.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",
@@ -132,7 +132,7 @@
132
132
  "react": "^18 || ^19",
133
133
  "react-dom": "^18 || ^19",
134
134
  "react-router": "^7",
135
- "@tangle-network/brand": "^0.7.0"
135
+ "@tangle-network/brand": "^0.8.0"
136
136
  },
137
137
  "peerDependenciesMeta": {
138
138
  "@nanostores/react": {
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react'
2
2
  import { ChatMessage } from './chat-message'
3
- import { ToolCallStep } from '../run/tool-call-step'
3
+ import { InlineToolItem } from '../run/inline-tool-item'
4
+ import type { ToolPart } from '../types/parts'
4
5
 
5
6
  const meta: Meta<typeof ChatMessage> = {
6
7
  title: 'Chat/ChatMessage',
@@ -26,6 +27,30 @@ type Story = StoryObj<typeof ChatMessage>
26
27
  const ts = (offsetMinutes = 0) =>
27
28
  new Date(Date.now() - offsetMinutes * 60 * 1000)
28
29
 
30
+ const readToolPart: ToolPart = {
31
+ type: 'tool',
32
+ id: 'chat-read',
33
+ tool: 'read',
34
+ state: {
35
+ status: 'completed',
36
+ input: { file_path: 'src/hooks/useFetchData.ts' },
37
+ output: `export function useFetchData<T>(url: string) {\n const [data, setData] = useState<T | null>(null)\n const [loading, setLoading] = useState(true)\n // ...`,
38
+ time: { start: Date.now() - 1200, end: Date.now() - 1152 },
39
+ },
40
+ }
41
+
42
+ const searchToolPart: ToolPart = {
43
+ type: 'tool',
44
+ id: 'chat-search',
45
+ tool: 'grep',
46
+ state: {
47
+ status: 'completed',
48
+ input: { pattern: 'cache', path: 'src/hooks/useFetchData.ts' },
49
+ output: 'No cache layer found. Data fetched on every mount.',
50
+ time: { start: Date.now() - 900, end: Date.now() - 888 },
51
+ },
52
+ }
53
+
29
54
  export const UserMessage: Story = {
30
55
  args: {
31
56
  role: 'user',
@@ -117,20 +142,8 @@ export const AssistantWithToolCalls: Story = {
117
142
  content: 'Let me read the current implementation first.',
118
143
  toolCalls: (
119
144
  <div className="mt-3 space-y-2">
120
- <ToolCallStep
121
- type="read"
122
- label="Read src/hooks/useFetchData.ts"
123
- status="success"
124
- output={`export function useFetchData<T>(url: string) {\n const [data, setData] = useState<T | null>(null)\n const [loading, setLoading] = useState(true)\n // ...`}
125
- duration={48}
126
- />
127
- <ToolCallStep
128
- type="grep"
129
- label="Search for cache references"
130
- status="success"
131
- output="No cache layer found. Data fetched on every mount."
132
- duration={12}
133
- />
145
+ <InlineToolItem part={readToolPart} groupPosition="first" />
146
+ <InlineToolItem part={searchToolPart} groupPosition="last" />
134
147
  </div>
135
148
  ),
136
149
  timestamp: ts(1),
@@ -6,7 +6,6 @@
6
6
  */
7
7
 
8
8
  import { type ReactNode } from "react";
9
- import { User, Bot } from "lucide-react";
10
9
  import { cn } from "../lib/utils";
11
10
  import { Markdown } from "../markdown/markdown";
12
11
 
@@ -28,9 +27,9 @@ export interface ChatMessageProps {
28
27
  assistantLabel?: string;
29
28
  /** Hide the role label row entirely */
30
29
  hideRoleLabel?: boolean;
31
- /** Hide the avatar icon */
30
+ /** @deprecated Avatars were removed from the bubble design; this prop is ignored. */
32
31
  hideAvatar?: boolean;
33
- /** Custom avatar element (replaces default User/Bot icon) */
32
+ /** @deprecated Avatars were removed from the bubble design; this prop is ignored. */
34
33
  avatar?: ReactNode;
35
34
  }
36
35
 
@@ -44,36 +43,28 @@ export function ChatMessage({
44
43
  userLabel = "You",
45
44
  assistantLabel = "Agent",
46
45
  hideRoleLabel,
47
- hideAvatar,
48
- avatar,
49
46
  }: ChatMessageProps) {
50
47
  const isUser = role === "user";
51
48
 
52
49
  return (
53
50
  <div
54
51
  className={cn(
55
- "flex gap-3",
56
- isUser ? "flex-row-reverse" : "flex-row",
52
+ "flex flex-col gap-1",
53
+ isUser ? "items-end" : "items-start",
57
54
  className,
58
55
  )}
59
56
  >
60
- {/* Avatar */}
61
- {!hideAvatar && (
62
- avatar ? (
63
- <div className="mt-0.5 shrink-0">{avatar}</div>
64
- ) : (
65
- <div
66
- className={cn(
67
- "mt-0.5 flex shrink-0 items-center justify-center rounded-[calc(var(--radius-md)+2px)] border",
68
- "h-[var(--avatar-size)] w-[var(--avatar-size)]",
69
- isUser
70
- ? "border-border bg-[var(--accent-surface-soft)] text-[var(--accent-text)]"
71
- : "border-border bg-muted text-[var(--brand-cool)]",
72
- )}
73
- >
74
- {isUser ? <User className="h-3.5 w-3.5" /> : <Bot className="h-3.5 w-3.5" />}
75
- </div>
76
- )
57
+ {!hideRoleLabel && (
58
+ <div className={cn("flex items-center gap-2 px-1", isUser && "flex-row-reverse")}>
59
+ <span className="font-medium text-foreground text-xs">
60
+ {isUser ? userLabel : assistantLabel}
61
+ </span>
62
+ {timestamp && (
63
+ <span className="text-muted-foreground text-xs">
64
+ {formatTime(timestamp)}
65
+ </span>
66
+ )}
67
+ </div>
77
68
  )}
78
69
 
79
70
  {/* Bubble */}
@@ -86,20 +77,6 @@ export function ChatMessage({
86
77
  : "border-border bg-card",
87
78
  )}
88
79
  >
89
- {/* Role label + timestamp */}
90
- {!hideRoleLabel && (
91
- <div className={cn("flex items-center gap-2", isUser && "flex-row-reverse")}>
92
- <span className="text-[var(--font-size-xs)] font-[var(--chat-label-weight,600)] uppercase tracking-[var(--chat-label-tracking,0.14em)] text-foreground">
93
- {isUser ? userLabel : assistantLabel}
94
- </span>
95
- {timestamp && (
96
- <span className="text-[var(--font-size-xs)] text-muted-foreground">
97
- {formatTime(timestamp)}
98
- </span>
99
- )}
100
- </div>
101
- )}
102
-
103
80
  {/* Message body */}
104
81
  {isUser ? (
105
82
  <div className="whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)] text-foreground">
@@ -115,7 +115,7 @@ export const InlineToolItem = memo(
115
115
  className,
116
116
  )}
117
117
  >
118
- <div className="flex items-center gap-2 px-2.5 py-1.5">
118
+ <div className="flex items-center gap-2.5 px-3 py-2.5">
119
119
  <div className={cn(
120
120
  "shrink-0",
121
121
  isRunning && "text-primary",
@@ -151,13 +151,13 @@ export const InlineToolItem = memo(
151
151
  </span>
152
152
  ) : null}
153
153
  {isError ? (
154
- <span className="rounded-full border border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] px-1.5 py-px text-[10px] font-semibold uppercase text-[var(--surface-danger-text)]">
155
- Failed
154
+ <span className="text-[10px] font-medium text-[var(--surface-danger-text)]">
155
+ failed
156
156
  </span>
157
157
  ) : null}
158
158
  {isRunning ? (
159
- <span className="rounded-full border border-[var(--border-accent)] bg-primary/10 px-1.5 py-px text-[10px] font-semibold uppercase text-primary">
160
- Running
159
+ <span className="text-[10px] font-medium text-primary">
160
+ running
161
161
  </span>
162
162
  ) : null}
163
163
  {open ? (
@@ -58,23 +58,17 @@ function AssistantShell({
58
58
  children: ReactNode;
59
59
  }) {
60
60
  return (
61
- <div className="flex gap-2.5">
62
- <div className="mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--brand-primary)] text-white">
63
- <Bot className="h-3.5 w-3.5" />
64
- </div>
65
-
66
- <div className={ASSISTANT_SHELL}>
67
- <div className="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-[var(--text-muted)]">
68
- <span>{branding.label}</span>
69
- {isStreaming ? (
70
- <span className="inline-flex items-center gap-1.5 text-[var(--text-muted)]">
71
- <Loader2 className="h-3 w-3 animate-spin" />
72
- Thinking
73
- </span>
74
- ) : null}
75
- </div>
76
- {children}
61
+ <div className="flex flex-col gap-1">
62
+ <div className="flex items-center gap-2 px-1 font-medium text-muted-foreground text-xs">
63
+ <span>{branding.label}</span>
64
+ {isStreaming ? (
65
+ <span className="inline-flex items-center gap-1.5">
66
+ <Loader2 className="h-3 w-3 animate-spin" />
67
+ Thinking
68
+ </span>
69
+ ) : null}
77
70
  </div>
71
+ <div className={ASSISTANT_SHELL}>{children}</div>
78
72
  </div>
79
73
  );
80
74
  }
@@ -411,15 +405,7 @@ export const RunGroup = memo(
411
405
  )}
412
406
  >
413
407
  <div className="flex items-center gap-2">
414
- <div
415
- className={cn(
416
- "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--brand-primary)] text-white",
417
- )}
418
- >
419
- <Bot className="h-3.5 w-3.5" />
420
- </div>
421
-
422
- <span className={cn("text-sm font-semibold", branding.textClass)}>
408
+ <span className={cn("font-semibold text-sm", branding.textClass)}>
423
409
  {branding.label}
424
410
  </span>
425
411
 
@@ -0,0 +1,117 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { InlineToolItem } from "../run/inline-tool-item";
3
+ import type { ToolPart } from "../types/parts";
4
+
5
+ const meta: Meta = {
6
+ title: "Foundations/Theme Showcase",
7
+ parameters: { layout: "fullscreen" },
8
+ };
9
+ export default meta;
10
+ type Story = StoryObj;
11
+
12
+ const THEMES: { name: string; theme?: string }[] = [
13
+ { name: "Tangle (neutral)", theme: undefined }, // default :root dark
14
+ { name: "Tangle · light", theme: "tangle-light" },
15
+ { name: "Aubergine (bazaar)", theme: "aubergine" },
16
+ { name: "Aubergine · light", theme: "aubergine-light" },
17
+ { name: "Experimental green", theme: "arena" },
18
+ { name: "Experimental green · light", theme: "arena-light" },
19
+ ];
20
+
21
+ const NOW = Date.now();
22
+
23
+ const themeToolParts: ToolPart[] = [
24
+ {
25
+ type: "tool",
26
+ id: "theme-read",
27
+ tool: "read",
28
+ state: {
29
+ status: "completed",
30
+ input: { file_path: "src/batch-writer.ts" },
31
+ time: { start: NOW - 3100, end: NOW - 2500 },
32
+ },
33
+ },
34
+ {
35
+ type: "tool",
36
+ id: "theme-search",
37
+ tool: "grep",
38
+ state: {
39
+ status: "completed",
40
+ input: { pattern: "await sleep\\(" },
41
+ time: { start: NOW - 2200, end: NOW - 1800 },
42
+ },
43
+ },
44
+ {
45
+ type: "tool",
46
+ id: "theme-build",
47
+ tool: "bash",
48
+ state: {
49
+ status: "error",
50
+ input: { command: "pnpm test" },
51
+ error: "1 failing test",
52
+ time: { start: NOW - 1600, end: NOW - 400 },
53
+ },
54
+ },
55
+ ];
56
+
57
+ function Cell({ name, theme }: { name: string; theme?: string }) {
58
+ return (
59
+ <div
60
+ data-theme={theme}
61
+ className="flex flex-col gap-3 rounded-2xl border border-border bg-background p-5"
62
+ >
63
+ <div className="flex items-center justify-between">
64
+ <span className="font-semibold text-foreground text-sm">{name}</span>
65
+ <span className="font-medium text-muted-foreground text-xs">
66
+ {theme?.includes("light") ? "Light" : "Dark"}
67
+ </span>
68
+ </div>
69
+
70
+ <div className="space-y-1.5 rounded-xl border border-[var(--border-subtle)] bg-surface-container-high p-3">
71
+ <div className="flex items-center gap-2">
72
+ <span className="size-2 rounded-full bg-primary" />
73
+ <span className="font-semibold text-foreground text-xs">Agent</span>
74
+ <span className="text-[11px] text-muted-foreground">4 tools · 3s</span>
75
+ </div>
76
+ {themeToolParts.map((part, index) => (
77
+ <InlineToolItem
78
+ key={part.id}
79
+ part={part}
80
+ groupPosition={index === 0 ? "first" : index === themeToolParts.length - 1 ? "last" : "middle"}
81
+ />
82
+ ))}
83
+ </div>
84
+
85
+ <p className="text-muted-foreground text-xs leading-relaxed">
86
+ Body copy on the canvas. Surfaces separate by fill; the border is a quiet edge.
87
+ </p>
88
+
89
+ <div className="flex items-center gap-2">
90
+ <button
91
+ type="button"
92
+ className="rounded-lg bg-primary px-3 py-1.5 font-medium text-primary-foreground text-xs"
93
+ >
94
+ Primary
95
+ </button>
96
+ <button
97
+ type="button"
98
+ className="rounded-lg border border-border bg-muted px-3 py-1.5 font-medium text-foreground text-xs"
99
+ >
100
+ Secondary
101
+ </button>
102
+ </div>
103
+ </div>
104
+ );
105
+ }
106
+
107
+ export const AllThemes: Story = {
108
+ render: () => (
109
+ <div className="min-h-screen bg-[#0a0a0c] p-6">
110
+ <div className="grid grid-cols-1 gap-5 md:grid-cols-2">
111
+ {THEMES.map((t) => (
112
+ <Cell key={t.name} name={t.name} theme={t.theme} />
113
+ ))}
114
+ </div>
115
+ </div>
116
+ ),
117
+ };
@@ -1,294 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react'
2
- import { ToolCallFeed, type FeedSegment } from './tool-call-feed'
3
-
4
- // -- Fixtures --
5
-
6
- const debuggingSession: FeedSegment[] = [
7
- {
8
- kind: 'text',
9
- content: "I'll investigate the failing test and fix it. Let me start by finding the relevant files.",
10
- },
11
- {
12
- kind: 'tool_group',
13
- title: 'Exploration',
14
- calls: [
15
- {
16
- id: 'tc-glob-1',
17
- type: 'glob',
18
- label: 'Find **/*.test.{ts,tsx}',
19
- status: 'success',
20
- duration: 28,
21
- output: 'src/utils/format.test.ts\nsrc/utils/tool-display.test.ts\nsrc/run/run-group.test.ts',
22
- },
23
- {
24
- id: 'tc-read-1',
25
- type: 'read',
26
- label: 'Read src/run/run-group.test.ts',
27
- status: 'success',
28
- duration: 55,
29
- detail: 'src/run/run-group.test.ts',
30
- output: "import { render } from '@testing-library/react'\nimport { RunGroup } from './run-group'\n\ndescribe('RunGroup', () => {\n it('renders tool parts', () => { ... })\n})",
31
- },
32
- {
33
- id: 'tc-read-2',
34
- type: 'read',
35
- label: 'Read src/run/run-group.tsx',
36
- status: 'success',
37
- duration: 61,
38
- detail: 'src/run/run-group.tsx',
39
- output: 'const allParts = useMemo(() => {\n const parts = []\n for (const msg of run.messages) {\n const msgParts = partMap[msg.id] ?? []\n ...',
40
- },
41
- ],
42
- },
43
- {
44
- kind: 'text',
45
- content: "I see the issue. The test is passing `undefined` for `partMap` in one of the cases, but the component always expects a `Record<string, SessionPart[]>`. Let me check the test setup.",
46
- },
47
- {
48
- kind: 'tool_call',
49
- call: {
50
- id: 'tc-grep-1',
51
- type: 'grep',
52
- label: 'Search for partMap in run-group.test.ts',
53
- status: 'success',
54
- duration: 44,
55
- output: 'src/run/run-group.test.ts:18: render(<RunGroup run={mockRun} partMap={undefined} collapsed={false} onToggle={jest.fn()} />)',
56
- },
57
- },
58
- {
59
- kind: 'text',
60
- content: "Found it — the test is passing `partMap={undefined}`. The prop type should default to `{}` rather than requiring the caller to pass a defined value. I'll add a default parameter.",
61
- },
62
- {
63
- kind: 'tool_call',
64
- call: {
65
- id: 'tc-edit-1',
66
- type: 'edit',
67
- label: 'Edit src/run/run-group.tsx',
68
- status: 'success',
69
- duration: 38,
70
- detail: 'Added partMap = {} default',
71
- },
72
- },
73
- {
74
- kind: 'tool_call',
75
- call: {
76
- id: 'tc-bash-1',
77
- type: 'bash',
78
- label: 'pnpm test --run src/run/run-group.test.ts',
79
- status: 'success',
80
- duration: 1240,
81
- output: ' ✓ src/run/run-group.test.ts (3)\n\nTest Files 1 passed (1)\n Tests 3 passed (3)\n Duration 1.24s',
82
- },
83
- },
84
- {
85
- kind: 'text',
86
- content: 'All tests pass. The fix was a one-line change: adding `partMap = {}` as a default parameter so callers that omit it (or pass `undefined`) get a safe empty map rather than a crash.',
87
- },
88
- ]
89
-
90
- const multiPhaseSession: FeedSegment[] = [
91
- {
92
- kind: 'text',
93
- content: "I'll refactor the `formatDuration` utility to support minutes, add tests, and verify the full suite still passes.",
94
- },
95
- {
96
- kind: 'tool_group',
97
- title: 'Read current implementation',
98
- calls: [
99
- {
100
- id: 'mp-read-1',
101
- type: 'read',
102
- label: 'Read src/utils/format.ts',
103
- status: 'success',
104
- duration: 42,
105
- output: "export function formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`\n return `${(ms / 1000).toFixed(1)}s`\n}",
106
- },
107
- {
108
- id: 'mp-read-2',
109
- type: 'read',
110
- label: 'Read src/utils/format.test.ts',
111
- status: 'success',
112
- duration: 38,
113
- output: "describe('formatDuration', () => {\n it('returns ms for sub-second values', ...)\n it('returns seconds for 1000-59999ms', ...)\n})",
114
- },
115
- ],
116
- },
117
- {
118
- kind: 'tool_group',
119
- title: 'Implement changes',
120
- calls: [
121
- {
122
- id: 'mp-write-1',
123
- type: 'write',
124
- label: 'Write src/utils/format.ts',
125
- status: 'success',
126
- duration: 51,
127
- detail: 'Added minutes branch and truncateText helper',
128
- },
129
- {
130
- id: 'mp-write-2',
131
- type: 'write',
132
- label: 'Write src/utils/format.test.ts',
133
- status: 'success',
134
- duration: 48,
135
- detail: 'Added 3 new test cases for minutes and truncation',
136
- },
137
- ],
138
- },
139
- {
140
- kind: 'tool_call',
141
- call: {
142
- id: 'mp-bash-1',
143
- type: 'bash',
144
- label: 'pnpm test --run',
145
- status: 'success',
146
- duration: 1870,
147
- output: 'Test Files 3 passed (3)\n Tests 18 passed (18)\n Duration 1.87s',
148
- },
149
- },
150
- {
151
- kind: 'text',
152
- content: 'Done. `formatDuration` now returns `"2m 14s"` for values over 60 seconds. All 18 tests pass including the 3 new cases.',
153
- },
154
- ]
155
-
156
- const streamingSession: FeedSegment[] = [
157
- {
158
- kind: 'text',
159
- content: "Let me run the full test suite and check for any issues.",
160
- },
161
- {
162
- kind: 'tool_call',
163
- call: {
164
- id: 'stream-bash-1',
165
- type: 'bash',
166
- label: 'pnpm test --run',
167
- status: 'running',
168
- },
169
- },
170
- ]
171
-
172
- const errorSession: FeedSegment[] = [
173
- {
174
- kind: 'text',
175
- content: "Attempting to build the project.",
176
- },
177
- {
178
- kind: 'tool_call',
179
- call: {
180
- id: 'err-bash-1',
181
- type: 'bash',
182
- label: 'pnpm build',
183
- status: 'error',
184
- duration: 3200,
185
- detail: 'Build failed with TypeScript errors',
186
- output: `error TS2322: Type 'string | undefined' is not assignable to type 'string'.\n src/utils/format.ts:8:5\n\nerror TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'.\n src/run/run-item-primitives.tsx:6:48\n\nFound 2 errors.`,
187
- },
188
- },
189
- {
190
- kind: 'text',
191
- content: "Two TypeScript errors. The first is in `format.ts` — I need to add a null guard before calling `.toFixed()`. The second is in `run-item-primitives.tsx` — `startTime` can be undefined per the `ToolTime` type.",
192
- },
193
- {
194
- kind: 'tool_group',
195
- title: 'Apply fixes',
196
- calls: [
197
- {
198
- id: 'err-edit-1',
199
- type: 'edit',
200
- label: 'Edit src/utils/format.ts',
201
- status: 'success',
202
- duration: 35,
203
- detail: 'Added undefined guard on ms parameter',
204
- },
205
- {
206
- id: 'err-edit-2',
207
- type: 'edit',
208
- label: 'Edit src/run/run-item-primitives.tsx',
209
- status: 'success',
210
- duration: 29,
211
- detail: 'Changed startTime prop type to number (non-optional at callsite)',
212
- },
213
- ],
214
- },
215
- {
216
- kind: 'tool_call',
217
- call: {
218
- id: 'err-bash-2',
219
- type: 'bash',
220
- label: 'pnpm build',
221
- status: 'success',
222
- duration: 4100,
223
- output: 'dist/index.js 42.3 kB\ndist/index.js.map 88.1 kB\n✓ built in 4.10s',
224
- },
225
- },
226
- {
227
- kind: 'text',
228
- content: 'Build succeeds. Both TypeScript errors resolved.',
229
- },
230
- ]
231
-
232
- // --
233
-
234
- const meta: Meta<typeof ToolCallFeed> = {
235
- title: 'Run/ToolCallFeed',
236
- component: ToolCallFeed,
237
- parameters: {
238
- layout: 'fullscreen',
239
- backgrounds: { default: 'dark' },
240
- },
241
- }
242
-
243
- export default meta
244
- type Story = StoryObj<typeof ToolCallFeed>
245
-
246
- export const DebuggingSession: Story = {
247
- name: 'Debugging a failing test',
248
- args: { segments: debuggingSession },
249
- render: (args) => (
250
- <div className="p-6 max-w-2xl">
251
- <ToolCallFeed {...args} />
252
- </div>
253
- ),
254
- }
255
-
256
- export const MultiPhaseRefactor: Story = {
257
- name: 'Multi-phase refactor',
258
- args: { segments: multiPhaseSession },
259
- render: (args) => (
260
- <div className="p-6 max-w-2xl">
261
- <ToolCallFeed {...args} />
262
- </div>
263
- ),
264
- }
265
-
266
- export const Streaming: Story = {
267
- name: 'In-progress (running)',
268
- args: { segments: streamingSession },
269
- render: (args) => (
270
- <div className="p-6 max-w-2xl">
271
- <ToolCallFeed {...args} />
272
- </div>
273
- ),
274
- }
275
-
276
- export const BuildErrorAndFix: Story = {
277
- name: 'Build error → fix → success',
278
- args: { segments: errorSession },
279
- render: (args) => (
280
- <div className="p-6 max-w-2xl">
281
- <ToolCallFeed {...args} />
282
- </div>
283
- ),
284
- }
285
-
286
- export const Empty: Story = {
287
- args: { segments: [] },
288
- render: (args) => (
289
- <div className="p-6 max-w-2xl">
290
- <ToolCallFeed {...args} />
291
- <p className="text-sm text-muted-foreground italic">(empty feed — renders nothing)</p>
292
- </div>
293
- ),
294
- }
@@ -1,198 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react'
2
- import { ToolCallStep, ToolCallGroup } from './tool-call-step'
3
-
4
- const meta: Meta<typeof ToolCallStep> = {
5
- title: 'Run/ToolCallStep',
6
- component: ToolCallStep,
7
- parameters: {
8
- layout: 'fullscreen',
9
- backgrounds: { default: 'dark' },
10
- },
11
- }
12
-
13
- export default meta
14
- type Story = StoryObj<typeof ToolCallStep>
15
-
16
- // -- Single step stories --
17
-
18
- export const BashSuccess: Story = {
19
- args: {
20
- type: 'bash',
21
- label: 'pnpm test --run --reporter=verbose',
22
- status: 'success',
23
- duration: 1420,
24
- output: `Test Files 2 passed (2)\n Tests 15 passed (15)\n Duration 1.42s`,
25
- },
26
- render: (args) => (
27
- <div className="p-6 max-w-2xl">
28
- <ToolCallStep {...args} />
29
- </div>
30
- ),
31
- }
32
-
33
- export const BashRunning: Story = {
34
- args: {
35
- type: 'bash',
36
- label: 'pnpm build',
37
- status: 'running',
38
- },
39
- render: (args) => (
40
- <div className="p-6 max-w-2xl">
41
- <ToolCallStep {...args} />
42
- </div>
43
- ),
44
- }
45
-
46
- export const BashError: Story = {
47
- args: {
48
- type: 'bash',
49
- label: 'pnpm test --run',
50
- status: 'error',
51
- duration: 890,
52
- detail: 'src/components/RunGroup.test.tsx',
53
- output: `FAIL src/components/RunGroup.test.tsx\n ● RunGroup › renders tool parts\n\n TypeError: Cannot read properties of undefined (reading 'map')\n at RunGroup (src/run/run-group.tsx:42:40)`,
54
- },
55
- render: (args) => (
56
- <div className="p-6 max-w-2xl">
57
- <ToolCallStep {...args} />
58
- </div>
59
- ),
60
- }
61
-
62
- export const ReadFile: Story = {
63
- args: {
64
- type: 'read',
65
- label: 'Read src/run/run-group.tsx',
66
- status: 'success',
67
- duration: 62,
68
- detail: '/home/user/project/src/run/run-group.tsx',
69
- output: `import { memo, useMemo } from 'react'\nexport const RunGroup = memo(({ run, partMap, ...`,
70
- },
71
- render: (args) => (
72
- <div className="p-6 max-w-2xl">
73
- <ToolCallStep {...args} />
74
- </div>
75
- ),
76
- }
77
-
78
- export const WriteFile: Story = {
79
- args: {
80
- type: 'write',
81
- label: 'Write src/utils/format.ts',
82
- status: 'success',
83
- duration: 45,
84
- detail: '/home/user/project/src/utils/format.ts',
85
- },
86
- render: (args) => (
87
- <div className="p-6 max-w-2xl">
88
- <ToolCallStep {...args} />
89
- </div>
90
- ),
91
- }
92
-
93
- export const EditFile: Story = {
94
- args: {
95
- type: 'edit',
96
- label: 'Edit src/run/run-group.tsx',
97
- status: 'success',
98
- duration: 38,
99
- detail: 'Replaced 3 lines at L42',
100
- },
101
- render: (args) => (
102
- <div className="p-6 max-w-2xl">
103
- <ToolCallStep {...args} />
104
- </div>
105
- ),
106
- }
107
-
108
- export const GlobFind: Story = {
109
- args: {
110
- type: 'glob',
111
- label: 'Find **/*.test.ts',
112
- status: 'success',
113
- duration: 28,
114
- output: `src/utils/format.test.ts\nsrc/utils/tool-display.test.ts\nsrc/run/run-group.test.ts`,
115
- },
116
- render: (args) => (
117
- <div className="p-6 max-w-2xl">
118
- <ToolCallStep {...args} />
119
- </div>
120
- ),
121
- }
122
-
123
- export const GrepSearch: Story = {
124
- args: {
125
- type: 'grep',
126
- label: 'Search for partMap\\[',
127
- status: 'success',
128
- duration: 44,
129
- output: `src/run/run-group.tsx:184: const msgParts = partMap[msg.id] ?? []\nsrc/hooks/useRunGroup.ts:44: setPartMap(prev => ({ ...prev, [msgId]: parts }))`,
130
- },
131
- render: (args) => (
132
- <div className="p-6 max-w-2xl">
133
- <ToolCallStep {...args} />
134
- </div>
135
- ),
136
- }
137
-
138
- export const NoExpandable: Story = {
139
- name: 'No detail/output (not expandable)',
140
- args: {
141
- type: 'bash',
142
- label: 'git add -p',
143
- status: 'success',
144
- duration: 120,
145
- },
146
- render: (args) => (
147
- <div className="p-6 max-w-2xl">
148
- <ToolCallStep {...args} />
149
- </div>
150
- ),
151
- }
152
-
153
- // -- Group stories --
154
-
155
- export const GroupedToolCalls: Story = {
156
- name: 'ToolCallGroup — exploration phase',
157
- render: () => (
158
- <div className="p-6 max-w-2xl">
159
- <ToolCallGroup title="Exploration">
160
- <ToolCallStep type="glob" label="Find **/*.test.ts" status="success" duration={28} output="src/utils/format.test.ts\nsrc/utils/tool-display.test.ts\nsrc/run/run-group.test.ts" />
161
- <ToolCallStep type="read" label="Read src/run/run-group.tsx" status="success" duration={62} detail="Checking for partMap access patterns" output="export const RunGroup = memo(({ run, partMap, ..." />
162
- <ToolCallStep type="grep" label="Search for partMap\[" status="success" duration={44} output="src/run/run-group.tsx:184\nsrc/hooks/useRunGroup.ts:44" />
163
- </ToolCallGroup>
164
- </div>
165
- ),
166
- }
167
-
168
- export const MixedStatuses: Story = {
169
- name: 'Mixed statuses in a group',
170
- render: () => (
171
- <div className="p-6 max-w-2xl">
172
- <ToolCallGroup title="Test cycle">
173
- <ToolCallStep type="bash" label="pnpm test --run src/utils/" status="success" duration={890} />
174
- <ToolCallStep type="edit" label="Edit src/run/run-group.tsx" status="success" duration={38} />
175
- <ToolCallStep type="bash" label="pnpm test --run" status="error" duration={740} output="FAIL src/run/run-group.test.ts\n ● 1 test failed" />
176
- <ToolCallStep type="bash" label="pnpm test --run" status="running" />
177
- </ToolCallGroup>
178
- </div>
179
- ),
180
- }
181
-
182
- export const AllTypes: Story = {
183
- render: () => (
184
- <div className="p-6 max-w-2xl space-y-2">
185
- <p className="text-xs font-mono uppercase tracking-widest text-muted-foreground px-1 pb-1">All tool types</p>
186
- <ToolCallStep type="bash" label="npm run build" status="success" duration={4200} />
187
- <ToolCallStep type="read" label="Read package.json" status="success" duration={18} />
188
- <ToolCallStep type="write" label="Write dist/index.js" status="success" duration={55} />
189
- <ToolCallStep type="edit" label="Edit tsconfig.json" status="success" duration={30} />
190
- <ToolCallStep type="glob" label="Find src/**/*.tsx" status="success" duration={22} />
191
- <ToolCallStep type="grep" label="Search for export default" status="success" duration={66} />
192
- <ToolCallStep type="list" label="List node_modules/.cache" status="success" duration={14} />
193
- <ToolCallStep type="download" label="Download schema.json" status="success" duration={380} />
194
- <ToolCallStep type="inspect" label="Inspect bundle size" status="running" />
195
- <ToolCallStep type="audit" label="Audit dependencies" status="error" duration={1100} />
196
- </div>
197
- ),
198
- }