basalt-ui 1.26.0 → 1.28.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 (1069) hide show
  1. package/AGENTS.md +23 -23
  2. package/CHANGELOG.md +67 -0
  3. package/MIGRATING.md +896 -37
  4. package/README.md +33 -3
  5. package/agent/rules/basalt-batteries.md +25 -10
  6. package/agent/rules/basalt-controls.md +73 -43
  7. package/agent/rules/basalt-mantine.md +17 -6
  8. package/agent/rules/basalt-state.md +5 -0
  9. package/agent/rules/basalt-tokens.md +1 -1
  10. package/configs/oxlint-plugin.js +1286 -73
  11. package/configs/oxlint-plugin.test.ts +1338 -29
  12. package/configs/oxlint.json +12 -5
  13. package/dist/agent/coalesce.d.ts.map +1 -1
  14. package/dist/agent/coalesce.js +2 -1
  15. package/dist/agent/merge.d.ts.map +1 -1
  16. package/dist/agent/merge.js +2 -1
  17. package/dist/agent/part-list.d.ts.map +1 -1
  18. package/dist/agent/part-list.js +2 -0
  19. package/dist/agent/stick-to-bottom.d.ts +3 -6
  20. package/dist/agent/stick-to-bottom.d.ts.map +1 -1
  21. package/dist/agent/stick-to-bottom.js +6 -3
  22. package/dist/agent-chat/composer.d.ts +6 -2
  23. package/dist/agent-chat/composer.d.ts.map +1 -1
  24. package/dist/agent-chat/composer.js +49 -33
  25. package/dist/agent-chat/relative-time.d.ts +0 -9
  26. package/dist/agent-chat/relative-time.d.ts.map +1 -1
  27. package/dist/agent-chat/relative-time.js +2 -1
  28. package/dist/agent-chat/thread-detail-panel.d.ts +3 -2
  29. package/dist/agent-chat/thread-detail-panel.d.ts.map +1 -1
  30. package/dist/agent-chat/thread-detail-panel.js +8 -5
  31. package/dist/agent-chat/thread-feed-row.d.ts +2 -1
  32. package/dist/agent-chat/thread-feed-row.d.ts.map +1 -1
  33. package/dist/agent-chat/thread-feed-row.js +92 -92
  34. package/dist/agent-chat/thread-feed.d.ts +3 -2
  35. package/dist/agent-chat/thread-feed.d.ts.map +1 -1
  36. package/dist/agent-chat/thread-feed.js +8 -3
  37. package/dist/agent-chat/thread-message.d.ts +4 -3
  38. package/dist/agent-chat/thread-message.d.ts.map +1 -1
  39. package/dist/agent-chat/thread-message.js +70 -31
  40. package/dist/agent-chat/thread-outcome-card.d.ts +3 -2
  41. package/dist/agent-chat/thread-outcome-card.d.ts.map +1 -1
  42. package/dist/agent-chat/thread-outcome-card.js +9 -2
  43. package/dist/agent-chat/thread-workspace.d.ts +7 -2
  44. package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
  45. package/dist/agent-chat/thread-workspace.js +62 -29
  46. package/dist/agent-chat/tool-chip.d.ts +3 -2
  47. package/dist/agent-chat/tool-chip.d.ts.map +1 -1
  48. package/dist/agent-chat/tool-chip.js +7 -2
  49. package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
  50. package/dist/charts/hooks/useBandPlot.js +15 -8
  51. package/dist/charts/index.d.ts +8 -7
  52. package/dist/charts/index.d.ts.map +1 -1
  53. package/dist/charts/index.js +45 -5
  54. package/dist/charts/kinds/BandStrip.d.ts +10 -7
  55. package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
  56. package/dist/charts/kinds/BandStrip.js +12 -7
  57. package/dist/charts/kinds/Bars.d.ts +12 -6
  58. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  59. package/dist/charts/kinds/Bars.js +37 -11
  60. package/dist/charts/kinds/Donut.d.ts +6 -5
  61. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  62. package/dist/charts/kinds/Donut.js +19 -2
  63. package/dist/charts/kinds/DualPanel.d.ts +6 -5
  64. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  65. package/dist/charts/kinds/DualPanel.js +36 -8
  66. package/dist/charts/kinds/Heatmap.d.ts +10 -6
  67. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  68. package/dist/charts/kinds/Heatmap.js +58 -31
  69. package/dist/charts/kinds/MirroredBars.d.ts +6 -5
  70. package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
  71. package/dist/charts/kinds/MirroredBars.js +9 -2
  72. package/dist/charts/kinds/MultiLine.d.ts +11 -6
  73. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  74. package/dist/charts/kinds/MultiLine.js +24 -15
  75. package/dist/charts/kinds/StackedArea.d.ts +18 -8
  76. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  77. package/dist/charts/kinds/StackedArea.js +29 -4
  78. package/dist/charts/kinds/ZonedLine.d.ts +12 -8
  79. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  80. package/dist/charts/kinds/ZonedLine.js +33 -23
  81. package/dist/charts/layout/auto-margin.d.ts +65 -8
  82. package/dist/charts/layout/auto-margin.d.ts.map +1 -1
  83. package/dist/charts/layout/auto-margin.js +45 -10
  84. package/dist/charts/layout/log-ticks.d.ts +27 -0
  85. package/dist/charts/layout/log-ticks.d.ts.map +1 -0
  86. package/dist/charts/layout/log-ticks.js +42 -0
  87. package/dist/charts/primitives/Axes.d.ts +11 -2
  88. package/dist/charts/primitives/Axes.d.ts.map +1 -1
  89. package/dist/charts/primitives/Axes.js +15 -5
  90. package/dist/charts/primitives/CartesianChart.d.ts +45 -7
  91. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  92. package/dist/charts/primitives/CartesianChart.js +86 -28
  93. package/dist/charts/primitives/ChartCard.d.ts +27 -2
  94. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  95. package/dist/charts/primitives/ChartCard.js +37 -17
  96. package/dist/charts/primitives/ChartFrame.d.ts +30 -4
  97. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  98. package/dist/charts/primitives/ChartFrame.js +45 -15
  99. package/dist/charts/primitives/ChartLegend.d.ts +13 -2
  100. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  101. package/dist/charts/primitives/ChartLegend.js +33 -13
  102. package/dist/charts/primitives/ChartPending.d.ts +77 -4
  103. package/dist/charts/primitives/ChartPending.d.ts.map +1 -1
  104. package/dist/charts/primitives/ChartPending.js +100 -6
  105. package/dist/charts/primitives/ChartTooltip.d.ts +7 -0
  106. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  107. package/dist/charts/primitives/ChartTooltip.js +18 -5
  108. package/dist/charts/primitives/Crosshair.d.ts +3 -0
  109. package/dist/charts/primitives/Crosshair.d.ts.map +1 -1
  110. package/dist/charts/primitives/Crosshair.js +3 -1
  111. package/dist/charts/primitives/chart-frame-layout.d.ts +95 -0
  112. package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -0
  113. package/dist/charts/primitives/chart-frame-layout.js +89 -0
  114. package/dist/charts/primitives/chart-tier.d.ts +16 -0
  115. package/dist/charts/primitives/chart-tier.d.ts.map +1 -0
  116. package/dist/charts/primitives/chart-tier.js +20 -0
  117. package/dist/charts/series.d.ts +55 -1
  118. package/dist/charts/series.d.ts.map +1 -1
  119. package/dist/charts/series.js +27 -1
  120. package/dist/charts/sparklines/BarSparkline.d.ts +3 -3
  121. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  122. package/dist/charts/sparklines/BarSparkline.js +9 -3
  123. package/dist/charts/sparklines/LineSparkline.d.ts +3 -3
  124. package/dist/charts/sparklines/LineSparkline.d.ts.map +1 -1
  125. package/dist/charts/sparklines/LineSparkline.js +15 -3
  126. package/dist/charts/sparklines/index.d.ts +2 -2
  127. package/dist/charts/sparklines/index.d.ts.map +1 -1
  128. package/dist/charts/utils/format.d.ts +92 -2
  129. package/dist/charts/utils/format.d.ts.map +1 -1
  130. package/dist/charts/utils/format.js +64 -1
  131. package/dist/charts/utils/ticks.d.ts +81 -2
  132. package/dist/charts/utils/ticks.d.ts.map +1 -1
  133. package/dist/charts/utils/ticks.js +54 -4
  134. package/dist/cli/index.d.ts.map +1 -1
  135. package/dist/cli/index.js +2 -2
  136. package/dist/commands/ShortcutsHelp.d.ts +3 -2
  137. package/dist/commands/ShortcutsHelp.d.ts.map +1 -1
  138. package/dist/commands/ShortcutsHelp.js +8 -2
  139. package/dist/commands/define-commands.d.ts.map +1 -1
  140. package/dist/commands/define-commands.js +2 -2
  141. package/dist/commands/define-overlays.d.ts +102 -0
  142. package/dist/commands/define-overlays.d.ts.map +1 -1
  143. package/dist/commands/define-overlays.js +102 -2
  144. package/dist/commands/index.d.ts +10 -1
  145. package/dist/commands/index.d.ts.map +1 -1
  146. package/dist/commands/index.js +3 -1
  147. package/dist/commands/overlays-mount.d.ts +13 -6
  148. package/dist/commands/overlays-mount.d.ts.map +1 -1
  149. package/dist/commands/overlays-mount.js +23 -14
  150. package/dist/common/errors.d.ts +34 -0
  151. package/dist/common/errors.d.ts.map +1 -0
  152. package/dist/common/errors.js +36 -0
  153. package/dist/common/index.d.ts +15 -0
  154. package/dist/common/index.d.ts.map +1 -0
  155. package/dist/common/index.js +29 -0
  156. package/dist/common/props.d.ts +63 -0
  157. package/dist/common/props.d.ts.map +1 -0
  158. package/dist/common/props.js +6 -0
  159. package/dist/common/query-state-like.d.ts +43 -0
  160. package/dist/common/query-state-like.d.ts.map +1 -0
  161. package/dist/common/query-state-like.js +20 -0
  162. package/dist/common/refs.d.ts +29 -0
  163. package/dist/common/refs.d.ts.map +1 -0
  164. package/dist/common/refs.js +29 -0
  165. package/dist/common/scroll-parent.d.ts +23 -0
  166. package/dist/common/scroll-parent.d.ts.map +1 -0
  167. package/dist/common/scroll-parent.js +21 -0
  168. package/dist/common/validate.d.ts +38 -0
  169. package/dist/common/validate.d.ts.map +1 -0
  170. package/dist/common/validate.js +36 -0
  171. package/dist/connectivity/connectivity-indicator.d.ts +3 -1
  172. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  173. package/dist/connectivity/connectivity-indicator.js +3 -1
  174. package/dist/connectivity/connectivity-provider.d.ts.map +1 -1
  175. package/dist/connectivity/connectivity-provider.js +4 -1
  176. package/dist/connectivity/index.d.ts +1 -0
  177. package/dist/connectivity/index.d.ts.map +1 -1
  178. package/dist/connectivity/types.d.ts +2 -2
  179. package/dist/connectivity/types.d.ts.map +1 -1
  180. package/dist/connectivity/use-connectivity.d.ts.map +1 -1
  181. package/dist/content/article-layout.module.css +11 -4
  182. package/dist/content/callout.d.ts +8 -4
  183. package/dist/content/callout.d.ts.map +1 -1
  184. package/dist/content/callout.js +15 -8
  185. package/dist/content/guide.d.ts +5 -4
  186. package/dist/content/guide.d.ts.map +1 -1
  187. package/dist/content/guide.js +13 -4
  188. package/dist/content/heading-anchor.d.ts +24 -2
  189. package/dist/content/heading-anchor.d.ts.map +1 -1
  190. package/dist/content/heading-anchor.js +23 -9
  191. package/dist/content/index.d.ts.map +1 -1
  192. package/dist/content/markdown.d.ts.map +1 -1
  193. package/dist/content/markdown.js +2 -0
  194. package/dist/content/prose.module.css +9 -7
  195. package/dist/content/reading-progress.d.ts +10 -4
  196. package/dist/content/reading-progress.d.ts.map +1 -1
  197. package/dist/content/reading-progress.js +26 -10
  198. package/dist/content/slug.d.ts +4 -0
  199. package/dist/content/slug.d.ts.map +1 -1
  200. package/dist/content/toc.d.ts +11 -0
  201. package/dist/content/toc.d.ts.map +1 -1
  202. package/dist/content/toc.js +23 -10
  203. package/dist/controls/actions.d.ts +49 -6
  204. package/dist/controls/actions.d.ts.map +1 -1
  205. package/dist/controls/actions.js +39 -8
  206. package/dist/controls/compare-filter.d.ts +3 -2
  207. package/dist/controls/compare-filter.d.ts.map +1 -1
  208. package/dist/controls/compare-filter.js +17 -2
  209. package/dist/controls/control-group.d.ts +3 -3
  210. package/dist/controls/control-group.d.ts.map +1 -1
  211. package/dist/controls/control-group.js +9 -2
  212. package/dist/controls/controls.module.css +169 -186
  213. package/dist/controls/controls.module.css.d.ts +9 -11
  214. package/dist/controls/enum-filter.d.ts +13 -3
  215. package/dist/controls/enum-filter.d.ts.map +1 -1
  216. package/dist/controls/enum-filter.js +32 -15
  217. package/dist/controls/filter-context.d.ts +15 -6
  218. package/dist/controls/filter-context.d.ts.map +1 -1
  219. package/dist/controls/filter-pill.d.ts +3 -3
  220. package/dist/controls/filter-pill.d.ts.map +1 -1
  221. package/dist/controls/filter-pill.js +4 -1
  222. package/dist/controls/filter-set.d.ts +3 -2
  223. package/dist/controls/filter-set.d.ts.map +1 -1
  224. package/dist/controls/filter-set.js +66 -41
  225. package/dist/controls/filter-sheet.d.ts +20 -80
  226. package/dist/controls/filter-sheet.d.ts.map +1 -1
  227. package/dist/controls/filter-sheet.js +9 -95
  228. package/dist/controls/index.d.ts +3 -0
  229. package/dist/controls/index.d.ts.map +1 -1
  230. package/dist/controls/index.js +6 -0
  231. package/dist/controls/multi-select-filter.d.ts +18 -2
  232. package/dist/controls/multi-select-filter.d.ts.map +1 -1
  233. package/dist/controls/multi-select-filter.js +124 -22
  234. package/dist/controls/number-filter.d.ts +28 -0
  235. package/dist/controls/number-filter.d.ts.map +1 -0
  236. package/dist/controls/number-filter.js +172 -0
  237. package/dist/controls/panel-row.d.ts +132 -0
  238. package/dist/controls/panel-row.d.ts.map +1 -0
  239. package/dist/controls/panel-row.js +173 -0
  240. package/dist/controls/panel-row.module.css +86 -0
  241. package/dist/controls/panel-row.module.css.d.ts +13 -0
  242. package/dist/controls/range-filter.d.ts +20 -5
  243. package/dist/controls/range-filter.d.ts.map +1 -1
  244. package/dist/controls/range-filter.js +75 -38
  245. package/dist/controls/search-filter.d.ts +3 -2
  246. package/dist/controls/search-filter.d.ts.map +1 -1
  247. package/dist/controls/search-filter.js +26 -11
  248. package/dist/controls/select-filter.d.ts +2 -1
  249. package/dist/controls/select-filter.d.ts.map +1 -1
  250. package/dist/controls/select-filter.js +4 -0
  251. package/dist/controls/slider-control.d.ts +36 -0
  252. package/dist/controls/slider-control.d.ts.map +1 -0
  253. package/dist/controls/slider-control.js +64 -0
  254. package/dist/controls/sync-button.d.ts +3 -2
  255. package/dist/controls/sync-button.d.ts.map +1 -1
  256. package/dist/controls/sync-button.js +16 -3
  257. package/dist/controls/toggle-filter.d.ts +3 -2
  258. package/dist/controls/toggle-filter.d.ts.map +1 -1
  259. package/dist/controls/toggle-filter.js +29 -12
  260. package/dist/controls/view-tabs.d.ts +3 -2
  261. package/dist/controls/view-tabs.d.ts.map +1 -1
  262. package/dist/controls/view-tabs.js +43 -11
  263. package/dist/controls-dates/date-range-picker.d.ts +1 -1
  264. package/dist/controls-dates/date-range-picker.d.ts.map +1 -1
  265. package/dist/controls-dates/date-range-picker.js +6 -1
  266. package/dist/dashboard/delta-badge.d.ts +5 -1
  267. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  268. package/dist/dashboard/empty-state.d.ts +10 -3
  269. package/dist/dashboard/empty-state.d.ts.map +1 -1
  270. package/dist/dashboard/empty-state.js +37 -6
  271. package/dist/dashboard/index.d.ts +6 -4
  272. package/dist/dashboard/index.d.ts.map +1 -1
  273. package/dist/dashboard/index.js +10 -2
  274. package/dist/dashboard/query-state.d.ts +48 -27
  275. package/dist/dashboard/query-state.d.ts.map +1 -1
  276. package/dist/dashboard/query-state.js +111 -57
  277. package/dist/dashboard/settings-section.d.ts +19 -9
  278. package/dist/dashboard/settings-section.d.ts.map +1 -1
  279. package/dist/dashboard/settings-section.js +74 -25
  280. package/dist/dashboard/stat-card.d.ts +76 -19
  281. package/dist/dashboard/stat-card.d.ts.map +1 -1
  282. package/dist/dashboard/stat-card.js +78 -38
  283. package/dist/dashboard/stat-card.module.css +112 -26
  284. package/dist/dashboard/stat-card.module.css.d.ts +5 -0
  285. package/dist/dashboard/stat-group.d.ts +41 -0
  286. package/dist/dashboard/stat-group.d.ts.map +1 -0
  287. package/dist/dashboard/stat-group.js +30 -0
  288. package/dist/dashboard/stat-group.module.css +66 -0
  289. package/dist/dashboard/stat-group.module.css.d.ts +8 -0
  290. package/dist/dashboard/widget-grid.d.ts +27 -0
  291. package/dist/dashboard/widget-grid.d.ts.map +1 -0
  292. package/dist/dashboard/widget-grid.js +41 -0
  293. package/dist/dashboard/widget-grid.module.css +53 -0
  294. package/dist/dashboard/widget-grid.module.css.d.ts +9 -0
  295. package/dist/data/data-table.d.ts +151 -12
  296. package/dist/data/data-table.d.ts.map +1 -1
  297. package/dist/data/data-table.js +392 -138
  298. package/dist/data/data-table.module.css +64 -0
  299. package/dist/data/data-table.module.css.d.ts +11 -0
  300. package/dist/data/index.d.ts +2 -2
  301. package/dist/data/index.d.ts.map +1 -1
  302. package/dist/data/index.js +24 -2
  303. package/dist/data/query-branch.d.ts +28 -0
  304. package/dist/data/query-branch.d.ts.map +1 -0
  305. package/dist/data/query-branch.js +10 -0
  306. package/dist/data/table.d.ts +2 -2
  307. package/dist/data/table.d.ts.map +1 -1
  308. package/dist/data/virtual-list.d.ts +65 -6
  309. package/dist/data/virtual-list.d.ts.map +1 -1
  310. package/dist/data/virtual-list.js +89 -38
  311. package/dist/data/virtual.d.ts +1 -1
  312. package/dist/data/virtual.d.ts.map +1 -1
  313. package/dist/forms/FormErrorSummary.d.ts +4 -3
  314. package/dist/forms/FormErrorSummary.d.ts.map +1 -1
  315. package/dist/forms/FormErrorSummary.js +6 -4
  316. package/dist/forms/create-form.d.ts +2 -2
  317. package/dist/forms/field.d.ts +46 -6
  318. package/dist/forms/field.d.ts.map +1 -1
  319. package/dist/forms/field.js +10 -5
  320. package/dist/forms/form-layout.d.ts +142 -0
  321. package/dist/forms/form-layout.d.ts.map +1 -0
  322. package/dist/forms/form-layout.js +110 -0
  323. package/dist/forms/form-layout.module.css +145 -0
  324. package/dist/forms/form-layout.module.css.d.ts +20 -0
  325. package/dist/forms/form-state.d.ts +42 -0
  326. package/dist/forms/form-state.d.ts.map +1 -0
  327. package/dist/forms/form-state.js +26 -0
  328. package/dist/forms/index.d.ts +22 -6
  329. package/dist/forms/index.d.ts.map +1 -1
  330. package/dist/forms/index.js +19 -3
  331. package/dist/forms/use-field-array.d.ts +60 -0
  332. package/dist/forms/use-field-array.d.ts.map +1 -0
  333. package/dist/forms/use-field-array.js +20 -0
  334. package/dist/forms/use-form-draft.d.ts +25 -10
  335. package/dist/forms/use-form-draft.d.ts.map +1 -1
  336. package/dist/forms/use-form-draft.js +55 -18
  337. package/dist/forms/use-form-submit.d.ts +88 -0
  338. package/dist/forms/use-form-submit.d.ts.map +1 -0
  339. package/dist/forms/use-form-submit.js +63 -0
  340. package/dist/guard/index.d.ts +1 -1
  341. package/dist/guard/index.d.ts.map +1 -1
  342. package/dist/guard/index.js +27 -11
  343. package/dist/index.d.ts +10 -8
  344. package/dist/index.d.ts.map +1 -1
  345. package/dist/index.js +44 -4
  346. package/dist/nav/types.d.ts +15 -6
  347. package/dist/nav/types.d.ts.map +1 -1
  348. package/dist/notifications/bell.d.ts +3 -2
  349. package/dist/notifications/bell.d.ts.map +1 -1
  350. package/dist/notifications/bell.js +73 -56
  351. package/dist/notifications/center.d.ts +15 -4
  352. package/dist/notifications/center.d.ts.map +1 -1
  353. package/dist/notifications/center.js +5 -2
  354. package/dist/notifications/define-notifications.d.ts +56 -14
  355. package/dist/notifications/define-notifications.d.ts.map +1 -1
  356. package/dist/notifications/define-notifications.js +2 -1
  357. package/dist/notifications/index.d.ts +6 -2
  358. package/dist/notifications/index.d.ts.map +1 -1
  359. package/dist/notifications/index.js +5 -1
  360. package/dist/notifications/mount-guard.d.ts +8 -0
  361. package/dist/notifications/mount-guard.d.ts.map +1 -0
  362. package/dist/notifications/mount-guard.js +19 -0
  363. package/dist/notifications/notify.d.ts +58 -1
  364. package/dist/notifications/notify.d.ts.map +1 -1
  365. package/dist/notifications/notify.js +46 -2
  366. package/dist/notifications/overlay.d.ts.map +1 -1
  367. package/dist/notifications/overlay.js +2 -0
  368. package/dist/notifications/store.d.ts +7 -0
  369. package/dist/notifications/store.d.ts.map +1 -1
  370. package/dist/provider/index.d.ts +37 -5
  371. package/dist/provider/index.d.ts.map +1 -1
  372. package/dist/provider/index.js +66 -28
  373. package/dist/provider/lab-theme.d.ts.map +1 -1
  374. package/dist/provider/lab-theme.js +2 -1
  375. package/dist/query/devtools.d.ts.map +1 -1
  376. package/dist/query/devtools.js +1 -0
  377. package/dist/router-tanstack/field.d.ts +1 -1
  378. package/dist/router-tanstack/field.d.ts.map +1 -1
  379. package/dist/router-tanstack/index.d.ts +1 -1
  380. package/dist/router-tanstack/index.d.ts.map +1 -1
  381. package/dist/router-tanstack/search-store.d.ts +4 -1
  382. package/dist/router-tanstack/search-store.d.ts.map +1 -1
  383. package/dist/router-tanstack/search-store.js +92 -8
  384. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  385. package/dist/router-tanstack/use-nav.js +1 -1
  386. package/dist/section/index.d.ts +1 -1
  387. package/dist/section/index.d.ts.map +1 -1
  388. package/dist/section/section.d.ts +36 -2
  389. package/dist/section/section.d.ts.map +1 -1
  390. package/dist/section/section.js +49 -57
  391. package/dist/section/section.module.css +11 -0
  392. package/dist/shell/account-types.d.ts +5 -2
  393. package/dist/shell/account-types.d.ts.map +1 -1
  394. package/dist/shell/app-brand.d.ts +13 -0
  395. package/dist/shell/app-brand.d.ts.map +1 -0
  396. package/dist/shell/app-brand.js +94 -0
  397. package/dist/shell/app-brand.module.css +109 -0
  398. package/dist/shell/app-brand.module.css.d.ts +14 -0
  399. package/dist/shell/app-breadcrumbs.d.ts +4 -2
  400. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  401. package/dist/shell/app-breadcrumbs.js +33 -22
  402. package/dist/shell/app-header.module.css +10 -5
  403. package/dist/shell/app-main.module.css +107 -0
  404. package/dist/shell/app-main.module.css.d.ts +11 -0
  405. package/dist/shell/app-mobile-nav.d.ts +3 -2
  406. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  407. package/dist/shell/app-mobile-nav.js +29 -15
  408. package/dist/shell/app-mobile-nav.module.css +48 -27
  409. package/dist/shell/app-mobile-nav.module.css.d.ts +0 -1
  410. package/dist/shell/app-sidebar-account.d.ts +2 -1
  411. package/dist/shell/app-sidebar-account.d.ts.map +1 -1
  412. package/dist/shell/app-sidebar-account.js +57 -22
  413. package/dist/shell/app-sidebar.d.ts +9 -3
  414. package/dist/shell/app-sidebar.d.ts.map +1 -1
  415. package/dist/shell/app-sidebar.js +55 -109
  416. package/dist/shell/app-sidebar.module.css +53 -96
  417. package/dist/shell/app-sidebar.module.css.d.ts +0 -5
  418. package/dist/shell/index.d.ts +14 -6
  419. package/dist/shell/index.d.ts.map +1 -1
  420. package/dist/shell/index.js +47 -12
  421. package/dist/shell/isomorphic-layout-effect.d.ts +14 -0
  422. package/dist/shell/isomorphic-layout-effect.d.ts.map +1 -0
  423. package/dist/shell/isomorphic-layout-effect.js +5 -0
  424. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  425. package/dist/shell/mobile-nav-model.js +27 -6
  426. package/dist/shell/nav-count-badge.d.ts +5 -3
  427. package/dist/shell/nav-count-badge.d.ts.map +1 -1
  428. package/dist/shell/nav-count-badge.js +3 -1
  429. package/dist/shell/page-aside.d.ts +43 -0
  430. package/dist/shell/page-aside.d.ts.map +1 -0
  431. package/dist/shell/page-aside.js +202 -0
  432. package/dist/shell/page-aside.module.css +174 -0
  433. package/dist/shell/page-aside.module.css.d.ts +14 -0
  434. package/dist/shell/page-bar.d.ts +55 -15
  435. package/dist/shell/page-bar.d.ts.map +1 -1
  436. package/dist/shell/page-bar.js +130 -41
  437. package/dist/shell/page-bar.module.css +107 -13
  438. package/dist/shell/page-bar.module.css.d.ts +4 -1
  439. package/dist/shell/sidebar-search.d.ts +4 -2
  440. package/dist/shell/sidebar-search.d.ts.map +1 -1
  441. package/dist/shell/sidebar-search.js +9 -4
  442. package/dist/shell/sidebar-search.module.css +1 -1
  443. package/dist/state/fields.d.ts +227 -34
  444. package/dist/state/fields.d.ts.map +1 -1
  445. package/dist/state/fields.js +77 -30
  446. package/dist/state/persisted-or-local.d.ts +9 -0
  447. package/dist/state/persisted-or-local.d.ts.map +1 -0
  448. package/dist/state/persisted-or-local.js +24 -0
  449. package/dist/state/persisted.d.ts +12 -0
  450. package/dist/state/persisted.d.ts.map +1 -1
  451. package/dist/state/persisted.js +36 -18
  452. package/dist/state.d.ts +1 -1
  453. package/dist/state.d.ts.map +1 -1
  454. package/dist/state.js +2 -6
  455. package/dist/surfaces.d.ts +12 -11
  456. package/dist/surfaces.d.ts.map +1 -1
  457. package/dist/surfaces.js +43 -12
  458. package/dist/theme/ctl-theme.d.ts.map +1 -1
  459. package/dist/theme/index.d.ts.map +1 -1
  460. package/dist/theme/index.js +102 -17
  461. package/dist/theme/nav-link.module.css +37 -26
  462. package/dist/theme/segmented-control.module.css +22 -0
  463. package/dist/theme/segmented-control.module.css.d.ts +1 -0
  464. package/dist/theme-lab/boot.d.ts +66 -0
  465. package/dist/theme-lab/boot.d.ts.map +1 -0
  466. package/dist/theme-lab/boot.js +82 -0
  467. package/dist/theme-lab/index.d.ts +4 -43
  468. package/dist/theme-lab/index.d.ts.map +1 -1
  469. package/dist/theme-lab/index.js +9 -69
  470. package/dist/theme-toggle/index.d.ts +3 -2
  471. package/dist/theme-toggle/index.d.ts.map +1 -1
  472. package/dist/theme-toggle/index.js +8 -2
  473. package/dist/tokens/index.d.ts +42 -0
  474. package/dist/tokens/index.d.ts.map +1 -1
  475. package/dist/tokens/index.js +35 -6
  476. package/dist/tokens/palette.d.ts +45 -25
  477. package/dist/tokens/palette.d.ts.map +1 -1
  478. package/dist/tokens/palette.js +31 -14
  479. package/dist/tokens.css +4 -5
  480. package/dist/utils/is-dev.d.ts +3 -0
  481. package/dist/utils/is-dev.d.ts.map +1 -0
  482. package/dist/utils/is-dev.js +6 -0
  483. package/dist/widget-header/delta-badge.d.ts +7 -1
  484. package/dist/widget-header/delta-badge.d.ts.map +1 -1
  485. package/dist/widget-header/delta-badge.js +8 -1
  486. package/dist/widget-header/index.d.ts +2 -2
  487. package/dist/widget-header/index.d.ts.map +1 -1
  488. package/dist/widget-header/index.js +3 -1
  489. package/dist/widget-header/widget-header.d.ts +99 -4
  490. package/dist/widget-header/widget-header.d.ts.map +1 -1
  491. package/dist/widget-header/widget-header.js +45 -18
  492. package/dist/widget-header/widget-header.module.css +87 -7
  493. package/dist/widget-header/widget-header.module.css.d.ts +1 -0
  494. package/llms.txt +6 -6
  495. package/package.json +49 -25
  496. package/src/agent/coalesce.ts +2 -1
  497. package/src/agent/merge.ts +2 -1
  498. package/src/agent/part-list.tsx +2 -0
  499. package/src/agent/stick-to-bottom.tsx +9 -11
  500. package/src/agent-chat/composer.tsx +124 -103
  501. package/src/agent-chat/relative-time.ts +2 -1
  502. package/src/agent-chat/thread-detail-panel.tsx +9 -5
  503. package/src/agent-chat/thread-feed-row.tsx +17 -9
  504. package/src/agent-chat/thread-feed.tsx +9 -3
  505. package/src/agent-chat/thread-message.tsx +48 -13
  506. package/src/agent-chat/thread-outcome-card.tsx +9 -1
  507. package/src/agent-chat/thread-workspace.tsx +72 -33
  508. package/src/agent-chat/tool-chip.tsx +8 -2
  509. package/src/charts/hooks/useBandPlot.ts +26 -8
  510. package/src/charts/index.ts +39 -4
  511. package/src/charts/kinds/BandStrip.tsx +36 -14
  512. package/src/charts/kinds/Bars.tsx +67 -16
  513. package/src/charts/kinds/Donut.tsx +30 -3
  514. package/src/charts/kinds/DualPanel.tsx +56 -10
  515. package/src/charts/kinds/Heatmap.tsx +90 -33
  516. package/src/charts/kinds/MirroredBars.tsx +21 -3
  517. package/src/charts/kinds/MultiLine.tsx +61 -18
  518. package/src/charts/kinds/StackedArea.tsx +77 -6
  519. package/src/charts/kinds/ZonedLine.tsx +71 -31
  520. package/src/charts/layout/auto-margin.ts +134 -21
  521. package/src/charts/layout/log-ticks.ts +90 -0
  522. package/src/charts/primitives/Axes.tsx +33 -5
  523. package/src/charts/primitives/CartesianChart.tsx +194 -41
  524. package/src/charts/primitives/ChartCard.tsx +66 -25
  525. package/src/charts/primitives/ChartFrame.tsx +107 -26
  526. package/src/charts/primitives/ChartLegend.tsx +43 -13
  527. package/src/charts/primitives/ChartPending.tsx +185 -7
  528. package/src/charts/primitives/ChartTooltip.tsx +37 -5
  529. package/src/charts/primitives/Crosshair.tsx +6 -3
  530. package/src/charts/primitives/chart-frame-layout.ts +212 -0
  531. package/src/charts/primitives/chart-tier.tsx +37 -0
  532. package/src/charts/series.ts +77 -1
  533. package/src/charts/sparklines/BarSparkline.tsx +11 -3
  534. package/src/charts/sparklines/LineSparkline.tsx +18 -4
  535. package/src/charts/sparklines/index.ts +2 -2
  536. package/src/charts/utils/format.ts +163 -2
  537. package/src/charts/utils/ticks.ts +149 -4
  538. package/src/cli/index.ts +8 -3
  539. package/src/commands/ShortcutsHelp.tsx +10 -3
  540. package/src/commands/define-commands.ts +2 -2
  541. package/src/commands/define-overlays.ts +216 -1
  542. package/src/commands/index.ts +12 -0
  543. package/src/commands/overlays-mount.tsx +60 -26
  544. package/src/common/errors.ts +73 -0
  545. package/src/common/index.ts +21 -0
  546. package/src/common/props.ts +71 -0
  547. package/src/common/query-state-like.ts +64 -0
  548. package/src/common/refs.ts +58 -0
  549. package/src/common/scroll-parent.ts +42 -0
  550. package/src/common/validate.ts +94 -0
  551. package/src/connectivity/connectivity-indicator.tsx +6 -1
  552. package/src/connectivity/connectivity-provider.tsx +4 -1
  553. package/src/connectivity/index.ts +1 -0
  554. package/src/connectivity/types.ts +5 -2
  555. package/src/connectivity/use-connectivity.ts +3 -2
  556. package/src/content/article-layout.module.css +11 -4
  557. package/src/content/callout.tsx +13 -7
  558. package/src/content/guide.tsx +14 -4
  559. package/src/content/heading-anchor.tsx +14 -3
  560. package/src/content/index.ts +2 -0
  561. package/src/content/markdown.tsx +2 -0
  562. package/src/content/prose.module.css +9 -7
  563. package/src/content/reading-progress.tsx +40 -15
  564. package/src/content/slug.ts +4 -0
  565. package/src/content/toc.tsx +47 -10
  566. package/src/controls/actions.tsx +89 -9
  567. package/src/controls/compare-filter.tsx +20 -3
  568. package/src/controls/control-group.tsx +11 -4
  569. package/src/controls/controls.module.css +169 -186
  570. package/src/controls/controls.module.css.d.ts +9 -11
  571. package/src/controls/enum-filter.tsx +46 -19
  572. package/src/controls/filter-context.tsx +21 -8
  573. package/src/controls/filter-pill.tsx +6 -3
  574. package/src/controls/filter-set.tsx +32 -13
  575. package/src/controls/filter-sheet.tsx +44 -194
  576. package/src/controls/index.ts +8 -0
  577. package/src/controls/multi-select-filter.tsx +179 -29
  578. package/src/controls/number-filter.tsx +329 -0
  579. package/src/controls/panel-row.module.css +86 -0
  580. package/src/controls/panel-row.module.css.d.ts +13 -0
  581. package/src/controls/panel-row.tsx +392 -0
  582. package/src/controls/range-filter.tsx +133 -43
  583. package/src/controls/search-filter.tsx +31 -13
  584. package/src/controls/select-filter.tsx +11 -1
  585. package/src/controls/slider-control.tsx +159 -0
  586. package/src/controls/sync-button.tsx +13 -3
  587. package/src/controls/toggle-filter.tsx +37 -17
  588. package/src/controls/view-tabs.tsx +63 -11
  589. package/src/controls-dates/date-range-picker.tsx +8 -1
  590. package/src/dashboard/delta-badge.tsx +5 -1
  591. package/src/dashboard/empty-state.tsx +72 -25
  592. package/src/dashboard/index.ts +19 -2
  593. package/src/dashboard/query-state.tsx +154 -84
  594. package/src/dashboard/settings-section.tsx +87 -31
  595. package/src/dashboard/stat-card.module.css +112 -26
  596. package/src/dashboard/stat-card.module.css.d.ts +5 -0
  597. package/src/dashboard/stat-card.tsx +207 -68
  598. package/src/dashboard/stat-group.module.css +66 -0
  599. package/src/dashboard/stat-group.module.css.d.ts +8 -0
  600. package/src/dashboard/stat-group.tsx +74 -0
  601. package/src/dashboard/widget-grid.module.css +53 -0
  602. package/src/dashboard/widget-grid.module.css.d.ts +9 -0
  603. package/src/dashboard/widget-grid.tsx +127 -0
  604. package/src/data/data-table.module.css +64 -0
  605. package/src/data/data-table.module.css.d.ts +11 -0
  606. package/src/data/data-table.tsx +927 -346
  607. package/src/data/index.ts +30 -2
  608. package/src/data/query-branch.ts +53 -0
  609. package/src/data/table.ts +5 -0
  610. package/src/data/virtual-list.tsx +189 -55
  611. package/src/data/virtual.ts +5 -1
  612. package/src/forms/FormErrorSummary.tsx +13 -6
  613. package/src/forms/create-form.ts +2 -2
  614. package/src/forms/field.ts +60 -11
  615. package/src/forms/form-layout.module.css +145 -0
  616. package/src/forms/form-layout.module.css.d.ts +20 -0
  617. package/src/forms/form-layout.tsx +305 -0
  618. package/src/forms/form-state.tsx +94 -0
  619. package/src/forms/index.ts +38 -7
  620. package/src/forms/use-field-array.ts +94 -0
  621. package/src/forms/use-form-draft.ts +145 -53
  622. package/src/forms/use-form-submit.ts +199 -0
  623. package/src/guard/index.ts +91 -23
  624. package/src/index.ts +79 -10
  625. package/src/nav/types.ts +15 -6
  626. package/src/notifications/bell.tsx +18 -4
  627. package/src/notifications/center.tsx +18 -5
  628. package/src/notifications/define-notifications.ts +64 -18
  629. package/src/notifications/index.ts +12 -1
  630. package/src/notifications/mount-guard.ts +41 -0
  631. package/src/notifications/notify.tsx +122 -3
  632. package/src/notifications/overlay.tsx +2 -0
  633. package/src/notifications/store.ts +7 -0
  634. package/src/provider/index.tsx +101 -13
  635. package/src/provider/lab-theme.ts +2 -1
  636. package/src/query/devtools.tsx +1 -0
  637. package/src/router-tanstack/field.ts +5 -0
  638. package/src/router-tanstack/index.ts +4 -0
  639. package/src/router-tanstack/search-store.ts +171 -12
  640. package/src/router-tanstack/use-nav.ts +18 -13
  641. package/src/section/index.ts +1 -1
  642. package/src/section/section.module.css +11 -0
  643. package/src/section/section.tsx +163 -96
  644. package/src/shell/account-types.ts +5 -2
  645. package/src/shell/app-brand.module.css +109 -0
  646. package/src/shell/app-brand.module.css.d.ts +14 -0
  647. package/src/shell/app-brand.tsx +140 -0
  648. package/src/shell/app-breadcrumbs.tsx +19 -9
  649. package/src/shell/app-header.module.css +10 -5
  650. package/src/shell/app-main.module.css +107 -0
  651. package/src/shell/app-main.module.css.d.ts +11 -0
  652. package/src/shell/app-mobile-nav.module.css +48 -27
  653. package/src/shell/app-mobile-nav.module.css.d.ts +0 -1
  654. package/src/shell/app-mobile-nav.tsx +43 -11
  655. package/src/shell/app-sidebar-account.tsx +29 -6
  656. package/src/shell/app-sidebar.module.css +53 -96
  657. package/src/shell/app-sidebar.module.css.d.ts +0 -5
  658. package/src/shell/app-sidebar.tsx +40 -113
  659. package/src/shell/index.tsx +156 -65
  660. package/src/shell/isomorphic-layout-effect.ts +14 -0
  661. package/src/shell/mobile-nav-model.ts +66 -10
  662. package/src/shell/nav-count-badge.tsx +6 -1
  663. package/src/shell/page-aside.module.css +174 -0
  664. package/src/shell/page-aside.module.css.d.ts +14 -0
  665. package/src/shell/page-aside.tsx +377 -0
  666. package/src/shell/page-bar.module.css +107 -13
  667. package/src/shell/page-bar.module.css.d.ts +4 -1
  668. package/src/shell/page-bar.tsx +294 -94
  669. package/src/shell/sidebar-search.module.css +1 -1
  670. package/src/shell/sidebar-search.tsx +13 -4
  671. package/src/state/fields.ts +360 -69
  672. package/src/state/persisted-or-local.ts +50 -0
  673. package/src/state/persisted.ts +76 -26
  674. package/src/state.ts +4 -7
  675. package/src/surfaces.ts +43 -12
  676. package/src/theme/ctl-theme.tsx +10 -0
  677. package/src/theme/index.ts +129 -16
  678. package/src/theme/nav-link.module.css +37 -26
  679. package/src/theme/segmented-control.module.css +22 -0
  680. package/src/theme/segmented-control.module.css.d.ts +1 -0
  681. package/src/theme-lab/boot.ts +140 -0
  682. package/src/theme-lab/index.tsx +21 -112
  683. package/src/theme-toggle/index.tsx +10 -3
  684. package/src/tokens/index.ts +50 -6
  685. package/src/tokens/palette.ts +58 -30
  686. package/src/utils/is-dev.ts +4 -0
  687. package/src/widget-header/delta-badge.tsx +25 -5
  688. package/src/widget-header/index.ts +10 -2
  689. package/src/widget-header/widget-header.module.css +87 -7
  690. package/src/widget-header/widget-header.module.css.d.ts +1 -0
  691. package/src/widget-header/widget-header.tsx +166 -42
  692. package/dist/agent/adapter.test.d.ts +0 -2
  693. package/dist/agent/adapter.test.d.ts.map +0 -1
  694. package/dist/agent/ai-sdk-transport.test.d.ts +0 -2
  695. package/dist/agent/ai-sdk-transport.test.d.ts.map +0 -1
  696. package/dist/agent/coalesce.test.d.ts +0 -2
  697. package/dist/agent/coalesce.test.d.ts.map +0 -1
  698. package/dist/agent/foreign.test.d.ts +0 -2
  699. package/dist/agent/foreign.test.d.ts.map +0 -1
  700. package/dist/agent/id.test.d.ts +0 -2
  701. package/dist/agent/id.test.d.ts.map +0 -1
  702. package/dist/agent/merge.test.d.ts +0 -2
  703. package/dist/agent/merge.test.d.ts.map +0 -1
  704. package/dist/agent/part-list.test.d.ts +0 -2
  705. package/dist/agent/part-list.test.d.ts.map +0 -1
  706. package/dist/agent/parts.test.d.ts +0 -2
  707. package/dist/agent/parts.test.d.ts.map +0 -1
  708. package/dist/agent/thread.test.d.ts +0 -2
  709. package/dist/agent/thread.test.d.ts.map +0 -1
  710. package/dist/agent/transport.test.d.ts +0 -2
  711. package/dist/agent/transport.test.d.ts.map +0 -1
  712. package/dist/agent/use-agent-stream.test.d.ts +0 -2
  713. package/dist/agent/use-agent-stream.test.d.ts.map +0 -1
  714. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts +0 -2
  715. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts.map +0 -1
  716. package/dist/agent/use-agent-thread-runs.resume.test.d.ts +0 -2
  717. package/dist/agent/use-agent-thread-runs.resume.test.d.ts.map +0 -1
  718. package/dist/agent/use-agent-thread-runs.test.d.ts +0 -2
  719. package/dist/agent/use-agent-thread-runs.test.d.ts.map +0 -1
  720. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts +0 -2
  721. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts.map +0 -1
  722. package/dist/agent-chat/composer.test.d.ts +0 -2
  723. package/dist/agent-chat/composer.test.d.ts.map +0 -1
  724. package/dist/agent-chat/relative-time.test.d.ts +0 -2
  725. package/dist/agent-chat/relative-time.test.d.ts.map +0 -1
  726. package/dist/agent-chat/thread-feed-row.test.d.ts +0 -2
  727. package/dist/agent-chat/thread-feed-row.test.d.ts.map +0 -1
  728. package/dist/agent-chat/thread-feed.test.d.ts +0 -2
  729. package/dist/agent-chat/thread-feed.test.d.ts.map +0 -1
  730. package/dist/agent-chat/thread-message.test.d.ts +0 -2
  731. package/dist/agent-chat/thread-message.test.d.ts.map +0 -1
  732. package/dist/agent-chat/thread-workspace.test.d.ts +0 -2
  733. package/dist/agent-chat/thread-workspace.test.d.ts.map +0 -1
  734. package/dist/agent-chat/tool-chip.test.d.ts +0 -2
  735. package/dist/agent-chat/tool-chip.test.d.ts.map +0 -1
  736. package/dist/agent-chat/virtualize.type-guard.test.d.ts +0 -2
  737. package/dist/agent-chat/virtualize.type-guard.test.d.ts.map +0 -1
  738. package/dist/charts/cursor/resolve.test.d.ts +0 -2
  739. package/dist/charts/cursor/resolve.test.d.ts.map +0 -1
  740. package/dist/charts/cursor/store.test.d.ts +0 -2
  741. package/dist/charts/cursor/store.test.d.ts.map +0 -1
  742. package/dist/charts/kinds/BandStrip.test.d.ts +0 -2
  743. package/dist/charts/kinds/BandStrip.test.d.ts.map +0 -1
  744. package/dist/charts/kinds/Bars.test.d.ts +0 -2
  745. package/dist/charts/kinds/Bars.test.d.ts.map +0 -1
  746. package/dist/charts/kinds/DualPanel.test.d.ts +0 -2
  747. package/dist/charts/kinds/DualPanel.test.d.ts.map +0 -1
  748. package/dist/charts/kinds/MirroredBars.test.d.ts +0 -2
  749. package/dist/charts/kinds/MirroredBars.test.d.ts.map +0 -1
  750. package/dist/charts/kinds/MultiLine.test.d.ts +0 -2
  751. package/dist/charts/kinds/MultiLine.test.d.ts.map +0 -1
  752. package/dist/charts/kinds/StackedArea.test.d.ts +0 -2
  753. package/dist/charts/kinds/StackedArea.test.d.ts.map +0 -1
  754. package/dist/charts/kinds/ZonedLine.test.d.ts +0 -2
  755. package/dist/charts/kinds/ZonedLine.test.d.ts.map +0 -1
  756. package/dist/charts/layout/auto-margin.test.d.ts +0 -2
  757. package/dist/charts/layout/auto-margin.test.d.ts.map +0 -1
  758. package/dist/charts/primitives/CartesianChart.test.d.ts +0 -2
  759. package/dist/charts/primitives/CartesianChart.test.d.ts.map +0 -1
  760. package/dist/charts/primitives/ChartCard.test.d.ts +0 -2
  761. package/dist/charts/primitives/ChartCard.test.d.ts.map +0 -1
  762. package/dist/charts/primitives/ChartFrame.test.d.ts +0 -2
  763. package/dist/charts/primitives/ChartFrame.test.d.ts.map +0 -1
  764. package/dist/charts/primitives/ChartLegend.test.d.ts +0 -2
  765. package/dist/charts/primitives/ChartLegend.test.d.ts.map +0 -1
  766. package/dist/charts/primitives/ChartTooltip.test.d.ts +0 -2
  767. package/dist/charts/primitives/ChartTooltip.test.d.ts.map +0 -1
  768. package/dist/charts/primitives/XZoneRects.test.d.ts +0 -2
  769. package/dist/charts/primitives/XZoneRects.test.d.ts.map +0 -1
  770. package/dist/charts/series.test.d.ts +0 -2
  771. package/dist/charts/series.test.d.ts.map +0 -1
  772. package/dist/charts/utils/domain.test.d.ts +0 -2
  773. package/dist/charts/utils/domain.test.d.ts.map +0 -1
  774. package/dist/charts/utils/measure-text.test.d.ts +0 -2
  775. package/dist/charts/utils/measure-text.test.d.ts.map +0 -1
  776. package/dist/cli/check-coverage.test.d.ts +0 -2
  777. package/dist/cli/check-coverage.test.d.ts.map +0 -1
  778. package/dist/cli/check-theme.test.d.ts +0 -2
  779. package/dist/cli/check-theme.test.d.ts.map +0 -1
  780. package/dist/cli/help.test.d.ts +0 -2
  781. package/dist/cli/help.test.d.ts.map +0 -1
  782. package/dist/cli/init-sync.test.d.ts +0 -2
  783. package/dist/cli/init-sync.test.d.ts.map +0 -1
  784. package/dist/cli/placement-engine.test.d.ts +0 -2
  785. package/dist/cli/placement-engine.test.d.ts.map +0 -1
  786. package/dist/cli/shipped-versions.test.d.ts +0 -2
  787. package/dist/cli/shipped-versions.test.d.ts.map +0 -1
  788. package/dist/cli/tarball-parity.test.d.ts +0 -2
  789. package/dist/cli/tarball-parity.test.d.ts.map +0 -1
  790. package/dist/cli/template-format.test.d.ts +0 -2
  791. package/dist/cli/template-format.test.d.ts.map +0 -1
  792. package/dist/cli/template-ledger.test.d.ts +0 -2
  793. package/dist/cli/template-ledger.test.d.ts.map +0 -1
  794. package/dist/cli/tokens-css.test.d.ts +0 -2
  795. package/dist/cli/tokens-css.test.d.ts.map +0 -1
  796. package/dist/cli/toolchain-wiring.test.d.ts +0 -2
  797. package/dist/cli/toolchain-wiring.test.d.ts.map +0 -1
  798. package/dist/cli/waiver-audit.test.d.ts +0 -2
  799. package/dist/cli/waiver-audit.test.d.ts.map +0 -1
  800. package/dist/content/article-actions.test.d.ts +0 -2
  801. package/dist/content/article-actions.test.d.ts.map +0 -1
  802. package/dist/content/article-model.test.d.ts +0 -2
  803. package/dist/content/article-model.test.d.ts.map +0 -1
  804. package/dist/content/block-split.test.d.ts +0 -2
  805. package/dist/content/block-split.test.d.ts.map +0 -1
  806. package/dist/content/heading-components.test.d.ts +0 -2
  807. package/dist/content/heading-components.test.d.ts.map +0 -1
  808. package/dist/content/markdown.test.d.ts +0 -2
  809. package/dist/content/markdown.test.d.ts.map +0 -1
  810. package/dist/content/prose.module.css.test.d.ts +0 -2
  811. package/dist/content/prose.module.css.test.d.ts.map +0 -1
  812. package/dist/content/sanitize.test.d.ts +0 -2
  813. package/dist/content/sanitize.test.d.ts.map +0 -1
  814. package/dist/content/slug.test.d.ts +0 -2
  815. package/dist/content/slug.test.d.ts.map +0 -1
  816. package/dist/controls/actions.test.d.ts +0 -2
  817. package/dist/controls/actions.test.d.ts.map +0 -1
  818. package/dist/controls/actions.type-guard.test.d.ts +0 -2
  819. package/dist/controls/actions.type-guard.test.d.ts.map +0 -1
  820. package/dist/controls/control-group.test.d.ts +0 -2
  821. package/dist/controls/control-group.test.d.ts.map +0 -1
  822. package/dist/controls/controls.router.test.d.ts +0 -2
  823. package/dist/controls/controls.router.test.d.ts.map +0 -1
  824. package/dist/controls/filter-set.test.d.ts +0 -2
  825. package/dist/controls/filter-set.test.d.ts.map +0 -1
  826. package/dist/controls/peer-boundary.test.d.ts +0 -2
  827. package/dist/controls/peer-boundary.test.d.ts.map +0 -1
  828. package/dist/controls/props-contract.test.d.ts +0 -2
  829. package/dist/controls/props-contract.test.d.ts.map +0 -1
  830. package/dist/controls/sync-button.test.d.ts +0 -2
  831. package/dist/controls/sync-button.test.d.ts.map +0 -1
  832. package/dist/dashboard/query-state.test.d.ts +0 -2
  833. package/dist/dashboard/query-state.test.d.ts.map +0 -1
  834. package/dist/dashboard/settings-section.test.d.ts +0 -2
  835. package/dist/dashboard/settings-section.test.d.ts.map +0 -1
  836. package/dist/dashboard/stat-card.test.d.ts +0 -2
  837. package/dist/dashboard/stat-card.test.d.ts.map +0 -1
  838. package/dist/data/data-table.test.d.ts +0 -2
  839. package/dist/data/data-table.test.d.ts.map +0 -1
  840. package/dist/guard/check-source.test.d.ts +0 -2
  841. package/dist/guard/check-source.test.d.ts.map +0 -1
  842. package/dist/guard/grace.test.d.ts +0 -20
  843. package/dist/guard/grace.test.d.ts.map +0 -1
  844. package/dist/guard/guard-hook.test.d.ts +0 -2
  845. package/dist/guard/guard-hook.test.d.ts.map +0 -1
  846. package/dist/provider/index.test.d.ts +0 -2
  847. package/dist/provider/index.test.d.ts.map +0 -1
  848. package/dist/provider/lab-theme.test.d.ts +0 -2
  849. package/dist/provider/lab-theme.test.d.ts.map +0 -1
  850. package/dist/query/error-message.test.d.ts +0 -2
  851. package/dist/query/error-message.test.d.ts.map +0 -1
  852. package/dist/router-tanstack/multi-search-param-store.test.d.ts +0 -2
  853. package/dist/router-tanstack/multi-search-param-store.test.d.ts.map +0 -1
  854. package/dist/router-tanstack/nav.test.d.ts +0 -2
  855. package/dist/router-tanstack/nav.test.d.ts.map +0 -1
  856. package/dist/router-tanstack/search-param-store.test.d.ts +0 -2
  857. package/dist/router-tanstack/search-param-store.test.d.ts.map +0 -1
  858. package/dist/router-tanstack/search-store.router.test.d.ts +0 -2
  859. package/dist/router-tanstack/search-store.router.test.d.ts.map +0 -1
  860. package/dist/router-tanstack/search-store.test.d.ts +0 -2
  861. package/dist/router-tanstack/search-store.test.d.ts.map +0 -1
  862. package/dist/router-tanstack/use-nav.test.d.ts +0 -2
  863. package/dist/router-tanstack/use-nav.test.d.ts.map +0 -1
  864. package/dist/section/section.test.d.ts +0 -2
  865. package/dist/section/section.test.d.ts.map +0 -1
  866. package/dist/shell/app-mobile-nav.test.d.ts +0 -2
  867. package/dist/shell/app-mobile-nav.test.d.ts.map +0 -1
  868. package/dist/shell/app-sidebar.test.d.ts +0 -2
  869. package/dist/shell/app-sidebar.test.d.ts.map +0 -1
  870. package/dist/shell/index.test.d.ts +0 -2
  871. package/dist/shell/index.test.d.ts.map +0 -1
  872. package/dist/shell/mobile-nav-model.test.d.ts +0 -2
  873. package/dist/shell/mobile-nav-model.test.d.ts.map +0 -1
  874. package/dist/shell/page-bar.test.d.ts +0 -2
  875. package/dist/shell/page-bar.test.d.ts.map +0 -1
  876. package/dist/state/fields.test.d.ts +0 -2
  877. package/dist/state/fields.test.d.ts.map +0 -1
  878. package/dist/styles.floor.test.d.ts +0 -2
  879. package/dist/styles.floor.test.d.ts.map +0 -1
  880. package/dist/theme/border-coverage.test.d.ts +0 -18
  881. package/dist/theme/border-coverage.test.d.ts.map +0 -1
  882. package/dist/theme/contrast.test.d.ts +0 -2
  883. package/dist/theme/contrast.test.d.ts.map +0 -1
  884. package/dist/theme/ctl-theme.test.d.ts +0 -2
  885. package/dist/theme/ctl-theme.test.d.ts.map +0 -1
  886. package/dist/theme/ctl-tier-coverage.test.d.ts +0 -2
  887. package/dist/theme/ctl-tier-coverage.test.d.ts.map +0 -1
  888. package/dist/theme/density-option.test.d.ts +0 -2
  889. package/dist/theme/density-option.test.d.ts.map +0 -1
  890. package/dist/theme/density-relations.test.d.ts +0 -2
  891. package/dist/theme/density-relations.test.d.ts.map +0 -1
  892. package/dist/theme/derive-theme.test.d.ts +0 -2
  893. package/dist/theme/derive-theme.test.d.ts.map +0 -1
  894. package/dist/theme/divider-law.test.d.ts +0 -2
  895. package/dist/theme/divider-law.test.d.ts.map +0 -1
  896. package/dist/theme/fonts-option.test.d.ts +0 -2
  897. package/dist/theme/fonts-option.test.d.ts.map +0 -1
  898. package/dist/theme/icon-slot.test.d.ts +0 -2
  899. package/dist/theme/icon-slot.test.d.ts.map +0 -1
  900. package/dist/theme/layout-rhythm-css.test.d.ts +0 -2
  901. package/dist/theme/layout-rhythm-css.test.d.ts.map +0 -1
  902. package/dist/theme/radius-option.test.d.ts +0 -2
  903. package/dist/theme/radius-option.test.d.ts.map +0 -1
  904. package/dist/theme/radius.test.d.ts +0 -2
  905. package/dist/theme/radius.test.d.ts.map +0 -1
  906. package/dist/theme/shadow-surfaces.test.d.ts +0 -26
  907. package/dist/theme/shadow-surfaces.test.d.ts.map +0 -1
  908. package/dist/theme/spacing.test.d.ts +0 -2
  909. package/dist/theme/spacing.test.d.ts.map +0 -1
  910. package/dist/theme/use-basalt-spacing.test.d.ts +0 -2
  911. package/dist/theme/use-basalt-spacing.test.d.ts.map +0 -1
  912. package/dist/theme-lab/derive-controls.test.d.ts +0 -2
  913. package/dist/theme-lab/derive-controls.test.d.ts.map +0 -1
  914. package/dist/theme-lab/derive-state.test.d.ts +0 -2
  915. package/dist/theme-lab/derive-state.test.d.ts.map +0 -1
  916. package/dist/theme-lab/theme-lab.test.d.ts +0 -2
  917. package/dist/theme-lab/theme-lab.test.d.ts.map +0 -1
  918. package/dist/tokens/build-fonts-css.test.d.ts +0 -2
  919. package/dist/tokens/build-fonts-css.test.d.ts.map +0 -1
  920. package/dist/tokens/build-radius-css.test.d.ts +0 -2
  921. package/dist/tokens/build-radius-css.test.d.ts.map +0 -1
  922. package/dist/tokens/chart-margin.test.d.ts +0 -2
  923. package/dist/tokens/chart-margin.test.d.ts.map +0 -1
  924. package/dist/tokens/density.test.d.ts +0 -2
  925. package/dist/tokens/density.test.d.ts.map +0 -1
  926. package/dist/tokens/derive-radius.test.d.ts +0 -2
  927. package/dist/tokens/derive-radius.test.d.ts.map +0 -1
  928. package/dist/tokens/derive.test.d.ts +0 -2
  929. package/dist/tokens/derive.test.d.ts.map +0 -1
  930. package/dist/tokens/palette.test.d.ts +0 -2
  931. package/dist/tokens/palette.test.d.ts.map +0 -1
  932. package/dist/tokens/text-scale.test.d.ts +0 -2
  933. package/dist/tokens/text-scale.test.d.ts.map +0 -1
  934. package/dist/vite-color-mix.test.d.ts +0 -2
  935. package/dist/vite-color-mix.test.d.ts.map +0 -1
  936. package/dist/vite.test.d.ts +0 -2
  937. package/dist/vite.test.d.ts.map +0 -1
  938. package/dist/widget-header/delta-badge.test.d.ts +0 -2
  939. package/dist/widget-header/delta-badge.test.d.ts.map +0 -1
  940. package/dist/widget-header/no-mantine.test.d.ts +0 -2
  941. package/dist/widget-header/no-mantine.test.d.ts.map +0 -1
  942. package/dist/widget-header/widget-header.test.d.ts +0 -2
  943. package/dist/widget-header/widget-header.test.d.ts.map +0 -1
  944. package/src/agent/adapter.test.ts +0 -1640
  945. package/src/agent/ai-sdk-transport.test.ts +0 -658
  946. package/src/agent/coalesce.test.ts +0 -387
  947. package/src/agent/foreign.test.ts +0 -51
  948. package/src/agent/id.test.ts +0 -169
  949. package/src/agent/merge.test.ts +0 -206
  950. package/src/agent/part-list.test.tsx +0 -112
  951. package/src/agent/parts.test.ts +0 -720
  952. package/src/agent/thread.test.ts +0 -220
  953. package/src/agent/transport.test.ts +0 -70
  954. package/src/agent/use-agent-stream.test.tsx +0 -287
  955. package/src/agent/use-agent-thread-runs.foreign.test.tsx +0 -138
  956. package/src/agent/use-agent-thread-runs.resume.test.tsx +0 -294
  957. package/src/agent/use-agent-thread-runs.test.tsx +0 -1120
  958. package/src/agent/use-agent-thread-runs.wedge.test.tsx +0 -387
  959. package/src/agent-chat/composer.test.tsx +0 -924
  960. package/src/agent-chat/relative-time.test.ts +0 -95
  961. package/src/agent-chat/thread-feed-row.test.tsx +0 -510
  962. package/src/agent-chat/thread-feed.test.tsx +0 -241
  963. package/src/agent-chat/thread-message.test.tsx +0 -1285
  964. package/src/agent-chat/thread-workspace.test.tsx +0 -169
  965. package/src/agent-chat/tool-chip.test.tsx +0 -139
  966. package/src/agent-chat/virtualize.type-guard.test.ts +0 -49
  967. package/src/charts/cursor/resolve.test.ts +0 -151
  968. package/src/charts/cursor/store.test.ts +0 -64
  969. package/src/charts/kinds/BandStrip.test.tsx +0 -282
  970. package/src/charts/kinds/Bars.test.tsx +0 -183
  971. package/src/charts/kinds/DualPanel.test.tsx +0 -527
  972. package/src/charts/kinds/MirroredBars.test.tsx +0 -232
  973. package/src/charts/kinds/MultiLine.test.tsx +0 -218
  974. package/src/charts/kinds/StackedArea.test.tsx +0 -114
  975. package/src/charts/kinds/ZonedLine.test.tsx +0 -147
  976. package/src/charts/layout/auto-margin.test.ts +0 -42
  977. package/src/charts/primitives/CartesianChart.test.tsx +0 -647
  978. package/src/charts/primitives/ChartCard.test.tsx +0 -66
  979. package/src/charts/primitives/ChartFrame.test.tsx +0 -142
  980. package/src/charts/primitives/ChartLegend.test.tsx +0 -78
  981. package/src/charts/primitives/ChartTooltip.test.tsx +0 -44
  982. package/src/charts/primitives/XZoneRects.test.tsx +0 -123
  983. package/src/charts/series.test.ts +0 -112
  984. package/src/charts/utils/domain.test.ts +0 -54
  985. package/src/charts/utils/measure-text.test.ts +0 -36
  986. package/src/cli/check-coverage.test.ts +0 -297
  987. package/src/cli/check-theme.test.ts +0 -837
  988. package/src/cli/help.test.ts +0 -170
  989. package/src/cli/init-sync.test.ts +0 -343
  990. package/src/cli/placement-engine.test.ts +0 -298
  991. package/src/cli/shipped-versions.test.ts +0 -83
  992. package/src/cli/tarball-parity.test.ts +0 -44
  993. package/src/cli/template-format.test.ts +0 -64
  994. package/src/cli/template-ledger.test.ts +0 -219
  995. package/src/cli/tokens-css.test.ts +0 -249
  996. package/src/cli/toolchain-wiring.test.ts +0 -1358
  997. package/src/cli/waiver-audit.test.ts +0 -215
  998. package/src/content/article-actions.test.ts +0 -78
  999. package/src/content/article-model.test.ts +0 -119
  1000. package/src/content/block-split.test.ts +0 -75
  1001. package/src/content/heading-components.test.tsx +0 -65
  1002. package/src/content/markdown.test.tsx +0 -603
  1003. package/src/content/prose.module.css.test.ts +0 -39
  1004. package/src/content/sanitize.test.ts +0 -659
  1005. package/src/content/slug.test.ts +0 -62
  1006. package/src/controls/actions.test.tsx +0 -252
  1007. package/src/controls/actions.type-guard.test.ts +0 -63
  1008. package/src/controls/control-group.test.tsx +0 -229
  1009. package/src/controls/controls.router.test.tsx +0 -637
  1010. package/src/controls/filter-set.test.tsx +0 -472
  1011. package/src/controls/peer-boundary.test.ts +0 -85
  1012. package/src/controls/props-contract.test.tsx +0 -106
  1013. package/src/controls/sync-button.test.tsx +0 -183
  1014. package/src/dashboard/query-state.test.tsx +0 -152
  1015. package/src/dashboard/settings-section.test.tsx +0 -56
  1016. package/src/dashboard/stat-card.test.tsx +0 -154
  1017. package/src/data/data-table.test.tsx +0 -434
  1018. package/src/guard/check-source.test.ts +0 -3355
  1019. package/src/guard/grace.test.ts +0 -149
  1020. package/src/guard/guard-hook.test.ts +0 -203
  1021. package/src/provider/index.test.tsx +0 -101
  1022. package/src/provider/lab-theme.test.ts +0 -147
  1023. package/src/query/error-message.test.ts +0 -67
  1024. package/src/router-tanstack/multi-search-param-store.test.ts +0 -124
  1025. package/src/router-tanstack/nav.test.ts +0 -111
  1026. package/src/router-tanstack/search-param-store.test.ts +0 -135
  1027. package/src/router-tanstack/search-store.router.test.tsx +0 -661
  1028. package/src/router-tanstack/search-store.test.ts +0 -418
  1029. package/src/router-tanstack/use-nav.test.tsx +0 -187
  1030. package/src/section/section.test.tsx +0 -204
  1031. package/src/shell/app-mobile-nav.test.tsx +0 -403
  1032. package/src/shell/app-sidebar.test.tsx +0 -509
  1033. package/src/shell/index.test.tsx +0 -428
  1034. package/src/shell/mobile-nav-model.test.ts +0 -517
  1035. package/src/shell/page-bar.test.tsx +0 -316
  1036. package/src/state/fields.test.tsx +0 -258
  1037. package/src/styles.floor.test.ts +0 -36
  1038. package/src/theme/border-coverage.test.ts +0 -272
  1039. package/src/theme/contrast.test.ts +0 -354
  1040. package/src/theme/ctl-theme.test.tsx +0 -275
  1041. package/src/theme/ctl-tier-coverage.test.ts +0 -170
  1042. package/src/theme/density-option.test.ts +0 -96
  1043. package/src/theme/density-relations.test.ts +0 -506
  1044. package/src/theme/derive-theme.test.ts +0 -101
  1045. package/src/theme/divider-law.test.ts +0 -259
  1046. package/src/theme/fonts-option.test.ts +0 -75
  1047. package/src/theme/icon-slot.test.tsx +0 -275
  1048. package/src/theme/layout-rhythm-css.test.ts +0 -72
  1049. package/src/theme/radius-option.test.ts +0 -86
  1050. package/src/theme/radius.test.ts +0 -142
  1051. package/src/theme/shadow-surfaces.test.ts +0 -488
  1052. package/src/theme/spacing.test.ts +0 -495
  1053. package/src/theme/use-basalt-spacing.test.tsx +0 -55
  1054. package/src/theme-lab/derive-controls.test.ts +0 -94
  1055. package/src/theme-lab/derive-state.test.ts +0 -71
  1056. package/src/theme-lab/theme-lab.test.ts +0 -46
  1057. package/src/tokens/build-fonts-css.test.ts +0 -52
  1058. package/src/tokens/build-radius-css.test.ts +0 -25
  1059. package/src/tokens/chart-margin.test.ts +0 -39
  1060. package/src/tokens/density.test.ts +0 -259
  1061. package/src/tokens/derive-radius.test.ts +0 -62
  1062. package/src/tokens/derive.test.ts +0 -30
  1063. package/src/tokens/palette.test.ts +0 -114
  1064. package/src/tokens/text-scale.test.ts +0 -51
  1065. package/src/vite-color-mix.test.ts +0 -46
  1066. package/src/vite.test.ts +0 -423
  1067. package/src/widget-header/delta-badge.test.tsx +0 -60
  1068. package/src/widget-header/no-mantine.test.ts +0 -33
  1069. package/src/widget-header/widget-header.test.tsx +0 -187
