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
@@ -0,0 +1,65 @@
1
+ import type { JSX } from 'react';
2
+ import type { AgentThread, ForeignPart, PartRenderer, PartRenderers, StreamStatus, TranscriptPart } from '../agent/index.js';
3
+ import type { ComposerProps, ComposerSubmit } from './composer.js';
4
+ import type { MessageAffordances } from './message-affordances.js';
5
+ import type { VirtualizeProps } from './virtualize.js';
6
+ type ThreadFeedRowBase = {
7
+ /**
8
+ * The thread this row projects. Typed against `AgentThread<TranscriptPart>` — not the plain,
9
+ * `AgentPart`-defaulted `AgentThread` `ThreadFeed` uses, and not a type parameter. A consumer
10
+ * with a registered `ForeignPart` union passes its own threads straight through with no generic
11
+ * argument and no cast, because `TranscriptPart` is the widest part type and arrays are
12
+ * structurally covariant: `AgentPart[]` and `ConsumerPart[]` both widen into `TranscriptPart[]`.
13
+ * Widening the row is what lets both `ThreadFeed` (non-generic, `AgentThread<AgentPart>`) and a
14
+ * part-registry consumer feed the same component.
15
+ */
16
+ readonly thread: AgentThread<TranscriptPart>;
17
+ /** Whether this row's transcript+composer body is currently visible. */
18
+ readonly expanded: boolean;
19
+ /** Called with the thread's id when the header is clicked/activated. */
20
+ readonly onToggle: (id: string) => void;
21
+ /** The live (in-flight) assistant turn's parts, when a run is streaming for this thread. */
22
+ readonly liveParts?: readonly TranscriptPart[];
23
+ /** The live run's status — drives the in-progress indicator on the live block. */
24
+ readonly liveStatus?: StreamStatus;
25
+ /** Consumer renderers keyed by part.type, forwarded to `ThreadTranscript`. */
26
+ readonly renderers?: PartRenderers;
27
+ /** Forwarded to `ThreadTranscript` — see its own prop doc. */
28
+ readonly fallbackRenderer?: PartRenderer<ForeignPart>;
29
+ /**
30
+ * Per-message hover-row contract (timestamp/copy/regenerate/actions), forwarded verbatim to the
31
+ * row's `ThreadTranscript`. Shared with it via `message-affordances.ts` so the two cannot drift
32
+ * on what an unset field means.
33
+ */
34
+ readonly affordances?: MessageAffordances;
35
+ /** Forwarded to `ThreadTranscript` — suppresses role label + chrome on same-speaker runs.
36
+ * @default true */
37
+ readonly groupConsecutive?: boolean;
38
+ /** Called with the composer's submit payload. */
39
+ readonly onSend: (payload: ComposerSubmit) => void;
40
+ /** Shown as the composer's Stop action while `liveStatus === 'streaming'`. */
41
+ readonly onStop?: () => void;
42
+ /** Forwarded to the row's `Composer`, minus the three props this component always wires itself
43
+ * (`onSubmit`, `onStop`, and `streaming` — the last derived from `liveStatus` so a consumer can't
44
+ * pass one that disagrees with the row's own live status). */
45
+ readonly composerProps?: Omit<ComposerProps, 'onSubmit' | 'onStop' | 'streaming'>;
46
+ };
47
+ /**
48
+ * `virtualize`/`height` are forwarded straight to the row's `ThreadTranscript`, carrying the same
49
+ * union guard: an inline row holding a very long thread can window it, and doing so REQUIRES a
50
+ * `height` (the transcript then owns a fixed-height scroll node inside the expanded body, instead
51
+ * of the body growing to the thread's full length). Omit both and the row's transcript is
52
+ * content-sized, as before.
53
+ */
54
+ export type ThreadFeedRowProps = ThreadFeedRowBase & VirtualizeProps;
55
+ /**
56
+ * One inline-expanding thread row: a header (title + relative timestamp) that toggles a lazily
57
+ * mounted, kept-mounted transcript + composer body. See the module doc for the mount lifecycle
58
+ * invariant this component exists to guarantee.
59
+ *
60
+ * @example
61
+ * <ThreadFeedRow thread={thread} expanded={expanded} onToggle={onToggle} onSend={onSend} />
62
+ */
63
+ export declare function ThreadFeedRow(props: ThreadFeedRowProps): JSX.Element;
64
+ export {};
65
+ //# sourceMappingURL=thread-feed-row.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-feed-row.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-feed-row.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAEhC,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,cAAc,EACf,MAAM,UAAU,CAAA;AAEjB,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAI/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AA6BnD,KAAK,iBAAiB,GAAG;IACvB;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,cAAc,CAAC,CAAA;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;IAC9C,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAA;IAClC,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAA;IAClC,8DAA8D;IAC9D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,CAAA;IACrD;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAA;IACzC;uBACmB;IACnB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;IACnC,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IAClD,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IAC5B;;kEAE8D;IAC9D,QAAQ,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,QAAQ,GAAG,WAAW,CAAC,CAAA;CAClF,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,GAAG,eAAe,CAAA;AASpE;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CA8HpE"}
@@ -0,0 +1,156 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Box, Group, Stack, Text, UnstyledButton } from "@mantine/core";
3
+ import { useState } from "react";
4
+ import { VX } from "../tokens/index.js";
5
+ import { Composer } from "./composer.js";
6
+ import { formatRelativeTime } from "./relative-time.js";
7
+ import { ThreadTranscript } from "./thread-message.js";
8
+ import { resolveVirtualize } from "./virtualize.js";
9
+ function ChevronGlyph({ expanded }) {
10
+ return /* @__PURE__ */ jsx(
11
+ "svg",
12
+ {
13
+ width: 14,
14
+ height: 14,
15
+ viewBox: "0 0 24 24",
16
+ fill: "none",
17
+ "aria-hidden": true,
18
+ style: {
19
+ transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
20
+ transition: "transform 120ms ease",
21
+ flexShrink: 0
22
+ },
23
+ children: /* @__PURE__ */ jsx(
24
+ "path",
25
+ {
26
+ d: "M9 6l6 6l-6 6",
27
+ stroke: "currentColor",
28
+ strokeWidth: 2,
29
+ strokeLinecap: "round",
30
+ strokeLinejoin: "round"
31
+ }
32
+ )
33
+ }
34
+ );
35
+ }
36
+ function rowTitle(thread) {
37
+ return thread.outcome?.title ?? "Untitled thread";
38
+ }
39
+ function ThreadFeedRow(props) {
40
+ const {
41
+ thread,
42
+ expanded,
43
+ onToggle,
44
+ liveParts,
45
+ liveStatus,
46
+ renderers,
47
+ fallbackRenderer,
48
+ affordances,
49
+ groupConsecutive,
50
+ onSend,
51
+ onStop,
52
+ composerProps
53
+ } = props;
54
+ const virtualized = resolveVirtualize(props);
55
+ const virtualizeProps = virtualized === null ? {} : { virtualize: virtualized.options, height: virtualized.height };
56
+ const [hasOpened, setHasOpened] = useState(expanded);
57
+ if (expanded && !hasOpened) {
58
+ setHasOpened(true);
59
+ }
60
+ const summary = thread.outcome?.summary ?? "";
61
+ const streaming = liveStatus === "streaming";
62
+ return /* @__PURE__ */ jsxs(
63
+ Box,
64
+ {
65
+ style: {
66
+ borderRadius: VX.radiusCard,
67
+ boxShadow: VX.shadowCard,
68
+ backgroundColor: VX.surface.panel,
69
+ overflow: "hidden"
70
+ },
71
+ children: [
72
+ /* @__PURE__ */ jsx(
73
+ UnstyledButton,
74
+ {
75
+ onClick: () => onToggle(thread.id),
76
+ w: "100%",
77
+ "aria-expanded": expanded,
78
+ style: {
79
+ display: "block",
80
+ padding: "var(--mantine-spacing-xs) var(--mantine-spacing-sm)"
81
+ },
82
+ children: /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "xs", wrap: "nowrap", align: "center", children: [
83
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1, minWidth: 0 }, children: [
84
+ /* @__PURE__ */ jsx(
85
+ Text,
86
+ {
87
+ fw: 550,
88
+ lineClamp: 1,
89
+ style: {
90
+ fontFamily: "var(--basalt-font-head)",
91
+ fontSize: VX.text.md,
92
+ fontStretch: "88%"
93
+ },
94
+ children: rowTitle(thread)
95
+ }
96
+ ),
97
+ summary.length > 0 && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", lineClamp: 1, children: summary })
98
+ ] }),
99
+ /* @__PURE__ */ jsx(
100
+ Text,
101
+ {
102
+ style: {
103
+ fontFamily: "var(--basalt-font-mono)",
104
+ fontSize: VX.text.micro,
105
+ color: VX.faint,
106
+ flexShrink: 0
107
+ },
108
+ children: formatRelativeTime(thread.updatedAt)
109
+ }
110
+ ),
111
+ /* @__PURE__ */ jsx(ChevronGlyph, { expanded })
112
+ ] })
113
+ }
114
+ ),
115
+ hasOpened && /* @__PURE__ */ jsx(
116
+ Box,
117
+ {
118
+ "data-testid": "thread-feed-row-body",
119
+ style: {
120
+ display: expanded ? "block" : "none",
121
+ padding: "var(--mantine-spacing-sm)",
122
+ paddingTop: 0
123
+ },
124
+ children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
125
+ /* @__PURE__ */ jsx(
126
+ ThreadTranscript,
127
+ {
128
+ messages: thread.messages,
129
+ ...liveParts !== void 0 ? { liveParts } : {},
130
+ ...liveStatus !== void 0 ? { liveStatus } : {},
131
+ ...renderers !== void 0 ? { renderers } : {},
132
+ ...fallbackRenderer !== void 0 ? { fallbackRenderer } : {},
133
+ ...affordances !== void 0 ? { affordances } : {},
134
+ ...groupConsecutive !== void 0 ? { groupConsecutive } : {},
135
+ ...virtualizeProps
136
+ }
137
+ ),
138
+ /* @__PURE__ */ jsx(
139
+ Composer,
140
+ {
141
+ onSubmit: onSend,
142
+ streaming,
143
+ ...onStop !== void 0 ? { onStop } : {},
144
+ ...composerProps
145
+ }
146
+ )
147
+ ] })
148
+ }
149
+ )
150
+ ]
151
+ }
152
+ );
153
+ }
154
+ export {
155
+ ThreadFeedRow
156
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=thread-feed-row.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-feed-row.test.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-feed-row.test.tsx"],"names":[],"mappings":""}
@@ -1,18 +1,47 @@
1
- import type { JSX } from 'react';
1
+ import type { JSX, ReactNode } from 'react';
2
2
  import type { AgentThread } from '../agent/index.js';
3
+ import type { ComposerSubmit } from './composer.js';
3
4
  export type ThreadFeedProps = {
4
5
  readonly threads: AgentThread[];
5
6
  /** The currently open thread id, or null when none is selected. */
6
7
  readonly activeId: string | null;
7
- /** Called with a thread's id when its row is selected. */
8
+ /** Called with a thread's id when its row is selected. Never called to report a collapse — see
9
+ * `variant`'s doc for how the built-in `'inline'` row's expand/collapse is decoupled from this. */
8
10
  readonly onSelect: (id: string) => void;
11
+ /**
12
+ * Row shape: `'outcome'` (default) renders today's `ThreadOutcomeCard` inbox row, unchanged.
13
+ * `'inline'` renders `ThreadFeedRow`, the Slack-style inline-expanding row, wired to this feed's
14
+ * own `activeId` for which row is open (so at most one row is open at a time, mirroring the
15
+ * inbox's single-selection model) — with its OWN collapse tracking layered on top, not a raw
16
+ * pass-through of `onSelect` as `onToggle`. `onSelect` is documented as a selection event, and
17
+ * collapsing a row that remains the active thread isn't one; wiring `onToggle={onSelect}`
18
+ * directly (the previous shape) meant a plain `onSelect={setActiveId}` consumer could never
19
+ * collapse a row, since re-selecting the same id is a no-op state update. Collapsing here is
20
+ * local, UI-only state, reset whenever `activeId` changes to point at a DIFFERENT thread. Since
21
+ * this reset is a `useEffect` keyed on `activeId`, re-driving it to the SAME id from outside
22
+ * this component is a no-op React dep comparison, so it does not clear the override.
23
+ */
24
+ readonly variant?: 'outcome' | 'inline';
25
+ /**
26
+ * Wires the built-in `'inline'` row's composer to a real send channel — called with the thread
27
+ * and the composer's submit payload. Omit it and the row's composer renders disabled (visibly
28
+ * inert) instead of a live control that silently discards whatever is typed into it; a consumer
29
+ * that needs the composer live must supply this (or `renderRow`, for full control over the row).
30
+ */
31
+ readonly onSend?: (thread: AgentThread, payload: ComposerSubmit) => void;
32
+ /** Overrides row rendering entirely, for BOTH variants — when supplied, this is called for every
33
+ * thread instead of either built-in row. Gives full control over live-run wiring
34
+ * (`onStop`/`liveParts`/`liveStatus`) the built-in `'inline'` row doesn't expose; see `variant`
35
+ * and `onSend`. */
36
+ readonly renderRow?: (thread: AgentThread) => ReactNode;
9
37
  };
10
38
  /**
11
- * A scrollable feed of thread rows, animated on add/remove/reorder. Each row renders via
12
- * `ThreadOutcomeCard`; selection highlighting is derived from `activeId`.
39
+ * A scrollable feed of thread rows, animated on add/remove/reorder. Row content comes from
40
+ * `renderRow` when supplied, else from the built-in `variant` row (`ThreadOutcomeCard` or
41
+ * `ThreadFeedRow`) — see `ThreadFeedProps` for the full contract.
13
42
  *
14
43
  * @example
15
44
  * <ThreadFeed threads={threads} activeId={activeId} onSelect={(id) => select(id)} />
16
45
  */
17
- export declare function ThreadFeed({ threads, activeId, onSelect }: ThreadFeedProps): JSX.Element;
46
+ export declare function ThreadFeed({ threads, activeId, onSelect, variant, onSend, renderRow, }: ThreadFeedProps): JSX.Element;
18
47
  //# sourceMappingURL=thread-feed.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"thread-feed.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-feed.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAI3C,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,CAAA;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,eAAe,GAAG,GAAG,CAAC,OAAO,CA4CxF"}
1
+ {"version":3,"file":"thread-feed.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-feed.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAShD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,CAAA;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC;uGACmG;IACnG,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IACxE;;;uBAGmB;IACnB,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,SAAS,CAAA;CACxD,CAAA;AAgCD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,OAAmB,EACnB,MAAM,EACN,SAAS,GACV,EAAE,eAAe,GAAG,GAAG,CAAC,OAAO,CAkE/B"}
@@ -1,21 +1,61 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { ScrollArea, Stack } from "@mantine/core";
2
+ import { Box, ScrollArea, Stack } from "@mantine/core";
3
3
  import { useReducedMotion } from "@mantine/hooks";
4
4
  import { AnimatePresence, motion } from "motion/react";
5
+ import { useEffect, useState } from "react";
5
6
  import { MOTION_SPRING } from "../motion/index.js";
7
+ import { ThreadFeedRow } from "./thread-feed-row.js";
6
8
  import { ThreadOutcomeCard } from "./thread-outcome-card.js";
7
- function ThreadFeed({ threads, activeId, onSelect }) {
8
- const reduceMotion = useReducedMotion();
9
- if (reduceMotion) {
10
- return /* @__PURE__ */ jsx(ScrollArea, { style: { height: "100%" }, children: /* @__PURE__ */ jsx(Stack, { gap: "sm", p: "sm", children: threads.map((thread) => /* @__PURE__ */ jsx(
11
- ThreadOutcomeCard,
9
+ function noopSend() {
10
+ }
11
+ function defaultRow(thread, variant, activeId, collapsedId, onToggle, onSelect, onSend) {
12
+ if (variant === "inline") {
13
+ const expanded = thread.id === activeId && thread.id !== collapsedId;
14
+ return /* @__PURE__ */ jsx(
15
+ ThreadFeedRow,
12
16
  {
13
17
  thread,
14
- selected: thread.id === activeId,
15
- onSelect: () => onSelect(thread.id)
16
- },
17
- thread.id
18
- )) }) });
18
+ expanded,
19
+ onToggle,
20
+ onSend: onSend !== void 0 ? (payload) => onSend(thread, payload) : noopSend,
21
+ ...onSend === void 0 ? { composerProps: { disabled: true } } : {}
22
+ }
23
+ );
24
+ }
25
+ return /* @__PURE__ */ jsx(
26
+ ThreadOutcomeCard,
27
+ {
28
+ thread,
29
+ selected: thread.id === activeId,
30
+ onSelect: () => onSelect(thread.id)
31
+ }
32
+ );
33
+ }
34
+ function ThreadFeed({
35
+ threads,
36
+ activeId,
37
+ onSelect,
38
+ variant = "outcome",
39
+ onSend,
40
+ renderRow
41
+ }) {
42
+ const reduceMotion = useReducedMotion();
43
+ const [collapsedId, setCollapsedId] = useState(null);
44
+ useEffect(() => {
45
+ setCollapsedId(null);
46
+ }, [activeId]);
47
+ function handleInlineToggle(id) {
48
+ const isOpen = id === activeId && id !== collapsedId;
49
+ if (isOpen) {
50
+ setCollapsedId(id);
51
+ return;
52
+ }
53
+ setCollapsedId(null);
54
+ onSelect(id);
55
+ }
56
+ const rowFor = (thread) => renderRow !== void 0 ? renderRow(thread) : defaultRow(thread, variant, activeId, collapsedId, handleInlineToggle, onSelect, onSend);
57
+ if (reduceMotion) {
58
+ return /* @__PURE__ */ jsx(ScrollArea, { style: { height: "100%" }, children: /* @__PURE__ */ jsx(Stack, { gap: "sm", p: "sm", children: threads.map((thread) => /* @__PURE__ */ jsx(Box, { children: rowFor(thread) }, thread.id)) }) });
19
59
  }
20
60
  return /* @__PURE__ */ jsx(ScrollArea, { style: { height: "100%" }, children: /* @__PURE__ */ jsx(Stack, { gap: "sm", p: "sm", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", initial: false, children: threads.map((thread) => /* @__PURE__ */ jsx(
21
61
  motion.div,
@@ -25,14 +65,7 @@ function ThreadFeed({ threads, activeId, onSelect }) {
25
65
  animate: { opacity: 1, y: 0 },
26
66
  exit: { opacity: 0, scale: 0.97 },
27
67
  transition: MOTION_SPRING,
28
- children: /* @__PURE__ */ jsx(
29
- ThreadOutcomeCard,
30
- {
31
- thread,
32
- selected: thread.id === activeId,
33
- onSelect: () => onSelect(thread.id)
34
- }
35
- )
68
+ children: rowFor(thread)
36
69
  },
37
70
  thread.id
38
71
  )) }) }) });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=thread-feed.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-feed.test.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-feed.test.tsx"],"names":[],"mappings":""}
