basalt-ui 1.11.0 → 1.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 (301) hide show
  1. package/AGENTS.md +21 -21
  2. package/CHANGELOG.md +32 -0
  3. package/README.md +63 -51
  4. package/agent/rules/basalt-agent.md +14 -0
  5. package/agent/rules/basalt-content.md +16 -3
  6. package/configs/oxlint-plugin.js +7 -3
  7. package/configs/oxlint-plugin.test.ts +34 -5
  8. package/configs/oxlint.json +4 -4
  9. package/dist/agent/adapter.d.ts +150 -0
  10. package/dist/agent/adapter.d.ts.map +1 -0
  11. package/dist/agent/adapter.js +482 -0
  12. package/dist/agent/adapter.test.d.ts +2 -0
  13. package/dist/agent/adapter.test.d.ts.map +1 -0
  14. package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
  15. package/dist/agent/ai-sdk-transport.js +6 -2
  16. package/dist/agent/ai-sdk-transport.test.d.ts +2 -0
  17. package/dist/agent/ai-sdk-transport.test.d.ts.map +1 -0
  18. package/dist/agent/coalesce.d.ts +20 -4
  19. package/dist/agent/coalesce.d.ts.map +1 -1
  20. package/dist/agent/coalesce.js +76 -8
  21. package/dist/agent/coalesce.test.d.ts +2 -0
  22. package/dist/agent/coalesce.test.d.ts.map +1 -0
  23. package/dist/agent/foreign.test.d.ts +2 -0
  24. package/dist/agent/foreign.test.d.ts.map +1 -0
  25. package/dist/agent/id.d.ts +78 -0
  26. package/dist/agent/id.d.ts.map +1 -1
  27. package/dist/agent/id.js +30 -0
  28. package/dist/agent/id.test.d.ts +2 -0
  29. package/dist/agent/id.test.d.ts.map +1 -0
  30. package/dist/agent/index.d.ts +2 -0
  31. package/dist/agent/index.d.ts.map +1 -1
  32. package/dist/agent/index.js +3 -0
  33. package/dist/agent/merge.d.ts.map +1 -1
  34. package/dist/agent/merge.js +19 -7
  35. package/dist/agent/merge.test.d.ts +2 -0
  36. package/dist/agent/merge.test.d.ts.map +1 -0
  37. package/dist/agent/part-list.d.ts +49 -34
  38. package/dist/agent/part-list.d.ts.map +1 -1
  39. package/dist/agent/part-list.js +42 -6
  40. package/dist/agent/part-list.test.d.ts +2 -0
  41. package/dist/agent/part-list.test.d.ts.map +1 -0
  42. package/dist/agent/parts.d.ts +46 -10
  43. package/dist/agent/parts.d.ts.map +1 -1
  44. package/dist/agent/parts.js +16 -6
  45. package/dist/agent/parts.test.d.ts +2 -0
  46. package/dist/agent/parts.test.d.ts.map +1 -0
  47. package/dist/agent/thread.d.ts +18 -0
  48. package/dist/agent/thread.d.ts.map +1 -1
  49. package/dist/agent/thread.js +8 -2
  50. package/dist/agent/thread.test.d.ts +2 -0
  51. package/dist/agent/thread.test.d.ts.map +1 -0
  52. package/dist/agent/transport.test.d.ts +2 -0
  53. package/dist/agent/transport.test.d.ts.map +1 -0
  54. package/dist/agent/use-agent-stream.d.ts.map +1 -1
  55. package/dist/agent/use-agent-stream.js +2 -2
  56. package/dist/agent/use-agent-stream.test.d.ts +2 -0
  57. package/dist/agent/use-agent-stream.test.d.ts.map +1 -0
  58. package/dist/agent/use-agent-thread-runs.d.ts +24 -3
  59. package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
  60. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts +2 -0
  61. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts.map +1 -0
  62. package/dist/agent/use-agent-thread-runs.js +55 -15
  63. package/dist/agent/use-agent-thread-runs.resume.test.d.ts +2 -0
  64. package/dist/agent/use-agent-thread-runs.resume.test.d.ts.map +1 -0
  65. package/dist/agent/use-agent-thread-runs.test.d.ts +2 -0
  66. package/dist/agent/use-agent-thread-runs.test.d.ts.map +1 -0
  67. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts +2 -0
  68. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts.map +1 -0
  69. package/dist/agent-chat/composer.d.ts +130 -8
  70. package/dist/agent-chat/composer.d.ts.map +1 -1
  71. package/dist/agent-chat/composer.js +189 -23
  72. package/dist/agent-chat/composer.test.d.ts +2 -0
  73. package/dist/agent-chat/composer.test.d.ts.map +1 -0
  74. package/dist/agent-chat/index.d.ts +5 -1
  75. package/dist/agent-chat/index.d.ts.map +1 -1
  76. package/dist/agent-chat/index.js +2 -0
  77. package/dist/agent-chat/message-affordances.d.ts +59 -0
  78. package/dist/agent-chat/message-affordances.d.ts.map +1 -0
  79. package/dist/agent-chat/message-affordances.js +7 -0
  80. package/dist/agent-chat/relative-time.d.ts +27 -0
  81. package/dist/agent-chat/relative-time.d.ts.map +1 -0
  82. package/dist/agent-chat/relative-time.js +27 -0
  83. package/dist/agent-chat/relative-time.test.d.ts +2 -0
  84. package/dist/agent-chat/relative-time.test.d.ts.map +1 -0
  85. package/dist/agent-chat/thread-detail-panel.d.ts.map +1 -1
  86. package/dist/agent-chat/thread-detail-panel.js +1 -1
  87. package/dist/agent-chat/thread-feed-row.d.ts +65 -0
  88. package/dist/agent-chat/thread-feed-row.d.ts.map +1 -0
  89. package/dist/agent-chat/thread-feed-row.js +156 -0
  90. package/dist/agent-chat/thread-feed-row.test.d.ts +2 -0
  91. package/dist/agent-chat/thread-feed-row.test.d.ts.map +1 -0
  92. package/dist/agent-chat/thread-feed.d.ts +34 -5
  93. package/dist/agent-chat/thread-feed.d.ts.map +1 -1
  94. package/dist/agent-chat/thread-feed.js +52 -19
  95. package/dist/agent-chat/thread-feed.test.d.ts +2 -0
  96. package/dist/agent-chat/thread-feed.test.d.ts.map +1 -0
  97. package/dist/agent-chat/thread-message.d.ts +183 -2
  98. package/dist/agent-chat/thread-message.d.ts.map +1 -1
  99. package/dist/agent-chat/thread-message.js +329 -37
  100. package/dist/agent-chat/thread-message.test.d.ts +2 -0
  101. package/dist/agent-chat/thread-message.test.d.ts.map +1 -0
  102. package/dist/agent-chat/thread-outcome-card.d.ts.map +1 -1
  103. package/dist/agent-chat/thread-outcome-card.js +1 -16
  104. package/dist/agent-chat/thread-workspace.d.ts +14 -2
  105. package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
  106. package/dist/agent-chat/thread-workspace.js +30 -4
  107. package/dist/agent-chat/thread-workspace.test.d.ts +2 -0
  108. package/dist/agent-chat/thread-workspace.test.d.ts.map +1 -0
  109. package/dist/agent-chat/tool-chip.test.d.ts +2 -0
  110. package/dist/agent-chat/tool-chip.test.d.ts.map +1 -0
  111. package/dist/agent-chat/virtualize.d.ts +123 -0
  112. package/dist/agent-chat/virtualize.d.ts.map +1 -0
  113. package/dist/agent-chat/virtualize.js +9 -0
  114. package/dist/agent-chat/virtualize.type-guard.test.d.ts +2 -0
  115. package/dist/agent-chat/virtualize.type-guard.test.d.ts.map +1 -0
  116. package/dist/charts/primitives/ChartFrame.test.d.ts +2 -0
  117. package/dist/charts/primitives/ChartFrame.test.d.ts.map +1 -0
  118. package/dist/charts/utils/domain.test.d.ts +2 -0
  119. package/dist/charts/utils/domain.test.d.ts.map +1 -0
  120. package/dist/cli/check-theme.test.d.ts +2 -0
  121. package/dist/cli/check-theme.test.d.ts.map +1 -0
  122. package/dist/cli/help.test.d.ts +2 -0
  123. package/dist/cli/help.test.d.ts.map +1 -0
  124. package/dist/cli/index.d.ts.map +1 -1
  125. package/dist/cli/init-sync.test.d.ts +2 -0
  126. package/dist/cli/init-sync.test.d.ts.map +1 -0
  127. package/dist/cli/placement-engine.test.d.ts +2 -0
  128. package/dist/cli/placement-engine.test.d.ts.map +1 -0
  129. package/dist/cli/tarball-parity.test.d.ts +2 -0
  130. package/dist/cli/tarball-parity.test.d.ts.map +1 -0
  131. package/dist/cli/template-format.test.d.ts +2 -0
  132. package/dist/cli/template-format.test.d.ts.map +1 -0
  133. package/dist/cli/template-ledger.test.d.ts +2 -0
  134. package/dist/cli/template-ledger.test.d.ts.map +1 -0
  135. package/dist/cli/tokens-css.test.d.ts +2 -0
  136. package/dist/cli/tokens-css.test.d.ts.map +1 -0
  137. package/dist/content/article-actions.test.d.ts +2 -0
  138. package/dist/content/article-actions.test.d.ts.map +1 -0
  139. package/dist/content/article-model.test.d.ts +2 -0
  140. package/dist/content/article-model.test.d.ts.map +1 -0
  141. package/dist/content/block-split.test.d.ts +2 -0
  142. package/dist/content/block-split.test.d.ts.map +1 -0
  143. package/dist/content/fence-block.d.ts +37 -10
  144. package/dist/content/fence-block.d.ts.map +1 -1
  145. package/dist/content/fence-block.js +80 -14
  146. package/dist/content/heading-components.d.ts.map +1 -1
  147. package/dist/content/heading-components.js +7 -4
  148. package/dist/content/heading-components.test.d.ts +2 -0
  149. package/dist/content/heading-components.test.d.ts.map +1 -0
  150. package/dist/content/index.d.ts +6 -3
  151. package/dist/content/index.d.ts.map +1 -1
  152. package/dist/content/index.js +5 -1
  153. package/dist/content/markdown.d.ts +101 -14
  154. package/dist/content/markdown.d.ts.map +1 -1
  155. package/dist/content/markdown.js +141 -28
  156. package/dist/content/markdown.test.d.ts +2 -0
  157. package/dist/content/markdown.test.d.ts.map +1 -0
  158. package/dist/content/prose.module.css.test.d.ts +2 -0
  159. package/dist/content/prose.module.css.test.d.ts.map +1 -0
  160. package/dist/content/sanitize.d.ts +170 -0
  161. package/dist/content/sanitize.d.ts.map +1 -0
  162. package/dist/content/sanitize.js +178 -0
  163. package/dist/content/sanitize.test.d.ts +2 -0
  164. package/dist/content/sanitize.test.d.ts.map +1 -0
  165. package/dist/content/slug.test.d.ts +2 -0
  166. package/dist/content/slug.test.d.ts.map +1 -0
  167. package/dist/dashboard/stat-card.test.d.ts +2 -0
  168. package/dist/dashboard/stat-card.test.d.ts.map +1 -0
  169. package/dist/data/index.d.ts +5 -1
  170. package/dist/data/index.d.ts.map +1 -1
  171. package/dist/data/virtual-list.d.ts.map +1 -1
  172. package/dist/data/virtual.d.ts +2 -1
  173. package/dist/data/virtual.d.ts.map +1 -1
  174. package/dist/guard/check-source.test.d.ts +2 -0
  175. package/dist/guard/check-source.test.d.ts.map +1 -0
  176. package/dist/guard/guard-hook.test.d.ts +2 -0
  177. package/dist/guard/guard-hook.test.d.ts.map +1 -0
  178. package/dist/index.d.ts +4 -4
  179. package/dist/index.d.ts.map +1 -1
  180. package/dist/index.js +11 -1
  181. package/dist/provider/index.test.d.ts +2 -0
  182. package/dist/provider/index.test.d.ts.map +1 -0
  183. package/dist/provider/lab-theme.test.d.ts +2 -0
  184. package/dist/provider/lab-theme.test.d.ts.map +1 -0
  185. package/dist/router-tanstack/multi-search-param-store.test.d.ts +2 -0
  186. package/dist/router-tanstack/multi-search-param-store.test.d.ts.map +1 -0
  187. package/dist/styles.floor.test.d.ts +2 -0
  188. package/dist/styles.floor.test.d.ts.map +1 -0
  189. package/dist/surfaces.d.ts +5 -5
  190. package/dist/surfaces.d.ts.map +1 -1
  191. package/dist/surfaces.js +14 -4
  192. package/dist/theme/border-coverage.test.d.ts +18 -0
  193. package/dist/theme/border-coverage.test.d.ts.map +1 -0
  194. package/dist/theme/contrast.test.d.ts +2 -0
  195. package/dist/theme/contrast.test.d.ts.map +1 -0
  196. package/dist/theme/density-option.test.d.ts +2 -0
  197. package/dist/theme/density-option.test.d.ts.map +1 -0
  198. package/dist/theme/density-relations.test.d.ts +2 -0
  199. package/dist/theme/density-relations.test.d.ts.map +1 -0
  200. package/dist/theme/derive-theme.test.d.ts +2 -0
  201. package/dist/theme/derive-theme.test.d.ts.map +1 -0
  202. package/dist/theme/fonts-option.test.d.ts +2 -0
  203. package/dist/theme/fonts-option.test.d.ts.map +1 -0
  204. package/dist/theme/radius-option.test.d.ts +2 -0
  205. package/dist/theme/radius-option.test.d.ts.map +1 -0
  206. package/dist/theme/radius.test.d.ts +2 -0
  207. package/dist/theme/radius.test.d.ts.map +1 -0
  208. package/dist/theme/shadow-surfaces.test.d.ts +26 -0
  209. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -0
  210. package/dist/theme/spacing.test.d.ts +2 -0
  211. package/dist/theme/spacing.test.d.ts.map +1 -0
  212. package/dist/theme/use-basalt-spacing.test.d.ts +2 -0
  213. package/dist/theme/use-basalt-spacing.test.d.ts.map +1 -0
  214. package/dist/theme-lab/derive-controls.test.d.ts +2 -0
  215. package/dist/theme-lab/derive-controls.test.d.ts.map +1 -0
  216. package/dist/theme-lab/derive-state.test.d.ts +2 -0
  217. package/dist/theme-lab/derive-state.test.d.ts.map +1 -0
  218. package/dist/theme-lab/theme-lab.test.d.ts +2 -0
  219. package/dist/theme-lab/theme-lab.test.d.ts.map +1 -0
  220. package/dist/tokens/build-fonts-css.test.d.ts +2 -0
  221. package/dist/tokens/build-fonts-css.test.d.ts.map +1 -0
  222. package/dist/tokens/build-radius-css.test.d.ts +2 -0
  223. package/dist/tokens/build-radius-css.test.d.ts.map +1 -0
  224. package/dist/tokens/density.test.d.ts +2 -0
  225. package/dist/tokens/density.test.d.ts.map +1 -0
  226. package/dist/tokens/derive-radius.test.d.ts +2 -0
  227. package/dist/tokens/derive-radius.test.d.ts.map +1 -0
  228. package/dist/tokens/derive.test.d.ts +2 -0
  229. package/dist/tokens/derive.test.d.ts.map +1 -0
  230. package/dist/tokens/palette.test.d.ts +2 -0
  231. package/dist/tokens/palette.test.d.ts.map +1 -0
  232. package/dist/vite-color-mix.test.d.ts +2 -0
  233. package/dist/vite-color-mix.test.d.ts.map +1 -0
  234. package/dist/vite.test.d.ts +2 -0
  235. package/dist/vite.test.d.ts.map +1 -0
  236. package/llms.txt +9 -9
  237. package/package.json +7 -2
  238. package/src/agent/adapter.test.ts +1640 -0
  239. package/src/agent/adapter.ts +945 -0
  240. package/src/agent/ai-sdk-transport.test.ts +117 -26
  241. package/src/agent/ai-sdk-transport.ts +8 -2
  242. package/src/agent/coalesce.test.ts +202 -9
  243. package/src/agent/coalesce.ts +126 -15
  244. package/src/agent/foreign.test.ts +5 -1
  245. package/src/agent/id.test.ts +101 -3
  246. package/src/agent/id.ts +118 -0
  247. package/src/agent/index.ts +8 -0
  248. package/src/agent/merge.test.ts +87 -0
  249. package/src/agent/merge.ts +57 -10
  250. package/src/agent/part-list.test.tsx +31 -1
  251. package/src/agent/part-list.tsx +77 -29
  252. package/src/agent/parts.test.ts +121 -4
  253. package/src/agent/parts.ts +78 -17
  254. package/src/agent/thread.test.ts +75 -2
  255. package/src/agent/thread.ts +25 -1
  256. package/src/agent/use-agent-stream.test.tsx +31 -5
  257. package/src/agent/use-agent-stream.ts +6 -3
  258. package/src/agent/use-agent-thread-runs.foreign.test.tsx +5 -0
  259. package/src/agent/use-agent-thread-runs.resume.test.tsx +16 -11
  260. package/src/agent/use-agent-thread-runs.test.tsx +356 -17
  261. package/src/agent/use-agent-thread-runs.ts +159 -23
  262. package/src/agent/use-agent-thread-runs.wedge.test.tsx +93 -7
  263. package/src/agent-chat/composer.test.tsx +924 -0
  264. package/src/agent-chat/composer.tsx +521 -52
  265. package/src/agent-chat/index.ts +13 -1
  266. package/src/agent-chat/message-affordances.ts +61 -0
  267. package/src/agent-chat/relative-time.test.ts +95 -0
  268. package/src/agent-chat/relative-time.ts +55 -0
  269. package/src/agent-chat/thread-detail-panel.tsx +3 -1
  270. package/src/agent-chat/thread-feed-row.test.tsx +510 -0
  271. package/src/agent-chat/thread-feed-row.tsx +274 -0
  272. package/src/agent-chat/thread-feed.test.tsx +241 -0
  273. package/src/agent-chat/thread-feed.tsx +119 -18
  274. package/src/agent-chat/thread-message.test.tsx +1078 -4
  275. package/src/agent-chat/thread-message.tsx +795 -42
  276. package/src/agent-chat/thread-outcome-card.tsx +1 -25
  277. package/src/agent-chat/thread-workspace.test.tsx +169 -0
  278. package/src/agent-chat/thread-workspace.tsx +70 -6
  279. package/src/agent-chat/virtualize.ts +132 -0
  280. package/src/agent-chat/virtualize.type-guard.test.ts +49 -0
  281. package/src/cli/index.ts +0 -1
  282. package/src/cli/template-format.test.ts +2 -2
  283. package/src/content/fence-block.tsx +242 -15
  284. package/src/content/heading-components.test.tsx +65 -0
  285. package/src/content/heading-components.tsx +26 -5
  286. package/src/content/index.ts +19 -3
  287. package/src/content/markdown.test.tsx +603 -0
  288. package/src/content/markdown.tsx +355 -35
  289. package/src/content/sanitize.test.ts +659 -0
  290. package/src/content/sanitize.ts +510 -0
  291. package/src/dashboard/stat-card.test.tsx +6 -1
  292. package/src/data/index.ts +5 -1
  293. package/src/data/virtual-list.tsx +2 -1
  294. package/src/data/virtual.ts +2 -1
  295. package/src/index.ts +18 -1
  296. package/src/surfaces.ts +13 -3
  297. package/src/theme/contrast.test.ts +1 -4
  298. package/src/theme/density-relations.test.ts +4 -2
  299. package/src/theme/fonts-option.test.ts +7 -1
  300. package/src/theme/shadow-surfaces.test.ts +21 -6
  301. package/src/theme/use-basalt-spacing.test.tsx +2 -1
