@tangle-network/ui 11.5.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 (66) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +70 -2
  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-7O22XQ4D.js → chunk-PLWRAS5Z.js} +1 -1
  12. package/dist/{chunk-NZLLDHEG.js → chunk-QGTIBEPX.js} +608 -420
  13. package/dist/{chunk-AZWDI2JG.js → chunk-UYTRNR7Z.js} +1 -1
  14. package/dist/{chunk-RKC3GXHE.js → chunk-VDCZHOOO.js} +99 -104
  15. package/dist/{chunk-J2G7KCYH.js → chunk-XT4JMYVB.js} +109 -67
  16. package/dist/{document-editor-pane-MX3FIZSE.js → document-editor-pane-A3JURPW5.js} +1 -1
  17. package/dist/{document-editor-pane-C3cYEDWZ.d.ts → document-editor-pane-IAEQn_Dl.d.ts} +8 -4
  18. package/dist/editor.d.ts +5 -4
  19. package/dist/editor.js +1 -1
  20. package/dist/files.d.ts +1 -1
  21. package/dist/files.js +1 -1
  22. package/dist/hooks.js +6 -5
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +16 -11
  25. package/dist/primitives.d.ts +15 -1
  26. package/dist/primitives.js +5 -1
  27. package/dist/run.d.ts +22 -17
  28. package/dist/run.js +8 -6
  29. package/dist/sdk-hooks.js +6 -5
  30. package/dist/utils.d.ts +6 -0
  31. package/dist/utils.js +1 -1
  32. package/package.json +1 -1
  33. package/src/chat/agent-timeline.stories.tsx +163 -10
  34. package/src/chat/agent-timeline.test.tsx +114 -7
  35. package/src/chat/agent-timeline.tsx +120 -158
  36. package/src/chat/chat-container.test.tsx +59 -0
  37. package/src/chat/user-message.test.tsx +33 -0
  38. package/src/chat/user-message.tsx +13 -8
  39. package/src/editor/document-editor-pane.test.tsx +58 -0
  40. package/src/editor/editor-lazy.test.tsx +102 -0
  41. package/src/editor/editor-lazy.ts +40 -0
  42. package/src/editor/editor-loading.tsx +25 -0
  43. package/src/editor/editor-peers.test.ts +250 -0
  44. package/src/editor/editor-peers.ts +272 -0
  45. package/src/editor/editor-provider.test.tsx +226 -0
  46. package/src/editor/editor-provider.tsx +301 -265
  47. package/src/editor/editor-toolbar.tsx +1 -1
  48. package/src/editor/markdown-document-editor.test.tsx +121 -0
  49. package/src/editor/markdown-document-editor.tsx +114 -82
  50. package/src/editor/tiptap-editor.test.tsx +228 -0
  51. package/src/editor/tiptap-editor.tsx +162 -130
  52. package/src/primitives/index.ts +3 -0
  53. package/src/primitives/text-shimmer.test.tsx +34 -0
  54. package/src/primitives/text-shimmer.tsx +82 -0
  55. package/src/run/inline-thinking-item.stories.tsx +2 -2
  56. package/src/run/inline-thinking-item.tsx +2 -2
  57. package/src/run/inline-tool-item.test.tsx +76 -0
  58. package/src/run/inline-tool-item.tsx +9 -7
  59. package/src/run/run-group.tsx +4 -3
  60. package/src/run/run-row-shell.test.tsx +74 -4
  61. package/src/run/run-row-shell.tsx +81 -53
  62. package/src/run/tool-call-step.tsx +9 -7
  63. package/src/utils/tool-display.test.ts +53 -0
  64. package/src/utils/tool-display.ts +16 -15
  65. package/dist/chunk-LQS34IGP.js +0 -0
  66. /package/dist/{chunk-EJ5NBW7V.js → chunk-LDLOJKPC.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @tangle-network/ui
2
2
 
3
+ ## 11.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0982a63: Quiet transcript rows: the agent transcript (`AgentTimeline`, `ChatContainer`'s `timeline` presentation) now reads as one column of prose with tool calls as quiet one-liners between the paragraphs. `RunRowShell` (and so `InlineToolItem` / `InlineThinkingItem`) rests with no card chrome — transparent border and fill, a bare 14px glyph, title and description at `text-sm` in `text-muted-foreground` that brighten on hover — and takes the bordered `--md3-surface-container` card only while open; the expand chevron replaces the glyph on hover and open, the success dot is gone (success is silence), the running spinner and live-ticking duration are gone (a running row shimmers its title through the new `TextShimmer` primitive, exported from `/primitives`), and the completed duration shows on hover only. `RunRowStatusDot` still exists and now draws only the error dot; `RunRowShellProps.startTime` is accepted and ignored (deprecated). `getToolDisplayMetadata` titles are the bare verb (`Read`, `Edit`, `Write`, `Shell`, `Search`, `Find`, `Web search`, `Fetch`, `Task`) and the path, command, pattern or query lives in `description` only, so a row no longer prints its subject twice; `targetPath` / `commandSnippet` are unchanged. `AgentTimeline` drops the connector spine, the uppercase "Tool activity" header above grouped calls (the group keeps that name as its `aria-label`), and the per-block timestamp on assistant prose; prose renders at 15px/1.5 with a muted streaming caret, and vertical rhythm now follows the pair of neighbours (`mt-4` at a user turn, `mt-1` inside tool sequences and between prose and tools). `UserMessage` loses its border and shows its timestamp beside the bubble on hover. Consumers that composed `RunRowShell` with a shimmer title of their own get the intended result now that the shell draws no spinner beside it.
8
+
9
+ ## 11.6.0
10
+
11
+ ### Minor Changes
12
+
13
+ - b52965b: Load the `./editor` entry's optional peers through a dynamic `import()`.
14
+
15
+ The entry declares `@tiptap/*`, `@hocuspocus/provider` and `yjs` as optional peers, but reached them through static imports. A bundler resolves an uninstalled optional peer to a stub module that carries a default export only, so `import { EditorContent } from "@tiptap/react"` and `import { HocuspocusProvider } from "@hocuspocus/provider"` failed the build of every consumer that did not install them, with `MISSING_EXPORT`. The failure was not limited to `./editor`: `./files` reaches `DocumentEditorPane` through a lazy import, so `@tangle-network/ui/files` failed the same way.
16
+
17
+ `EditorProvider`, `TiptapEditor` and the local markdown editor are now built from namespaces that `loadCollaborationPeers`/`loadDocumentEditorPeers` resolve at first render. A consumer without the peers builds clean and sees an error that names the packages to install, and only when it renders an editor. Preview-only use of `DocumentEditorPane` needs no peer at all. The two loaders stay separate so the local markdown editor still runs on `@tiptap/react` and `@tiptap/starter-kit` alone, without yjs or Hocuspocus.
18
+
19
+ A bundler reads the literal specifier in a dynamic `import()`, so the clean build is not automatic in every bundler. Vite and Rollup leave the unresolved peer to run time on their own. esbuild does so only when the call carries a `.catch()`, which every peer import now has, and `pnpm test:package` builds the packed consumer under esbuild as well as Vite to hold that. webpack has no such rule: a webpack consumer that installs none of the peers must give `resolve.fallback` the value `false` for each one, which `packages/ui/README.md` documents.
20
+
21
+ `EditorProvider` loads `yjs` and `@hocuspocus/provider` on its own, through `loadEditorProviderPeers`. It reads no tiptap namespace, so a consumer that installs those two peers and drives its own editor from the provider's context keeps working — the same set the provider imported statically before.
22
+
23
+ Rendering an editor now goes through a `Suspense` boundary: `TiptapEditor` and the local editor show a "Loading editor…" placeholder for the first frame, and `EditorProvider` renders its children only after the peers land, because every child hook reads a context that only the loaded provider supplies.
24
+
3
25
  ## 11.5.0
4
26
 
5
27
  ### Minor Changes
package/README.md CHANGED
@@ -26,10 +26,78 @@ export function App() {
26
26
  }
27
27
  ```
28
28
 
29
+ ## Styling
30
+
31
+ This package ships no CSS. Its components are Tailwind v4 class names in source, and the build emits JavaScript only, so two pieces of consumer setup carry real weight.
32
+
33
+ ### Scan this package
34
+
35
+ Your Tailwind or UnoCSS build has to read this package's files. Neither scans `node_modules` on its own, so a class that only this package names is never generated. Nothing fails loudly when one is missing: the element still renders, still occupies space and still takes clicks, it just looks inert. A `Switch` with no track colour in either state, a `Tabs` where the selected tab is indistinguishable from the rest, and a `Textarea` collapsed to one row all come out of this.
36
+
37
+ Tailwind v4, in the stylesheet that imports `tailwindcss`. `@source` paths resolve relative to that stylesheet, so adjust the prefix to wherever yours lives:
38
+
39
+ ```css
40
+ /* from src/styles/app.css, with node_modules two levels up */
41
+ @source "../../node_modules/@tangle-network/ui/src/**/*.tsx";
42
+ @source "../../node_modules/@tangle-network/ui/src/**/*.ts";
43
+ ```
44
+
45
+ `sandbox-ui` runs exactly these two globs in `src/styles/globals.css`, alongside `@import "tailwindcss" source(none)` so the scan is the whole of what the bundle compiles from.
46
+
47
+ UnoCSS, in `uno.config.ts`:
48
+
49
+ ```ts
50
+ content: {
51
+ filesystem: ["node_modules/@tangle-network/ui/dist/**/*.js"],
52
+ }
53
+ ```
54
+
55
+ Under pnpm that plain glob can match zero files. `node_modules/@tangle-network/ui` is a symlink into `node_modules/.pnpm`, and UnoCSS's globber does not follow it, which leaves you exactly where you started with no error. Pre-expand the glob with something that does follow symlinks, resolve each hit through `realpath`, and hand UnoCSS concrete paths. `tangle-network/blueprint-agent`'s `apps/web/uno.config.ts` carries a worked version that walks the import graph of the two entries it uses rather than the whole `dist`.
56
+
57
+ Two things to check after wiring this up, because the failure is silent either way. Read the generated CSS for a class this package alone names, rather than eyeballing the app. And assert the scan resolved a non-zero number of files: a scan that silently matches nothing looks identical to no scan at all.
58
+
59
+ One consequence worth knowing. This is a scan, not a manifest, so a consumer emits only the classes the version it installed actually names. Upgrading this package can introduce a class your build has never generated, and it will go missing the same silent way.
60
+
61
+ ### Import the brand stylesheet
62
+
63
+ `@tangle-network/brand` is a peer dependency. It holds the tokens every colour here resolves through, and a few real classes that some components reference by name instead of composing out of utilities. `Badge` with `dot` is the one to know about: it emits `status-dot status-dot-running` and its siblings, which are defined in `@tangle-network/brand/styles/globals.css` and nowhere else. Without that import the dots have no size and no colour.
64
+
65
+ ```css
66
+ @import "@tangle-network/brand/styles";
67
+ @import "tailwindcss";
68
+ ```
69
+
70
+ See [`@tangle-network/brand`'s README](../brand/README.md) for the token-level breakdown, the finer-grained imports, and the fonts, which brand deliberately does not bundle.
71
+
29
72
  ## Subpaths
30
73
 
31
- Fourteen named exports: `primitives`, `chat`, `run`, `openui`, `files`, `editor`, `markdown`, `auth`, `hooks`, `sdk-hooks`, `stores`, `types`, `utils`, `tool-previews`.
74
+ Sixteen named exports: `primitives`, `chat`, `run`, `openui`, `files`, `editor`, `markdown`, `auth`, `hooks`, `sdk-hooks`, `stores`, `types`, `utils`, `tool-previews`, `nav`, `redaction`.
32
75
 
33
76
  ## Optional peers
34
77
 
35
- `@nanostores/react`, `nanostores`, `@tanstack/react-query`, `@hocuspocus/provider`, `@tiptap/*`, `yjs` — install only the peers you actually use. The package will type-check and tree-shake without them.
78
+ `@tiptap/core`, `@tiptap/react`, `@tiptap/starter-kit`, `@tiptap/extension-collaboration`, `@tiptap/extension-collaboration-caret`, `@hocuspocus/provider` and `yjs` back the `./editor` entry. The entry reaches every one of them through a dynamic `import()`, and gets a loud error that names the missing packages only when it renders an editor. `./editor` splits the cost three ways, so each surface costs only the peers it uses:
79
+
80
+ | Surface | Peers it needs |
81
+ | --- | --- |
82
+ | `DocumentEditorPane` in preview mode | none |
83
+ | `MarkdownDocumentEditor` (local) | `@tiptap/react`, `@tiptap/starter-kit` |
84
+ | `EditorProvider` (collaboration transport) | `@hocuspocus/provider`, `yjs` |
85
+ | `TiptapEditor` (collaborative) | all of the above, plus both collaboration extensions |
86
+
87
+ `EditorProvider` builds the document and the socket from `yjs` and `@hocuspocus/provider` alone, so a consumer that drives its own editor from its context — through `useEditorConnection`, `useCollaborators` and the other `./editor` hooks — installs those two and no tiptap package.
88
+
89
+ A bundler still reads the literal specifier in a dynamic `import()`, so what a consumer without the peers must do depends on the bundler:
90
+
91
+ | Bundler | A consumer that installs none of the peers |
92
+ | --- | --- |
93
+ | Vite, Rollup | Builds. The unresolved peer becomes a chunk that throws when it loads. |
94
+ | esbuild | Builds. Each import carries a `.catch()`, which moves the unresolved path from build time to run time. |
95
+ | webpack | Needs configuration. Give `resolve.fallback` the value `false` for each peer you do not install, for example `resolve: { fallback: { "@tiptap/react": false } }`. |
96
+
97
+ A TypeScript consumer resolves these entries through the emitted declarations, and those declarations name the optional peers in their own import statements. Keep `skipLibCheck: true` — the common default — and a consumer without the peers type-checks: its own imports resolve, and TypeScript skips the declaration files that name the packages it does not have. With `skipLibCheck: false`, TypeScript reads those files and reports `TS2307` for each absent peer; install the peers, or add a module declaration for each one you omit.
98
+
99
+ `pnpm test:package` builds a packed consumer that installs none of the peers, under both Vite and esbuild, and type-checks it. `scripts/validate-dist.mjs` rejects a static import of any of them, and rejects a dynamic import that lost its `.catch()`.
100
+
101
+ Because the peers now resolve at first render rather than at build time, a missing one surfaces as a thrown error while React renders. Wrap the editors in an error boundary, so the install list reaches a surface you control instead of unmounting the tree.
102
+
103
+ `nanostores` and `@nanostores/react` back `./stores`, and `react-router` backs `./nav`. Those two entries create their values at module scope, so they hold a static import and a consumer that imports them must install the peer. Every other entry stays free of all of these.
package/dist/chat.d.ts CHANGED
@@ -87,9 +87,11 @@ interface UserMessageProps {
87
87
  actions?: ReactNode;
88
88
  }
89
89
  /**
90
- * The single user message bubble — a quiet, right-aligned bordered bubble on the
91
- * muted surface (no loud fill, no uppercase label). Used by both the run/message
92
- * list (session-model `parts`) and AgentTimeline (direct `content`/`timestamp`).
90
+ * The single user message bubble — a quiet, right-aligned fill on the muted
91
+ * surface (no border, no loud fill, no uppercase label). The timestamp sits
92
+ * beside the bubble and appears on hover, so it costs no height. Used by both
93
+ * the run/message list (session-model `parts`) and AgentTimeline (direct
94
+ * `content`/`timestamp`).
93
95
  */
94
96
  declare const UserMessage: React.MemoExoticComponent<({ message: _message, parts, content, timestamp, actions }: UserMessageProps) => react_jsx_runtime.JSX.Element | null>;
95
97
 
@@ -126,6 +128,8 @@ interface AgentTimelineMessageItem {
126
128
  content: string;
127
129
  toolCalls?: ReactNode;
128
130
  isStreaming?: boolean;
131
+ /** Shown beside a user bubble on hover. Assistant prose carries no
132
+ * timestamp of its own — the turn's user message dates the exchange. */
129
133
  timestamp?: Date;
130
134
  after?: ReactNode;
131
135
  }
@@ -140,6 +144,7 @@ interface AgentTimelineToolItem {
140
144
  interface AgentTimelineToolGroupItem {
141
145
  id: string;
142
146
  kind: "tool_group";
147
+ /** Accessible name of the group. Not drawn — the rows are the label. */
143
148
  title?: string;
144
149
  calls: ToolCallData[];
145
150
  /** Source tool parts, parallel to `calls`. */
@@ -166,6 +171,9 @@ interface AgentTimelineArtifactItem {
166
171
  interface AgentTimelineCustomItem {
167
172
  id: string;
168
173
  kind: "custom";
174
+ /** Laid out as a run row: it takes the same horizontal bleed as tool rows,
175
+ * so a `RunRowShell`-based row (reasoning) aligns its glyph with the prose
176
+ * edge. */
169
177
  content: ReactNode;
170
178
  }
171
179
  type AgentTimelineItem = AgentTimelineMessageItem | AgentTimelineToolItem | AgentTimelineToolGroupItem | AgentTimelineStatusItem | AgentTimelineArtifactItem | AgentTimelineCustomItem;
@@ -177,14 +185,16 @@ interface AgentTimelineProps {
177
185
  /** Optional actions rendered beside each tool item (e.g. "open in artifacts").
178
186
  * Receives the source tool part carried on the item. */
179
187
  renderToolActions?: (part: ToolPart) => ReactNode;
180
- /** When set, collapse the timeline to the first N spine rows behind a
181
- * "Show N more steps" toggle. Omit to always show every row. */
188
+ /** When set, collapse the timeline to the first N step rows (every item
189
+ * except user messages) behind a "Show N more steps" toggle. Omit to always
190
+ * show every row. */
182
191
  collapseAfter?: number;
183
192
  }
184
193
  /**
185
- * AgentTimeline — unified mixed-content timeline for agent-backed sandbox
186
- * sessions. Renders messages, tool steps, status cards, and artifact handoffs in
187
- * a single execution narrative.
194
+ * AgentTimeline — unified mixed-content transcript for agent-backed sandbox
195
+ * sessions. Renders messages, tool rows, status cards, and artifact handoffs
196
+ * as one plain column: prose at the reading size, tool calls as quiet
197
+ * one-liners between the paragraphs, no connector chrome.
188
198
  */
189
199
  declare function AgentTimeline({ items, isThinking, emptyState, className, renderToolActions, collapseAfter, }: AgentTimelineProps): react_jsx_runtime.JSX.Element | null;
190
200
 
package/dist/chat.js CHANGED
@@ -5,12 +5,13 @@ import {
5
5
  MessageList,
6
6
  ThinkingIndicator,
7
7
  UserMessage
8
- } from "./chunk-RKC3GXHE.js";
9
- import "./chunk-AZWDI2JG.js";
10
- import "./chunk-R5GVQDL3.js";
11
- import "./chunk-J2G7KCYH.js";
12
- import "./chunk-ULDNFLIM.js";
8
+ } from "./chunk-VDCZHOOO.js";
9
+ import "./chunk-UYTRNR7Z.js";
10
+ import "./chunk-CVX53NCC.js";
11
+ import "./chunk-XT4JMYVB.js";
12
+ import "./chunk-EIUE5GV2.js";
13
13
  import "./chunk-AAUNOHVL.js";
14
+ import "./chunk-L57WRIND.js";
14
15
  import "./chunk-3M7BPSSW.js";
15
16
  import "./chunk-GYPQXTJU.js";
16
17
  import "./chunk-MKTSMWVD.js";
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-OEX7NZE3.js";
12
12
  import {
13
13
  parseToolEvent
14
- } from "./chunk-XPYPZMYT.js";
14
+ } from "./chunk-6RIX7M27.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-J2G7KCYH.js";
4
+ } from "./chunk-XT4JMYVB.js";
5
5
  import {
6
6
  Markdown
7
7
  } from "./chunk-P5F43A65.js";
@@ -2,11 +2,14 @@ import {
2
2
  ExpandableContent,
3
3
  InlineToolItem,
4
4
  RunRowShell
5
- } from "./chunk-J2G7KCYH.js";
5
+ } from "./chunk-XT4JMYVB.js";
6
6
  import {
7
7
  formatDuration,
8
8
  truncateText
9
- } from "./chunk-ULDNFLIM.js";
9
+ } from "./chunk-EIUE5GV2.js";
10
+ import {
11
+ TextShimmer
12
+ } from "./chunk-L57WRIND.js";
10
13
  import {
11
14
  OpenUIArtifactRenderer
12
15
  } from "./chunk-3M7BPSSW.js";
