@tangle-network/ui 11.11.8 → 11.13.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 (279) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/_virtual/_rolldown/runtime.js +13 -0
  3. package/dist/auth/auth.d.ts +53 -0
  4. package/dist/{auth-DOETfnd9.js → auth/auth.js} +7 -37
  5. package/dist/auth/login-layout.d.ts +20 -0
  6. package/dist/auth/login-layout.js +36 -0
  7. package/dist/auth.d.ts +2 -1
  8. package/dist/auth.js +2 -1
  9. package/dist/chat/agent-timeline.d.ts +83 -0
  10. package/dist/chat/agent-timeline.js +236 -0
  11. package/dist/chat/chat-container.d.ts +51 -0
  12. package/dist/chat/chat-container.js +309 -0
  13. package/dist/chat/chat-message.d.ts +22 -0
  14. package/dist/chat/chat-message.js +38 -0
  15. package/dist/chat/message-list.d.ts +32 -0
  16. package/dist/chat/message-list.js +37 -0
  17. package/dist/chat/thinking-indicator.d.ts +6 -0
  18. package/dist/chat/thinking-indicator.js +38 -0
  19. package/dist/chat/user-message.d.ts +21 -0
  20. package/dist/chat/user-message.js +42 -0
  21. package/dist/chat.d.ts +6 -1
  22. package/dist/chat.js +6 -1
  23. package/dist/editor/document-editor-pane.d.ts +37 -0
  24. package/dist/editor/document-editor-pane.js +199 -0
  25. package/dist/editor/editor-lazy.js +37 -0
  26. package/dist/editor/editor-loading.js +21 -0
  27. package/dist/editor/editor-peers.d.ts +6 -0
  28. package/dist/editor/editor-peers.js +155 -0
  29. package/dist/{document-editor-pane-DHM8DDhb.d.ts → editor/editor-provider.d.ts} +10 -47
  30. package/dist/editor/editor-provider.js +253 -0
  31. package/dist/editor/editor-toolbar.d.ts +11 -0
  32. package/dist/editor/editor-toolbar.js +106 -0
  33. package/dist/editor/markdown-conversion.js +23 -0
  34. package/dist/editor/markdown-document-editor.js +103 -0
  35. package/dist/editor/tiptap-editor.d.ts +45 -0
  36. package/dist/editor/tiptap-editor.js +271 -0
  37. package/dist/editor/use-editor.d.ts +67 -0
  38. package/dist/editor/use-editor.js +158 -0
  39. package/dist/editor.d.ts +6 -119
  40. package/dist/editor.js +5 -1
  41. package/dist/files/file-artifact-pane.d.ts +47 -0
  42. package/dist/files/file-artifact-pane.js +114 -0
  43. package/dist/files/file-format.d.ts +51 -0
  44. package/dist/{file-format-BgXbr-Ib.js → files/file-format.js} +1 -1
  45. package/dist/files/file-preview.d.ts +27 -0
  46. package/dist/files/file-preview.js +359 -0
  47. package/dist/files/file-tabs.d.ts +19 -0
  48. package/dist/files/file-tabs.js +48 -0
  49. package/dist/files/file-tree.d.ts +31 -0
  50. package/dist/files/file-tree.js +135 -0
  51. package/dist/files/rich-file-tree.d.ts +62 -0
  52. package/dist/files/rich-file-tree.js +122 -0
  53. package/dist/files.d.ts +6 -1
  54. package/dist/files.js +6 -2
  55. package/dist/hooks/use-auth.d.ts +40 -0
  56. package/dist/hooks/use-auth.js +91 -0
  57. package/dist/hooks/use-auto-scroll.d.ts +11 -0
  58. package/dist/hooks/use-auto-scroll.js +48 -0
  59. package/dist/hooks/use-dropdown-menu.d.ts +12 -0
  60. package/dist/hooks/use-dropdown-menu.js +31 -0
  61. package/dist/hooks/use-live-time.d.ts +7 -0
  62. package/dist/hooks/use-live-time.js +22 -0
  63. package/dist/hooks/use-realtime-session.d.ts +32 -0
  64. package/dist/hooks/use-realtime-session.js +235 -0
  65. package/dist/hooks/use-run-collapse-state.d.ts +13 -0
  66. package/dist/hooks/use-run-collapse-state.js +24 -0
  67. package/dist/hooks/use-run-groups.d.ts +11 -0
  68. package/dist/hooks/use-run-groups.js +85 -0
  69. package/dist/hooks/use-sdk-session.d.ts +59 -0
  70. package/dist/hooks/use-sdk-session.js +322 -0
  71. package/dist/hooks/use-sse-stream.d.ts +124 -0
  72. package/dist/hooks/use-sse-stream.js +229 -0
  73. package/dist/hooks/use-tool-call-stream.d.ts +23 -0
  74. package/dist/hooks/use-tool-call-stream.js +75 -0
  75. package/dist/hooks.d.ts +11 -49
  76. package/dist/hooks.js +10 -110
  77. package/dist/index.d.ts +99 -25
  78. package/dist/index.js +86 -36
  79. package/dist/lib/focus.d.ts +55 -0
  80. package/dist/{focus-BcE_SHqz.js → lib/focus.js} +1 -1
  81. package/dist/lib/utils.d.ts +4 -0
  82. package/dist/{utils-C_uf36nf.js → lib/utils.js} +1 -1
  83. package/dist/{code-block-B9miUPht.d.ts → markdown/code-block.d.ts} +5 -6
  84. package/dist/{code-block-B36KVUSN.js → markdown/code-block.js} +22 -47
  85. package/dist/{markdown-C0atSabr.d.ts → markdown/markdown.d.ts} +4 -5
  86. package/dist/{markdown-Db08KAYF.js → markdown/markdown.js} +3 -3
  87. package/dist/markdown.d.ts +2 -2
  88. package/dist/markdown.js +2 -2
  89. package/dist/{openui-artifact-renderer-fkBQaoLO.d.ts → openui/openui-artifact-renderer.d.ts} +20 -21
  90. package/dist/{openui-artifact-renderer-DG_i2lOR.js → openui/openui-artifact-renderer.js} +9 -7
  91. package/dist/{schema-BpW9rSSG.js → openui/schema-compiled.js} +2 -354
  92. package/dist/{schema-DAstk-J4.d.ts → openui/schema-data.d.ts} +3 -24
  93. package/dist/openui/schema-data.js +206 -0
  94. package/dist/openui-schema.d.ts +22 -3
  95. package/dist/openui-schema.js +138 -1
  96. package/dist/openui.d.ts +3 -2
  97. package/dist/openui.js +3 -2
  98. package/dist/{artifact-pane-BL_DFWNl.d.ts → primitives/artifact-pane.d.ts} +3 -4
  99. package/dist/{artifact-pane-DMXT9a8U.js → primitives/artifact-pane.js} +2 -2
  100. package/dist/primitives/avatar.d.ts +8 -0
  101. package/dist/primitives/avatar.js +25 -0
  102. package/dist/primitives/badge.d.ts +22 -0
  103. package/dist/primitives/badge.js +47 -0
  104. package/dist/{button-yoIv77e2.d.ts → primitives/button.d.ts} +2 -2
  105. package/dist/primitives/button.js +113 -0
  106. package/dist/primitives/card.d.ts +18 -0
  107. package/dist/primitives/card.js +52 -0
  108. package/dist/primitives/code-block.d.ts +6 -0
  109. package/dist/primitives/code-block.js +13 -0
  110. package/dist/primitives/dialog.d.ts +23 -0
  111. package/dist/primitives/dialog.js +59 -0
  112. package/dist/primitives/drop-zone.d.ts +23 -0
  113. package/dist/primitives/drop-zone.js +104 -0
  114. package/dist/primitives/dropdown-menu.d.ts +29 -0
  115. package/dist/{dropdown-menu-BpcLa5vF.js → primitives/dropdown-menu.js} +2 -23
  116. package/dist/primitives/empty-state.d.ts +11 -0
  117. package/dist/primitives/empty-state.js +32 -0
  118. package/dist/primitives/heading.d.ts +23 -0
  119. package/dist/primitives/heading.js +38 -0
  120. package/dist/primitives/input.d.ts +23 -0
  121. package/dist/primitives/input.js +110 -0
  122. package/dist/primitives/label.d.ts +6 -0
  123. package/dist/primitives/label.js +16 -0
  124. package/dist/primitives/metric-strip.d.ts +48 -0
  125. package/dist/primitives/metric-strip.js +52 -0
  126. package/dist/primitives/page-header.d.ts +25 -0
  127. package/dist/primitives/page-header.js +47 -0
  128. package/dist/primitives/page-shell.d.ts +14 -0
  129. package/dist/primitives/page-shell.js +17 -0
  130. package/dist/primitives/progress.d.ts +9 -0
  131. package/dist/primitives/progress.js +29 -0
  132. package/dist/primitives/segmented-control.d.ts +40 -0
  133. package/dist/primitives/segmented-control.js +57 -0
  134. package/dist/primitives/select.d.ts +15 -0
  135. package/dist/primitives/select.js +74 -0
  136. package/dist/primitives/sidebar-drop-zone.d.ts +21 -0
  137. package/dist/primitives/sidebar-drop-zone.js +88 -0
  138. package/dist/primitives/skeleton.d.ts +10 -0
  139. package/dist/primitives/skeleton.js +50 -0
  140. package/dist/primitives/stat-card.d.ts +16 -0
  141. package/dist/primitives/stat-card.js +60 -0
  142. package/dist/primitives/status-pill.d.ts +44 -0
  143. package/dist/primitives/status-pill.js +119 -0
  144. package/dist/primitives/switch.d.ts +6 -0
  145. package/dist/primitives/switch.js +16 -0
  146. package/dist/primitives/table.d.ts +22 -0
  147. package/dist/primitives/table.js +63 -0
  148. package/dist/primitives/tabs.d.ts +13 -0
  149. package/dist/{tabs-BVHR6NkO.js → primitives/tabs.js} +3 -3
  150. package/dist/primitives/terminal-display.d.ts +25 -0
  151. package/dist/primitives/terminal-display.js +109 -0
  152. package/dist/primitives/text-shimmer.d.ts +16 -0
  153. package/dist/{text-shimmer-BUUo6I-g.js → primitives/text-shimmer.js} +2 -2
  154. package/dist/primitives/theme-toggle.d.ts +11 -0
  155. package/dist/primitives/theme-toggle.js +154 -0
  156. package/dist/primitives/toast.d.ts +29 -0
  157. package/dist/primitives/toast.js +142 -0
  158. package/dist/primitives/toolbar.d.ts +55 -0
  159. package/dist/primitives/toolbar.js +37 -0
  160. package/dist/primitives/upload-progress.d.ts +23 -0
  161. package/dist/primitives/upload-progress.js +72 -0
  162. package/dist/primitives.d.ts +35 -5
  163. package/dist/primitives.js +35 -9
  164. package/dist/{index-COgqsFAQ.d.ts → redaction/redacted-document.d.ts} +6 -7
  165. package/dist/{redaction-CGGB2ENG.js → redaction/redacted-document.js} +2 -2
  166. package/dist/redaction.d.ts +1 -1
  167. package/dist/redaction.js +1 -1
  168. package/dist/run/expandable-content.js +41 -0
  169. package/dist/run/expanded-tool-detail.d.ts +12 -0
  170. package/dist/run/expanded-tool-detail.js +163 -0
  171. package/dist/run/inline-thinking-item.d.ts +11 -0
  172. package/dist/run/inline-thinking-item.js +60 -0
  173. package/dist/run/inline-tool-item.d.ts +25 -0
  174. package/dist/run/inline-tool-item.js +63 -0
  175. package/dist/run/run-group.d.ts +26 -0
  176. package/dist/{run-group-BWGeesgs.js → run/run-group.js} +9 -60
  177. package/dist/run/run-item-primitives.d.ts +5 -0
  178. package/dist/{run-ZsIKPA6J.js → run/run-item-primitives.js} +2 -2
  179. package/dist/run/run-row-shell.d.ts +53 -0
  180. package/dist/run/run-row-shell.js +114 -0
  181. package/dist/{tool-call-feed-BeB1_4pX.d.ts → run/tool-call-feed.d.ts} +7 -12
  182. package/dist/{tool-call-feed-BpmMLpPX.js → run/tool-call-feed.js} +4 -4
  183. package/dist/run/tool-call-step.d.ts +5 -0
  184. package/dist/run/tool-call-step.js +82 -0
  185. package/dist/run.d.ts +8 -2
  186. package/dist/run.js +7 -4
  187. package/dist/sdk-hooks.d.ts +8 -1
  188. package/dist/sdk-hooks.js +8 -2
  189. package/dist/stores/active-sessions-store.d.ts +83 -0
  190. package/dist/{active-sessions-store-Gt1R0rFG.js → stores/active-sessions-store.js} +1 -1
  191. package/dist/stores/chat-store.d.ts +14 -0
  192. package/dist/stores/chat-store.js +33 -0
  193. package/dist/stores.d.ts +3 -16
  194. package/dist/stores.js +2 -33
  195. package/dist/tool-previews/command-preview.d.ts +11 -0
  196. package/dist/tool-previews/command-preview.js +91 -0
  197. package/dist/tool-previews/diff-preview.d.ts +7 -0
  198. package/dist/tool-previews/diff-preview.js +76 -0
  199. package/dist/tool-previews/glob-results-preview.d.ts +7 -0
  200. package/dist/tool-previews/glob-results-preview.js +53 -0
  201. package/dist/tool-previews/grep-results-preview.d.ts +7 -0
  202. package/dist/tool-previews/grep-results-preview.js +99 -0
  203. package/dist/tool-previews/preview-primitives.js +58 -0
  204. package/dist/tool-previews/question-preview.d.ts +7 -0
  205. package/dist/tool-previews/question-preview.js +63 -0
  206. package/dist/tool-previews/web-search-preview.d.ts +7 -0
  207. package/dist/tool-previews/web-search-preview.js +78 -0
  208. package/dist/tool-previews/write-file-preview.d.ts +11 -0
  209. package/dist/tool-previews/write-file-preview.js +52 -0
  210. package/dist/tool-previews.d.ts +7 -1
  211. package/dist/tool-previews.js +7 -1
  212. package/dist/{branding-26d5ARIr.d.ts → types/branding.d.ts} +2 -3
  213. package/dist/{message-vhUI0c5G.d.ts → types/message.d.ts} +2 -3
  214. package/dist/{parts-5mhy9yJ3.d.ts → types/parts.d.ts} +8 -9
  215. package/dist/{run-OyWmIDqu.d.ts → types/run.d.ts} +9 -10
  216. package/dist/{tool-display-DgmL5rIU.d.ts → types/tool-display.d.ts} +5 -6
  217. package/dist/types.d.ts +5 -5
  218. package/dist/utils/copy-text.d.ts +7 -0
  219. package/dist/{utils-Bl_CZ0Qp.js → utils/copy-text.js} +1 -11
  220. package/dist/utils/format.d.ts +22 -0
  221. package/dist/{format-DfcLWqMa.js → utils/format.js} +1 -1
  222. package/dist/utils/time-ago.d.ts +3 -0
  223. package/dist/utils/time-ago.js +11 -0
  224. package/dist/utils/tool-display.d.ts +16 -0
  225. package/dist/{tool-display-BAuAVa43.js → utils/tool-display.js} +1 -1
  226. package/dist/utils.d.ts +6 -1
  227. package/dist/utils.js +6 -5
  228. package/package.json +2 -2
  229. package/src/editor/document-editor-pane.tsx +4 -1
  230. package/src/files/file-artifact-pane.test.tsx +23 -0
  231. package/src/files/file-artifact-pane.tsx +3 -0
  232. package/src/files/rich-file-tree.stories.tsx +1 -1
  233. package/src/files/rich-file-tree.tsx +12 -9
  234. package/src/markdown/code-block.test.tsx +46 -2
  235. package/src/markdown/code-block.tsx +23 -60
  236. package/src/markdown/markdown.test.tsx +3 -2
  237. package/src/primitives/button.stories.tsx +37 -1
  238. package/src/primitives/button.test.tsx +230 -0
  239. package/src/primitives/button.tsx +62 -14
  240. package/src/primitives/card-anatomy.stories.tsx +51 -0
  241. package/src/primitives/card.test.tsx +71 -0
  242. package/src/primitives/card.tsx +25 -13
  243. package/src/primitives/heading.stories.tsx +35 -0
  244. package/src/primitives/heading.test.tsx +56 -0
  245. package/src/primitives/heading.tsx +76 -0
  246. package/src/primitives/index.ts +8 -0
  247. package/src/primitives/input.stories.tsx +28 -0
  248. package/src/primitives/input.test.tsx +161 -0
  249. package/src/primitives/input.tsx +34 -28
  250. package/src/primitives/page-header.stories.tsx +51 -0
  251. package/src/primitives/page-header.test.tsx +48 -0
  252. package/src/primitives/page-header.tsx +35 -35
  253. package/src/primitives/page-shell.stories.tsx +89 -0
  254. package/src/primitives/page-shell.test.tsx +24 -0
  255. package/src/primitives/page-shell.tsx +26 -0
  256. package/src/primitives/table-wrapper.stories.tsx +54 -0
  257. package/src/primitives/table.test.tsx +72 -0
  258. package/src/primitives/table.tsx +33 -12
  259. package/src/primitives/theme-toggle.test.tsx +167 -0
  260. package/src/primitives/theme-toggle.tsx +111 -39
  261. package/dist/active-sessions-store-nl70ZnMC.d.ts +0 -84
  262. package/dist/button-C87nEe2R.js +0 -78
  263. package/dist/chat-DDvF2oGQ.js +0 -672
  264. package/dist/diff-preview-BcK3nRXb.js +0 -533
  265. package/dist/document-editor-pane-CsFbH2qo.js +0 -1279
  266. package/dist/files--Unn___t.js +0 -752
  267. package/dist/index-2UntyjBb.d.ts +0 -52
  268. package/dist/index-BgUHilZm.d.ts +0 -108
  269. package/dist/index-CPDET_s1.d.ts +0 -549
  270. package/dist/index-Cg6gYAqh.d.ts +0 -73
  271. package/dist/index-Cs7j8V7l.d.ts +0 -203
  272. package/dist/index-CyGSLGVF.d.ts +0 -234
  273. package/dist/index-DjKxO357.d.ts +0 -128
  274. package/dist/primitives-Bx59fT-U.js +0 -1239
  275. package/dist/table-Bss73DhB.js +0 -146
  276. package/dist/tool-call-step-DNebqhmX.js +0 -434
  277. package/dist/use-auto-scroll-C5Pjeqon.js +0 -153
  278. package/dist/use-sse-stream-DjqRuDft.js +0 -884
  279. package/dist/use-sse-stream-Dm6R5YWz.d.ts +0 -282