@@ -10,12 +10,16 @@ import {
10
10
  Text,
11
11
  UnstyledButton
12
12
  } from "@mantine/core";
13
- import { useDisclosure } from "@mantine/hooks";
14
- import { Fragment, memo, useMemo } from "react";
13
+ import { useDisclosure, useFocusWithin, useHover, useMergedRef } from "@mantine/hooks";
14
+ import { Fragment, lazy, memo, Suspense, useLayoutEffect, useMemo, useRef } from "react";
15
15
  import { coalesceParts, narrowAgentPart, PartList } from "../agent/index.js";
16
16
  import { Markdown } from "../content/markdown.js";
17
+ import { CopyAction } from "../content/copy-action.js";
17
18
  import { VX } from "../tokens/index.js";
19
+ import { DEFAULT_AFFORDANCES } from "./message-affordances.js";
20
+ import { formatRelativeTime } from "./relative-time.js";
18
21
  import { ToolChip } from "./tool-chip.js";
22
+ import { resolveVirtualize } from "./virtualize.js";
19
23
  const MICRO_LABEL_STYLE = {
20
24
  fontFamily: "var(--basalt-font-mono)",
21
25
  fontSize: VX.text.micro,
@@ -24,8 +28,11 @@ const MICRO_LABEL_STYLE = {
24
28
  textTransform: "uppercase",
25
29
  color: VX.faint
26
30
  };
27
- function TextRenderer({ part }) {
28
- return /* @__PURE__ */ jsx(Markdown, { streaming: true, density: "chat", children: part.text });
31
+ function TextRenderer({
32
+ part,
33
+ settled
34
+ }) {
35
+ return /* @__PURE__ */ jsx(Markdown, { streaming: !settled, contentTrust: "untrusted", density: "chat", children: part.text });
29
36
  }
30
37
  const RAIL_STYLE = {
31
38
  borderLeft: `2px solid ${VX.divider}`,
@@ -95,6 +102,51 @@ function resolveSegments(parts, renderers, fallbackRenderer) {
95
102
  flushBuffer();
96
103
  return segments;
97
104
  }
105
+ function extractCoalescedText(segments) {
106
+ const chunks = [];
107
+ for (const segment of segments) {
108
+ if (segment.kind !== "agent") continue;
109
+ for (const part of segment.parts) {
110
+ if (part.type === "text") chunks.push(part.text);
111
+ }
112
+ }
113
+ return chunks.join("\n\n");
114
+ }
115
+ const GROUP_WINDOW_MS = 5 * 6e4;
116
+ function isConsecutiveWithPredecessor(message, predecessor) {
117
+ if (predecessor === void 0) return false;
118
+ if (predecessor.role !== message.role) return false;
119
+ return message.createdAt - predecessor.createdAt <= GROUP_WINDOW_MS;
120
+ }
121
+ function MessageAffordanceRow({
122
+ message,
123
+ coalescedText,
124
+ affordances,
125
+ isLastAssistant,
126
+ visible
127
+ }) {
128
+ const timestampMode = affordances.timestamp ?? DEFAULT_AFFORDANCES.timestamp;
129
+ const showTimestamp = timestampMode !== "none";
130
+ const showCopy = affordances.copy ?? DEFAULT_AFFORDANCES.copy;
131
+ const showRegenerate = isLastAssistant && affordances.onRegenerate !== void 0;
132
+ const customActions = affordances.actions?.({ message });
133
+ if (!showTimestamp && !showCopy && !showRegenerate && customActions === void 0) return null;
134
+ const timestampLabel = timestampMode === "absolute" ? new Date(message.createdAt).toLocaleString() : formatRelativeTime(message.createdAt);
135
+ return /* @__PURE__ */ jsxs(
136
+ Group,
137
+ {
138
+ gap: 8,
139
+ "data-testid": `message-affordances-${message.id}`,
140
+ style: { opacity: visible ? 1 : 0, transition: "opacity 120ms ease" },
141
+ children: [
142
+ showTimestamp && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: timestampLabel }),
143
+ showCopy && /* @__PURE__ */ jsx(CopyAction, { value: coalescedText, ariaLabel: "Copy message" }),
144
+ showRegenerate && /* @__PURE__ */ jsx(UnstyledButton, { onClick: () => affordances.onRegenerate?.(message.id), children: /* @__PURE__ */ jsx(Text, { style: MICRO_LABEL_STYLE, children: "Regenerate" }) }),
145
+ customActions
146
+ ]
147
+ }
148
+ );
149
+ }
98
150
  const ROLE_LABEL = {
99
151
  user: "You",
100
152
  assistant: "Assistant"
@@ -108,83 +160,323 @@ function MessageBlockImpl({
108
160
  message,
109
161
  streaming = false,
110
162
  renderers,
111
- fallbackRenderer
163
+ fallbackRenderer,
164
+ grouped = false,
165
+ affordances,
166
+ isLastAssistant = false
112
167
  }) {
113
168
  messageBlockRenderCounter.count += 1;
114
169
  const settled = !streaming;
170
+ const { hovered, ref: hoverRef } = useHover();
171
+ const { focused, ref: focusWithinRef } = useFocusWithin();
172
+ const ref = useMergedRef(hoverRef, focusWithinRef);
115
173
  const segments = useMemo(
116
174
  () => resolveSegments(message.parts, renderers, fallbackRenderer),
117
175
  [message.parts, renderers, fallbackRenderer]
118
176
  );
119
177
  const finishIndicator = message.finish === void 0 ? void 0 : FINISH_INDICATOR[message.finish];
178
+ const coalescedText = useMemo(
179
+ () => affordances === void 0 ? "" : extractCoalescedText(segments),
180
+ [affordances, segments]
181
+ );
182
+ const showHeader = !grouped || streaming || finishIndicator !== void 0;
120
183
  return /* @__PURE__ */ jsx(
121
184
  Box,
122
185
  {
186
+ ref,
187
+ "data-testid": `agent-message-${message.id}`,
188
+ "data-grouped": grouped,
123
189
  style: {
124
190
  padding: "var(--vx-space-agent-message-inset-y) var(--vx-space-agent-message-inset-x)",
125
- backgroundColor: message.role === "user" ? VX.surface.subtle : VX.surface.panel,
126
- borderRadius: VX.radiusCard,
127
- boxShadow: VX.shadowCard
191
+ ...grouped ? {} : {
192
+ backgroundColor: message.role === "user" ? VX.surface.subtle : VX.surface.panel,
193
+ borderRadius: VX.radiusCard,
194
+ boxShadow: VX.shadowCard
195
+ }
128
196
  },
129
197
  children: /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
130
- /* @__PURE__ */ jsxs(Group, { gap: 6, align: "center", children: [
131
- /* @__PURE__ */ jsx(Text, { style: MICRO_LABEL_STYLE, children: ROLE_LABEL[message.role] }),
198
+ showHeader && /* @__PURE__ */ jsxs(Group, { gap: 6, align: "center", children: [
199
+ !grouped && /* @__PURE__ */ jsx(Text, { style: MICRO_LABEL_STYLE, children: ROLE_LABEL[message.role] }),
132
200
  streaming && /* @__PURE__ */ jsx(Loader, { size: "xs" }),
133
201
  finishIndicator !== void 0 && /* @__PURE__ */ jsx(Text, { style: { ...MICRO_LABEL_STYLE, color: finishIndicator.statusToken }, children: finishIndicator.label })
134
202
  ] }),
135
203
  segments.map(
136
- (segment) => segment.kind === "agent" ? /* @__PURE__ */ jsx(PartList, { parts: segment.parts, components: threadPartRenderers }, segment.key) : /* @__PURE__ */ jsx(Fragment, { children: segment.render({
204
+ (segment) => segment.kind === "agent" ? /* @__PURE__ */ jsx(
205
+ PartList,
206
+ {
207
+ parts: segment.parts,
208
+ components: threadPartRenderers,
209
+ settled
210
+ },
211
+ segment.key
212
+ ) : /* @__PURE__ */ jsx(Fragment, { children: segment.render({
137
213
  part: segment.part,
138
214
  messageId: message.id,
139
215
  partId: segment.part.id,
140
216
  settled,
141
217
  role: message.role
142
218
  }) }, segment.key)
219
+ ),
220
+ affordances !== void 0 && /* @__PURE__ */ jsx(
221
+ MessageAffordanceRow,
222
+ {
223
+ message,
224
+ coalescedText,
225
+ affordances,
226
+ isLastAssistant,
227
+ visible: hovered || focused
228
+ }
143
229
  )
144
230
  ] })
145
231
  }
146
232
  );
147
233
  }
148
234
  function areMessageBlockPropsEqual(prev, next) {
149
- return prev.message === next.message && prev.streaming === next.streaming && prev.renderers === next.renderers && prev.fallbackRenderer === next.fallbackRenderer;
235
+ return prev.message === next.message && prev.streaming === next.streaming && prev.renderers === next.renderers && prev.fallbackRenderer === next.fallbackRenderer && prev.grouped === next.grouped && prev.affordances === next.affordances && prev.isLastAssistant === next.isLastAssistant;
150
236
  }
151
237
  const MessageBlock = memo(MessageBlockImpl, areMessageBlockPropsEqual);
238
+ const DEFAULT_VIRTUALIZE_OVERSCAN = 6;
239
+ const DEFAULT_VIRTUALIZE_ESTIMATE_SIZE = 160;
240
+ const DEFAULT_VIRTUALIZE_SCROLL_END_THRESHOLD = 64;
241
+ const DEFAULT_VIRTUALIZE_INITIAL_SCROLL = "end";
242
+ const MAX_INITIAL_SCROLL_ATTEMPTS = 5;
243
+ function NonVirtualizedRows({ rows, height }) {
244
+ return (
245
+ // theme-allow — degrade target owns its own scroll node, same as the real virtualizer below.
246
+ /* @__PURE__ */ jsx(Box, { style: { height, overflow: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: "sm", children: rows.map((row) => /* @__PURE__ */ jsx(Fragment, { children: row.node }, row.key)) }) })
247
+ );
248
+ }
249
+ const nonVirtualizedRowsFallback = NonVirtualizedRows;
250
+ function resolveGuardedMeasurement(host, element, size) {
251
+ if (size > 0) return size;
252
+ const index = host.indexFromElement(element);
253
+ const key = host.options.getItemKey(index);
254
+ return host.itemSizeCache.get(key) ?? host.options.estimateSize(index);
255
+ }
256
+ function resolveInitialScrollAction(initialScroll, rowCount, containerHeight) {
257
+ if (initialScroll === "start") return "skip-done";
258
+ if (rowCount === 0) return "skip-done";
259
+ if (containerHeight <= 0) return "skip-retry";
260
+ return "scroll";
261
+ }
262
+ function applyInitialScroll(state, initialScroll, rowCount, getContainerHeight, scrollToEnd, isAtEnd) {
263
+ if (state.hasApplied) return;
264
+ if (state.attempts > 0) {
265
+ if (isAtEnd() || state.attempts >= MAX_INITIAL_SCROLL_ATTEMPTS) {
266
+ state.hasApplied = true;
267
+ return;
268
+ }
269
+ scrollToEnd();
270
+ state.attempts += 1;
271
+ return;
272
+ }
273
+ const action = resolveInitialScrollAction(initialScroll, rowCount, getContainerHeight());
274
+ if (action === "skip-retry") return;
275
+ if (action === "skip-done") {
276
+ state.hasApplied = true;
277
+ return;
278
+ }
279
+ scrollToEnd();
280
+ state.attempts += 1;
281
+ }
282
+ function isContainerAtEnd(container, threshold) {
283
+ if (container === null) return false;
284
+ const distanceFromEnd = container.scrollHeight - container.clientHeight - container.scrollTop;
285
+ return distanceFromEnd <= threshold;
286
+ }
287
+ const LazyVirtualizedRows = lazy(
288
+ () => import("@tanstack/react-virtual").then(({ useVirtualizer, measureElement: defaultMeasureElement }) => {
289
+ function VirtualizedRowsInner({
290
+ rows,
291
+ height,
292
+ overscan,
293
+ estimateSize,
294
+ initialScroll
295
+ }) {
296
+ const parentRef = useRef(null);
297
+ const initialScrollStateRef = useRef({
298
+ hasApplied: false,
299
+ attempts: 0
300
+ });
301
+ const virtualizer = useVirtualizer({
302
+ count: rows.length,
303
+ getScrollElement: () => parentRef.current,
304
+ estimateSize: () => estimateSize,
305
+ overscan,
306
+ getItemKey: (index) => rows[index]?.key ?? index,
307
+ anchorTo: "end",
308
+ followOnAppend: true,
309
+ scrollEndThreshold: DEFAULT_VIRTUALIZE_SCROLL_END_THRESHOLD,
310
+ useFlushSync: false,
311
+ // Guards against the hidden-ancestor cache-poisoning mechanism
312
+ // `resolveGuardedMeasurement`'s doc and `virtualize.ts`'s composition rules describe —
313
+ // NOT `enabled: false`, which virtual-core 3.17.1 wires to WIPE
314
+ // `itemSizeCache`/`measurementsCache` entirely (`getMeasurements`'s `!enabled` branch)
315
+ // rather than pause measurement, so toggling it off/on around a collapse would be
316
+ // strictly worse than the bug it was meant to fix.
317
+ measureElement: (element, entry, instance) => resolveGuardedMeasurement(
318
+ instance,
319
+ element,
320
+ defaultMeasureElement(element, entry, instance)
321
+ )
322
+ });
323
+ useLayoutEffect(() => {
324
+ applyInitialScroll(
325
+ initialScrollStateRef.current,
326
+ initialScroll,
327
+ rows.length,
328
+ () => parentRef.current?.offsetHeight ?? 0,
329
+ () => virtualizer.scrollToEnd({ behavior: "auto" }),
330
+ () => isContainerAtEnd(parentRef.current, DEFAULT_VIRTUALIZE_SCROLL_END_THRESHOLD)
331
+ );
332
+ });
333
+ return (
334
+ // theme-allow — TanStack Virtual measures/scrolls this element (never nest in BasaltStickToBottom, see ./virtualize).
335
+ /* @__PURE__ */ jsx(Box, { ref: parentRef, style: { height, overflow: "auto" }, children: /* @__PURE__ */ jsx(Box, { style: { height: virtualizer.getTotalSize(), position: "relative" }, children: virtualizer.getVirtualItems().map((virtualItem) => {
336
+ const row = rows[virtualItem.index];
337
+ if (row === void 0) return null;
338
+ return /* @__PURE__ */ jsx(
339
+ Box,
340
+ {
341
+ ref: virtualizer.measureElement,
342
+ "data-index": virtualItem.index,
343
+ style: {
344
+ position: "absolute",
345
+ top: 0,
346
+ left: 0,
347
+ width: "100%",
348
+ transform: `translateY(${virtualItem.start}px)`
349
+ },
350
+ children: row.node
351
+ },
352
+ row.key
353
+ );
354
+ }) }) })
355
+ );
356
+ }
357
+ return { default: VirtualizedRowsInner };
358
+ }).catch(() => ({ default: NonVirtualizedRows }))
359
+ );
360
+ function VirtualizeSuspenseFallback({ height }) {
361
+ return /* @__PURE__ */ jsx(Box, { style: { height, overflow: "auto" } });
362
+ }
363
+ function VirtualizedTranscript(props) {
364
+ return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(VirtualizeSuspenseFallback, { height: props.height }), children: /* @__PURE__ */ jsx(LazyVirtualizedRows, { ...props }) });
365
+ }
152
366
  const EMPTY_RENDERERS = {};
153
- function ThreadTranscript({
154
- messages,
155
- liveParts,
156
- liveStatus,
157
- renderers: renderersProp,
158
- fallbackRenderer = DEFAULT_FALLBACK_RENDERER
159
- }) {
367
+ function useStableCallback(callback) {
368
+ const callbackRef = useRef(callback);
369
+ callbackRef.current = callback;
370
+ const stable = useMemo(
371
+ () => (...args) => callbackRef.current?.(...args),
372
+ []
373
+ );
374
+ return callback === void 0 ? void 0 : stable;
375
+ }
376
+ function ThreadTranscript(props) {
377
+ const {
378
+ messages,
379
+ liveParts,
380
+ liveStatus,
381
+ renderers: renderersProp,
382
+ fallbackRenderer = DEFAULT_FALLBACK_RENDERER,
383
+ affordances: affordancesProp,
384
+ groupConsecutive = true
385
+ } = props;
386
+ const virtualized = resolveVirtualize(props);
160
387
  const renderers = useMemo(() => renderersProp ?? EMPTY_RENDERERS, [renderersProp]);
161
388
  const liveMessage = useMemo(() => {
162
389
  if (liveParts === void 0 || liveParts.length === 0) return null;
163
390
  return { id: "__live__", role: "assistant", parts: liveParts, createdAt: 0 };
164
391
  }, [liveParts]);
165
- return /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
166
- messages.map((message) => /* @__PURE__ */ jsx(
167
- MessageBlock,
168
- {
169
- message,
170
- renderers,
171
- fallbackRenderer
172
- },
173
- message.id
174
- )),
175
- liveMessage !== null && /* @__PURE__ */ jsx("div", { "aria-live": "polite", "aria-atomic": "false", "aria-relevant": "additions", children: /* @__PURE__ */ jsx(
176
- MessageBlock,
392
+ const lastAssistantMessageId = useMemo(() => {
393
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
394
+ const candidate = messages[i];
395
+ if (candidate?.role === "assistant") return candidate.id;
396
+ }
397
+ return null;
398
+ }, [messages]);
399
+ const stableOnRegenerate = useStableCallback(affordancesProp?.onRegenerate);
400
+ const actions = affordancesProp?.actions;
401
+ const resolvedAffordances = useMemo(
402
+ () => ({
403
+ timestamp: affordancesProp?.timestamp ?? DEFAULT_AFFORDANCES.timestamp,
404
+ copy: affordancesProp?.copy ?? DEFAULT_AFFORDANCES.copy,
405
+ // `exactOptionalPropertyTypes` forbids assigning a possibly-`undefined` value to an optional
406
+ // property directly — spread each field in only when it is actually present, rather than
407
+ // widening `MessageAffordances` itself to accept an explicit `undefined`.
408
+ ...stableOnRegenerate !== void 0 && { onRegenerate: stableOnRegenerate },
409
+ ...actions !== void 0 && { actions }
410
+ }),
411
+ [affordancesProp?.timestamp, affordancesProp?.copy, stableOnRegenerate, actions]
412
+ );
413
+ const settledRows = useMemo(() => {
414
+ const out = [];
415
+ messages.forEach((message, index) => {
416
+ out.push({
417
+ key: message.id,
418
+ node: /* @__PURE__ */ jsx(
419
+ MessageBlock,
420
+ {
421
+ message,
422
+ renderers,
423
+ fallbackRenderer,
424
+ grouped: groupConsecutive && isConsecutiveWithPredecessor(message, messages[index - 1]),
425
+ isLastAssistant: message.id === lastAssistantMessageId,
426
+ affordances: resolvedAffordances
427
+ }
428
+ )
429
+ });
430
+ });
431
+ return out;
432
+ }, [
433
+ messages,
434
+ renderers,
435
+ fallbackRenderer,
436
+ groupConsecutive,
437
+ lastAssistantMessageId,
438
+ resolvedAffordances
439
+ ]);
440
+ const liveRow = useMemo(() => {
441
+ if (liveMessage === null) return null;
442
+ return {
443
+ key: liveMessage.id,
444
+ node: /* @__PURE__ */ jsx("div", { "aria-live": "polite", "aria-atomic": "false", "aria-relevant": "additions", children: /* @__PURE__ */ jsx(
445
+ MessageBlock,
446
+ {
447
+ message: liveMessage,
448
+ streaming: liveStatus === "streaming",
449
+ renderers,
450
+ fallbackRenderer
451
+ }
452
+ ) })
453
+ };
454
+ }, [liveMessage, liveStatus, renderers, fallbackRenderer]);
455
+ const rows = useMemo(
456
+ () => liveRow === null ? settledRows : [...settledRows, liveRow],
457
+ [settledRows, liveRow]
458
+ );
459
+ if (virtualized !== null) {
460
+ return /* @__PURE__ */ jsx(
461
+ VirtualizedTranscript,
177
462
  {
178
- message: liveMessage,
179
- streaming: liveStatus === "streaming",
180
- renderers,
181
- fallbackRenderer
463
+ rows,
464
+ height: virtualized.height,
465
+ overscan: virtualized.options.overscan ?? DEFAULT_VIRTUALIZE_OVERSCAN,
466
+ estimateSize: virtualized.options.estimateSize ?? DEFAULT_VIRTUALIZE_ESTIMATE_SIZE,
467
+ initialScroll: virtualized.options.initialScroll ?? DEFAULT_VIRTUALIZE_INITIAL_SCROLL
182
468
  }
183
- ) })
184
- ] });
469
+ );
470
+ }
471
+ return /* @__PURE__ */ jsx(Stack, { gap: "sm", children: rows.map((row) => /* @__PURE__ */ jsx(Fragment, { children: row.node }, row.key)) });
185
472
  }