@@ -54,10 +57,9 @@ var InlineThinkingItem = memo(
54
57
  RunRowShell,
55
58
  {
56
59
  icon: /* @__PURE__ */ jsx(Brain, { className: "h-3.5 w-3.5" }),
57
- title: isActive ? "Thinking\u2026" : "Reasoning",
60
+ title: isActive ? /* @__PURE__ */ jsx(TextShimmer, { children: "Thinking\u2026" }) : "Reasoning",
58
61
  description: preview,
59
62
  status: isActive ? "running" : "idle",
60
- startTime,
61
63
  durationMs,
62
64
  open,
63
65
  onOpenChange: setOpen,
@@ -107,7 +109,7 @@ function SpineRow({
107
109
  "span",
108
110
  {
109
111
  className: cn(
110
- "relative mt-1.5 h-[var(--timeline-dot-size,0.5rem)] w-[var(--timeline-dot-size,0.5rem)] rounded-full ring-4 ring-[var(--bg-root)]",
112
+ "relative mt-2.5 h-[var(--timeline-dot-size,0.5rem)] w-[var(--timeline-dot-size,0.5rem)] rounded-full ring-4 ring-[var(--bg-root)]",
111
113
  accentClassName
112
114
  )
113
115
  }
@@ -18,10 +18,6 @@ function extractCommand(input) {
18
18
  function extractFilePath(input) {
19
19
  return extractString(input, "file_path") ?? extractString(input, "path") ?? extractString(input, "filePath") ?? extractString(input, "file");
20
20
  }
21
- function cleanPath(path) {
22
- const parts = path.split("/");
23
- return parts.length > 3 ? ".../" + parts.slice(-2).join("/") : path;
24
- }
25
21
  var TOOL_CATEGORY_ICONS = {
26
22
  command: "terminal",
27
23
  write: "file-plus",
@@ -87,7 +83,7 @@ function getToolDisplayMetadata(part) {
87
83
  case "command":
88
84
  case "execute":
89
85
  return {
90
- title: "Run command",
86
+ title: "Shell",
91
87
  description: command ? truncateCommand(command) : void 0,
92
88
  displayVariant: "command",
93
89
  commandSnippet: command
@@ -96,7 +92,7 @@ function getToolDisplayMetadata(part) {
96
92
  case "write_file":
97
93
  case "create_file":
98
94
  return {
99
- title: filePath ? `Write ${cleanPath(filePath)}` : "Write file",
95
+ title: "Write",
100
96
  description: filePath,
101
97
  displayVariant: "write-file",
102
98
  targetPath: filePath
@@ -104,7 +100,7 @@ function getToolDisplayMetadata(part) {
104
100
  case "edit":
105
101
  case "patch":
106
102
  return {
107
- title: filePath ? `Edit ${cleanPath(filePath)}` : "Edit file",
103
+ title: "Edit",
108
104
  description: filePath,
109
105
  hasDiffOutput: true,
110
106
  diffFilePath: filePath,
@@ -115,7 +111,7 @@ function getToolDisplayMetadata(part) {
115
111
  case "read_file":
116
112
  case "cat":
117
113
  return {
118
- title: filePath ? `Read ${cleanPath(filePath)}` : "Read file",
114
+ title: "Read",
119
115
  description: filePath,
120
116
  displayVariant: "read-file",
121
117
  targetPath: filePath
@@ -125,7 +121,7 @@ function getToolDisplayMetadata(part) {
125
121
  case "rg": {
126
122
  const pattern = extractString(input, "pattern");
127
123
  return {
128
- title: pattern ? `Search: ${pattern}` : "Search",
124
+ title: "Search",
129
125
  description: pattern,
130
126
  displayVariant: "grep"
131
127
  };
@@ -135,7 +131,7 @@ function getToolDisplayMetadata(part) {
135
131
  case "ls": {
136
132
  const pattern = extractString(input, "pattern");
137
133
  return {
138
- title: pattern ? `Find: ${pattern}` : "Find files",
134
+ title: "Find",
139
135
  description: pattern,
140
136
  displayVariant: "glob"
141
137
  };
@@ -145,8 +141,8 @@ function getToolDisplayMetadata(part) {
145
141
  case "fetch": {
146
142
  const query = extractString(input, "query") ?? extractString(input, "url");
147
143
  return {
148
- title: query ? `Web: ${truncateCommand(query)}` : "Web search",
149
- description: query,
144
+ title: name === "web_search" ? "Web search" : "Fetch",
145
+ description: query ? truncateCommand(query) : void 0,
150
146
  displayVariant: "web-search"
151
147
  };
152
148
  }
@@ -155,8 +151,8 @@ function getToolDisplayMetadata(part) {
155
151
  case "spawn": {
156
152
  const desc = extractString(input, "description") ?? extractString(input, "prompt");
157
153
  return {
158
- title: desc ? `Task: ${truncateCommand(desc)}` : "Agent task",
159
- description: desc
154
+ title: "Task",
155
+ description: desc ? truncateCommand(desc) : void 0
160
156
  };
161
157
  }
162
158
  default:
@@ -0,0 +1,59 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-RQHJBTEU.js";
4
+
5
+ // src/primitives/text-shimmer.tsx
6
+ import { useEffect, useLayoutEffect } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var CLASS_NAME = "tangle-text-shimmer";
9
+ var STYLE_ID = "tangle-text-shimmer-styles";
10
+ var SHIMMER_CSS = `
11
+ @keyframes ${CLASS_NAME} {
12
+ from { -webkit-mask-position: 150% center; mask-position: 150% center; }
13
+ to { -webkit-mask-position: -50% center; mask-position: -50% center; }
14
+ }
15
+ .${CLASS_NAME} {
16
+ -webkit-mask-image: linear-gradient(90deg, rgba(0,0,0,0.72) 0%, #000 45%, rgba(0,0,0,0.72) 60%);
17
+ mask-image: linear-gradient(90deg, rgba(0,0,0,0.72) 0%, #000 45%, rgba(0,0,0,0.72) 60%);
18
+ -webkit-mask-size: 250% 100%;
19
+ mask-size: 250% 100%;
20
+ animation: ${CLASS_NAME} 1.4s linear infinite;
21
+ }
22
+ @media (prefers-reduced-motion: reduce) {
23
+ .${CLASS_NAME} {
24
+ animation: none;
25
+ -webkit-mask-image: none;
26
+ mask-image: none;
27
+ text-decoration: underline dotted currentColor;
28
+ text-underline-offset: 3px;
29
+ }
30
+ }
31
+ `;
32
+ function ensureShimmerStyles() {
33
+ if (typeof document === "undefined") return;
34
+ if (document.getElementById(STYLE_ID)) return;
35
+ const style = document.createElement("style");
36
+ style.id = STYLE_ID;
37
+ style.textContent = SHIMMER_CSS;
38
+ document.head.appendChild(style);
39
+ }
40
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
41
+ function TextShimmer({
42
+ children,
43
+ className,
44
+ durationMs = 1400
45
+ }) {
46
+ useIsomorphicLayoutEffect(ensureShimmerStyles, []);
47
+ return /* @__PURE__ */ jsx(
48
+ "span",
49
+ {
50
+ className: cn(CLASS_NAME, "text-foreground", className),
51
+ style: { animationDuration: `${durationMs}ms` },
52
+ children
53
+ }
54
+ );
55
+ }
56
+
57
+ export {
58
+ TextShimmer
59
+ };
@@ -0,0 +1,19 @@
1
+ import {
2
+ formatDuration
3
+ } from "./chunk-EIUE5GV2.js";
4
+
5
+ // src/run/run-item-primitives.tsx
6
+ import { useEffect, useState } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ function LiveDuration({ startTime }) {
9
+ const [elapsed, setElapsed] = useState(Date.now() - startTime);
10
+ useEffect(() => {
11
+ const id = setInterval(() => setElapsed(Date.now() - startTime), 100);
12
+ return () => clearInterval(id);
13
+ }, [startTime]);
14
+ return /* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-neutral-400 dark:text-neutral-500 tabular-nums", children: formatDuration(elapsed) });
15
+ }
16
+
17
+ export {
18
+ LiveDuration
19
+ };
@@ -560,7 +560,7 @@ import { lazy, Suspense } from "react";
560
560
  import { Download as Download2, X as X3 } from "lucide-react";
561
561
  import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
562
562
  var LazyDocumentEditorPane = lazy(async () => {
563
- const module = await import("./document-editor-pane-MX3FIZSE.js");
563
+ const module = await import("./document-editor-pane-A3JURPW5.js");
564
564
  return { default: module.DocumentEditorPane };
565
565
  });
566
566
  function FileArtifactPane({