@@ -0,0 +1,7 @@
1
+ import { ToolPart } from "../types/parts.js";
2
+ //#region src/tool-previews/question-preview.d.ts
3
+ export interface QuestionPreviewProps {
4
+ part: ToolPart;
5
+ }
6
+ export declare const QuestionPreview: import("react").MemoExoticComponent<({ part }: QuestionPreviewProps) => import("react").JSX.Element>;
7
+ //#endregion
@@ -0,0 +1,63 @@
1
+ import { PreviewCard, PreviewEmpty, PreviewError, PreviewLoading } from "./preview-primitives.js";
2
+ import { memo, useMemo } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { MessageSquareQuote } from "lucide-react";
5
+ //#region src/tool-previews/question-preview.tsx
6
+ function toQuestionList(input) {
7
+ if (!input) return [];
8
+ if (typeof input === "string") return [input];
9
+ if (Array.isArray(input)) return input.map((item) => String(item)).filter(Boolean);
10
+ if (typeof input === "object") {
11
+ const record = input;
12
+ const value = record.questions ?? record.question ?? record.prompt;
13
+ if (Array.isArray(value)) return value.map((item) => String(item)).filter(Boolean);
14
+ if (typeof value === "string") return [value];
15
+ }
16
+ return [];
17
+ }
18
+ function toAnswerList(output) {
19
+ if (!output) return [];
20
+ if (typeof output === "string") return [output];
21
+ if (Array.isArray(output)) return output.map((item) => String(item)).filter(Boolean);
22
+ if (typeof output === "object") {
23
+ const record = output;
24
+ const value = record.answers ?? record.answer ?? record.response;
25
+ if (Array.isArray(value)) return value.map((item) => String(item)).filter(Boolean);
26
+ if (typeof value === "string") return [value];
27
+ }
28
+ return [];
29
+ }
30
+ const QuestionPreview = memo(({ part }) => {
31
+ const questions = useMemo(() => toQuestionList(part.state.input), [part.state.input]);
32
+ const answers = useMemo(() => toAnswerList(part.state.output), [part.state.output]);
33
+ return /* @__PURE__ */ jsxs(PreviewCard, {
34
+ icon: /* @__PURE__ */ jsx(MessageSquareQuote, { className: "h-4 w-4" }),
35
+ title: "Agent question",
36
+ description: questions.length > 1 ? `${questions.length} questions require attention` : void 0,
37
+ children: [
38
+ part.state.status === "running" ? /* @__PURE__ */ jsx(PreviewLoading, { label: "Waiting for an answer…" }) : null,
39
+ part.state.error ? /* @__PURE__ */ jsx(PreviewError, { error: part.state.error }) : null,
40
+ questions.length === 0 ? /* @__PURE__ */ jsx(PreviewEmpty, { label: "No question text was provided." }) : null,
41
+ questions.map((question, index) => /* @__PURE__ */ jsxs("div", {
42
+ className: "rounded-[var(--radius-md)] border border-border bg-muted px-3 py-3",
43
+ children: [
44
+ /* @__PURE__ */ jsxs("div", {
45
+ className: "text-xs font-semibold uppercase tracking-[0.08em] text-muted-foreground",
46
+ children: ["Question ", index + 1]
47
+ }),
48
+ /* @__PURE__ */ jsx("div", {
49
+ className: "mt-2 text-sm leading-relaxed text-foreground",
50
+ children: question
51
+ }),
52
+ /* @__PURE__ */ jsx("div", {
53
+ className: "mt-3 rounded-[var(--radius-sm)] border border-border bg-card px-3 py-2 text-sm text-foreground",
54
+ children: answers[index] ?? (part.state.status === "completed" ? "No answer recorded." : "Awaiting answer")
55
+ })
56
+ ]
57
+ }, `${question}-${index}`))
58
+ ]
59
+ });
60
+ });
61
+ QuestionPreview.displayName = "QuestionPreview";
62
+ //#endregion
63
+ export { QuestionPreview };
@@ -0,0 +1,7 @@
1
+ import { ToolPart } from "../types/parts.js";
2
+ //#region src/tool-previews/web-search-preview.d.ts
3
+ export interface WebSearchPreviewProps {
4
+ part: ToolPart;
5
+ }
6
+ export declare const WebSearchPreview: import("react").MemoExoticComponent<({ part }: WebSearchPreviewProps) => import("react").JSX.Element>;
7
+ //#endregion
@@ -0,0 +1,78 @@
1
+ import { focusRing } from "../lib/focus.js";
2
+ import { PreviewCard, PreviewEmpty, PreviewError, PreviewLoading } from "./preview-primitives.js";
3
+ import { memo, useMemo } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Globe } from "lucide-react";
6
+ //#region src/tool-previews/web-search-preview.tsx
7
+ function coerceString(value) {
8
+ return typeof value === "string" && value.trim() ? value : void 0;
9
+ }
10
+ function extractQuery(input, output) {
11
+ if (input && typeof input === "object") {
12
+ const record = input;
13
+ const query = coerceString(record.query) ?? coerceString(record.url);
14
+ if (query) return query;
15
+ }
16
+ if (output && typeof output === "object") return coerceString(output.query);
17
+ }
18
+ function parseResult(value) {
19
+ if (!value || typeof value !== "object") return null;
20
+ const record = value;
21
+ const title = coerceString(record.title) ?? coerceString(record.name) ?? "Untitled result";
22
+ const url = coerceString(record.url) ?? coerceString(record.link);
23
+ if (!url) return null;
24
+ return {
25
+ title,
26
+ url,
27
+ snippet: coerceString(record.snippet) ?? coerceString(record.description) ?? coerceString(record.text)
28
+ };
29
+ }
30
+ function extractResults(output) {
31
+ if (output && typeof output === "object") {
32
+ const record = output;
33
+ const candidate = record.results ?? record.items ?? record.output;
34
+ if (Array.isArray(candidate)) return candidate.map(parseResult).filter((value) => value !== null);
35
+ }
36
+ if (Array.isArray(output)) return output.map(parseResult).filter((value) => value !== null);
37
+ return [];
38
+ }
39
+ const WebSearchPreview = memo(({ part }) => {
40
+ const query = extractQuery(part.state.input, part.state.output);
41
+ const results = useMemo(() => extractResults(part.state.output), [part.state.output]);
42
+ return /* @__PURE__ */ jsxs(PreviewCard, {
43
+ icon: /* @__PURE__ */ jsx(Globe, { className: "h-4 w-4" }),
44
+ title: query ? `Web results for ${query}` : "Web results",
45
+ description: results.length > 0 ? `${results.length} result${results.length === 1 ? "" : "s"}` : void 0,
46
+ children: [
47
+ part.state.status === "running" ? /* @__PURE__ */ jsx(PreviewLoading, { label: "Searching the web…" }) : null,
48
+ part.state.error ? /* @__PURE__ */ jsx(PreviewError, { error: part.state.error }) : null,
49
+ part.state.status === "completed" && results.length === 0 ? /* @__PURE__ */ jsx(PreviewEmpty, { label: "No web results were returned." }) : null,
50
+ /* @__PURE__ */ jsx("div", {
51
+ className: "space-y-2",
52
+ children: results.map((result) => /* @__PURE__ */ jsxs("a", {
53
+ href: result.url,
54
+ target: "_blank",
55
+ rel: "noreferrer",
56
+ className: `block rounded-[var(--radius-md)] border border-border bg-muted/55 px-3 py-3 transition-colors hover:border-[var(--border-strong)] hover:bg-accent/50 ${focusRing}`,
57
+ children: [
58
+ /* @__PURE__ */ jsx("div", {
59
+ className: "text-sm font-medium text-foreground",
60
+ children: result.title
61
+ }),
62
+ /* @__PURE__ */ jsx("div", {
63
+ className: "mt-1 truncate text-xs text-primary",
64
+ children: result.url
65
+ }),
66
+ result.snippet ? /* @__PURE__ */ jsx("p", {
67
+ className: "mt-2 text-sm leading-relaxed text-muted-foreground",
68
+ children: result.snippet
69
+ }) : null
70
+ ]
71
+ }, result.url))
72
+ })
73
+ ]
74
+ });
75
+ });
76
+ WebSearchPreview.displayName = "WebSearchPreview";
77
+ //#endregion
78
+ export { WebSearchPreview };
@@ -0,0 +1,11 @@
1
+ import { ToolPart } from "../types/parts.js";
2
+ //#region src/tool-previews/write-file-preview.d.ts
3
+ export interface WriteFilePreviewProps {
4
+ part: ToolPart;
5
+ }
6
+ /**
7
+ * Preview for file write/create operations.
8
+ * Shows file path, line count, and the written content.
9
+ */
10
+ export declare const WriteFilePreview: import("react").MemoExoticComponent<({ part }: WriteFilePreviewProps) => import("react").JSX.Element | null>;
11
+ //#endregion
@@ -0,0 +1,52 @@
1
+ import { CodeBlock, CopyButton } from "../markdown/code-block.js";
2
+ import { PreviewCard, PreviewError, PreviewLoading } from "./preview-primitives.js";
3
+ import { getSyntaxLanguage } from "../files/file-format.js";
4
+ import { memo } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { FileEdit } from "lucide-react";
7
+ //#region src/tool-previews/write-file-preview.tsx
8
+ function extractWriteContent(input) {
9
+ if (!input || typeof input !== "object") return null;
10
+ const obj = input;
11
+ return {
12
+ path: String(obj.file_path ?? obj.path ?? obj.filePath ?? "unknown"),
13
+ content: String(obj.content ?? obj.contents ?? obj.data ?? "")
14
+ };
15
+ }
16
+ /**
17
+ * Preview for file write/create operations.
18
+ * Shows file path, line count, and the written content.
19
+ */
20
+ const WriteFilePreview = memo(({ part }) => {
21
+ const write = extractWriteContent(part.state.input);
22
+ if (!write) return null;
23
+ const lineCount = write.content.split("\n").length;
24
+ const language = getSyntaxLanguage(write.path);
25
+ return /* @__PURE__ */ jsxs(PreviewCard, {
26
+ icon: /* @__PURE__ */ jsx(FileEdit, { className: "h-4 w-4" }),
27
+ title: "Write file",
28
+ description: write.path,
29
+ meta: /* @__PURE__ */ jsxs("span", {
30
+ className: "text-xs text-muted-foreground",
31
+ children: [
32
+ "+",
33
+ lineCount,
34
+ " line",
35
+ lineCount !== 1 ? "s" : ""
36
+ ]
37
+ }),
38
+ children: [
39
+ part.state.status === "running" ? /* @__PURE__ */ jsx(PreviewLoading, { label: "Writing file…" }) : null,
40
+ part.state.error ? /* @__PURE__ */ jsx(PreviewError, { error: part.state.error }) : null,
41
+ /* @__PURE__ */ jsx(CodeBlock, {
42
+ code: write.content,
43
+ language,
44
+ className: "rounded-[var(--radius-md)]",
45
+ children: /* @__PURE__ */ jsx(CopyButton, { text: write.content })
46
+ })
47
+ ]
48
+ });
49
+ });
50
+ WriteFilePreview.displayName = "WriteFilePreview";
51
+ //#endregion
52
+ export { WriteFilePreview };
@@ -1,2 +1,8 @@
1
- import { a as WebSearchPreview, c as GlobResultsPreviewProps, d as WriteFilePreview, f as WriteFilePreviewProps, i as QuestionPreviewProps, l as GrepResultsPreview, m as CommandPreviewProps, n as DiffPreviewProps, o as WebSearchPreviewProps, p as CommandPreview, r as QuestionPreview, s as GlobResultsPreview, t as DiffPreview, u as GrepResultsPreviewProps } from "./index-2UntyjBb.js";
1
+ import { CommandPreview, CommandPreviewProps } from "./tool-previews/command-preview.js";
2
+ import { WriteFilePreview, WriteFilePreviewProps } from "./tool-previews/write-file-preview.js";
3
+ import { GrepResultsPreview, GrepResultsPreviewProps } from "./tool-previews/grep-results-preview.js";
4
+ import { GlobResultsPreview, GlobResultsPreviewProps } from "./tool-previews/glob-results-preview.js";
5
+ import { WebSearchPreview, WebSearchPreviewProps } from "./tool-previews/web-search-preview.js";
6
+ import { QuestionPreview, QuestionPreviewProps } from "./tool-previews/question-preview.js";
7
+ import { DiffPreview, DiffPreviewProps } from "./tool-previews/diff-preview.js";
2
8
  export { CommandPreview, type CommandPreviewProps, DiffPreview, type DiffPreviewProps, GlobResultsPreview, type GlobResultsPreviewProps, GrepResultsPreview, type GrepResultsPreviewProps, QuestionPreview, type QuestionPreviewProps, WebSearchPreview, type WebSearchPreviewProps, WriteFilePreview, type WriteFilePreviewProps };