186
473
  export {
474
+ MAX_INITIAL_SCROLL_ATTEMPTS,
187
475
  ThreadTranscript,
476
+ applyInitialScroll,
188
477
  messageBlockRenderCounter,
478
+ nonVirtualizedRowsFallback,
479
+ resolveGuardedMeasurement,
480
+ resolveInitialScrollAction,
189
481
  threadPartRenderers
190
482
  };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=thread-message.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-message.test.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-message.test.tsx"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"thread-outcome-card.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-outcome-card.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,UAAU,CAAA;AAyHzD,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;CAC9B,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,QAAQ,EACR,QAAQ,GACT,EAAE,sBAAsB,GAAG,GAAG,CAAC,OAAO,CA+BtC"}
1
+ {"version":3,"file":"thread-outcome-card.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-outcome-card.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,UAAU,CAAA;AAiGzD,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;CAC9B,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,QAAQ,EACR,QAAQ,GACT,EAAE,sBAAsB,GAAG,GAAG,CAAC,OAAO,CA+BtC"}
@@ -2,26 +2,11 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Badge, Group, Skeleton, Stack, Text, UnstyledButton } from "@mantine/core";
3
3
  import { useHover } from "@mantine/hooks";
4
4
  import { alpha, VX } from "../tokens/index.js";