@@ -1,5 +1,7 @@
1
1
  import type { JSX } from 'react';
2
2
  import type { AgentPartRenderers, ChatMessage, ForeignPart, PartRenderer, PartRenderers, StreamStatus, TranscriptPart } from '../agent/index.js';
3
+ import type { MessageAffordances } from './message-affordances.js';
4
+ import type { VirtualizeOptions, VirtualizeProps } from './virtualize.js';
3
5
  /**
4
6
  * Mantine overrides for `PartList`'s default headless renderers — the house style for rendering
5
7
  * an `AgentPart` inside a chat surface.
@@ -19,6 +21,165 @@ export declare const threadPartRenderers: Partial<AgentPartRenderers>;
19
21
  export declare const messageBlockRenderCounter: {
20
22
  count: number;
21
23
  };
24
+ /** Caps `applyInitialScroll`'s clobber-recovery retries (see `./virtualize`'s fourth composition
25
+ * rule for the full mechanism this guards). A small fixed budget, not a time/frame-based deadline:
26
+ * every observed case of virtual-core's `_willUpdate` clobbering this effect's jump resolves within
27
+ * one extra commit, so this exists purely as a backstop against a pathological DOM that never
28
+ * reports settled — not as the expected path. Once exhausted, the effect gives up and marks the
29
+ * jump permanently done wherever the last attempt landed, rather than fighting the DOM (or a user
30
+ * who has since scrolled away) forever. Exported (like `messageBlockRenderCounter`) purely so its
31
+ * test can assert the exact bound without duplicating the number; not part of the public surface. */
32
+ export declare const MAX_INITIAL_SCROLL_ATTEMPTS = 5;
33
+ type TranscriptRow = {
34
+ readonly key: string;
35
+ readonly node: JSX.Element;
36
+ };
37
+ type VirtualizedTranscriptProps = {
38
+ readonly rows: readonly TranscriptRow[];
39
+ readonly height: number | string;
40
+ readonly overscan: number;
41
+ readonly estimateSize: number;
42
+ readonly initialScroll: NonNullable<VirtualizeOptions['initialScroll']>;
43
+ };
44
+ /**
45
+ * The degrade target: every row rendered unwindowed inside a fixed-height scroll container.
46
+ * This is what a `virtualize: true` transcript falls back to when `@tanstack/react-virtual` fails
47
+ * to resolve (peer absent) — same visual shape (a scrollable `height`-bound pane), just without
48
+ * windowing.
49
+ *
50
+ * NOT used as the `Suspense` fallback (see `VirtualizeSuspenseFallback` below) — mounting every
51
+ * real `MessageBlock` subtree for the one tick the lazy import takes to settle would make that
52
+ * settle a genuine unmount/remount of every rendered row, re-firing each row's effects once and
53
+ * risking hitting a message mid-stream during the resolve window.
54
+ */
55
+ declare function NonVirtualizedRows({ rows, height }: VirtualizedTranscriptProps): JSX.Element;
56
+ /** Test-only escape hatch (mirrors `messageBlockRenderCounter` above) — proves the peer-absent
57
+ * degrade target itself renders every row without windowing and without throwing, without having
58
+ * to fight `React.lazy`'s permanent once-per-module-instance resolution cache to simulate the
59
+ * peer's absence at the `import()` layer (see thread-message.test.tsx's virtualization describe
60
+ * block for why that approach was rejected). Not exported from `agent-chat/index.ts`. */
61
+ export declare const nonVirtualizedRowsFallback: typeof NonVirtualizedRows;
62
+ /**
63
+ * Minimal structural slice of `Virtualizer` (virtual-core 3.17.1) that
64
+ * {@link resolveGuardedMeasurement} needs — `indexFromElement`/`itemSizeCache`/`options` are all
65
+ * public instance members, but typing against the real (generic, peer-only) `Virtualizer` class
66
+ * would force this test-only-exported helper to import the peer's runtime types at module scope,
67
+ * the exact static-import-forces-the-peer problem the surrounding `lazy(() => import(...))` exists
68
+ * to avoid. A structural type sidesteps that without loosening to `any`. Generic over the item
69
+ * element type (rather than fixed at `Element`) so it stays exactly assignable FROM a real
70
+ * `Virtualizer<TScrollElement, TItemElement>` at the call site below — `indexFromElement` is
71
+ * declared with arrow-property (not method) syntax upstream, so it is checked contravariantly, and
72
+ * a fixed `Element` parameter there does not structurally match a real instance's narrower
73
+ * `TItemElement` parameter.
74
+ */
75
+ type MeasurementCacheHost<TItemElement extends Element> = {
76
+ readonly indexFromElement: (node: TItemElement) => number;
77
+ readonly itemSizeCache: Map<unknown, number>;
78
+ readonly options: {
79
+ readonly getItemKey: (index: number) => unknown;
80
+ readonly estimateSize: (index: number) => number;
81
+ };
82
+ };
83
+ /**
84
+ * Resolves the size a `measureElement` implementation should report, given what the underlying
85
+ * (default or custom) measurement already produced: that value unchanged when it is a genuine
86
+ * measurement, or the row's own last-known size when it is `0` — never a real reading for a mounted
87
+ * row, so a `0` only ever means the ResizeObserver measured through a `display: none` (or otherwise
88
+ * unlaid-out) ancestor, e.g. a virtualized transcript sitting inside a collapsed `ThreadFeedRow`.
89
+ * Falling through to it unguarded is what corrupted `itemSizeCache`/`measurementsCache`: virtual-
90
+ * core's default `resizeItem` writes whatever `measureElement` returns with no floor, so every
91
+ * mounted row remeasures at 0px the instant an ancestor hides, `getTotalSize()` collapses within
92
+ * the same tick, and the collapse is never undone — a later real remeasurement only OVERWRITES
93
+ * entries the ResizeObserver happens to re-fire for, it does not restore ones that silently kept
94
+ * their poisoned 0 (see `virtualize.ts`'s composition-rule doc for the full mechanism and the
95
+ * measured numbers).
96
+ *
97
+ * Takes the already-computed `size` plus the {@link MeasurementCacheHost} slice, rather than
98
+ * wrapping the `measureElement` function itself, so this stays a plain, non-generic-over-a-
99
+ * generic-function call at its one call site below (`instance` there is `Virtualizer<TScrollElement,
100
+ * TItemElement>` for THAT call's concrete type arguments, not the peer's own generic
101
+ * `measureElement` export — wrapping the latter directly hits TS's higher-rank inference limits on
102
+ * a doubly-generic HOF). That shape is also what makes this unit-testable with a fake host object
103
+ * and no ResizeObserver/layout engine at all (happy-dom has neither). Not exported from
104
+ * `agent-chat/index.ts`: a test-only escape hatch, matching
105
+ * `messageBlockRenderCounter`/`nonVirtualizedRowsFallback` above.
106
+ */
107
+ export declare function resolveGuardedMeasurement<TItemElement extends Element>(host: MeasurementCacheHost<TItemElement>, element: TItemElement, size: number): number;
108
+ /**
109
+ * Decides what `VirtualizedRowsInner`'s one-shot initial-scroll effect should do this render,
110
+ * given the inputs the effect closes over. Pure and peer/DOM-free — pulled out of the lazy-loaded
111
+ * closure for the same reason `resolveGuardedMeasurement` is: it stays unit-testable without a
112
+ * real `Virtualizer` instance or `@tanstack/react-virtual` itself.
113
+ *
114
+ * `'start'` and an empty transcript resolve to `'skip-done'` — there is nothing to scroll to, and
115
+ * DONE is permanent (see `virtualize.ts`'s fourth composition rule: the real scroll must fire at
116
+ * most once, ever, so a later append never yanks a user who has scrolled up). A non-positive
117
+ * `containerHeight` — the transcript's own scroll node measuring `0`, e.g. mounted behind a
118
+ * `display: none` ancestor per `virtualize.ts`'s third composition rule — resolves to
119
+ * `'skip-retry'`: NOT done, so the caller's effect (which re-runs after EVERY commit until the jump
120
+ * lands — see {@link applyInitialScroll}) gets another chance the next time this subtree renders,
121
+ * instead of permanently stranding the transcript at the top the moment it becomes visible. Only a
122
+ * genuinely measured, non-empty, `'end'`-targeted mount resolves to `'scroll'`.
123
+ */
124
+ export declare function resolveInitialScrollAction(initialScroll: NonNullable<VirtualizeOptions['initialScroll']>, rowCount: number, containerHeight: number): 'scroll' | 'skip-done' | 'skip-retry';
125
+ /** The mutable half of the one-shot initial-scroll contract. `hasApplied` is whether it has
126
+ * PERMANENTLY finished — either there was nothing to do (`'start'`/empty transcript), or
127
+ * `scrollToEnd` fired and was subsequently CONFIRMED (via a real DOM read — see
128
+ * {@link applyInitialScroll}'s doc for why virtual-core's own tracked offset can't be trusted for
129
+ * this) to have survived whatever else committed since. `attempts` counts how many times
130
+ * `scrollToEnd` has been (re-)fired this mount; 0 means never attempted, and also gates whether a
131
+ * call re-derives {@link resolveInitialScrollAction} at all. One instance lives for the lifetime of
132
+ * a `VirtualizedRowsInner` mount, held in a `useRef` so it survives re-renders without itself
133
+ * triggering one. */
134
+ export type InitialScrollState = {
135
+ hasApplied: boolean;
136
+ attempts: number;
137
+ };
138
+ /**
139
+ * The stateful half of "fires once, on first mount, and never again" (`virtualize.ts`'s fourth
140
+ * composition rule): wraps {@link resolveInitialScrollAction}'s per-call decision with the
141
+ * ACROSS-CALLS memory that decision alone can't carry. Exported (like `resolveGuardedMeasurement`)
142
+ * so `VirtualizedRowsInner`'s effect below calls this SAME function rather than a hand-mirrored
143
+ * copy for tests — a fake `scrollToEnd` is the only DOM/peer-free way to prove the guard, since a
144
+ * real `Virtualizer`'s own `followOnAppend` also calls the real DOM `scrollTo` on a later append,
145
+ * making that specific call indistinguishable from this one at the DOM boundary (see this
146
+ * function's test suite for the measurement that ruled that boundary out).
147
+ *
148
+ * Firing `scrollToEnd()` once is NOT the same as landing there: virtual-core 3.17.1's own
149
+ * `_willUpdate` (verified against the installed dist) writes to the SAME scroll container from
150
+ * `this.scrollOffset`, which updates only from the ASYNCHRONOUS native `scroll` event — never from
151
+ * the synchronous call that requested a scroll. A commit that runs `_willUpdate` before this
152
+ * effect gets a turn (element-attach) or that re-derives an anchor while `scrollOffset` still holds
153
+ * the pre-jump value writes that STALE offset straight back to `scrollTop`, undoing the jump within
154
+ * about one extra commit of it landing (see `./virtualize`'s fourth composition rule for the full
155
+ * trace this was diagnosed from). So `resolveInitialScrollAction`'s `'scroll'` outcome does not, by
156
+ * itself, set `hasApplied`: `isAtEnd` — a real `scrollTop`/`scrollHeight`/`clientHeight` read, the
157
+ * one piece of ground truth that is NEVER stale, unlike virtual-core's own tracked offset — is
158
+ * consulted on the NEXT call (i.e. the next commit, exactly the cadence this dependency-free effect
159
+ * already runs on) to confirm the jump survived. If it didn't, `scrollToEnd` fires again; if it did,
160
+ * `hasApplied` is set and the effect is inert for the rest of the mount. `MAX_INITIAL_SCROLL_ATTEMPTS`
161
+ * bounds this so a DOM that pathologically never reports settled — or a user who scrolls away mid-
162
+ * measurement — can never be fought forever; once exhausted, `hasApplied` is set regardless, wherever
163
+ * the last attempt left the container.
164
+ *
165
+ * `state.hasApplied` is the ONLY thing that can permanently stop this from calling `scrollToEnd`
166
+ * again: `'start'` and an empty transcript set it immediately (nothing to verify), and `'skip-retry'`
167
+ * deliberately leaves it `false` so a transcript that mounted behind a hidden ancestor gets another
168
+ * attempt on the caller's next commit, rather than being silently abandoned the moment it becomes
169
+ * visible. That retry — and the clobber-recovery above — is why the caller's effect carries NO
170
+ * dependency array: the commit that matters (a collapsed `ThreadFeedRow` flipping its body back to
171
+ * `display: block`, or virtual-core's own follow-up commit after this effect's own jump) changes
172
+ * neither the row count nor the virtualizer identity, so a dependency-keyed effect would never
173
+ * re-run for it.
174
+ *
175
+ * `getContainerHeight` is a THUNK, not a number, for the same reason: reading `offsetHeight` forces
176
+ * a synchronous layout, and it is read at most ONCE per mount — only on the very first call
177
+ * (`attempts === 0`) — never again while verifying or retrying, so a landed transcript still pays
178
+ * nothing for the effect it keeps re-running (a streaming transcript commits on every chunk).
179
+ * `isAtEnd` is cheaper (a plain property read, no forced reflow beyond what the browser already
180
+ * tracks) and is exactly what the verification/retry loop above consults instead.
181
+ */
182
+ export declare function applyInitialScroll(state: InitialScrollState, initialScroll: NonNullable<VirtualizeOptions['initialScroll']>, rowCount: number, getContainerHeight: () => number, scrollToEnd: () => void, isAtEnd: () => boolean): void;
22
183
  type ThreadTranscriptBase = {
23
184
  /** Settled messages for the thread, oldest first. */
24
185
  readonly messages: readonly ChatMessage<TranscriptPart>[];
@@ -31,18 +192,38 @@ type ThreadTranscriptBase = {
31
192
  /** Called for a part whose type is neither an AgentPart variant nor a registered key. Defaults
32
193
  * to a visible `UnknownPartChip` outside production, `null` in production — never throws. */
33
194
  readonly fallbackRenderer?: PartRenderer<ForeignPart>;
195
+ /** Per-message hover-row affordances (timestamp/copy/regenerate/custom actions). Unset fields
196
+ * fall back to `DEFAULT_AFFORDANCES`. Never shown on the live/streaming message — see
197
+ * `MessageBlockProps.affordances`. */
198
+ readonly affordances?: MessageAffordances;
199
+ /** Suppresses the role label and surface chrome on a message whose predecessor shares its role
200
+ * and landed within 5 minutes — the Slack rhythm.
201
+ * @default true */
202
+ readonly groupConsecutive?: boolean;
34
203
  };
35
- export type ThreadTranscriptProps = ThreadTranscriptBase;
204
+ export type ThreadTranscriptProps = ThreadTranscriptBase & VirtualizeProps;
36
205
  /**
37
206
  * Renders a thread's settled messages, each role-labelled ("You" / "Assistant"). Each part
38
207
  * resolves in order: a registered `renderers[part.type]` first, then basalt's own `PartList` for
39
208
  * the built-in six, then `fallbackRenderer`. When `liveParts` is non-empty, an extra in-progress
40
209
  * assistant block is appended at the tail.
41
210
  *
211
+ * Set `virtualize` (with a required `height`) to window a long thread over
212
+ * `@tanstack/react-virtual` (an optional peer) instead of rendering every message. The virtualized
213
+ * transcript owns its own fixed-height scroll container and must NOT be nested inside
214
+ * `BasaltStickToBottom` — see `./virtualize`'s module doc. With the peer absent, `virtualize` still
215
+ * renders (unwindowed, inside the same fixed-height pane) rather than throwing. A virtualized
216
+ * transcript scrolls itself to the newest message once, on mount (`initialScroll: 'start'` opts
217
+ * out) — see `./virtualize`'s fourth composition rule.
218
+ *
42
219
  * @example
43
220
  * import { ThreadTranscript } from 'basalt-ui'
44
221
  * <ThreadTranscript messages={thread.messages} liveParts={liveParts} liveStatus={runStatus} />
222
+ *
223
+ * @example
224
+ * // Windowed, for a long thread:
225
+ * <ThreadTranscript messages={thread.messages} virtualize height={480} />
45
226
  */
46
- export declare function ThreadTranscript({ messages, liveParts, liveStatus, renderers: renderersProp, fallbackRenderer, }: ThreadTranscriptProps): JSX.Element;
227
+ export declare function ThreadTranscript(props: ThreadTranscriptProps): JSX.Element;
47
228
  export {};
48
229
  //# sourceMappingURL=thread-message.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"thread-message.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-message.tsx"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,KAAK,EAEV,kBAAkB,EAClB,WAAW,EAEX,WAAW,EACX,YAAY,EACZ,aAAa,EAGb,YAAY,EAGZ,cAAc,EACf,MAAM,UAAU,CAAA;AAkFjB;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,OAAO,CAAC,kBAAkB,CAM3D,CAAA;AAqID;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;CAAe,CAAA;AA8ErD,KAAK,oBAAoB,GAAG;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,CAAC,cAAc,CAAC,EAAE,CAAA;IACzD,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;IAC9C,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAA;IAClC,kFAAkF;IAClF,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAA;IAClC;iGAC6F;IAC7F,QAAQ,CAAC,gBAAgB,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,CAAA;CACtD,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,CAAA;AAExD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EAAE,aAAa,EACxB,gBAA4C,GAC7C,EAAE,qBAAqB,GAAG,GAAG,CAAC,OAAO,CAgCrC"}
1
+ {"version":3,"file":"thread-message.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-message.tsx"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,KAAK,EAEV,kBAAkB,EAClB,WAAW,EAEX,WAAW,EACX,YAAY,EACZ,aAAa,EAGb,YAAY,EAGZ,cAAc,EACf,MAAM,UAAU,CAAA;AAMjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAI/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAkGtE;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,OAAO,CAAC,kBAAkB,CAM3D,CAAA;AAgPD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;CAAe,CAAA;AAoKrD;;;;;;;qGAOqG;AACrG,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAE5C,KAAK,aAAa,GAAG;IACnB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAA;CAC3B,CAAA;AAED,KAAK,0BAA0B,GAAG;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC,CAAA;CACxE,CAAA;AAED;;;;;;;;;;GAUG;AACH,iBAAS,kBAAkB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,0BAA0B,GAAG,GAAG,CAAC,OAAO,CAWrF;AAED;;;;yFAIyF;AACzF,eAAO,MAAM,0BAA0B,2BAAqB,CAAA;AAE5D;;;;;;;;;;;;GAYG;AACH,KAAK,oBAAoB,CAAC,YAAY,SAAS,OAAO,IAAI;IACxD,QAAQ,CAAC,gBAAgB,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,CAAA;IACzD,QAAQ,CAAC,aAAa,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC5C,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;QAC/C,QAAQ,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;KACjD,CAAA;CACF,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,SAAS,OAAO,EACpE,IAAI,EAAE,oBAAoB,CAAC,YAAY,CAAC,EACxC,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,MAAM,GACX,MAAM,CAOR;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,WAAW,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC,EAC9D,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,QAAQ,GAAG,WAAW,GAAG,YAAY,CAKvC;AAED;;;;;;;;qBAQqB;AACrB,MAAM,MAAM,kBAAkB,GAAG;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,kBAAkB,EACzB,aAAa,EAAE,WAAW,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC,EAC9D,QAAQ,EAAE,MAAM,EAChB,kBAAkB,EAAE,MAAM,MAAM,EAChC,WAAW,EAAE,MAAM,IAAI,EACvB,OAAO,EAAE,MAAM,OAAO,GACrB,IAAI,CAwBN;AAqJD,KAAK,oBAAoB,GAAG;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,CAAC,cAAc,CAAC,EAAE,CAAA;IACzD,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;IAC9C,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAA;IAClC,kFAAkF;IAClF,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAA;IAClC;iGAC6F;IAC7F,QAAQ,CAAC,gBAAgB,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,CAAA;IACrD;;0CAEsC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAA;IACzC;;uBAEmB;IACnB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG,eAAe,CAAA;AA2C1E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,GAAG,CAAC,OAAO,CAwI1E"}