@@ -1,2 +1,8 @@
1
- import { a as GrepResultsPreview, i as GlobResultsPreview, n as QuestionPreview, o as WriteFilePreview, r as WebSearchPreview, s as CommandPreview, t as DiffPreview } from "./diff-preview-BcK3nRXb.js";
1
+ import { CommandPreview } from "./tool-previews/command-preview.js";
2
+ import { WriteFilePreview } from "./tool-previews/write-file-preview.js";
3
+ import { GrepResultsPreview } from "./tool-previews/grep-results-preview.js";
4
+ import { GlobResultsPreview } from "./tool-previews/glob-results-preview.js";
5
+ import { WebSearchPreview } from "./tool-previews/web-search-preview.js";
6
+ import { QuestionPreview } from "./tool-previews/question-preview.js";
7
+ import { DiffPreview } from "./tool-previews/diff-preview.js";
2
8
  export { CommandPreview, DiffPreview, GlobResultsPreview, GrepResultsPreview, QuestionPreview, WebSearchPreview, WriteFilePreview };
@@ -1,6 +1,6 @@
1
1
  //#region src/types/branding.d.ts
2
2
  /** Visual branding for a run group header — injected by consuming app. */
3
- interface AgentBranding {
3
+ export interface AgentBranding {
4
4
  label: string;
5
5
  accentClass: string;
6
6
  bgClass: string;
@@ -10,5 +10,4 @@ interface AgentBranding {
10
10
  iconClass: string;
11
11
  textClass: string;
12
12
  }
13
- //#endregion
14
- export { AgentBranding as t };
13
+ //#endregion
@@ -1,6 +1,6 @@
1
1
  //#region src/types/message.d.ts
2
2
  /** A single message in a chat session. */
3
- interface SessionMessage {
3
+ export interface SessionMessage {
4
4
  id: string;
5
5
  role: 'user' | 'assistant' | 'system';
6
6
  sessionID?: string;
@@ -12,5 +12,4 @@ interface SessionMessage {
12
12
  /** Monotonically increasing insertion index for stable ordering. */
13
13
  _insertionIndex?: number;
14
14
  }
15
- //#endregion
16
- export { SessionMessage as t };
15
+ //#endregion
@@ -1,16 +1,16 @@
1
1
  //#region src/types/parts.d.ts
2
- interface TextPart {
2
+ export interface TextPart {
3
3
  type: 'text';
4
4
  text: string;
5
5
  /** If true this text was synthesised client-side (e.g. echo of user input). */
6
6
  synthetic?: boolean;
7
7
  }
8
- type ToolStatus = 'pending' | 'running' | 'completed' | 'error';
9
- interface ToolTime {
8
+ export type ToolStatus = 'pending' | 'running' | 'completed' | 'error';
9
+ export interface ToolTime {
10
10
  start?: number;
11
11
  end?: number;
12
12
  }
13
- interface ToolState {
13
+ export interface ToolState {
14
14
  status: ToolStatus;
15
15
  input?: unknown;
16
16
  output?: unknown;
@@ -18,7 +18,7 @@ interface ToolState {
18
18
  metadata?: Record<string, unknown>;
19
19
  time?: ToolTime;
20
20
  }
21
- interface ToolPart {
21
+ export interface ToolPart {
22
22
  type: 'tool';
23
23
  /** Unique ID for this tool invocation. */
24
24
  id: string;
@@ -27,11 +27,10 @@ interface ToolPart {
27
27
  state: ToolState;
28
28
  callID?: string;
29
29
  }
30
- interface ReasoningPart {
30
+ export interface ReasoningPart {
31
31
  type: 'reasoning';
32
32
  text: string;
33
33
  time?: ToolTime;
34
34
  }
35
- type SessionPart = TextPart | ToolPart | ReasoningPart;
36
- //#endregion
37
- export { ToolState as a, ToolPart as i, SessionPart as n, ToolStatus as o, TextPart as r, ToolTime as s, ReasoningPart as t };
35
+ export type SessionPart = TextPart | ToolPart | ReasoningPart;
36
+ //#endregion
@@ -1,15 +1,15 @@
1
- import { t as SessionMessage } from "./message-vhUI0c5G.js";
1
+ import { SessionMessage } from "./message.js";
2
2
  //#region src/types/run.d.ts
3
3
  /** Broad category of a tool invocation, used for display grouping. */
4
- type ToolCategory = "command" | "write" | "read" | "search" | "edit" | "task" | "web" | "todo" | "other";
5
- interface RunStats {
4
+ export type ToolCategory = "command" | "write" | "read" | "search" | "edit" | "task" | "web" | "todo" | "other";
5
+ export interface RunStats {
6
6
  toolCount: number;
7
7
  messageCount: number;
8
8
  thinkingDurationMs: number;
9
9
  textPartCount: number;
10
10
  toolCategories: Set<ToolCategory>;
11
11
  }
12
- interface FinalTextPart {
12
+ export interface FinalTextPart {
13
13
  messageId: string;
14
14
  partIndex: number;
15
15
  text: string;
@@ -19,7 +19,7 @@ interface FinalTextPart {
19
19
  * logical "turn" of the agent. Runs are collapsible in the UI and
20
20
  * show a summary header when collapsed.
21
21
  */
22
- interface Run {
22
+ export interface Run {
23
23
  id: string;
24
24
  messages: SessionMessage[];
25
25
  isComplete: boolean;
@@ -28,14 +28,13 @@ interface Run {
28
28
  summaryText: string | null;
29
29
  finalTextPart: FinalTextPart | null;
30
30
  }
31
- interface MessageRun {
31
+ export interface MessageRun {
32
32
  type: "run";
33
33
  run: Run;
34
34
  }
35
- interface MessageUser {
35
+ export interface MessageUser {
36
36
  type: "user";
37
37
  message: SessionMessage;
38
38
  }
39
- type GroupedMessage = MessageRun | MessageUser;
40
- //#endregion
41
- export { Run as a, MessageUser as i, GroupedMessage as n, RunStats as o, MessageRun as r, ToolCategory as s, FinalTextPart as t };
39
+ export type GroupedMessage = MessageRun | MessageUser;
40
+ //#endregion
@@ -1,21 +1,21 @@
1
- import { i as ToolPart } from "./parts-5mhy9yJ3.js";
1
+ import { ToolPart } from "./parts.js";
2
2
  import { ReactNode } from "react";
3
3
  //#region src/types/tool-display.d.ts
4
4
  /**
5
5
  * Variant-specific rendering instructions for tool output.
6
6
  * Maps directly to specialised preview components.
7
7
  */
8
- type DisplayVariant = "command" | "write-file" | "read-file" | "diff" | "question" | "web-search" | "grep" | "glob" | "default";
8
+ export type DisplayVariant = "command" | "write-file" | "read-file" | "diff" | "question" | "web-search" | "grep" | "glob" | "default";
9
9
  /**
10
10
  * Custom renderer for tool details. Return a ReactNode to override the
11
11
  * default ExpandedToolDetail, or null to fall back to the built-in renderer.
12
12
  */
13
- type CustomToolRenderer = (part: ToolPart) => ReactNode | null;
13
+ export type CustomToolRenderer = (part: ToolPart) => ReactNode | null;
14
14
  /**
15
15
  * Visual metadata for a tool invocation — computed from the tool name,
16
16
  * input, and output by `getToolDisplayMetadata()`.
17
17
  */
18
- interface ToolDisplayMetadata {
18
+ export interface ToolDisplayMetadata {
19
19
  title: string;
20
20
  description?: string;
21
21
  inputTitle?: string;
@@ -28,5 +28,4 @@ interface ToolDisplayMetadata {
28
28
  commandSnippet?: string;
29
29
  targetPath?: string;
30
30
  }
31
- //#endregion
32
- export { DisplayVariant as n, ToolDisplayMetadata as r, CustomToolRenderer as t };
31
+ //#endregion
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { t as SessionMessage } from "./message-vhUI0c5G.js";
2
- import { a as ToolState, i as ToolPart, n as SessionPart, o as ToolStatus, r as TextPart, s as ToolTime, t as ReasoningPart } from "./parts-5mhy9yJ3.js";
3
- import { t as AgentBranding } from "./branding-26d5ARIr.js";
4
- import { n as DisplayVariant, r as ToolDisplayMetadata, t as CustomToolRenderer } from "./tool-display-DgmL5rIU.js";
5
- import { a as Run, i as MessageUser, n as GroupedMessage, o as RunStats, r as MessageRun, s as ToolCategory, t as FinalTextPart } from "./run-OyWmIDqu.js";
1
+ import { SessionMessage } from "./types/message.js";
2
+ import { ReasoningPart, SessionPart, TextPart, ToolPart, ToolState, ToolStatus, ToolTime } from "./types/parts.js";
3
+ import { AgentBranding } from "./types/branding.js";
4
+ import { CustomToolRenderer, DisplayVariant, ToolDisplayMetadata } from "./types/tool-display.js";
5
+ import { FinalTextPart, GroupedMessage, MessageRun, MessageUser, Run, RunStats, ToolCategory } from "./types/run.js";
6
6
  export { AgentBranding, CustomToolRenderer, DisplayVariant, FinalTextPart, GroupedMessage, MessageRun, MessageUser, ReasoningPart, Run, RunStats, SessionMessage, SessionPart, TextPart, ToolCategory, ToolDisplayMetadata, ToolPart, ToolState, ToolStatus, ToolTime };
@@ -0,0 +1,7 @@
1
+ //#region src/utils/copy-text.d.ts
2
+ /**
3
+ * Copy text to clipboard with a non-secure-context fallback.
4
+ * Returns true if a copy strategy likely succeeded.
5
+ */
6
+ export declare function copyText(text: string): Promise<boolean>;
7
+ //#endregion
@@ -24,14 +24,4 @@ async function copyText(text) {
24
24
  }
25
25
  }
26
26
  //#endregion
27
- //#region src/utils/time-ago.ts
28
- function timeAgo(ts) {
29
- const secs = Math.floor((Date.now() - ts) / 1e3);
30
- if (secs < 5) return "just now";
31
- if (secs < 60) return `${secs}s ago`;
32
- const mins = Math.floor(secs / 60);
33
- if (mins < 60) return `${mins}m ago`;
34
- return `${Math.floor(mins / 60)}h ago`;
35
- }
36
- //#endregion
37
- export { copyText as n, timeAgo as t };
27
+ export { copyText };
@@ -0,0 +1,22 @@
1
+ //#region src/utils/format.d.ts
2
+ /** Format a duration in milliseconds to a human-readable string. */
3
+ export declare function formatDuration(ms: number): string;
4
+ /** Truncate text to `max` characters, appending "..." if truncated. */
5
+ export declare function truncateText(text: string, max: number): string;
6
+ /**
7
+ * Format an uptime duration in milliseconds with progressive
8
+ * granularity, so short-lived sandboxes don't render as "0d 0h".
9
+ * - < 60s → "Ns"
10
+ * - < 60m → "Nm Ss"
11
+ * - < 24h → "Nh Mm"
12
+ * - otherwise → "Nd Hh"
13
+ */
14
+ export declare function formatUptime(ms: number): string;
15
+ /**
16
+ * Format a byte count using binary units (KiB/MiB/GiB, surfaced as
17
+ * "KB/MB/GB" for readability). KB and MB use one decimal below 10 and
18
+ * round above; GB keeps two decimals below 10 so half-GB changes stay
19
+ * visible on memory dashboards, and drops to one decimal above.
20
+ */
21
+ export declare function formatBytes(bytes: number): string;
22
+ //#endregion
@@ -51,4 +51,4 @@ function formatBytes(bytes) {
51
51
  return `${gb < 10 ? gb.toFixed(2) : gb.toFixed(1)} GB`;
52
52
  }
53
53
  //#endregion
54
- export { truncateText as i, formatDuration as n, formatUptime as r, formatBytes as t };
54
+ export { formatBytes, formatDuration, formatUptime, truncateText };
@@ -0,0 +1,3 @@
1
+ //#region src/utils/time-ago.d.ts
2
+ export declare function timeAgo(ts: number): string;
3
+ //#endregion
@@ -0,0 +1,11 @@
1
+ //#region src/utils/time-ago.ts
2
+ function timeAgo(ts) {
3
+ const secs = Math.floor((Date.now() - ts) / 1e3);
4
+ if (secs < 5) return "just now";
5
+ if (secs < 60) return `${secs}s ago`;
6
+ const mins = Math.floor(secs / 60);
7
+ if (mins < 60) return `${mins}m ago`;
8
+ return `${Math.floor(mins / 60)}h ago`;
9
+ }
10
+ //#endregion
11
+ export { timeAgo };
@@ -0,0 +1,16 @@
1
+ import { ToolPart } from "../types/parts.js";
2
+ import { ToolDisplayMetadata } from "../types/tool-display.js";
3
+ import { ToolCategory } from "../types/run.js";
4
+ //#region src/utils/tool-display.d.ts
5
+ export declare const TOOL_CATEGORY_ICONS: Record<ToolCategory, string>;
6
+ export declare function getToolCategory(toolName: string): ToolCategory;
7
+ /**
8
+ * Title is the bare verb ("Read", "Edit", "Shell"); the path, command, pattern
9
+ * or query lives in `description` only, so a row prints its subject once.
10
+ * `targetPath` / `commandSnippet` carry the untruncated value for consumers
11
+ * that need it (diff headers, expanded detail).
12
+ */
13
+ export declare function getToolDisplayMetadata(part: ToolPart): ToolDisplayMetadata;
14
+ /** Extract error text from a tool part, if any. */
15
+ export declare function getToolErrorText(part: ToolPart, fallback?: string): string | undefined;
16
+ //#endregion
@@ -155,4 +155,4 @@ function getToolErrorText(part, fallback) {
155
155
  return part.state.error || fallback;
156
156
  }
157
157
  //#endregion
158
- export { getToolErrorText as i, getToolCategory as n, getToolDisplayMetadata as r, TOOL_CATEGORY_ICONS as t };
158
+ export { TOOL_CATEGORY_ICONS, getToolCategory, getToolDisplayMetadata, getToolErrorText };
package/dist/utils.d.ts CHANGED
@@ -1,2 +1,7 @@
1
- import { a as focusRingInset, c as getToolCategory, d as timeAgo, f as formatBytes, g as copyText, h as truncateText, i as focusRing, l as getToolDisplayMetadata, m as formatUptime, n as focusFieldInvalid, o as cn, p as formatDuration, r as focusFieldWithin, s as TOOL_CATEGORY_ICONS, t as focusField, u as getToolErrorText } from "./index-BgUHilZm.js";
1
+ import { copyText } from "./utils/copy-text.js";
2
+ import { formatBytes, formatDuration, formatUptime, truncateText } from "./utils/format.js";
3
+ import { timeAgo } from "./utils/time-ago.js";
4
+ import { TOOL_CATEGORY_ICONS, getToolCategory, getToolDisplayMetadata, getToolErrorText } from "./utils/tool-display.js";
5
+ import { cn } from "./lib/utils.js";
6
+ import { focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset } from "./lib/focus.js";
2
7
  export { TOOL_CATEGORY_ICONS, cn, copyText, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatDuration, formatUptime, getToolCategory, getToolDisplayMetadata, getToolErrorText, timeAgo, truncateText };
package/dist/utils.js CHANGED
@@ -1,6 +1,7 @@
1
- import { a as focusRingInset, i as focusRing, n as focusFieldInvalid, r as focusFieldWithin, t as focusField } from "./focus-BcE_SHqz.js";
2
- import { t as cn } from "./utils-C_uf36nf.js";
3
- import { i as getToolErrorText, n as getToolCategory, r as getToolDisplayMetadata, t as TOOL_CATEGORY_ICONS } from "./tool-display-BAuAVa43.js";
4
- import { i as truncateText, n as formatDuration, r as formatUptime, t as formatBytes } from "./format-DfcLWqMa.js";
5
- import { n as copyText, t as timeAgo } from "./utils-Bl_CZ0Qp.js";
1
+ import { focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset } from "./lib/focus.js";
2
+ import { cn } from "./lib/utils.js";
3
+ import { TOOL_CATEGORY_ICONS, getToolCategory, getToolDisplayMetadata, getToolErrorText } from "./utils/tool-display.js";
4
+ import { formatBytes, formatDuration, formatUptime, truncateText } from "./utils/format.js";
5
+ import { copyText } from "./utils/copy-text.js";
6
+ import { timeAgo } from "./utils/time-ago.js";
6
7
  export { TOOL_CATEGORY_ICONS, cn, copyText, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatDuration, formatUptime, getToolCategory, getToolDisplayMetadata, getToolErrorText, timeAgo, truncateText };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.11.8",
3
+ "version": "11.13.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",
@@ -142,7 +142,7 @@
142
142
  "peerDependencies": {
143
143
  "@hocuspocus/provider": ">=2.15.0 <5.0.0",
144
144
  "@nanostores/react": ">=0.8.4 <3.0.0",
145
- "@tangle-network/brand": "^1.9.0",
145
+ "@tangle-network/brand": "^1.10.0",
146
146
  "@tiptap/core": ">=3.20.4 <4.0.0",
147
147
  "@tiptap/extension-collaboration": ">=3.20.4 <4.0.0",
148
148
  "@tiptap/extension-collaboration-caret": ">=3.7.0 <4.0.0",
@@ -37,6 +37,8 @@ export interface DocumentEditorPaneProps
37
37
  tabs?: ArtifactPaneProps["tabs"];
38
38
  toolbar?: ReactNode;
39
39
  markdown?: string;
40
+ /** Persisted baseline when the host controls the current markdown draft. */
41
+ savedMarkdown?: string;
40
42
  /** Transform Markdown links and images through the shared URL policy. */
41
43
  urlTransform?: UrlTransform;
42
44
  mode?: DocumentEditorMode;
@@ -191,6 +193,7 @@ export function DocumentEditorPane({
191
193
  tabs,
192
194
  toolbar,
193
195
  markdown = "",
196
+ savedMarkdown,
194
197
  urlTransform,
195
198
  mode,
196
199
  defaultMode = "preview",
@@ -212,7 +215,7 @@ export function DocumentEditorPane({
212
215
  useState<DocumentEditorMode>(defaultMode);
213
216
  const activeMode = mode ?? uncontrolledMode;
214
217
  const isCollaborative = backend === "collaborative" && Boolean(collaboration);
215
- const isDirty = normalizeMarkdown(draft) !== normalizeMarkdown(markdown);
218
+ const isDirty = normalizeMarkdown(draft) !== normalizeMarkdown(savedMarkdown ?? markdown);
216
219
  const saveStateLabel = readOnly
217
220
  ? "Read only"
218
221
  : isCollaborative
@@ -231,3 +231,26 @@ describe("parseCsv", () => {
231
231
  expect(parseCsv("\n\n")).toEqual([]);
232
232
  });
233
233
  });
234
+
235
+ describe("FileArtifactPane controlled drafts", () => {
236
+ it("retains a host draft and saves it against the persisted baseline", async () => {
237
+ const onSave = vi.fn();
238
+ const draft = "# Unsaved source draft";
239
+ const props = {
240
+ filename: "notes.md",
241
+ content: draft,
242
+ editor: { enabled: true, savedContent: "# Saved notes", onSave },
243
+ };
244
+ const view = render(<FileArtifactPane {...props} />);
245
+ const save = await screen.findByRole("button", { name: "Save changes" });
246
+ expect(screen.getByRole("heading", { name: "Unsaved source draft" })).toBeVisible();
247
+ expect(screen.getByText("Unsaved changes")).toBeVisible();
248
+ expect(save).toBeEnabled();
249
+ fireEvent.click(save);
250
+ expect(onSave).toHaveBeenCalledWith(draft);
251
+
252
+ view.rerender(<FileArtifactPane {...props} editor={{ ...props.editor, savedContent: draft }} />);
253
+ expect(screen.getByText("Saved")).toBeVisible();
254
+ expect(save).toBeDisabled();
255
+ });
256
+ });
@@ -18,6 +18,8 @@ const LazyDocumentEditorPane = lazy(async () => {
18
18
 
19
19
  export interface FileArtifactPaneEditorOptions {
20
20
  enabled?: boolean;
21
+ /** Persisted baseline when content is the current draft. */
22
+ savedContent?: string;
21
23
  mode?: DocumentEditorMode;
22
24
  defaultMode?: DocumentEditorMode;
23
25
  onModeChange?: (mode: DocumentEditorMode) => void;
@@ -155,6 +157,7 @@ export function FileArtifactPane({
155
157
  headerClassName={headerClassName}
156
158
  hideTitleBlock={hideTitleBlock}
157
159
  markdown={content ?? ""}
160
+ savedMarkdown={editor.savedContent}
158
161
  urlTransform={urlTransform}
159
162
  mode={editor.mode}
160
163
  defaultMode={editor.defaultMode}
@@ -8,7 +8,7 @@ const meta: Meta<typeof RichFileTree> = {
8
8
  parameters: { layout: "padded", backgrounds: { default: "dark" } },
9
9
  decorators: [
10
10
  (Story) => (
11
- <div className="w-[420px] h-[480px] rounded-xl border border-border bg-card overflow-hidden">
11
+ <div className="w-full max-w-[420px] h-[480px] rounded-xl border border-border bg-card overflow-hidden">
12
12
  <Story />
13
13
  </div>
14
14
  ),