5
+ import { formatRelativeTime } from "./relative-time.js";
5
6
  const STATUS_BADGE = {
6
7
  attention: { label: "Needs review", statusToken: VX.status.warn },
7
8
  error: { label: "Failed", statusToken: VX.status.bad }
8
9
  };
9
- const RELATIVE_TIME_FORMAT = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
10
- const RELATIVE_TIME_UNITS = [
11
- { unit: "year", ms: 31536e6 },
12
- { unit: "month", ms: 2628e6 },
13
- { unit: "week", ms: 6048e5 },
14
- { unit: "day", ms: 864e5 },
15
- { unit: "hour", ms: 36e5 },
16
- { unit: "minute", ms: 6e4 }
17
- ];
18
- function formatRelativeTime(timestamp) {
19
- const diffMs = timestamp - Date.now();
20
- const absMs = Math.abs(diffMs);
21
- if (absMs < 6e4) return "just now";
22
- const unit = RELATIVE_TIME_UNITS.find(({ ms }) => absMs >= ms) ?? RELATIVE_TIME_UNITS.at(-1);
23
- return RELATIVE_TIME_FORMAT.format(Math.round(diffMs / unit.ms), unit.unit);
24
- }
25
10
  function OutcomeSkeleton() {
26
11
  return /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
27
12
  /* @__PURE__ */ jsx(Skeleton, { height: 14, width: "60%", radius: "sm" }),
@@ -12,8 +12,20 @@ export type ThreadWorkspaceProps = {
12
12
  readonly resolveOutcome: OutcomeResolver<AgentPart>;
13
13
  /** Placeholder for the new-thread composer pinned under the feed. */
14
14
  readonly newThreadPlaceholder?: string;
15
- /** Rendered in place of the feed while there are no threads yet. */
15
+ /**
16
+ * Rendered in place of the feed once the store is hydrated and there really are no threads.
17
+ * Shown ONLY when `store.hydrated` is true — while an async store's initial load is still in
18
+ * flight, `ThreadWorkspace` holds a neutral loading state instead (see `FeedHydratingState`),
19
+ * so a server-backed workspace never asserts "no threads yet" before it actually knows that.
20
+ */
16
21
  readonly emptyState?: ReactNode;
22
+ /**
23
+ * Rendered in place of the feed when the store's initial load has failed — `!store.hydrated &&
24
+ * store.error !== undefined` (see `ThreadsStore.hydrated`'s doc for the discriminant). Shown
25
+ * instead of `FeedHydratingState`, so a rejected `listThreads` surfaces a legible failure rather
26
+ * than an endless skeleton.
27
+ */
28
+ readonly errorState?: ReactNode;
17
29
  };
18
30
  /**
19
31
  * A two-pane master-detail thread workspace: a scrollable feed of threads with an anchored
@@ -23,5 +35,5 @@ export type ThreadWorkspaceProps = {
23
35
  * @example
24
36
  * <ThreadWorkspace useThreads={useThreads} transport={transport} resolveOutcome={heuristicOutcome} />
25
37
  */
26
- export declare function ThreadWorkspace({ useThreads, transport, resolveOutcome, newThreadPlaceholder, emptyState, }: ThreadWorkspaceProps): JSX.Element;
38
+ export declare function ThreadWorkspace({ useThreads, transport, resolveOutcome, newThreadPlaceholder, emptyState, errorState, }: ThreadWorkspaceProps): JSX.Element;
27
39
  //# sourceMappingURL=thread-workspace.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"thread-workspace.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-workspace.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AASxF,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,YAAY,CAAA;IACvC,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IACrD,mEAAmE;IACnE,QAAQ,CAAC,cAAc,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IACnD,qEAAqE;IACrE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC,oEAAoE;IACpE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAA;CAChC,CAAA;AAgBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,UAAU,EACV,SAAS,EACT,cAAc,EACd,oBAAoB,EACpB,UAAU,GACX,EAAE,oBAAoB,GAAG,GAAG,CAAC,OAAO,CAwFpC"}
1
+ {"version":3,"file":"thread-workspace.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-workspace.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAUxF,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,YAAY,CAAA;IACvC,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IACrD,mEAAmE;IACnE,QAAQ,CAAC,cAAc,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IACnD,qEAAqE;IACrE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAA;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAA;CAChC,CAAA;AA4DD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,UAAU,EACV,SAAS,EACT,cAAc,EACd,oBAAoB,EACpB,UAAU,EACV,UAAU,GACX,EAAE,oBAAoB,GAAG,GAAG,CAAC,OAAO,CA8FpC"}
@@ -1,7 +1,8 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Box, Divider, Flex, Stack, Text } from "@mantine/core";
2
+ import { Box, Divider, Flex, Skeleton, Stack, Text } from "@mantine/core";
3
3
  import { useMediaQuery } from "@mantine/hooks";