@@ -1,1120 +0,0 @@
1
- /**
2
- * useAgentThreadRuns — concurrency, busy-thread no-op, finalize ordering, failure status, retry,
3
- * and (as of B2) the stop()/stopAll() lifecycle (partial-turn preservation, the idle no-op
4
- * guard, and the resolveOutcome-race fix).
5
- *
6
- * Scope: the multi-thread run manager AS THE PUBLIC API EXISTS TODAY. No mergePart/part ids/
7
- * offsets, no seven-state ToolCallPart, no ResumableAgentTransport/isResumable/idempotentReplay
8
- * beyond the plain optional `resume` already on AgentTransport, no ThreadsStoreAdapter — later
9
- * releases, not exercised here.
10
- *
11
- * The mount-reconcile/resume path (StartPart handling, orphaned-thread resumption on mount) has
12
- * its own file, `use-agent-thread-runs.resume.test.tsx`; the F3 wedge has its own file,
13
- * `use-agent-thread-runs.wedge.test.tsx`.
14
- */
15
- import { describe, expect, test } from 'bun:test'
16
- import { act, renderHook, waitFor } from '@testing-library/react'
17
- import { useAgentThreadRuns } from './use-agent-thread-runs'
18
- import type { AgentThread, ThreadsStore } from './thread'
19
- import type { AgentTransport } from './transport'
20
- import type { AgentPart, AgentPartDraft } from './parts'
21
- import type { AgentOutcome, OutcomeResolver } from './outcome'
22
-
23
- // ── test-only ThreadsStore double — see use-agent-thread-runs.wedge.test.tsx for the rationale ──
24
- function createTestThreadsStore(
25
- opts: {
26
- readonly initial?: AgentThread<AgentPart>[]
27
- readonly onCall?: (method: string) => void
28
- } = {},
29
- ): ThreadsStore<AgentPart> {
30
- let threads = opts.initial ?? []
31
- let activeId: string | null = null
32
- const onCall = opts.onCall ?? ((): void => {})
33
-
34
- return {
35
- get threads() {
36
- return threads
37
- },
38
- get activeId() {
39
- return activeId
40
- },
41
- select(id) {
42
- activeId = id
43
- },
44
- create(createOpts) {
45
- const id = crypto.randomUUID()
46
- const now = Date.now()
47
- const thread: AgentThread<AgentPart> = {
48
- id,
49
- messages: [],
50
- outcome: null,
51
- status: 'pending',
52
- read: false,
53
- createdAt: now,
54
- updatedAt: now,
55
- ...(createOpts?.meta !== undefined ? { meta: createOpts.meta } : {}),
56
- }
57
- threads = [thread, ...threads]
58
- return id
59
- },
60
- appendMessage(id, message) {
61
- onCall('appendMessage')
62
- threads = threads.map((thread) =>
63
- thread.id === id
64
- ? { ...thread, messages: [...thread.messages, message], updatedAt: Date.now() }
65
- : thread,
66
- )
67
- },
68
- setOutcome(id, outcome) {
69
- onCall('setOutcome')
70
- threads = threads.map((thread) =>
71
- thread.id === id ? { ...thread, outcome, updatedAt: Date.now() } : thread,
72
- )
73
- },
74
- setStatus(id, status) {
75
- onCall('setStatus')
76
- threads = threads.map((thread) =>
77
- thread.id === id ? { ...thread, status, updatedAt: Date.now() } : thread,
78
- )
79
- },
80
- setResumeToken(id, token) {
81
- onCall('setResumeToken')
82
- threads = threads.map((thread) => {
83
- if (thread.id !== id) return thread
84
- const { resumeToken: _resumeToken, ...rest } = thread
85
- return {
86
- ...rest,
87
- ...(token !== undefined ? { resumeToken: token } : {}),
88
- updatedAt: Date.now(),
89
- }
90
- })
91
- },
92
- markRead(id) {
93
- threads = threads.map((thread) => (thread.id === id ? { ...thread, read: true } : thread))
94
- },
95
- remove(id) {
96
- threads = threads.filter((thread) => thread.id !== id)
97
- if (activeId === id) activeId = null
98
- },
99
- clear() {
100
- threads = []
101
- activeId = null
102
- },
103
- // Always-hydrated, never-erroring — mirrors the localStorage-backed store's real values
104
- // (see ThreadsStore.hydrated/.error doc comments); this double is a synchronous in-memory
105
- // stand-in with no async load path to fail.
106
- hydrated: true,
107
- error: undefined,
108
- }
109
- }
110
-
111
- function deferred<T>(): { promise: Promise<T>; resolve: (value: T) => void } {
112
- let resolve!: (value: T) => void
113
- const promise = new Promise<T>((r) => {
114
- resolve = r
115
- })
116
- return { promise, resolve }
117
- }
118
-
119
- const resolveOutcome: OutcomeResolver<AgentPart> = (): AgentOutcome => ({
120
- title: 'title',
121
- summary: 'summary',
122
- status: 'done',
123
- })
124
-
125
- describe('useAgentThreadRuns — concurrency, no-op guard, finalize order, failure, retry', () => {
126
- test('two threads stream fully independently', async () => {
127
- const store = createTestThreadsStore()
128
- const threadA = store.create()
129
- const threadB = store.create()
130
-
131
- const gates = new Map<string, ReturnType<typeof deferred<void>>>()
132
- gates.set('input-a', deferred<void>())
133
- gates.set('input-b', deferred<void>())
134
-
135
- const transport: AgentTransport<AgentPart, string> = {
136
- async *stream(input) {
137
- await gates.get(input)?.promise
138
- yield { id: 'p1', type: 'text', text: `done:${input}` }
139
- },
140
- }
141
-
142
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
143
-
144
- act(() => {
145
- result.current.start(threadA, 'input-a')
146
- result.current.start(threadB, 'input-b')
147
- })
148
-
149
- expect(result.current.runs.get(threadA)?.status).toBe('streaming')
150
- expect(result.current.runs.get(threadB)?.status).toBe('streaming')
151
-
152
- // Resolve A only — B must stay untouched. Resolving a promise is not itself a React update,
153
- // so this stays outside act(); waitFor (imported from @testing-library/react) is act-wrapped
154
- // internally and polls until the resulting state settles.
155
- gates.get('input-a')?.resolve(undefined)
156
-
157
- await waitFor(() => {
158
- expect(store.threads.find((t) => t.id === threadA)?.status).toBe('done')
159
- })
160
- expect(store.threads.find((t) => t.id === threadB)?.status).toBe('streaming')
161
- expect(result.current.runs.has(threadB)).toBe(true)
162
-
163
- // Resolve B — it settles independently, unaffected by A having already finished.
164
- gates.get('input-b')?.resolve(undefined)
165
-
166
- await waitFor(() => {
167
- expect(store.threads.find((t) => t.id === threadB)?.status).toBe('done')
168
- })
169
- })
170
-
171
- test('start() on a busy thread is a no-op and does not overwrite the cached input', async () => {
172
- const store = createTestThreadsStore()
173
- const threadId = store.create()
174
-
175
- const calls: string[] = []
176
- const gate = deferred<void>()
177
- const transport: AgentTransport<AgentPart, string> = {
178
- async *stream(input) {
179
- calls.push(input)
180
- await gate.promise
181
- yield { id: 'p1', type: 'text', text: 'ok' }
182
- },
183
- }
184
-
185
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
186
-
187
- act(() => {
188
- result.current.start(threadId, 'first')
189
- })
190
- expect(calls).toEqual(['first'])
191
-
192
- // Busy: the SAME thread already has a controller. This must no-op — no second stream() call,
193
- // and (critically) must NOT clobber the cached input retry() will replay.
194
- act(() => {
195
- result.current.start(threadId, 'second')
196
- })
197
- expect(calls).toEqual(['first'])
198
-
199
- // Let the first (and only) turn finish, then retry() — it must replay 'first', never 'second'.
200
- gate.resolve(undefined)
201
- await waitFor(() => {
202
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('done')
203
- })
204
-
205
- act(() => {
206
- result.current.retry(threadId)
207
- })
208
- expect(calls).toEqual(['first', 'first'])
209
-
210
- // Let the replayed run settle too, so no pending async work leaks past this test's act
211
- // boundary into the next one.
212
- await waitFor(() => {
213
- expect(result.current.runs.has(threadId)).toBe(false)
214
- })
215
- })
216
-
217
- test('finalize order on success: appendMessage → resolveOutcome → setOutcome → setStatus → setResumeToken', async () => {
218
- const order: string[] = []
219
- const store = createTestThreadsStore({ onCall: (method) => order.push(method) })
220
- const threadId = store.create()
221
-
222
- const gate = deferred<void>()
223
- const transport: AgentTransport<AgentPart, string> = {
224
- async *stream() {
225
- await gate.promise
226
- yield { id: 'p1', type: 'text', text: 'hi' }
227
- },
228
- }
229
-
230
- const orderedResolveOutcome: OutcomeResolver<AgentPart> = async (): Promise<AgentOutcome> => {
231
- order.push('resolveOutcome')
232
- return { title: 'title', summary: 'summary', status: 'done' }
233
- }
234
-
235
- const { result } = renderHook(() =>
236
- useAgentThreadRuns({ transport, store, resolveOutcome: orderedResolveOutcome }),
237
- )
238
-
239
- act(() => {
240
- result.current.start(threadId, 'hi')
241
- })
242
- // start() itself already logged 'appendMessage' (user message) + 'setStatus' ('streaming') —
243
- // baseline everything from here on.
244
- const baseline = order.length
245
-
246
- gate.resolve(undefined)
247
- await waitFor(() => {
248
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('done')
249
- })
250
-
251
- expect(order.slice(baseline)).toEqual([
252
- 'appendMessage',
253
- 'resolveOutcome',
254
- 'setOutcome',
255
- 'setStatus',
256
- 'setResumeToken',
257
- ])
258
-
259
- // The controller/runs entry is torn down as the final step of the same finalize — proven
260
- // indirectly: the run entry is gone, and a subsequent start() is accepted immediately (the
261
- // busy guard would block it if the controller were still registered).
262
- expect(result.current.runs.has(threadId)).toBe(false)
263
- })
264
-
265
- test('a thrown (non-abort) error sets the onFailureStatus for start() — "error"', async () => {
266
- const store = createTestThreadsStore()
267
- const threadId = store.create()
268
-
269
- const transport: AgentTransport<AgentPart, string> = {
270
- async *stream() {
271
- throw new Error('boom')
272
- // eslint-disable-next-line no-unreachable
273
- yield { id: 'p1', type: 'text', text: 'unreachable' }
274
- },
275
- }
276
-
277
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
278
-
279
- act(() => {
280
- result.current.start(threadId, 'hi')
281
- })
282
-
283
- await waitFor(() => {
284
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('error')
285
- })
286
- expect(result.current.runs.has(threadId)).toBe(false)
287
- })
288
-
289
- test('retry() is a no-op when nothing has been cached for that thread', () => {
290
- // No store.create() here on purpose: a freshly-created 'pending' thread is itself swept by
291
- // the mount-reconcile effect (it treats any 'pending'/'streaming' thread with no live
292
- // controller as orphaned — see use-agent-thread-runs.resume.test.tsx), which would confound
293
- // this assertion. Using an id with NO entry in the store at all isolates retry()'s own
294
- // "nothing cached" guard from that unrelated mount-time behavior.
295
- const store = createTestThreadsStore()
296
- const threadId = crypto.randomUUID()
297
-
298
- const calls: string[] = []
299
- const transport: AgentTransport<AgentPart, string> = {
300
- async *stream(input) {
301
- calls.push(input)
302
- yield { id: 'p1', type: 'text', text: 'x' }
303
- },
304
- }
305
-
306
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
307
-
308
- act(() => {
309
- result.current.retry(threadId)
310
- })
311
-
312
- expect(calls).toEqual([])
313
- expect(store.threads).toEqual([])
314
- })
315
-
316
- test('retry() re-invokes start() with the cached input and produces a genuine second run', async () => {
317
- const store = createTestThreadsStore()
318
- const threadId = store.create()
319
-
320
- const calls: string[] = []
321
- const transport: AgentTransport<AgentPart, string> = {
322
- async *stream(input) {
323
- calls.push(input)
324
- yield { id: 'p1', type: 'text', text: `done:${input}` }
325
- },
326
- }
327
-
328
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
329
-
330
- act(() => {
331
- result.current.start(threadId, 'original input')
332
- })
333
- await waitFor(() => {
334
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('done')
335
- })
336
- expect(calls).toEqual(['original input'])
337
-
338
- act(() => {
339
- result.current.retry(threadId)
340
- })
341
- // The positive path this test exists to pin: retry() actually calls start() again with the
342
- // SAME cached input — not a no-op, and not a clobbered/different input.
343
- expect(calls).toEqual(['original input', 'original input'])
344
- // A genuine second run is now live — the run entry is back and streaming.
345
- expect(result.current.runs.get(threadId)?.status).toBe('streaming')
346
-
347
- await waitFor(() => {
348
- expect(result.current.runs.has(threadId)).toBe(false)
349
- })
350
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('done')
351
- // Two full turns landed in history: user+assistant, twice.
352
- expect(store.threads.find((t) => t.id === threadId)?.messages).toHaveLength(4)
353
- })
354
-
355
- // Pins the fix for a race in consumeAndFinalize's success path: it awaits resolveOutcome and
356
- // then writes setOutcome/setStatus, and now RE-CHECKS the supersede/abort guards after that
357
- // await (not just before it) before performing those writes. Without the recheck, a slow
358
- // resolveOutcome racing a stop() call would clobber stop()'s synchronous 'done' with whatever
359
- // the outcome resolved to once it finally settled.
360
- test('stop() during a slow resolveOutcome must not be clobbered by the outcome settling later', async () => {
361
- const store = createTestThreadsStore()
362
- const threadId = store.create()
363
-
364
- let releaseOutcome: (() => void) | undefined
365
- const slowResolveOutcome: OutcomeResolver<AgentPart> = () =>
366
- new Promise<AgentOutcome>((resolve) => {
367
- releaseOutcome = () => resolve({ title: 't', summary: 's', status: 'attention' })
368
- })
369
-
370
- const transport: AgentTransport<AgentPart, string> = {
371
- async *stream() {
372
- yield { id: 'p1', type: 'text', text: 'hi' }
373
- },
374
- }
375
-
376
- const { result } = renderHook(() =>
377
- useAgentThreadRuns({ transport, store, resolveOutcome: slowResolveOutcome }),
378
- )
379
-
380
- act(() => {
381
- result.current.start(threadId, 'hi')
382
- })
383
-
384
- // Wait until the assistant message has landed (proving resolveOutcome has been called and is
385
- // now the pending await).
386
- await waitFor(() => {
387
- expect(store.threads.find((t) => t.id === threadId)?.messages.length).toBe(2)
388
- })
389
-
390
- act(() => {
391
- result.current.stop(threadId)
392
- })
393
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('done')
394
-
395
- releaseOutcome?.()
396
-
397
- await waitFor(() => {
398
- // The late resolveOutcome settling must not resurrect the thread past the 'done' stop()
399
- // already committed.
400
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('done')
401
- })
402
- })
403
- })
404
-
405
- describe('useAgentThreadRuns — stop()/stopAll() lifecycle', () => {
406
- test('stop() mid-stream persists the accumulated parts as a finish: "stopped" assistant message', async () => {
407
- const store = createTestThreadsStore()
408
- const threadId = store.create()
409
-
410
- const gate = deferred<void>()
411
- // Yields id-less drafts on purpose — the assertion below exists specifically to prove
412
- // withPartIds stamps the missing id as `${threadId}#0`, so the cast documents that the type
413
- // says AgentPart (identified) while this transport deliberately produces what the real
414
- // contract is AgentPartDraft (a transport is always permitted to omit id).
415
- const transport: AgentTransport<AgentPart, string> = {
416
- async *stream() {
417
- yield { type: 'text', text: 'partial' } as AgentPartDraft as AgentPart
418
- await gate.promise
419
- yield { type: 'text', text: 'never arrives' } as AgentPartDraft as AgentPart
420
- },
421
- }
422
-
423
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
424
-
425
- act(() => {
426
- result.current.start(threadId, 'hi')
427
- })
428
-
429
- await waitFor(() => {
430
- expect(result.current.runs.get(threadId)?.parts).toHaveLength(1)
431
- })
432
-
433
- act(() => {
434
- result.current.stop(threadId)
435
- })
436
-
437
- const thread = store.threads.find((t) => t.id === threadId)
438
- expect(thread?.status).toBe('done')
439
- expect(thread?.messages).toHaveLength(2)
440
- const assistantMessage = thread?.messages[1]
441
- expect(assistantMessage?.role).toBe('assistant')
442
- expect(assistantMessage?.finish).toBe('stopped')
443
- // withPartIds stamps the missing id as `${threadId}#0` — the first (and only) id-less part
444
- // this run's generator yielded.
445
- expect(assistantMessage?.parts).toEqual([
446
- { id: `${threadId}#0`, type: 'text', text: 'partial' },
447
- ])
448
- // The run entry is torn down synchronously — no lingering 'streaming' indicator.
449
- expect(result.current.runs.has(threadId)).toBe(false)
450
-
451
- // Release the still-suspended (aborted, guard-caught) generator so it doesn't leak into the
452
- // next test.
453
- gate.resolve(undefined)
454
- })
455
-
456
- test('stop() does not double-append when the stream already fully arrived before stop() was called', async () => {
457
- const store = createTestThreadsStore()
458
- const threadId = store.create()
459
-
460
- let releaseOutcome: (() => void) | undefined
461
- const slowResolveOutcome: OutcomeResolver<AgentPart> = () =>
462
- new Promise<AgentOutcome>((resolve) => {
463
- releaseOutcome = () => resolve({ title: 't', summary: 's', status: 'done' })
464
- })
465
-
466
- const transport: AgentTransport<AgentPart, string> = {
467
- async *stream() {
468
- yield { id: 'p1', type: 'text', text: 'hi' }
469
- },
470
- }
471
-
472
- const { result } = renderHook(() =>
473
- useAgentThreadRuns({ transport, store, resolveOutcome: slowResolveOutcome }),
474
- )
475
-
476
- act(() => {
477
- result.current.start(threadId, 'hi')
478
- })
479
-
480
- // Wait until consumeAndFinalize's own appendMessage has already landed (it happens BEFORE
481
- // its await on resolveOutcome) — this is the narrow window stop() can race without ever
482
- // being able to observe "not yet appended" (see finalizeStop's doc comment).
483
- await waitFor(() => {
484
- expect(store.threads.find((t) => t.id === threadId)?.messages.length).toBe(2)
485
- })
486
-
487
- act(() => {
488
- result.current.stop(threadId)
489
- })
490
-
491
- // Still exactly 2 messages (user + the ORIGINAL 'complete' assistant message) — stop() must
492
- // recognize the terminal message already landed and skip its own append rather than
493
- // duplicating it as a second 'stopped' message.
494
- const thread = store.threads.find((t) => t.id === threadId)
495
- expect(thread?.messages).toHaveLength(2)
496
- expect(thread?.messages[1]?.finish).toBe('complete')
497
-
498
- releaseOutcome?.()
499
- })
500
-
501
- test('stop() is a true no-op when threadId has no in-flight run', () => {
502
- const store = createTestThreadsStore()
503
- // No store.create() here on purpose — mirrors the "retry() is a no-op" test above: isolates
504
- // stop()'s own "nothing in flight" guard from the mount-reconcile effect's unrelated handling
505
- // of a freshly-created 'pending' thread.
506
- const threadId = crypto.randomUUID()
507
-
508
- const transport: AgentTransport<AgentPart, string> = {
509
- async *stream() {
510
- yield { id: 'p1', type: 'text', text: 'x' }
511
- },
512
- }
513
-
514
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
515
-
516
- act(() => {
517
- result.current.stop(threadId)
518
- })
519
-
520
- expect(store.threads).toEqual([])
521
- })
522
-
523
- // Pins the fix for the reference-equality hole: runsRef mirrors `runs` only as of the last
524
- // FLUSHED render (assigned during render, `:~355`), so a stop() landing inside the narrow
525
- // window between consumeAndFinalize's post-loop appendMessage and its `await resolveOutcome`
526
- // resolving could observe a STALE parts array (one delta behind) via runsRef — defeating the
527
- // old `lastMessage.parts === parts` guard and double-appending. This test lands stop()
528
- // deterministically inside that exact window (fired synchronously from inside the injected
529
- // resolveOutcome, itself called synchronously right after appendMessage, before any of the
530
- // hook's pending state updates can flush — flushing needs a macrotask, and nothing here crosses
531
- // one) rather than relying on timing luck, so it is a reliable regression guard for the fixed
532
- // marker-based mechanism, not just a one-off repro.
533
- test("stop() fired synchronously from inside resolveOutcome (before the final delta's render flushes) must not double-append", async () => {
534
- const store = createTestThreadsStore()
535
- const threadId = store.create()
536
-
537
- const gate = deferred<void>()
538
- const transport: AgentTransport<AgentPart, string> = {
539
- async *stream() {
540
- // Distinct ids: mergePart addresses by id, and two parts sharing the (undefined) id would
541
- // splice into ONE entry instead of accumulating two — this test needs the SECOND delta to
542
- // produce a genuinely NEW array entry (and therefore a new `parts` array reference) to
543
- // exercise the race at all.
544
- yield { id: 'p1', type: 'text', text: 'first' }
545
- await gate.promise
546
- yield { id: 'p2', type: 'text', text: 'second' }
547
- },
548
- }
549
-
550
- const hookRef: { stop?: (id: string) => void } = {}
551
- let resolveCalls = 0
552
- const raceResolveOutcome: OutcomeResolver<AgentPart> = async () => {
553
- resolveCalls += 1
554
- // Fire stop() from inside the FIRST resolveOutcome call — synchronously, before this
555
- // function even returns its promise. Everything from the second delta's setRuns dispatch to
556
- // this point runs purely through microtasks (async-generator machinery + this direct call),
557
- // so no macrotask has elapsed and React's scheduler cannot have flushed that dispatch into
558
- // runsRef yet — this reliably lands stop() inside the race window.
559
- if (resolveCalls === 1) {
560
- hookRef.stop?.(threadId)
561
- }
562
- return { title: 't', summary: 's', status: 'done' }
563
- }
564
-
565
- const { result } = renderHook(() =>
566
- useAgentThreadRuns({ transport, store, resolveOutcome: raceResolveOutcome }),
567
- )
568
- hookRef.stop = result.current.stop
569
-
570
- act(() => {
571
- result.current.start(threadId, 'hi')
572
- })
573
-
574
- // Let the FIRST delta's render flush — runsRef now mirrors a non-empty parts array
575
- // (['first']), which is what makes the eventual mismatch observable rather than vacuously
576
- // guarded by parts.length === 0.
577
- await waitFor(() => {
578
- expect(result.current.runs.get(threadId)?.parts).toHaveLength(1)
579
- })
580
-
581
- // Resolve the gate outside act() on purpose: this must not synchronously flush React state —
582
- // the whole point is to let the second delta, the loop exit, and appendMessage run purely as
583
- // microtask continuations, with stop() firing from inside resolveOutcome before any render for
584
- // the second delta occurs.
585
- gate.resolve(undefined)
586
-
587
- await waitFor(() => {
588
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('done')
589
- })
590
-
591
- const thread = store.threads.find((t) => t.id === threadId)
592
- const assistantMessages = thread?.messages.filter((m) => m.role === 'assistant') ?? []
593
- expect(assistantMessages).toHaveLength(1)
594
- // The surviving message must be the ORIGINAL 'complete' one with the full (['first','second'])
595
- // content — not a truncated 'stopped' message built from the stale ['first']-only parts.
596
- expect(assistantMessages[0]?.finish).toBe('complete')
597
- expect(assistantMessages[0]?.parts).toEqual([
598
- { id: 'p1', type: 'text', text: 'first' },
599
- { id: 'p2', type: 'text', text: 'second' },
600
- ])
601
- })
602
-
603
- // Pins the flip side of the marker fix: the marker must be cleared once a run concludes, or a
604
- // stale entry from an EARLIER finished run on the same thread would wrongly suppress a
605
- // legitimate append on a LATER run — a worse bug than the double-append this mechanism exists to
606
- // prevent.
607
- test('a stale marker from an earlier finished run must not suppress a legitimate later stop() append', async () => {
608
- const store = createTestThreadsStore()
609
- const threadId = store.create()
610
-
611
- const inputs: string[] = []
612
- let gate = deferred<void>()
613
- const transport: AgentTransport<AgentPart, string> = {
614
- async *stream(input) {
615
- inputs.push(input)
616
- // Distinct ids: two id-less parts sharing the (undefined) id would silently splice into
617
- // ONE entry (the F1 bug) — turn 1's own "did these two deltas stay separate?" assertion
618
- // below is invisible otherwise, since it only checked assistant MESSAGE count, not parts.
619
- yield { id: `${input}-partial`, type: 'text', text: `${input}-partial` }
620
- await gate.promise
621
- yield { id: `${input}-final`, type: 'text', text: `${input}-final` }
622
- },
623
- }
624
-
625
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
626
-
627
- // Turn 1: runs to full completion (no stop()) so consumeAndFinalize's own success path sets
628
- // and then clears the marker for this thread.
629
- act(() => {
630
- result.current.start(threadId, 'first-turn')
631
- })
632
- gate.resolve(undefined)
633
- await waitFor(() => {
634
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('done')
635
- })
636
- const turn1Assistant = store.threads
637
- .find((t) => t.id === threadId)
638
- ?.messages.filter((m) => m.role === 'assistant')
639
- expect(turn1Assistant).toHaveLength(1)
640
- // The two deltas landed as two SEPARATE parts, not spliced into one.
641
- expect(turn1Assistant?.[0]?.parts).toEqual([
642
- { id: 'first-turn-partial', type: 'text', text: 'first-turn-partial' },
643
- { id: 'first-turn-final', type: 'text', text: 'first-turn-final' },
644
- ])
645
-
646
- // Turn 2: a genuinely new run on the SAME thread, stopped mid-stream. If a stale marker from
647
- // turn 1 leaked, this stop() would be wrongly treated as "already persisted" and silently drop
648
- // the partial content instead of appending it.
649
- gate = deferred<void>()
650
- act(() => {
651
- result.current.retry(threadId)
652
- })
653
- expect(inputs).toEqual(['first-turn', 'first-turn'])
654
-
655
- await waitFor(() => {
656
- expect(result.current.runs.get(threadId)?.parts).toHaveLength(1)
657
- })
658
-
659
- act(() => {
660
- result.current.stop(threadId)
661
- })
662
-
663
- const thread = store.threads.find((t) => t.id === threadId)
664
- const assistantMessages = thread?.messages.filter((m) => m.role === 'assistant') ?? []
665
- expect(assistantMessages).toHaveLength(2)
666
- expect(assistantMessages[1]?.finish).toBe('stopped')
667
- expect(assistantMessages[1]?.parts).toEqual([
668
- { id: 'first-turn-partial', type: 'text', text: 'first-turn-partial' },
669
- ])
670
-
671
- gate.resolve(undefined)
672
- })
673
-
674
- test('stopAll() resolves every aborted thread instead of stranding them in "streaming"', async () => {
675
- const store = createTestThreadsStore()
676
- const threadA = store.create()
677
- const threadB = store.create()
678
-
679
- const gateA = deferred<void>()
680
- const gateB = deferred<void>()
681
- const transport: AgentTransport<AgentPart, string> = {
682
- async *stream(input) {
683
- if (input === 'a') {
684
- yield { id: 'p1', type: 'text', text: 'partial-a' }
685
- await gateA.promise
686
- } else {
687
- yield { id: 'p1', type: 'text', text: 'partial-b' }
688
- await gateB.promise
689
- }
690
- },
691
- }
692
-
693
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
694
-
695
- act(() => {
696
- result.current.start(threadA, 'a')
697
- result.current.start(threadB, 'b')
698
- })
699
-
700
- await waitFor(() => {
701
- expect(result.current.runs.get(threadA)?.parts).toHaveLength(1)
702
- expect(result.current.runs.get(threadB)?.parts).toHaveLength(1)
703
- })
704
-
705
- act(() => {
706
- result.current.stopAll()
707
- })
708
-
709
- expect(store.threads.find((t) => t.id === threadA)?.status).toBe('done')
710
- expect(store.threads.find((t) => t.id === threadB)?.status).toBe('done')
711
- expect(result.current.runs.size).toBe(0)
712
-
713
- // Release the still-suspended (aborted, guard-caught) generators so they don't leak into the
714
- // next test.
715
- gateA.resolve(undefined)
716
- gateB.resolve(undefined)
717
- })
718
-
719
- // F4 regression: stop()'s no-op guard used to read runsRef, which only mirrors `runs` as of the
720
- // last FLUSHED RENDER. start() then stop() called in the SAME synchronous act() batch — e.g.
721
- // `onClick={() => { start(id, text); stop(id) }}` — never lets a render land in between, so
722
- // runsRef was still empty when stop() ran and it wrongly no-op'd: the controller was never
723
- // aborted, and a transport that only resolves on its AbortSignal firing would strand the thread
724
- // in 'streaming' forever (this is exactly what `stopAll()`'s own regression test above cannot
725
- // see — it deliberately `waitFor`s a render to flush before calling stopAll()).
726
- test('F4 regression: start() then stop() in the same synchronous tick must actually abort, not strand the thread in "streaming"', async () => {
727
- const store = createTestThreadsStore()
728
- const threadId = store.create()
729
-
730
- // Hangs until its AbortSignal fires — never resolves on its own. If stop()'s no-op guard skips
731
- // the abort, this generator never settles and the thread is stuck 'streaming' forever.
732
- const transport: AgentTransport<AgentPart, string> = {
733
- // oxlint-disable-next-line require-yield -- hangs until aborted; never has anything to yield
734
- async *stream(_input, signal) {
735
- await new Promise((resolve) => {
736
- signal?.addEventListener('abort', () => resolve(undefined))
737
- })
738
- },
739
- }
740
-
741
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
742
-
743
- act(() => {
744
- result.current.start(threadId, 'hi')
745
- result.current.stop(threadId)
746
- })
747
-
748
- await waitFor(() => {
749
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('done')
750
- })
751
- expect(result.current.runs.has(threadId)).toBe(false)
752
- })
753
- })
754
-
755
- describe('useAgentThreadRuns — F1 regression: id-less drafts within one run', () => {
756
- test('drafts of different types do not destroy each other — mergePart no longer sees undefined === undefined', async () => {
757
- const store = createTestThreadsStore()
758
- const threadId = store.create()
759
-
760
- // Yields id-less drafts on purpose — the whole point of this regression is that TWO drafts
761
- // (of different types, so a real bug wouldn't be "same id" but "no id at all") must not
762
- // collide via `undefined === undefined`. The cast documents that the type says AgentPart
763
- // (identified) while a transport is always permitted to omit id (AgentPartDraft).
764
- const transport: AgentTransport<AgentPart, string> = {
765
- async *stream() {
766
- yield { type: 'text', text: 'Hi' } as AgentPartDraft as AgentPart
767
- yield {
768
- type: 'tool',
769
- toolCallId: 'c1',
770
- toolName: 'search',
771
- state: 'input-available',
772
- input: { q: 'x' },
773
- } as AgentPartDraft as AgentPart
774
- yield { type: 'text', text: 'Bye' } as AgentPartDraft as AgentPart
775
- },
776
- }
777
-
778
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
779
-
780
- act(() => {
781
- result.current.start(threadId, 'go')
782
- })
783
-
784
- await waitFor(() => {
785
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('done')
786
- })
787
-
788
- const assistantMessage = store.threads
789
- .find((t) => t.id === threadId)
790
- ?.messages.find((m) => m.role === 'assistant')
791
- expect(assistantMessage?.parts).toHaveLength(3)
792
- expect(assistantMessage?.parts.map((part) => part.type)).toEqual(['text', 'tool', 'text'])
793
- })
794
- })
795
-
796
- describe('useAgentThreadRuns — message-id minting under degraded crypto', () => {
797
- test('start() mints distinct message ids via crypto.getRandomValues when randomUUID is unavailable', async () => {
798
- const store = createTestThreadsStore()
799
- const threadA = store.create()
800
- const threadB = store.create()
801
-
802
- const transport: AgentTransport<AgentPart, string> = {
803
- async *stream(input) {
804
- yield { id: 'p1', type: 'text', text: `done:${input}` }
805
- },
806
- }
807
-
808
- const originalCrypto = globalThis.crypto
809
- Object.defineProperty(globalThis, 'crypto', {
810
- value: { getRandomValues: originalCrypto.getRandomValues.bind(originalCrypto) },
811
- configurable: true,
812
- })
813
- try {
814
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
815
-
816
- act(() => {
817
- result.current.start(threadA, 'input-a')
818
- result.current.start(threadB, 'input-b')
819
- })
820
-
821
- await waitFor(() => {
822
- expect(store.threads.find((t) => t.id === threadA)?.status).toBe('done')
823
- expect(store.threads.find((t) => t.id === threadB)?.status).toBe('done')
824
- })
825
-
826
- const allMessageIds = store.threads.flatMap((t) => t.messages.map((m) => m.id))
827
- expect(allMessageIds.length).toBeGreaterThan(0)
828
- expect(new Set(allMessageIds).size).toBe(allMessageIds.length)
829
- } finally {
830
- Object.defineProperty(globalThis, 'crypto', { value: originalCrypto, configurable: true })
831
- }
832
- })
833
-
834
- test('start() THROWS on a host with no usable crypto at all, rather than silently minting a colliding message id', () => {
835
- const store = createTestThreadsStore()
836
- const threadId = store.create()
837
-
838
- const transport: AgentTransport<AgentPart, string> = {
839
- async *stream() {
840
- yield { id: 'p1', type: 'text', text: 'unreachable' }
841
- },
842
- }
843
-
844
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
845
-
846
- const originalCrypto = globalThis.crypto
847
- Object.defineProperty(globalThis, 'crypto', { value: undefined, configurable: true })
848
- try {
849
- expect(() => {
850
- act(() => {
851
- result.current.start(threadId, 'hi')
852
- })
853
- }).toThrow(/idempotency key/)
854
- } finally {
855
- Object.defineProperty(globalThis, 'crypto', { value: originalCrypto, configurable: true })
856
- }
857
-
858
- // The deliberate divergence documented on mintMessageId: throwing means the write never
859
- // happened at all (no half-appended message, no orphaned run entry) rather than silently
860
- // dropping content behind a colliding id.
861
- expect(store.threads.find((t) => t.id === threadId)?.messages).toHaveLength(0)
862
- expect(result.current.runs.has(threadId)).toBe(false)
863
- })
864
- })
865
-
866
- // A ThreadsStore double that delegates everything to a real test store EXCEPT appendMessage,
867
- // which throws for messages matching `shouldThrow` — lets a test simulate consumer-adapter code
868
- // (appendMessage) throwing without touching the shared createTestThreadsStore factory used by
869
- // every other test in this file.
870
- function wrapStoreWithThrowingAppend(
871
- store: ThreadsStore<AgentPart>,
872
- shouldThrow: (message: AgentThread<AgentPart>['messages'][number]) => boolean,
873
- ): ThreadsStore<AgentPart> {
874
- return {
875
- get threads() {
876
- return store.threads
877
- },
878
- get activeId() {
879
- return store.activeId
880
- },
881
- select: store.select,
882
- create: store.create,
883
- appendMessage(id, message) {
884
- if (shouldThrow(message)) {
885
- throw new Error('consumer appendMessage boom')
886
- }
887
- store.appendMessage(id, message)
888
- },
889
- setOutcome: store.setOutcome,
890
- setStatus: store.setStatus,
891
- setResumeToken: store.setResumeToken,
892
- markRead: store.markRead,
893
- remove: store.remove,
894
- clear: store.clear,
895
- hydrated: store.hydrated,
896
- error: store.error,
897
- }
898
- }
899
-
900
- /** As above, but for the settle half of `finalizeStop` — a consumer store whose `setStatus` throws
901
- * (a remote-backed adapter rejecting a thread removed mid-stream is the realistic shape). Narrowed
902
- * by `shouldThrow` because the hook ALSO calls `setStatus` from its mount-time orphan sweep, which
903
- * is a different call site with a different (non-wedge) failure mode. */
904
- function wrapStoreWithThrowingSetStatus(
905
- store: ThreadsStore<AgentPart>,
906
- shouldThrow: (status: AgentThread<AgentPart>['status']) => boolean,
907
- ): ThreadsStore<AgentPart> {
908
- return {
909
- get threads() {
910
- return store.threads
911
- },
912
- get activeId() {
913
- return store.activeId
914
- },
915
- select: store.select,
916
- create: store.create,
917
- appendMessage: store.appendMessage,
918
- setOutcome: store.setOutcome,
919
- setStatus(id, status) {
920
- if (shouldThrow(status)) throw new Error('consumer setStatus boom')
921
- store.setStatus(id, status)
922
- },
923
- setResumeToken: store.setResumeToken,
924
- markRead: store.markRead,
925
- remove: store.remove,
926
- clear: store.clear,
927
- hydrated: store.hydrated,
928
- error: store.error,
929
- }
930
- }
931
-
932
- describe('useAgentThreadRuns — finalizeStop must reach a terminal state no matter what', () => {
933
- test('stop() on a host with no usable crypto still reaches a terminal status and clears the run entry (was: wedged at "streaming" forever)', async () => {
934
- const store = createTestThreadsStore()
935
- const threadId = store.create()
936
-
937
- const gate = deferred<void>()
938
- const transport: AgentTransport<AgentPart, string> = {
939
- async *stream() {
940
- yield { id: 'p1', type: 'text', text: 'partial' }
941
- await gate.promise
942
- yield { id: 'p2', type: 'text', text: 'never arrives' }
943
- },
944
- }
945
-
946
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
947
-
948
- act(() => {
949
- result.current.start(threadId, 'hi')
950
- })
951
-
952
- await waitFor(() => {
953
- expect(result.current.runs.get(threadId)?.parts).toHaveLength(1)
954
- })
955
-
956
- const originalCrypto = globalThis.crypto
957
- Object.defineProperty(globalThis, 'crypto', { value: undefined, configurable: true })
958
- try {
959
- act(() => {
960
- result.current.stop(threadId)
961
- })
962
-
963
- const thread = store.threads.find((t) => t.id === threadId)
964
- // Before the fix: mintMessageId's throw escaped finalizeStop entirely, so setStatus/setRuns
965
- // never ran and this stayed 'streaming' forever.
966
- expect(thread?.status).toBe('error')
967
- // The stopped message never minted an id, so appendMessage was never even attempted —
968
- // only the original user message is present.
969
- expect(thread?.messages).toHaveLength(1)
970
- expect(result.current.runs.has(threadId)).toBe(false)
971
-
972
- // A second stop() must not be the user's only recourse — and it isn't: controllersRef
973
- // already had this threadId deleted before finalizeStop ever ran, so this is an ordinary
974
- // no-op, not a retry of the failed append.
975
- act(() => {
976
- result.current.stop(threadId)
977
- })
978
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('error')
979
- } finally {
980
- Object.defineProperty(globalThis, 'crypto', { value: originalCrypto, configurable: true })
981
- }
982
-
983
- gate.resolve(undefined)
984
- })
985
-
986
- test("stop() reaches a terminal status and clears the run entry even when the consumer's appendMessage throws (was: wedged at 'streaming' forever)", async () => {
987
- const baseStore = createTestThreadsStore()
988
- const threadId = baseStore.create()
989
- const store = wrapStoreWithThrowingAppend(
990
- baseStore,
991
- (message) => message.role === 'assistant' && message.finish === 'stopped',
992
- )
993
-
994
- const gate = deferred<void>()
995
- const transport: AgentTransport<AgentPart, string> = {
996
- async *stream() {
997
- yield { id: 'p1', type: 'text', text: 'partial' }
998
- await gate.promise
999
- yield { id: 'p2', type: 'text', text: 'never arrives' }
1000
- },
1001
- }
1002
-
1003
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
1004
-
1005
- act(() => {
1006
- result.current.start(threadId, 'hi')
1007
- })
1008
-
1009
- await waitFor(() => {
1010
- expect(result.current.runs.get(threadId)?.parts).toHaveLength(1)
1011
- })
1012
-
1013
- act(() => {
1014
- result.current.stop(threadId)
1015
- })
1016
-
1017
- const thread = store.threads.find((t) => t.id === threadId)
1018
- expect(thread?.status).toBe('error')
1019
- // Only the user message — the consumer's appendMessage rejected the stopped message.
1020
- expect(thread?.messages).toHaveLength(1)
1021
- expect(result.current.runs.has(threadId)).toBe(false)
1022
-
1023
- // A second stop() remains a true no-op — not the user's only recourse.
1024
- act(() => {
1025
- result.current.stop(threadId)
1026
- })
1027
- expect(store.threads.find((t) => t.id === threadId)?.status).toBe('error')
1028
-
1029
- gate.resolve(undefined)
1030
- })
1031
-
1032
- test("stop() clears the hook's own run entry even when the consumer's setStatus throws (the second half of the same wedge)", async () => {
1033
- const baseStore = createTestThreadsStore()
1034
- const threadId = baseStore.create()
1035
- const store = wrapStoreWithThrowingSetStatus(baseStore, (status) => status === 'done')
1036
-
1037
- const gate = deferred<void>()
1038
- const transport: AgentTransport<AgentPart, string> = {
1039
- async *stream() {
1040
- yield { id: 'p1', type: 'text', text: 'partial' }
1041
- await gate.promise
1042
- yield { id: 'p2', type: 'text', text: 'never arrives' }
1043
- },
1044
- }
1045
-
1046
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
1047
-
1048
- act(() => {
1049
- result.current.start(threadId, 'hi')
1050
- })
1051
-
1052
- await waitFor(() => {
1053
- expect(result.current.runs.get(threadId)?.parts).toHaveLength(1)
1054
- })
1055
-
1056
- act(() => {
1057
- result.current.stop(threadId)
1058
- })
1059
-
1060
- // The store's own status is whatever the throwing adapter left it as — unrecoverable from
1061
- // here. What IS this hook's to guarantee is that `runs` no longer reports an in-flight turn:
1062
- // that entry is the hook's own state and nothing later can ever clear it (stop() already
1063
- // deleted the thread from controllersRef), so a throw above the teardown stranded it forever.
1064
- expect(result.current.runs.has(threadId)).toBe(false)
1065
- // The partial content still landed — the append runs before the settle.
1066
- expect(store.threads.find((t) => t.id === threadId)?.messages).toHaveLength(2)
1067
-
1068
- act(() => {
1069
- result.current.stop(threadId)
1070
- })
1071
- expect(result.current.runs.has(threadId)).toBe(false)
1072
-
1073
- gate.resolve(undefined)
1074
- })
1075
-
1076
- test('stop() still succeeds via crypto.getRandomValues when randomUUID is unavailable (rung 2 — the rung that matters in practice)', async () => {
1077
- const store = createTestThreadsStore()
1078
- const threadId = store.create()
1079
-
1080
- const gate = deferred<void>()
1081
- const transport: AgentTransport<AgentPart, string> = {
1082
- async *stream() {
1083
- yield { id: 'p1', type: 'text', text: 'partial' }
1084
- await gate.promise
1085
- yield { id: 'p2', type: 'text', text: 'never arrives' }
1086
- },
1087
- }
1088
-
1089
- const originalCrypto = globalThis.crypto
1090
- Object.defineProperty(globalThis, 'crypto', {
1091
- value: { getRandomValues: originalCrypto.getRandomValues.bind(originalCrypto) },
1092
- configurable: true,
1093
- })
1094
- try {
1095
- const { result } = renderHook(() => useAgentThreadRuns({ transport, store, resolveOutcome }))
1096
-
1097
- act(() => {
1098
- result.current.start(threadId, 'hi')
1099
- })
1100
-
1101
- await waitFor(() => {
1102
- expect(result.current.runs.get(threadId)?.parts).toHaveLength(1)
1103
- })
1104
-
1105
- act(() => {
1106
- result.current.stop(threadId)
1107
- })
1108
-
1109
- const thread = store.threads.find((t) => t.id === threadId)
1110
- expect(thread?.status).toBe('done')
1111
- expect(thread?.messages).toHaveLength(2)
1112
- expect(thread?.messages[1]?.finish).toBe('stopped')
1113
- expect(result.current.runs.has(threadId)).toBe(false)
1114
- } finally {
1115
- Object.defineProperty(globalThis, 'crypto', { value: originalCrypto, configurable: true })
1116
- }
1117
-
1118
- gate.resolve(undefined)
1119
- })
1120
- })