4
4
  import { useAgentThreadRuns } from "../agent/index.js";
5
+ import { VX } from "../tokens/index.js";
5
6
  import { Composer } from "./composer.js";
6
7
  import { ThreadDetailPanel } from "./thread-detail-panel.js";
7
8
  import { ThreadFeed } from "./thread-feed.js";
@@ -13,12 +14,37 @@ function FeedEmptyState() {
13
14
  /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ta: "center", children: "Send a message below to start your first thread." })
14
15
  ] });
15
16
  }
17
+ function FeedHydratingState() {
18
+ return /* @__PURE__ */ jsxs(Stack, { gap: "sm", p: "sm", "data-testid": "thread-workspace-hydrating", children: [
19
+ /* @__PURE__ */ jsx(Skeleton, { height: 54, radius: "sm" }),
20
+ /* @__PURE__ */ jsx(Skeleton, { height: 54, radius: "sm" }),
21
+ /* @__PURE__ */ jsx(Skeleton, { height: 54, radius: "sm" })
22
+ ] });
23
+ }
24
+ function FeedErrorState() {
25
+ return /* @__PURE__ */ jsxs(
26
+ Stack,
27
+ {
28
+ align: "center",
29
+ justify: "center",
30
+ gap: 4,
31
+ h: "100%",
32
+ p: "xl",
33
+ "data-testid": "thread-workspace-error",
34
+ children: [
35
+ /* @__PURE__ */ jsx(Text, { fw: 600, size: "sm", style: { color: VX.status.bad }, children: "Couldn't load threads" }),
36
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ta: "center", children: "Something went wrong loading your threads. Try refreshing the page." })
37
+ ]
38
+ }
39
+ );
40
+ }
16
41
  function ThreadWorkspace({
17
42
  useThreads,
18
43
  transport,
19
44
  resolveOutcome,
20
45
  newThreadPlaceholder,
21
- emptyState
46
+ emptyState,
47
+ errorState
22
48
  }) {
23
49
  const store = useThreads();
24
50
  const runs = useAgentThreadRuns({ transport, store, resolveOutcome });
@@ -50,12 +76,12 @@ function ThreadWorkspace({
50
76
  runs.retry(store.activeId);
51
77
  };
52
78
  const feed = /* @__PURE__ */ jsxs(Flex, { direction: "column", h: "100%", style: { overflow: "hidden" }, children: [
53
- /* @__PURE__ */ jsx(Box, { style: { flex: 1, minHeight: 0 }, children: store.threads.length === 0 ? emptyState ?? /* @__PURE__ */ jsx(FeedEmptyState, {}) : /* @__PURE__ */ jsx(ThreadFeed, { threads, activeId: store.activeId, onSelect: handleSelect }) }),
79
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1, minHeight: 0 }, children: threads.length === 0 ? store.hydrated ? emptyState ?? /* @__PURE__ */ jsx(FeedEmptyState, {}) : store.error !== void 0 ? errorState ?? /* @__PURE__ */ jsx(FeedErrorState, {}) : /* @__PURE__ */ jsx(FeedHydratingState, {}) : /* @__PURE__ */ jsx(ThreadFeed, { threads, activeId: store.activeId, onSelect: handleSelect }) }),
54
80
  /* @__PURE__ */ jsx(Divider, { color: "var(--vx-divider)" }),
55
81
  /* @__PURE__ */ jsx(Box, { p: "sm", children: /* @__PURE__ */ jsx(
56
82
  Composer,
57
83
  {
58
- onSubmit: handleNewThread,
84
+ onSubmit: ({ text }) => handleNewThread(text),
59
85
  ...newThreadPlaceholder !== void 0 ? { placeholder: newThreadPlaceholder } : {}
60
86
  }
61
87
  ) })
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=thread-workspace.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-workspace.test.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-workspace.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tool-chip.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-chip.test.d.ts","sourceRoot":"","sources":["../../src/agent-chat/tool-chip.test.tsx"],"names":[],"mappings":""}