basalt-ui 1.27.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 (1063) hide show
  1. package/AGENTS.md +23 -23
  2. package/CHANGELOG.md +59 -0
  3. package/MIGRATING.md +613 -20
  4. package/README.md +33 -3
  5. package/agent/rules/basalt-batteries.md +25 -10
  6. package/agent/rules/basalt-controls.md +56 -31
  7. package/agent/rules/basalt-mantine.md +17 -6
  8. package/agent/rules/basalt-tokens.md +1 -1
  9. package/configs/oxlint-plugin.js +1056 -28
  10. package/configs/oxlint-plugin.test.ts +1066 -0
  11. package/configs/oxlint.json +7 -0
  12. package/dist/agent/coalesce.d.ts.map +1 -1
  13. package/dist/agent/coalesce.js +2 -1
  14. package/dist/agent/merge.d.ts.map +1 -1
  15. package/dist/agent/merge.js +2 -1
  16. package/dist/agent/part-list.d.ts.map +1 -1
  17. package/dist/agent/part-list.js +2 -0
  18. package/dist/agent/stick-to-bottom.d.ts +3 -6
  19. package/dist/agent/stick-to-bottom.d.ts.map +1 -1
  20. package/dist/agent/stick-to-bottom.js +6 -3
  21. package/dist/agent-chat/composer.d.ts +6 -2
  22. package/dist/agent-chat/composer.d.ts.map +1 -1
  23. package/dist/agent-chat/composer.js +49 -33
  24. package/dist/agent-chat/relative-time.d.ts +0 -9
  25. package/dist/agent-chat/relative-time.d.ts.map +1 -1
  26. package/dist/agent-chat/relative-time.js +2 -1
  27. package/dist/agent-chat/thread-detail-panel.d.ts +3 -2
  28. package/dist/agent-chat/thread-detail-panel.d.ts.map +1 -1
  29. package/dist/agent-chat/thread-detail-panel.js +8 -5
  30. package/dist/agent-chat/thread-feed-row.d.ts +2 -1
  31. package/dist/agent-chat/thread-feed-row.d.ts.map +1 -1
  32. package/dist/agent-chat/thread-feed-row.js +92 -92
  33. package/dist/agent-chat/thread-feed.d.ts +3 -2
  34. package/dist/agent-chat/thread-feed.d.ts.map +1 -1
  35. package/dist/agent-chat/thread-feed.js +8 -3
  36. package/dist/agent-chat/thread-message.d.ts +4 -3
  37. package/dist/agent-chat/thread-message.d.ts.map +1 -1
  38. package/dist/agent-chat/thread-message.js +70 -31
  39. package/dist/agent-chat/thread-outcome-card.d.ts +3 -2
  40. package/dist/agent-chat/thread-outcome-card.d.ts.map +1 -1
  41. package/dist/agent-chat/thread-outcome-card.js +9 -2
  42. package/dist/agent-chat/thread-workspace.d.ts +7 -2
  43. package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
  44. package/dist/agent-chat/thread-workspace.js +62 -29
  45. package/dist/agent-chat/tool-chip.d.ts +3 -2
  46. package/dist/agent-chat/tool-chip.d.ts.map +1 -1
  47. package/dist/agent-chat/tool-chip.js +7 -2
  48. package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
  49. package/dist/charts/hooks/useBandPlot.js +15 -8
  50. package/dist/charts/index.d.ts +8 -7
  51. package/dist/charts/index.d.ts.map +1 -1
  52. package/dist/charts/index.js +45 -5
  53. package/dist/charts/kinds/BandStrip.d.ts +10 -7
  54. package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
  55. package/dist/charts/kinds/BandStrip.js +12 -7
  56. package/dist/charts/kinds/Bars.d.ts +12 -6
  57. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  58. package/dist/charts/kinds/Bars.js +37 -11
  59. package/dist/charts/kinds/Donut.d.ts +6 -5
  60. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  61. package/dist/charts/kinds/Donut.js +19 -2
  62. package/dist/charts/kinds/DualPanel.d.ts +6 -5
  63. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  64. package/dist/charts/kinds/DualPanel.js +36 -8
  65. package/dist/charts/kinds/Heatmap.d.ts +10 -6
  66. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  67. package/dist/charts/kinds/Heatmap.js +58 -31
  68. package/dist/charts/kinds/MirroredBars.d.ts +6 -5
  69. package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
  70. package/dist/charts/kinds/MirroredBars.js +9 -2
  71. package/dist/charts/kinds/MultiLine.d.ts +11 -6
  72. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  73. package/dist/charts/kinds/MultiLine.js +24 -15
  74. package/dist/charts/kinds/StackedArea.d.ts +18 -8
  75. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  76. package/dist/charts/kinds/StackedArea.js +29 -4
  77. package/dist/charts/kinds/ZonedLine.d.ts +12 -8
  78. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  79. package/dist/charts/kinds/ZonedLine.js +33 -23
  80. package/dist/charts/layout/auto-margin.d.ts +65 -8
  81. package/dist/charts/layout/auto-margin.d.ts.map +1 -1
  82. package/dist/charts/layout/auto-margin.js +45 -10
  83. package/dist/charts/layout/log-ticks.d.ts +27 -0
  84. package/dist/charts/layout/log-ticks.d.ts.map +1 -0
  85. package/dist/charts/layout/log-ticks.js +42 -0
  86. package/dist/charts/primitives/Axes.d.ts +11 -2
  87. package/dist/charts/primitives/Axes.d.ts.map +1 -1
  88. package/dist/charts/primitives/Axes.js +15 -5
  89. package/dist/charts/primitives/CartesianChart.d.ts +45 -7
  90. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  91. package/dist/charts/primitives/CartesianChart.js +86 -28
  92. package/dist/charts/primitives/ChartCard.d.ts +27 -2
  93. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  94. package/dist/charts/primitives/ChartCard.js +37 -17
  95. package/dist/charts/primitives/ChartFrame.d.ts +30 -4
  96. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  97. package/dist/charts/primitives/ChartFrame.js +45 -15
  98. package/dist/charts/primitives/ChartLegend.d.ts +13 -2
  99. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  100. package/dist/charts/primitives/ChartLegend.js +33 -13
  101. package/dist/charts/primitives/ChartPending.d.ts +77 -4
  102. package/dist/charts/primitives/ChartPending.d.ts.map +1 -1
  103. package/dist/charts/primitives/ChartPending.js +100 -6
  104. package/dist/charts/primitives/ChartTooltip.d.ts +7 -0
  105. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  106. package/dist/charts/primitives/ChartTooltip.js +18 -5
  107. package/dist/charts/primitives/Crosshair.d.ts +3 -0
  108. package/dist/charts/primitives/Crosshair.d.ts.map +1 -1
  109. package/dist/charts/primitives/Crosshair.js +3 -1
  110. package/dist/charts/primitives/chart-frame-layout.d.ts +95 -0
  111. package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -0
  112. package/dist/charts/primitives/chart-frame-layout.js +89 -0
  113. package/dist/charts/primitives/chart-tier.d.ts +16 -0
  114. package/dist/charts/primitives/chart-tier.d.ts.map +1 -0
  115. package/dist/charts/primitives/chart-tier.js +20 -0
  116. package/dist/charts/series.d.ts +55 -1
  117. package/dist/charts/series.d.ts.map +1 -1
  118. package/dist/charts/series.js +27 -1
  119. package/dist/charts/sparklines/BarSparkline.d.ts +3 -3
  120. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  121. package/dist/charts/sparklines/BarSparkline.js +9 -3
  122. package/dist/charts/sparklines/LineSparkline.d.ts +3 -3
  123. package/dist/charts/sparklines/LineSparkline.d.ts.map +1 -1
  124. package/dist/charts/sparklines/LineSparkline.js +15 -3
  125. package/dist/charts/sparklines/index.d.ts +2 -2
  126. package/dist/charts/sparklines/index.d.ts.map +1 -1
  127. package/dist/charts/utils/format.d.ts +92 -2
  128. package/dist/charts/utils/format.d.ts.map +1 -1
  129. package/dist/charts/utils/format.js +64 -1
  130. package/dist/charts/utils/ticks.d.ts +81 -2
  131. package/dist/charts/utils/ticks.d.ts.map +1 -1
  132. package/dist/charts/utils/ticks.js +54 -4
  133. package/dist/cli/index.d.ts.map +1 -1
  134. package/dist/cli/index.js +2 -2
  135. package/dist/commands/ShortcutsHelp.d.ts +3 -2
  136. package/dist/commands/ShortcutsHelp.d.ts.map +1 -1
  137. package/dist/commands/ShortcutsHelp.js +8 -2
  138. package/dist/commands/define-commands.d.ts.map +1 -1
  139. package/dist/commands/define-commands.js +2 -2
  140. package/dist/commands/define-overlays.d.ts +102 -0
  141. package/dist/commands/define-overlays.d.ts.map +1 -1
  142. package/dist/commands/define-overlays.js +102 -2
  143. package/dist/commands/index.d.ts +10 -1
  144. package/dist/commands/index.d.ts.map +1 -1
  145. package/dist/commands/index.js +3 -1
  146. package/dist/commands/overlays-mount.d.ts +13 -6
  147. package/dist/commands/overlays-mount.d.ts.map +1 -1
  148. package/dist/commands/overlays-mount.js +23 -14
  149. package/dist/common/errors.d.ts +34 -0
  150. package/dist/common/errors.d.ts.map +1 -0
  151. package/dist/common/errors.js +36 -0
  152. package/dist/common/index.d.ts +15 -0
  153. package/dist/common/index.d.ts.map +1 -0
  154. package/dist/common/index.js +29 -0
  155. package/dist/common/props.d.ts +63 -0
  156. package/dist/common/props.d.ts.map +1 -0
  157. package/dist/common/props.js +6 -0
  158. package/dist/common/query-state-like.d.ts +43 -0
  159. package/dist/common/query-state-like.d.ts.map +1 -0
  160. package/dist/common/query-state-like.js +20 -0
  161. package/dist/common/refs.d.ts +29 -0
  162. package/dist/common/refs.d.ts.map +1 -0
  163. package/dist/common/refs.js +29 -0
  164. package/dist/common/scroll-parent.d.ts +23 -0
  165. package/dist/common/scroll-parent.d.ts.map +1 -0
  166. package/dist/common/scroll-parent.js +21 -0
  167. package/dist/common/validate.d.ts +38 -0
  168. package/dist/common/validate.d.ts.map +1 -0
  169. package/dist/common/validate.js +36 -0
  170. package/dist/connectivity/connectivity-indicator.d.ts +3 -1
  171. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  172. package/dist/connectivity/connectivity-indicator.js +3 -1
  173. package/dist/connectivity/connectivity-provider.d.ts.map +1 -1
  174. package/dist/connectivity/connectivity-provider.js +4 -1
  175. package/dist/connectivity/index.d.ts +1 -0
  176. package/dist/connectivity/index.d.ts.map +1 -1
  177. package/dist/connectivity/types.d.ts +2 -2
  178. package/dist/connectivity/types.d.ts.map +1 -1
  179. package/dist/connectivity/use-connectivity.d.ts.map +1 -1
  180. package/dist/content/article-layout.module.css +11 -4
  181. package/dist/content/callout.d.ts +8 -4
  182. package/dist/content/callout.d.ts.map +1 -1
  183. package/dist/content/callout.js +15 -8
  184. package/dist/content/guide.d.ts +5 -4
  185. package/dist/content/guide.d.ts.map +1 -1
  186. package/dist/content/guide.js +13 -4
  187. package/dist/content/heading-anchor.d.ts +24 -2
  188. package/dist/content/heading-anchor.d.ts.map +1 -1
  189. package/dist/content/heading-anchor.js +23 -9
  190. package/dist/content/index.d.ts.map +1 -1
  191. package/dist/content/markdown.d.ts.map +1 -1
  192. package/dist/content/markdown.js +2 -0
  193. package/dist/content/prose.module.css +9 -7
  194. package/dist/content/reading-progress.d.ts +10 -4
  195. package/dist/content/reading-progress.d.ts.map +1 -1
  196. package/dist/content/reading-progress.js +26 -10
  197. package/dist/content/slug.d.ts +4 -0
  198. package/dist/content/slug.d.ts.map +1 -1
  199. package/dist/content/toc.d.ts +11 -0
  200. package/dist/content/toc.d.ts.map +1 -1
  201. package/dist/content/toc.js +23 -10
  202. package/dist/controls/actions.d.ts +49 -6
  203. package/dist/controls/actions.d.ts.map +1 -1
  204. package/dist/controls/actions.js +39 -8
  205. package/dist/controls/compare-filter.d.ts +3 -2
  206. package/dist/controls/compare-filter.d.ts.map +1 -1
  207. package/dist/controls/compare-filter.js +17 -2
  208. package/dist/controls/control-group.d.ts +3 -3
  209. package/dist/controls/control-group.d.ts.map +1 -1
  210. package/dist/controls/control-group.js +9 -2
  211. package/dist/controls/controls.module.css +169 -186
  212. package/dist/controls/controls.module.css.d.ts +9 -11
  213. package/dist/controls/enum-filter.d.ts +3 -2
  214. package/dist/controls/enum-filter.d.ts.map +1 -1
  215. package/dist/controls/enum-filter.js +28 -13
  216. package/dist/controls/filter-context.d.ts +8 -4
  217. package/dist/controls/filter-context.d.ts.map +1 -1
  218. package/dist/controls/filter-pill.d.ts +3 -3
  219. package/dist/controls/filter-pill.d.ts.map +1 -1
  220. package/dist/controls/filter-pill.js +4 -1
  221. package/dist/controls/filter-set.d.ts +3 -2
  222. package/dist/controls/filter-set.d.ts.map +1 -1
  223. package/dist/controls/filter-set.js +66 -41
  224. package/dist/controls/filter-sheet.d.ts +20 -80
  225. package/dist/controls/filter-sheet.d.ts.map +1 -1
  226. package/dist/controls/filter-sheet.js +9 -95
  227. package/dist/controls/index.d.ts +2 -0
  228. package/dist/controls/index.d.ts.map +1 -1
  229. package/dist/controls/index.js +4 -0
  230. package/dist/controls/multi-select-filter.d.ts +18 -2
  231. package/dist/controls/multi-select-filter.d.ts.map +1 -1
  232. package/dist/controls/multi-select-filter.js +122 -20
  233. package/dist/controls/number-filter.d.ts +7 -5
  234. package/dist/controls/number-filter.d.ts.map +1 -1
  235. package/dist/controls/number-filter.js +40 -11
  236. package/dist/controls/panel-row.d.ts +132 -0
  237. package/dist/controls/panel-row.d.ts.map +1 -0
  238. package/dist/controls/panel-row.js +173 -0
  239. package/dist/controls/panel-row.module.css +86 -0
  240. package/dist/controls/panel-row.module.css.d.ts +13 -0
  241. package/dist/controls/range-filter.d.ts +2 -1
  242. package/dist/controls/range-filter.d.ts.map +1 -1
  243. package/dist/controls/range-filter.js +57 -35
  244. package/dist/controls/search-filter.d.ts +3 -2
  245. package/dist/controls/search-filter.d.ts.map +1 -1
  246. package/dist/controls/search-filter.js +25 -10
  247. package/dist/controls/select-filter.d.ts +2 -1
  248. package/dist/controls/select-filter.d.ts.map +1 -1
  249. package/dist/controls/select-filter.js +4 -0
  250. package/dist/controls/slider-control.d.ts +36 -0
  251. package/dist/controls/slider-control.d.ts.map +1 -0
  252. package/dist/controls/slider-control.js +64 -0
  253. package/dist/controls/sync-button.d.ts +3 -2
  254. package/dist/controls/sync-button.d.ts.map +1 -1
  255. package/dist/controls/sync-button.js +16 -3
  256. package/dist/controls/toggle-filter.d.ts +3 -2
  257. package/dist/controls/toggle-filter.d.ts.map +1 -1
  258. package/dist/controls/toggle-filter.js +28 -11
  259. package/dist/controls/view-tabs.d.ts +3 -2
  260. package/dist/controls/view-tabs.d.ts.map +1 -1
  261. package/dist/controls/view-tabs.js +43 -11
  262. package/dist/controls-dates/date-range-picker.d.ts +1 -1
  263. package/dist/controls-dates/date-range-picker.d.ts.map +1 -1
  264. package/dist/controls-dates/date-range-picker.js +6 -1
  265. package/dist/dashboard/delta-badge.d.ts +5 -1
  266. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  267. package/dist/dashboard/empty-state.d.ts +10 -3
  268. package/dist/dashboard/empty-state.d.ts.map +1 -1
  269. package/dist/dashboard/empty-state.js +37 -6
  270. package/dist/dashboard/index.d.ts +6 -4
  271. package/dist/dashboard/index.d.ts.map +1 -1
  272. package/dist/dashboard/index.js +7 -1
  273. package/dist/dashboard/query-state.d.ts +48 -27
  274. package/dist/dashboard/query-state.d.ts.map +1 -1
  275. package/dist/dashboard/query-state.js +111 -57
  276. package/dist/dashboard/settings-section.d.ts +19 -9
  277. package/dist/dashboard/settings-section.d.ts.map +1 -1
  278. package/dist/dashboard/settings-section.js +74 -25
  279. package/dist/dashboard/stat-card.d.ts +44 -38
  280. package/dist/dashboard/stat-card.d.ts.map +1 -1
  281. package/dist/dashboard/stat-card.js +55 -42
  282. package/dist/dashboard/stat-card.module.css +46 -31
  283. package/dist/dashboard/stat-card.module.css.d.ts +1 -0
  284. package/dist/dashboard/stat-group.d.ts +41 -0
  285. package/dist/dashboard/stat-group.d.ts.map +1 -0
  286. package/dist/dashboard/stat-group.js +30 -0
  287. package/dist/dashboard/stat-group.module.css +66 -0
  288. package/dist/dashboard/stat-group.module.css.d.ts +8 -0
  289. package/dist/dashboard/widget-grid.d.ts +27 -0
  290. package/dist/dashboard/widget-grid.d.ts.map +1 -0
  291. package/dist/dashboard/widget-grid.js +41 -0
  292. package/dist/dashboard/widget-grid.module.css +53 -0
  293. package/dist/dashboard/widget-grid.module.css.d.ts +9 -0
  294. package/dist/data/data-table.d.ts +151 -12
  295. package/dist/data/data-table.d.ts.map +1 -1
  296. package/dist/data/data-table.js +384 -138
  297. package/dist/data/data-table.module.css +64 -0
  298. package/dist/data/data-table.module.css.d.ts +11 -0
  299. package/dist/data/index.d.ts +2 -2
  300. package/dist/data/index.d.ts.map +1 -1
  301. package/dist/data/index.js +24 -2
  302. package/dist/data/query-branch.d.ts +28 -0
  303. package/dist/data/query-branch.d.ts.map +1 -0
  304. package/dist/data/query-branch.js +10 -0
  305. package/dist/data/table.d.ts +2 -2
  306. package/dist/data/table.d.ts.map +1 -1
  307. package/dist/data/virtual-list.d.ts +65 -6
  308. package/dist/data/virtual-list.d.ts.map +1 -1
  309. package/dist/data/virtual-list.js +89 -38
  310. package/dist/data/virtual.d.ts +1 -1
  311. package/dist/data/virtual.d.ts.map +1 -1
  312. package/dist/forms/FormErrorSummary.d.ts +4 -3
  313. package/dist/forms/FormErrorSummary.d.ts.map +1 -1
  314. package/dist/forms/FormErrorSummary.js +6 -4
  315. package/dist/forms/create-form.d.ts +2 -2
  316. package/dist/forms/field.d.ts +46 -6
  317. package/dist/forms/field.d.ts.map +1 -1
  318. package/dist/forms/field.js +10 -5
  319. package/dist/forms/form-layout.d.ts +142 -0
  320. package/dist/forms/form-layout.d.ts.map +1 -0
  321. package/dist/forms/form-layout.js +110 -0
  322. package/dist/forms/form-layout.module.css +145 -0
  323. package/dist/forms/form-layout.module.css.d.ts +20 -0
  324. package/dist/forms/form-state.d.ts +42 -0
  325. package/dist/forms/form-state.d.ts.map +1 -0
  326. package/dist/forms/form-state.js +26 -0
  327. package/dist/forms/index.d.ts +22 -6
  328. package/dist/forms/index.d.ts.map +1 -1
  329. package/dist/forms/index.js +19 -3
  330. package/dist/forms/use-field-array.d.ts +60 -0
  331. package/dist/forms/use-field-array.d.ts.map +1 -0
  332. package/dist/forms/use-field-array.js +20 -0
  333. package/dist/forms/use-form-draft.d.ts +25 -10
  334. package/dist/forms/use-form-draft.d.ts.map +1 -1
  335. package/dist/forms/use-form-draft.js +55 -18
  336. package/dist/forms/use-form-submit.d.ts +88 -0
  337. package/dist/forms/use-form-submit.d.ts.map +1 -0
  338. package/dist/forms/use-form-submit.js +63 -0
  339. package/dist/guard/index.d.ts.map +1 -1
  340. package/dist/guard/index.js +26 -6
  341. package/dist/index.d.ts +10 -8
  342. package/dist/index.d.ts.map +1 -1
  343. package/dist/index.js +44 -4
  344. package/dist/nav/types.d.ts +15 -6
  345. package/dist/nav/types.d.ts.map +1 -1
  346. package/dist/notifications/bell.d.ts +3 -2
  347. package/dist/notifications/bell.d.ts.map +1 -1
  348. package/dist/notifications/bell.js +73 -56
  349. package/dist/notifications/center.d.ts +15 -4
  350. package/dist/notifications/center.d.ts.map +1 -1
  351. package/dist/notifications/center.js +5 -2
  352. package/dist/notifications/define-notifications.d.ts +56 -14
  353. package/dist/notifications/define-notifications.d.ts.map +1 -1
  354. package/dist/notifications/define-notifications.js +2 -1
  355. package/dist/notifications/index.d.ts +6 -2
  356. package/dist/notifications/index.d.ts.map +1 -1
  357. package/dist/notifications/index.js +5 -1
  358. package/dist/notifications/mount-guard.d.ts +8 -0
  359. package/dist/notifications/mount-guard.d.ts.map +1 -0
  360. package/dist/notifications/mount-guard.js +19 -0
  361. package/dist/notifications/notify.d.ts +58 -1
  362. package/dist/notifications/notify.d.ts.map +1 -1
  363. package/dist/notifications/notify.js +46 -2
  364. package/dist/notifications/overlay.d.ts.map +1 -1
  365. package/dist/notifications/overlay.js +2 -0
  366. package/dist/notifications/store.d.ts +7 -0
  367. package/dist/notifications/store.d.ts.map +1 -1
  368. package/dist/provider/index.d.ts +37 -5
  369. package/dist/provider/index.d.ts.map +1 -1
  370. package/dist/provider/index.js +66 -28
  371. package/dist/provider/lab-theme.d.ts.map +1 -1
  372. package/dist/provider/lab-theme.js +2 -1
  373. package/dist/query/devtools.d.ts.map +1 -1
  374. package/dist/query/devtools.js +1 -0
  375. package/dist/router-tanstack/field.d.ts +1 -1
  376. package/dist/router-tanstack/field.d.ts.map +1 -1
  377. package/dist/router-tanstack/search-store.d.ts.map +1 -1
  378. package/dist/router-tanstack/search-store.js +1 -1
  379. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  380. package/dist/router-tanstack/use-nav.js +1 -1
  381. package/dist/section/index.d.ts +1 -1
  382. package/dist/section/index.d.ts.map +1 -1
  383. package/dist/section/section.d.ts +36 -2
  384. package/dist/section/section.d.ts.map +1 -1
  385. package/dist/section/section.js +49 -57
  386. package/dist/section/section.module.css +11 -0
  387. package/dist/shell/account-types.d.ts +5 -2
  388. package/dist/shell/account-types.d.ts.map +1 -1
  389. package/dist/shell/app-brand.d.ts +13 -0
  390. package/dist/shell/app-brand.d.ts.map +1 -0
  391. package/dist/shell/app-brand.js +94 -0
  392. package/dist/shell/app-brand.module.css +109 -0
  393. package/dist/shell/app-brand.module.css.d.ts +14 -0
  394. package/dist/shell/app-breadcrumbs.d.ts +4 -2
  395. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  396. package/dist/shell/app-breadcrumbs.js +33 -22
  397. package/dist/shell/app-header.module.css +10 -5
  398. package/dist/shell/app-main.module.css +107 -0
  399. package/dist/shell/app-main.module.css.d.ts +11 -0
  400. package/dist/shell/app-mobile-nav.d.ts +3 -2
  401. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  402. package/dist/shell/app-mobile-nav.js +29 -15
  403. package/dist/shell/app-mobile-nav.module.css +48 -27
  404. package/dist/shell/app-mobile-nav.module.css.d.ts +0 -1
  405. package/dist/shell/app-sidebar-account.d.ts +2 -1
  406. package/dist/shell/app-sidebar-account.d.ts.map +1 -1
  407. package/dist/shell/app-sidebar-account.js +57 -22
  408. package/dist/shell/app-sidebar.d.ts +9 -3
  409. package/dist/shell/app-sidebar.d.ts.map +1 -1
  410. package/dist/shell/app-sidebar.js +55 -109
  411. package/dist/shell/app-sidebar.module.css +53 -96
  412. package/dist/shell/app-sidebar.module.css.d.ts +0 -5
  413. package/dist/shell/index.d.ts +14 -6
  414. package/dist/shell/index.d.ts.map +1 -1
  415. package/dist/shell/index.js +47 -12
  416. package/dist/shell/isomorphic-layout-effect.d.ts +14 -0
  417. package/dist/shell/isomorphic-layout-effect.d.ts.map +1 -0
  418. package/dist/shell/isomorphic-layout-effect.js +5 -0
  419. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  420. package/dist/shell/mobile-nav-model.js +27 -6
  421. package/dist/shell/nav-count-badge.d.ts +5 -3
  422. package/dist/shell/nav-count-badge.d.ts.map +1 -1
  423. package/dist/shell/nav-count-badge.js +3 -1
  424. package/dist/shell/page-aside.d.ts +43 -0
  425. package/dist/shell/page-aside.d.ts.map +1 -0
  426. package/dist/shell/page-aside.js +202 -0
  427. package/dist/shell/page-aside.module.css +174 -0
  428. package/dist/shell/page-aside.module.css.d.ts +14 -0
  429. package/dist/shell/page-bar.d.ts +55 -15
  430. package/dist/shell/page-bar.d.ts.map +1 -1
  431. package/dist/shell/page-bar.js +130 -41
  432. package/dist/shell/page-bar.module.css +107 -13
  433. package/dist/shell/page-bar.module.css.d.ts +4 -1
  434. package/dist/shell/sidebar-search.d.ts +4 -2
  435. package/dist/shell/sidebar-search.d.ts.map +1 -1
  436. package/dist/shell/sidebar-search.js +9 -4
  437. package/dist/shell/sidebar-search.module.css +1 -1
  438. package/dist/state/fields.d.ts +23 -3
  439. package/dist/state/fields.d.ts.map +1 -1
  440. package/dist/state/fields.js +13 -2
  441. package/dist/state/persisted-or-local.d.ts +9 -0
  442. package/dist/state/persisted-or-local.d.ts.map +1 -0
  443. package/dist/state/persisted-or-local.js +24 -0
  444. package/dist/state.d.ts +1 -1
  445. package/dist/state.d.ts.map +1 -1
  446. package/dist/state.js +2 -6
  447. package/dist/surfaces.d.ts +12 -11
  448. package/dist/surfaces.d.ts.map +1 -1
  449. package/dist/surfaces.js +42 -12
  450. package/dist/theme/ctl-theme.d.ts.map +1 -1
  451. package/dist/theme/index.d.ts.map +1 -1
  452. package/dist/theme/index.js +102 -17
  453. package/dist/theme/nav-link.module.css +37 -26
  454. package/dist/theme/segmented-control.module.css +22 -0
  455. package/dist/theme/segmented-control.module.css.d.ts +1 -0
  456. package/dist/theme-lab/boot.d.ts +66 -0
  457. package/dist/theme-lab/boot.d.ts.map +1 -0
  458. package/dist/theme-lab/boot.js +82 -0
  459. package/dist/theme-lab/index.d.ts +4 -43
  460. package/dist/theme-lab/index.d.ts.map +1 -1
  461. package/dist/theme-lab/index.js +9 -69
  462. package/dist/theme-toggle/index.d.ts +3 -2
  463. package/dist/theme-toggle/index.d.ts.map +1 -1
  464. package/dist/theme-toggle/index.js +8 -2
  465. package/dist/tokens/index.d.ts +42 -0
  466. package/dist/tokens/index.d.ts.map +1 -1
  467. package/dist/tokens/index.js +35 -6
  468. package/dist/tokens/palette.d.ts +45 -25
  469. package/dist/tokens/palette.d.ts.map +1 -1
  470. package/dist/tokens/palette.js +31 -14
  471. package/dist/tokens.css +4 -5
  472. package/dist/utils/is-dev.d.ts +3 -0
  473. package/dist/utils/is-dev.d.ts.map +1 -0
  474. package/dist/utils/is-dev.js +6 -0
  475. package/dist/widget-header/delta-badge.d.ts +7 -1
  476. package/dist/widget-header/delta-badge.d.ts.map +1 -1
  477. package/dist/widget-header/delta-badge.js +8 -1
  478. package/dist/widget-header/index.d.ts +2 -2
  479. package/dist/widget-header/index.d.ts.map +1 -1
  480. package/dist/widget-header/index.js +3 -1
  481. package/dist/widget-header/widget-header.d.ts +69 -4
  482. package/dist/widget-header/widget-header.d.ts.map +1 -1
  483. package/dist/widget-header/widget-header.js +42 -27
  484. package/dist/widget-header/widget-header.module.css +72 -7
  485. package/llms.txt +6 -6
  486. package/package.json +49 -25
  487. package/src/agent/coalesce.ts +2 -1
  488. package/src/agent/merge.ts +2 -1
  489. package/src/agent/part-list.tsx +2 -0
  490. package/src/agent/stick-to-bottom.tsx +9 -11
  491. package/src/agent-chat/composer.tsx +124 -103
  492. package/src/agent-chat/relative-time.ts +2 -1
  493. package/src/agent-chat/thread-detail-panel.tsx +9 -5
  494. package/src/agent-chat/thread-feed-row.tsx +17 -9
  495. package/src/agent-chat/thread-feed.tsx +9 -3
  496. package/src/agent-chat/thread-message.tsx +48 -13
  497. package/src/agent-chat/thread-outcome-card.tsx +9 -1
  498. package/src/agent-chat/thread-workspace.tsx +72 -33
  499. package/src/agent-chat/tool-chip.tsx +8 -2
  500. package/src/charts/hooks/useBandPlot.ts +26 -8
  501. package/src/charts/index.ts +39 -4
  502. package/src/charts/kinds/BandStrip.tsx +36 -14
  503. package/src/charts/kinds/Bars.tsx +67 -16
  504. package/src/charts/kinds/Donut.tsx +30 -3
  505. package/src/charts/kinds/DualPanel.tsx +56 -10
  506. package/src/charts/kinds/Heatmap.tsx +90 -33
  507. package/src/charts/kinds/MirroredBars.tsx +21 -3
  508. package/src/charts/kinds/MultiLine.tsx +61 -18
  509. package/src/charts/kinds/StackedArea.tsx +77 -6
  510. package/src/charts/kinds/ZonedLine.tsx +71 -31
  511. package/src/charts/layout/auto-margin.ts +134 -21
  512. package/src/charts/layout/log-ticks.ts +90 -0
  513. package/src/charts/primitives/Axes.tsx +33 -5
  514. package/src/charts/primitives/CartesianChart.tsx +194 -41
  515. package/src/charts/primitives/ChartCard.tsx +66 -25
  516. package/src/charts/primitives/ChartFrame.tsx +107 -26
  517. package/src/charts/primitives/ChartLegend.tsx +43 -13
  518. package/src/charts/primitives/ChartPending.tsx +185 -7
  519. package/src/charts/primitives/ChartTooltip.tsx +37 -5
  520. package/src/charts/primitives/Crosshair.tsx +6 -3
  521. package/src/charts/primitives/chart-frame-layout.ts +212 -0
  522. package/src/charts/primitives/chart-tier.tsx +37 -0
  523. package/src/charts/series.ts +77 -1
  524. package/src/charts/sparklines/BarSparkline.tsx +11 -3
  525. package/src/charts/sparklines/LineSparkline.tsx +18 -4
  526. package/src/charts/sparklines/index.ts +2 -2
  527. package/src/charts/utils/format.ts +163 -2
  528. package/src/charts/utils/ticks.ts +149 -4
  529. package/src/cli/index.ts +8 -3
  530. package/src/commands/ShortcutsHelp.tsx +10 -3
  531. package/src/commands/define-commands.ts +2 -2
  532. package/src/commands/define-overlays.ts +216 -1
  533. package/src/commands/index.ts +12 -0
  534. package/src/commands/overlays-mount.tsx +60 -26
  535. package/src/common/errors.ts +73 -0
  536. package/src/common/index.ts +21 -0
  537. package/src/common/props.ts +71 -0
  538. package/src/common/query-state-like.ts +64 -0
  539. package/src/common/refs.ts +58 -0
  540. package/src/common/scroll-parent.ts +42 -0
  541. package/src/common/validate.ts +94 -0
  542. package/src/connectivity/connectivity-indicator.tsx +6 -1
  543. package/src/connectivity/connectivity-provider.tsx +4 -1
  544. package/src/connectivity/index.ts +1 -0
  545. package/src/connectivity/types.ts +5 -2
  546. package/src/connectivity/use-connectivity.ts +3 -2
  547. package/src/content/article-layout.module.css +11 -4
  548. package/src/content/callout.tsx +13 -7
  549. package/src/content/guide.tsx +14 -4
  550. package/src/content/heading-anchor.tsx +14 -3
  551. package/src/content/index.ts +2 -0
  552. package/src/content/markdown.tsx +2 -0
  553. package/src/content/prose.module.css +9 -7
  554. package/src/content/reading-progress.tsx +40 -15
  555. package/src/content/slug.ts +4 -0
  556. package/src/content/toc.tsx +47 -10
  557. package/src/controls/actions.tsx +89 -9
  558. package/src/controls/compare-filter.tsx +20 -3
  559. package/src/controls/control-group.tsx +11 -4
  560. package/src/controls/controls.module.css +169 -186
  561. package/src/controls/controls.module.css.d.ts +9 -11
  562. package/src/controls/enum-filter.tsx +32 -16
  563. package/src/controls/filter-context.tsx +14 -6
  564. package/src/controls/filter-pill.tsx +6 -3
  565. package/src/controls/filter-set.tsx +32 -13
  566. package/src/controls/filter-sheet.tsx +44 -194
  567. package/src/controls/index.ts +7 -0
  568. package/src/controls/multi-select-filter.tsx +177 -27
  569. package/src/controls/number-filter.tsx +61 -23
  570. package/src/controls/panel-row.module.css +86 -0
  571. package/src/controls/panel-row.module.css.d.ts +13 -0
  572. package/src/controls/panel-row.tsx +392 -0
  573. package/src/controls/range-filter.tsx +76 -39
  574. package/src/controls/search-filter.tsx +30 -12
  575. package/src/controls/select-filter.tsx +11 -1
  576. package/src/controls/slider-control.tsx +159 -0
  577. package/src/controls/sync-button.tsx +13 -3
  578. package/src/controls/toggle-filter.tsx +36 -16
  579. package/src/controls/view-tabs.tsx +63 -11
  580. package/src/controls-dates/date-range-picker.tsx +8 -1
  581. package/src/dashboard/delta-badge.tsx +5 -1
  582. package/src/dashboard/empty-state.tsx +72 -25
  583. package/src/dashboard/index.ts +13 -1
  584. package/src/dashboard/query-state.tsx +154 -84
  585. package/src/dashboard/settings-section.tsx +87 -31
  586. package/src/dashboard/stat-card.module.css +46 -31
  587. package/src/dashboard/stat-card.module.css.d.ts +1 -0
  588. package/src/dashboard/stat-card.tsx +185 -127
  589. package/src/dashboard/stat-group.module.css +66 -0
  590. package/src/dashboard/stat-group.module.css.d.ts +8 -0
  591. package/src/dashboard/stat-group.tsx +74 -0
  592. package/src/dashboard/widget-grid.module.css +53 -0
  593. package/src/dashboard/widget-grid.module.css.d.ts +9 -0
  594. package/src/dashboard/widget-grid.tsx +127 -0
  595. package/src/data/data-table.module.css +64 -0
  596. package/src/data/data-table.module.css.d.ts +11 -0
  597. package/src/data/data-table.tsx +919 -346
  598. package/src/data/index.ts +30 -2
  599. package/src/data/query-branch.ts +53 -0
  600. package/src/data/table.ts +5 -0
  601. package/src/data/virtual-list.tsx +189 -55
  602. package/src/data/virtual.ts +5 -1
  603. package/src/forms/FormErrorSummary.tsx +13 -6
  604. package/src/forms/create-form.ts +2 -2
  605. package/src/forms/field.ts +60 -11
  606. package/src/forms/form-layout.module.css +145 -0
  607. package/src/forms/form-layout.module.css.d.ts +20 -0
  608. package/src/forms/form-layout.tsx +305 -0
  609. package/src/forms/form-state.tsx +94 -0
  610. package/src/forms/index.ts +38 -7
  611. package/src/forms/use-field-array.ts +94 -0
  612. package/src/forms/use-form-draft.ts +145 -53
  613. package/src/forms/use-form-submit.ts +199 -0
  614. package/src/guard/index.ts +73 -18
  615. package/src/index.ts +74 -10
  616. package/src/nav/types.ts +15 -6
  617. package/src/notifications/bell.tsx +18 -4
  618. package/src/notifications/center.tsx +18 -5
  619. package/src/notifications/define-notifications.ts +64 -18
  620. package/src/notifications/index.ts +12 -1
  621. package/src/notifications/mount-guard.ts +41 -0
  622. package/src/notifications/notify.tsx +122 -3
  623. package/src/notifications/overlay.tsx +2 -0
  624. package/src/notifications/store.ts +7 -0
  625. package/src/provider/index.tsx +101 -13
  626. package/src/provider/lab-theme.ts +2 -1
  627. package/src/query/devtools.tsx +1 -0
  628. package/src/router-tanstack/field.ts +1 -0
  629. package/src/router-tanstack/search-store.ts +3 -10
  630. package/src/router-tanstack/use-nav.ts +18 -13
  631. package/src/section/index.ts +1 -1
  632. package/src/section/section.module.css +11 -0
  633. package/src/section/section.tsx +163 -96
  634. package/src/shell/account-types.ts +5 -2
  635. package/src/shell/app-brand.module.css +109 -0
  636. package/src/shell/app-brand.module.css.d.ts +14 -0
  637. package/src/shell/app-brand.tsx +140 -0
  638. package/src/shell/app-breadcrumbs.tsx +19 -9
  639. package/src/shell/app-header.module.css +10 -5
  640. package/src/shell/app-main.module.css +107 -0
  641. package/src/shell/app-main.module.css.d.ts +11 -0
  642. package/src/shell/app-mobile-nav.module.css +48 -27
  643. package/src/shell/app-mobile-nav.module.css.d.ts +0 -1
  644. package/src/shell/app-mobile-nav.tsx +43 -11
  645. package/src/shell/app-sidebar-account.tsx +29 -6
  646. package/src/shell/app-sidebar.module.css +53 -96
  647. package/src/shell/app-sidebar.module.css.d.ts +0 -5
  648. package/src/shell/app-sidebar.tsx +40 -113
  649. package/src/shell/index.tsx +156 -65
  650. package/src/shell/isomorphic-layout-effect.ts +14 -0
  651. package/src/shell/mobile-nav-model.ts +66 -10
  652. package/src/shell/nav-count-badge.tsx +6 -1
  653. package/src/shell/page-aside.module.css +174 -0
  654. package/src/shell/page-aside.module.css.d.ts +14 -0
  655. package/src/shell/page-aside.tsx +377 -0
  656. package/src/shell/page-bar.module.css +107 -13
  657. package/src/shell/page-bar.module.css.d.ts +4 -1
  658. package/src/shell/page-bar.tsx +294 -94
  659. package/src/shell/sidebar-search.module.css +1 -1
  660. package/src/shell/sidebar-search.tsx +13 -4
  661. package/src/state/fields.ts +43 -10
  662. package/src/state/persisted-or-local.ts +50 -0
  663. package/src/state.ts +0 -9
  664. package/src/surfaces.ts +42 -12
  665. package/src/theme/ctl-theme.tsx +10 -0
  666. package/src/theme/index.ts +129 -16
  667. package/src/theme/nav-link.module.css +37 -26
  668. package/src/theme/segmented-control.module.css +22 -0
  669. package/src/theme/segmented-control.module.css.d.ts +1 -0
  670. package/src/theme-lab/boot.ts +140 -0
  671. package/src/theme-lab/index.tsx +21 -112
  672. package/src/theme-toggle/index.tsx +10 -3
  673. package/src/tokens/index.ts +50 -6
  674. package/src/tokens/palette.ts +58 -30
  675. package/src/utils/is-dev.ts +4 -0
  676. package/src/widget-header/delta-badge.tsx +25 -5
  677. package/src/widget-header/index.ts +10 -2
  678. package/src/widget-header/widget-header.module.css +72 -7
  679. package/src/widget-header/widget-header.tsx +150 -71
  680. package/dist/agent/adapter.test.d.ts +0 -2
  681. package/dist/agent/adapter.test.d.ts.map +0 -1
  682. package/dist/agent/ai-sdk-transport.test.d.ts +0 -2
  683. package/dist/agent/ai-sdk-transport.test.d.ts.map +0 -1
  684. package/dist/agent/coalesce.test.d.ts +0 -2
  685. package/dist/agent/coalesce.test.d.ts.map +0 -1
  686. package/dist/agent/foreign.test.d.ts +0 -2
  687. package/dist/agent/foreign.test.d.ts.map +0 -1
  688. package/dist/agent/id.test.d.ts +0 -2
  689. package/dist/agent/id.test.d.ts.map +0 -1
  690. package/dist/agent/merge.test.d.ts +0 -2
  691. package/dist/agent/merge.test.d.ts.map +0 -1
  692. package/dist/agent/part-list.test.d.ts +0 -2
  693. package/dist/agent/part-list.test.d.ts.map +0 -1
  694. package/dist/agent/parts.test.d.ts +0 -2
  695. package/dist/agent/parts.test.d.ts.map +0 -1
  696. package/dist/agent/thread.test.d.ts +0 -2
  697. package/dist/agent/thread.test.d.ts.map +0 -1
  698. package/dist/agent/transport.test.d.ts +0 -2
  699. package/dist/agent/transport.test.d.ts.map +0 -1
  700. package/dist/agent/use-agent-stream.test.d.ts +0 -2
  701. package/dist/agent/use-agent-stream.test.d.ts.map +0 -1
  702. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts +0 -2
  703. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts.map +0 -1
  704. package/dist/agent/use-agent-thread-runs.resume.test.d.ts +0 -2
  705. package/dist/agent/use-agent-thread-runs.resume.test.d.ts.map +0 -1
  706. package/dist/agent/use-agent-thread-runs.test.d.ts +0 -2
  707. package/dist/agent/use-agent-thread-runs.test.d.ts.map +0 -1
  708. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts +0 -2
  709. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts.map +0 -1
  710. package/dist/agent-chat/composer.test.d.ts +0 -2
  711. package/dist/agent-chat/composer.test.d.ts.map +0 -1
  712. package/dist/agent-chat/relative-time.test.d.ts +0 -2
  713. package/dist/agent-chat/relative-time.test.d.ts.map +0 -1
  714. package/dist/agent-chat/thread-feed-row.test.d.ts +0 -2
  715. package/dist/agent-chat/thread-feed-row.test.d.ts.map +0 -1
  716. package/dist/agent-chat/thread-feed.test.d.ts +0 -2
  717. package/dist/agent-chat/thread-feed.test.d.ts.map +0 -1
  718. package/dist/agent-chat/thread-message.test.d.ts +0 -2
  719. package/dist/agent-chat/thread-message.test.d.ts.map +0 -1
  720. package/dist/agent-chat/thread-workspace.test.d.ts +0 -2
  721. package/dist/agent-chat/thread-workspace.test.d.ts.map +0 -1
  722. package/dist/agent-chat/tool-chip.test.d.ts +0 -2
  723. package/dist/agent-chat/tool-chip.test.d.ts.map +0 -1
  724. package/dist/agent-chat/virtualize.type-guard.test.d.ts +0 -2
  725. package/dist/agent-chat/virtualize.type-guard.test.d.ts.map +0 -1
  726. package/dist/charts/cursor/resolve.test.d.ts +0 -2
  727. package/dist/charts/cursor/resolve.test.d.ts.map +0 -1
  728. package/dist/charts/cursor/store.test.d.ts +0 -2
  729. package/dist/charts/cursor/store.test.d.ts.map +0 -1
  730. package/dist/charts/kinds/BandStrip.test.d.ts +0 -2
  731. package/dist/charts/kinds/BandStrip.test.d.ts.map +0 -1
  732. package/dist/charts/kinds/Bars.test.d.ts +0 -2
  733. package/dist/charts/kinds/Bars.test.d.ts.map +0 -1
  734. package/dist/charts/kinds/DualPanel.test.d.ts +0 -2
  735. package/dist/charts/kinds/DualPanel.test.d.ts.map +0 -1
  736. package/dist/charts/kinds/MirroredBars.test.d.ts +0 -2
  737. package/dist/charts/kinds/MirroredBars.test.d.ts.map +0 -1
  738. package/dist/charts/kinds/MultiLine.test.d.ts +0 -2
  739. package/dist/charts/kinds/MultiLine.test.d.ts.map +0 -1
  740. package/dist/charts/kinds/StackedArea.test.d.ts +0 -2
  741. package/dist/charts/kinds/StackedArea.test.d.ts.map +0 -1
  742. package/dist/charts/kinds/ZonedLine.test.d.ts +0 -2
  743. package/dist/charts/kinds/ZonedLine.test.d.ts.map +0 -1
  744. package/dist/charts/layout/auto-margin.test.d.ts +0 -2
  745. package/dist/charts/layout/auto-margin.test.d.ts.map +0 -1
  746. package/dist/charts/primitives/CartesianChart.test.d.ts +0 -2
  747. package/dist/charts/primitives/CartesianChart.test.d.ts.map +0 -1
  748. package/dist/charts/primitives/ChartCard.test.d.ts +0 -2
  749. package/dist/charts/primitives/ChartCard.test.d.ts.map +0 -1
  750. package/dist/charts/primitives/ChartFrame.test.d.ts +0 -2
  751. package/dist/charts/primitives/ChartFrame.test.d.ts.map +0 -1
  752. package/dist/charts/primitives/ChartLegend.test.d.ts +0 -2
  753. package/dist/charts/primitives/ChartLegend.test.d.ts.map +0 -1
  754. package/dist/charts/primitives/ChartTooltip.test.d.ts +0 -2
  755. package/dist/charts/primitives/ChartTooltip.test.d.ts.map +0 -1
  756. package/dist/charts/primitives/XZoneRects.test.d.ts +0 -2
  757. package/dist/charts/primitives/XZoneRects.test.d.ts.map +0 -1
  758. package/dist/charts/series.test.d.ts +0 -2
  759. package/dist/charts/series.test.d.ts.map +0 -1
  760. package/dist/charts/utils/domain.test.d.ts +0 -2
  761. package/dist/charts/utils/domain.test.d.ts.map +0 -1
  762. package/dist/charts/utils/measure-text.test.d.ts +0 -2
  763. package/dist/charts/utils/measure-text.test.d.ts.map +0 -1
  764. package/dist/cli/check-coverage.test.d.ts +0 -2
  765. package/dist/cli/check-coverage.test.d.ts.map +0 -1
  766. package/dist/cli/check-theme.test.d.ts +0 -2
  767. package/dist/cli/check-theme.test.d.ts.map +0 -1
  768. package/dist/cli/help.test.d.ts +0 -2
  769. package/dist/cli/help.test.d.ts.map +0 -1
  770. package/dist/cli/init-sync.test.d.ts +0 -2
  771. package/dist/cli/init-sync.test.d.ts.map +0 -1
  772. package/dist/cli/placement-engine.test.d.ts +0 -2
  773. package/dist/cli/placement-engine.test.d.ts.map +0 -1
  774. package/dist/cli/shipped-versions.test.d.ts +0 -2
  775. package/dist/cli/shipped-versions.test.d.ts.map +0 -1
  776. package/dist/cli/tarball-parity.test.d.ts +0 -2
  777. package/dist/cli/tarball-parity.test.d.ts.map +0 -1
  778. package/dist/cli/template-format.test.d.ts +0 -2
  779. package/dist/cli/template-format.test.d.ts.map +0 -1
  780. package/dist/cli/template-ledger.test.d.ts +0 -2
  781. package/dist/cli/template-ledger.test.d.ts.map +0 -1
  782. package/dist/cli/tokens-css.test.d.ts +0 -2
  783. package/dist/cli/tokens-css.test.d.ts.map +0 -1
  784. package/dist/cli/toolchain-wiring.test.d.ts +0 -2
  785. package/dist/cli/toolchain-wiring.test.d.ts.map +0 -1
  786. package/dist/cli/waiver-audit.test.d.ts +0 -2
  787. package/dist/cli/waiver-audit.test.d.ts.map +0 -1
  788. package/dist/content/article-actions.test.d.ts +0 -2
  789. package/dist/content/article-actions.test.d.ts.map +0 -1
  790. package/dist/content/article-model.test.d.ts +0 -2
  791. package/dist/content/article-model.test.d.ts.map +0 -1
  792. package/dist/content/block-split.test.d.ts +0 -2
  793. package/dist/content/block-split.test.d.ts.map +0 -1
  794. package/dist/content/heading-components.test.d.ts +0 -2
  795. package/dist/content/heading-components.test.d.ts.map +0 -1
  796. package/dist/content/markdown.test.d.ts +0 -2
  797. package/dist/content/markdown.test.d.ts.map +0 -1
  798. package/dist/content/prose.module.css.test.d.ts +0 -2
  799. package/dist/content/prose.module.css.test.d.ts.map +0 -1
  800. package/dist/content/sanitize.test.d.ts +0 -2
  801. package/dist/content/sanitize.test.d.ts.map +0 -1
  802. package/dist/content/slug.test.d.ts +0 -2
  803. package/dist/content/slug.test.d.ts.map +0 -1
  804. package/dist/controls/actions.test.d.ts +0 -2
  805. package/dist/controls/actions.test.d.ts.map +0 -1
  806. package/dist/controls/actions.type-guard.test.d.ts +0 -2
  807. package/dist/controls/actions.type-guard.test.d.ts.map +0 -1
  808. package/dist/controls/control-group.test.d.ts +0 -2
  809. package/dist/controls/control-group.test.d.ts.map +0 -1
  810. package/dist/controls/controls.router.test.d.ts +0 -2
  811. package/dist/controls/controls.router.test.d.ts.map +0 -1
  812. package/dist/controls/filter-set.test.d.ts +0 -2
  813. package/dist/controls/filter-set.test.d.ts.map +0 -1
  814. package/dist/controls/peer-boundary.test.d.ts +0 -2
  815. package/dist/controls/peer-boundary.test.d.ts.map +0 -1
  816. package/dist/controls/props-contract.test.d.ts +0 -2
  817. package/dist/controls/props-contract.test.d.ts.map +0 -1
  818. package/dist/controls/range-filter.type-guard.test.d.ts +0 -2
  819. package/dist/controls/range-filter.type-guard.test.d.ts.map +0 -1
  820. package/dist/controls/sync-button.test.d.ts +0 -2
  821. package/dist/controls/sync-button.test.d.ts.map +0 -1
  822. package/dist/dashboard/query-state.test.d.ts +0 -2
  823. package/dist/dashboard/query-state.test.d.ts.map +0 -1
  824. package/dist/dashboard/settings-section.test.d.ts +0 -2
  825. package/dist/dashboard/settings-section.test.d.ts.map +0 -1
  826. package/dist/dashboard/stat-card.test.d.ts +0 -2
  827. package/dist/dashboard/stat-card.test.d.ts.map +0 -1
  828. package/dist/data/data-table.test.d.ts +0 -2
  829. package/dist/data/data-table.test.d.ts.map +0 -1
  830. package/dist/guard/check-source.test.d.ts +0 -2
  831. package/dist/guard/check-source.test.d.ts.map +0 -1
  832. package/dist/guard/grace.test.d.ts +0 -20
  833. package/dist/guard/grace.test.d.ts.map +0 -1
  834. package/dist/guard/guard-hook.test.d.ts +0 -2
  835. package/dist/guard/guard-hook.test.d.ts.map +0 -1
  836. package/dist/provider/index.test.d.ts +0 -2
  837. package/dist/provider/index.test.d.ts.map +0 -1
  838. package/dist/provider/lab-theme.test.d.ts +0 -2
  839. package/dist/provider/lab-theme.test.d.ts.map +0 -1
  840. package/dist/query/error-message.test.d.ts +0 -2
  841. package/dist/query/error-message.test.d.ts.map +0 -1
  842. package/dist/router-tanstack/multi-search-param-store.test.d.ts +0 -2
  843. package/dist/router-tanstack/multi-search-param-store.test.d.ts.map +0 -1
  844. package/dist/router-tanstack/nav.test.d.ts +0 -2
  845. package/dist/router-tanstack/nav.test.d.ts.map +0 -1
  846. package/dist/router-tanstack/search-param-store.test.d.ts +0 -2
  847. package/dist/router-tanstack/search-param-store.test.d.ts.map +0 -1
  848. package/dist/router-tanstack/search-store.router.test.d.ts +0 -2
  849. package/dist/router-tanstack/search-store.router.test.d.ts.map +0 -1
  850. package/dist/router-tanstack/search-store.test.d.ts +0 -2
  851. package/dist/router-tanstack/search-store.test.d.ts.map +0 -1
  852. package/dist/router-tanstack/use-nav.test.d.ts +0 -2
  853. package/dist/router-tanstack/use-nav.test.d.ts.map +0 -1
  854. package/dist/section/section.test.d.ts +0 -2
  855. package/dist/section/section.test.d.ts.map +0 -1
  856. package/dist/shell/app-mobile-nav.test.d.ts +0 -2
  857. package/dist/shell/app-mobile-nav.test.d.ts.map +0 -1
  858. package/dist/shell/app-sidebar.test.d.ts +0 -2
  859. package/dist/shell/app-sidebar.test.d.ts.map +0 -1
  860. package/dist/shell/index.test.d.ts +0 -2
  861. package/dist/shell/index.test.d.ts.map +0 -1
  862. package/dist/shell/mobile-nav-model.test.d.ts +0 -2
  863. package/dist/shell/mobile-nav-model.test.d.ts.map +0 -1
  864. package/dist/shell/page-bar.test.d.ts +0 -2
  865. package/dist/shell/page-bar.test.d.ts.map +0 -1
  866. package/dist/state/fields.test.d.ts +0 -2
  867. package/dist/state/fields.test.d.ts.map +0 -1
  868. package/dist/state/persisted.test.d.ts +0 -2
  869. package/dist/state/persisted.test.d.ts.map +0 -1
  870. package/dist/styles.floor.test.d.ts +0 -2
  871. package/dist/styles.floor.test.d.ts.map +0 -1
  872. package/dist/theme/border-coverage.test.d.ts +0 -18
  873. package/dist/theme/border-coverage.test.d.ts.map +0 -1
  874. package/dist/theme/contrast.test.d.ts +0 -2
  875. package/dist/theme/contrast.test.d.ts.map +0 -1
  876. package/dist/theme/ctl-theme.test.d.ts +0 -2
  877. package/dist/theme/ctl-theme.test.d.ts.map +0 -1
  878. package/dist/theme/ctl-tier-coverage.test.d.ts +0 -2
  879. package/dist/theme/ctl-tier-coverage.test.d.ts.map +0 -1
  880. package/dist/theme/density-option.test.d.ts +0 -2
  881. package/dist/theme/density-option.test.d.ts.map +0 -1
  882. package/dist/theme/density-relations.test.d.ts +0 -2
  883. package/dist/theme/density-relations.test.d.ts.map +0 -1
  884. package/dist/theme/derive-theme.test.d.ts +0 -2
  885. package/dist/theme/derive-theme.test.d.ts.map +0 -1
  886. package/dist/theme/divider-law.test.d.ts +0 -2
  887. package/dist/theme/divider-law.test.d.ts.map +0 -1
  888. package/dist/theme/fonts-option.test.d.ts +0 -2
  889. package/dist/theme/fonts-option.test.d.ts.map +0 -1
  890. package/dist/theme/icon-slot.test.d.ts +0 -2
  891. package/dist/theme/icon-slot.test.d.ts.map +0 -1
  892. package/dist/theme/layout-rhythm-css.test.d.ts +0 -2
  893. package/dist/theme/layout-rhythm-css.test.d.ts.map +0 -1
  894. package/dist/theme/radius-option.test.d.ts +0 -2
  895. package/dist/theme/radius-option.test.d.ts.map +0 -1
  896. package/dist/theme/radius.test.d.ts +0 -2
  897. package/dist/theme/radius.test.d.ts.map +0 -1
  898. package/dist/theme/shadow-surfaces.test.d.ts +0 -26
  899. package/dist/theme/shadow-surfaces.test.d.ts.map +0 -1
  900. package/dist/theme/spacing.test.d.ts +0 -2
  901. package/dist/theme/spacing.test.d.ts.map +0 -1
  902. package/dist/theme/use-basalt-spacing.test.d.ts +0 -2
  903. package/dist/theme/use-basalt-spacing.test.d.ts.map +0 -1
  904. package/dist/theme-lab/derive-controls.test.d.ts +0 -2
  905. package/dist/theme-lab/derive-controls.test.d.ts.map +0 -1
  906. package/dist/theme-lab/derive-state.test.d.ts +0 -2
  907. package/dist/theme-lab/derive-state.test.d.ts.map +0 -1
  908. package/dist/theme-lab/theme-lab.test.d.ts +0 -2
  909. package/dist/theme-lab/theme-lab.test.d.ts.map +0 -1
  910. package/dist/tokens/build-fonts-css.test.d.ts +0 -2
  911. package/dist/tokens/build-fonts-css.test.d.ts.map +0 -1
  912. package/dist/tokens/build-radius-css.test.d.ts +0 -2
  913. package/dist/tokens/build-radius-css.test.d.ts.map +0 -1
  914. package/dist/tokens/chart-margin.test.d.ts +0 -2
  915. package/dist/tokens/chart-margin.test.d.ts.map +0 -1
  916. package/dist/tokens/density.test.d.ts +0 -2
  917. package/dist/tokens/density.test.d.ts.map +0 -1
  918. package/dist/tokens/derive-radius.test.d.ts +0 -2
  919. package/dist/tokens/derive-radius.test.d.ts.map +0 -1
  920. package/dist/tokens/derive.test.d.ts +0 -2
  921. package/dist/tokens/derive.test.d.ts.map +0 -1
  922. package/dist/tokens/palette.test.d.ts +0 -2
  923. package/dist/tokens/palette.test.d.ts.map +0 -1
  924. package/dist/tokens/text-scale.test.d.ts +0 -2
  925. package/dist/tokens/text-scale.test.d.ts.map +0 -1
  926. package/dist/vite-color-mix.test.d.ts +0 -2
  927. package/dist/vite-color-mix.test.d.ts.map +0 -1
  928. package/dist/vite.test.d.ts +0 -2
  929. package/dist/vite.test.d.ts.map +0 -1
  930. package/dist/widget-header/delta-badge.test.d.ts +0 -2
  931. package/dist/widget-header/delta-badge.test.d.ts.map +0 -1
  932. package/dist/widget-header/no-mantine.test.d.ts +0 -2
  933. package/dist/widget-header/no-mantine.test.d.ts.map +0 -1
  934. package/dist/widget-header/widget-header.test.d.ts +0 -2
  935. package/dist/widget-header/widget-header.test.d.ts.map +0 -1
  936. package/src/agent/adapter.test.ts +0 -1640
  937. package/src/agent/ai-sdk-transport.test.ts +0 -658
  938. package/src/agent/coalesce.test.ts +0 -387
  939. package/src/agent/foreign.test.ts +0 -51
  940. package/src/agent/id.test.ts +0 -169
  941. package/src/agent/merge.test.ts +0 -206
  942. package/src/agent/part-list.test.tsx +0 -112
  943. package/src/agent/parts.test.ts +0 -720
  944. package/src/agent/thread.test.ts +0 -220
  945. package/src/agent/transport.test.ts +0 -70
  946. package/src/agent/use-agent-stream.test.tsx +0 -287
  947. package/src/agent/use-agent-thread-runs.foreign.test.tsx +0 -138
  948. package/src/agent/use-agent-thread-runs.resume.test.tsx +0 -294
  949. package/src/agent/use-agent-thread-runs.test.tsx +0 -1120
  950. package/src/agent/use-agent-thread-runs.wedge.test.tsx +0 -387
  951. package/src/agent-chat/composer.test.tsx +0 -924
  952. package/src/agent-chat/relative-time.test.ts +0 -95
  953. package/src/agent-chat/thread-feed-row.test.tsx +0 -510
  954. package/src/agent-chat/thread-feed.test.tsx +0 -241
  955. package/src/agent-chat/thread-message.test.tsx +0 -1285
  956. package/src/agent-chat/thread-workspace.test.tsx +0 -169
  957. package/src/agent-chat/tool-chip.test.tsx +0 -139
  958. package/src/agent-chat/virtualize.type-guard.test.ts +0 -49
  959. package/src/charts/cursor/resolve.test.ts +0 -151
  960. package/src/charts/cursor/store.test.ts +0 -64
  961. package/src/charts/kinds/BandStrip.test.tsx +0 -282
  962. package/src/charts/kinds/Bars.test.tsx +0 -183
  963. package/src/charts/kinds/DualPanel.test.tsx +0 -527
  964. package/src/charts/kinds/MirroredBars.test.tsx +0 -232
  965. package/src/charts/kinds/MultiLine.test.tsx +0 -218
  966. package/src/charts/kinds/StackedArea.test.tsx +0 -114
  967. package/src/charts/kinds/ZonedLine.test.tsx +0 -147
  968. package/src/charts/layout/auto-margin.test.ts +0 -42
  969. package/src/charts/primitives/CartesianChart.test.tsx +0 -647
  970. package/src/charts/primitives/ChartCard.test.tsx +0 -66
  971. package/src/charts/primitives/ChartFrame.test.tsx +0 -142
  972. package/src/charts/primitives/ChartLegend.test.tsx +0 -78
  973. package/src/charts/primitives/ChartTooltip.test.tsx +0 -44
  974. package/src/charts/primitives/XZoneRects.test.tsx +0 -123
  975. package/src/charts/series.test.ts +0 -112
  976. package/src/charts/utils/domain.test.ts +0 -54
  977. package/src/charts/utils/measure-text.test.ts +0 -36
  978. package/src/cli/check-coverage.test.ts +0 -297
  979. package/src/cli/check-theme.test.ts +0 -837
  980. package/src/cli/help.test.ts +0 -170
  981. package/src/cli/init-sync.test.ts +0 -343
  982. package/src/cli/placement-engine.test.ts +0 -298
  983. package/src/cli/shipped-versions.test.ts +0 -83
  984. package/src/cli/tarball-parity.test.ts +0 -44
  985. package/src/cli/template-format.test.ts +0 -64
  986. package/src/cli/template-ledger.test.ts +0 -219
  987. package/src/cli/tokens-css.test.ts +0 -249
  988. package/src/cli/toolchain-wiring.test.ts +0 -1358
  989. package/src/cli/waiver-audit.test.ts +0 -215
  990. package/src/content/article-actions.test.ts +0 -78
  991. package/src/content/article-model.test.ts +0 -119
  992. package/src/content/block-split.test.ts +0 -75
  993. package/src/content/heading-components.test.tsx +0 -65
  994. package/src/content/markdown.test.tsx +0 -603
  995. package/src/content/prose.module.css.test.ts +0 -39
  996. package/src/content/sanitize.test.ts +0 -659
  997. package/src/content/slug.test.ts +0 -62
  998. package/src/controls/actions.test.tsx +0 -252
  999. package/src/controls/actions.type-guard.test.ts +0 -63
  1000. package/src/controls/control-group.test.tsx +0 -229
  1001. package/src/controls/controls.router.test.tsx +0 -1053
  1002. package/src/controls/filter-set.test.tsx +0 -472
  1003. package/src/controls/peer-boundary.test.ts +0 -85
  1004. package/src/controls/props-contract.test.tsx +0 -137
  1005. package/src/controls/range-filter.type-guard.test.tsx +0 -122
  1006. package/src/controls/sync-button.test.tsx +0 -183
  1007. package/src/dashboard/query-state.test.tsx +0 -152
  1008. package/src/dashboard/settings-section.test.tsx +0 -56
  1009. package/src/dashboard/stat-card.test.tsx +0 -385
  1010. package/src/data/data-table.test.tsx +0 -434
  1011. package/src/guard/check-source.test.ts +0 -3388
  1012. package/src/guard/grace.test.ts +0 -158
  1013. package/src/guard/guard-hook.test.ts +0 -203
  1014. package/src/provider/index.test.tsx +0 -101
  1015. package/src/provider/lab-theme.test.ts +0 -147
  1016. package/src/query/error-message.test.ts +0 -67
  1017. package/src/router-tanstack/multi-search-param-store.test.ts +0 -124
  1018. package/src/router-tanstack/nav.test.ts +0 -111
  1019. package/src/router-tanstack/search-param-store.test.ts +0 -135
  1020. package/src/router-tanstack/search-store.router.test.tsx +0 -882
  1021. package/src/router-tanstack/search-store.test.ts +0 -578
  1022. package/src/router-tanstack/use-nav.test.tsx +0 -187
  1023. package/src/section/section.test.tsx +0 -204
  1024. package/src/shell/app-mobile-nav.test.tsx +0 -403
  1025. package/src/shell/app-sidebar.test.tsx +0 -509
  1026. package/src/shell/index.test.tsx +0 -428
  1027. package/src/shell/mobile-nav-model.test.ts +0 -517
  1028. package/src/shell/page-bar.test.tsx +0 -316
  1029. package/src/state/fields.test.tsx +0 -512
  1030. package/src/state/persisted.test.ts +0 -91
  1031. package/src/styles.floor.test.ts +0 -36
  1032. package/src/theme/border-coverage.test.ts +0 -272
  1033. package/src/theme/contrast.test.ts +0 -354
  1034. package/src/theme/ctl-theme.test.tsx +0 -275
  1035. package/src/theme/ctl-tier-coverage.test.ts +0 -170
  1036. package/src/theme/density-option.test.ts +0 -96
  1037. package/src/theme/density-relations.test.ts +0 -506
  1038. package/src/theme/derive-theme.test.ts +0 -101
  1039. package/src/theme/divider-law.test.ts +0 -259
  1040. package/src/theme/fonts-option.test.ts +0 -75
  1041. package/src/theme/icon-slot.test.tsx +0 -275
  1042. package/src/theme/layout-rhythm-css.test.ts +0 -72
  1043. package/src/theme/radius-option.test.ts +0 -86
  1044. package/src/theme/radius.test.ts +0 -142
  1045. package/src/theme/shadow-surfaces.test.ts +0 -488
  1046. package/src/theme/spacing.test.ts +0 -495
  1047. package/src/theme/use-basalt-spacing.test.tsx +0 -55
  1048. package/src/theme-lab/derive-controls.test.ts +0 -94
  1049. package/src/theme-lab/derive-state.test.ts +0 -71
  1050. package/src/theme-lab/theme-lab.test.ts +0 -46
  1051. package/src/tokens/build-fonts-css.test.ts +0 -52
  1052. package/src/tokens/build-radius-css.test.ts +0 -25
  1053. package/src/tokens/chart-margin.test.ts +0 -39
  1054. package/src/tokens/density.test.ts +0 -259
  1055. package/src/tokens/derive-radius.test.ts +0 -62
  1056. package/src/tokens/derive.test.ts +0 -30
  1057. package/src/tokens/palette.test.ts +0 -114
  1058. package/src/tokens/text-scale.test.ts +0 -51
  1059. package/src/vite-color-mix.test.ts +0 -46
  1060. package/src/vite.test.ts +0 -423
  1061. package/src/widget-header/delta-badge.test.tsx +0 -60
  1062. package/src/widget-header/no-mantine.test.ts +0 -33
  1063. package/src/widget-header/widget-header.test.tsx +0 -187
@@ -1,924 +0,0 @@
1
- /**
2
- * Composer — the slot-bearing prop set from AGENT-CHAT-SPEC.md §8: the `{ text, attachments }`
3
- * submit payload, the clear-only-on-success draft contract, the keyboard contract (including the
4
- * IME guard), the streaming/stop gate, and `draftKey` persistence across an unmount.
5
- *
6
- * `draftKey`s are UNIQUE PER TEST on purpose. Composer memoizes one store per key outside React
7
- * and that store's in-memory value — not localStorage — is the snapshot authority, so clearing
8
- * localStorage between tests would NOT reset a store that a previous test already hydrated.
9
- */
10
- import { MantineProvider } from '@mantine/core'
11
- import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
12
- import { createRef } from 'react'
13
- import { afterEach, describe, expect, test } from 'bun:test'
14
- import { Composer } from './composer'
15
- import type { ComposerAttachment, ComposerHandle, ComposerProps, ComposerSubmit } from './composer'
16
-
17
- afterEach(cleanup)
18
-
19
- function renderComposer(props: ComposerProps) {
20
- return render(
21
- <MantineProvider>
22
- <Composer {...props} />
23
- </MantineProvider>,
24
- )
25
- }
26
-
27
- const noop = (): void => {}
28
-
29
- function input(): HTMLTextAreaElement {
30
- return screen.getByRole('textbox') as HTMLTextAreaElement
31
- }
32
-
33
- function type(text: string): void {
34
- fireEvent.change(input(), { target: { value: text } })
35
- }
36
-
37
- /**
38
- * Several tests below capture a callback's argument into a `let ... | null` variable, then read
39
- * it back after a `fireEvent`/`act` call that (opaquely, from TS's point of view) triggers the
40
- * callback synchronously. TS's control-flow analysis can't see through that call boundary, so at
41
- * the read site it narrows the variable back to its `null` initializer instead of the declared
42
- * union — `expect()`'s generic then infers `null` and the `[]`/`[ATTACHMENT]` expectation fails
43
- * to match any overload. Reading through a NON-generic, explicitly-typed function sidesteps the
44
- * over-narrowing: the return type comes from the fixed signature, not re-inferred from the
45
- * (already over-narrowed) argument, the way a generic passthrough would.
46
- */
47
- function widen(value: readonly ComposerAttachment[] | null): readonly ComposerAttachment[] | null {
48
- return value
49
- }
50
-
51
- const ATTACHMENT: ComposerAttachment = {
52
- id: 'a1',
53
- name: 'shot.png',
54
- mediaType: 'image/png',
55
- size: 1024,
56
- url: 'blob:shot',
57
- }
58
-
59
- /**
60
- * Builds a genuine native `Promise` whose prototype chain has been repointed away from THIS
61
- * realm's `Promise.prototype` — the same shape a promise crossing an iframe/worker boundary would
62
- * have. `Object.getOwnPropertyDescriptors(Promise.prototype)` copies `then`, `catch`, `finally`,
63
- * `constructor` and `[Symbol.toStringTag]` onto a fresh object used as the new prototype, so every
64
- * method is the ORIGINAL native implementation (nothing reimplemented) and the value still
65
- * satisfies `Promise<T>` structurally with no cast. Only the prototype IDENTITY changes, which is
66
- * exactly what `instanceof Promise` tests — so it reads `false` for this value while `.then`/
67
- * `.catch` keep working, because those built-ins check the promise's internal slot, not its
68
- * exposed prototype. Descriptors are copied via `Object.getOwnPropertyDescriptors` rather than an
69
- * object-literal `{ then: ... }` so no literal `then` key appears in this file's source — that
70
- * keeps `unicorn/no-thenable` (which flags authoring a `then` property on a plain object) from
71
- * firing on what is actually a reflected copy of a real `Promise.prototype`, not a hand-rolled
72
- * thenable.
73
- */
74
- function crossRealmPromise<T>(executor: ConstructorParameters<typeof Promise<T>>[0]): Promise<T> {
75
- const native = new Promise<T>(executor)
76
- const otherRealmProto: object = Object.create(
77
- Object.prototype,
78
- Object.getOwnPropertyDescriptors(Promise.prototype),
79
- )
80
- Object.setPrototypeOf(native, otherRealmProto)
81
- return native
82
- }
83
-
84
- describe('submit payload + draft lifecycle', () => {
85
- test('submits the trimmed text with an empty attachment list and clears the draft', () => {
86
- const calls: ComposerSubmit[] = []
87
- renderComposer({
88
- onSubmit: (payload) => {
89
- calls.push(payload)
90
- },
91
- })
92
-
93
- type(' hello ')
94
- fireEvent.click(screen.getByLabelText('Send message'))
95
-
96
- expect(calls).toEqual([{ text: 'hello', attachments: [] }])
97
- expect(input().value).toBe('')
98
- })
99
-
100
- test('forwards pending attachments and asks for a reset — an empty caption still sends', () => {
101
- const calls: ComposerSubmit[] = []
102
- let reset: readonly ComposerAttachment[] | null = null
103
- renderComposer({
104
- onSubmit: (payload) => {
105
- calls.push(payload)
106
- },
107
- attachments: [ATTACHMENT],
108
- onAttachmentsChange: (next) => {
109
- reset = next
110
- },
111
- })
112
-
113
- fireEvent.click(screen.getByLabelText('Send message'))
114
-
115
- expect(calls).toEqual([{ text: '', attachments: [ATTACHMENT] }])
116
- expect(widen(reset)).toEqual([])
117
- })
118
-
119
- test('an empty draft with no attachments cannot be submitted', () => {
120
- let submits = 0
121
- renderComposer({
122
- onSubmit: () => {
123
- submits += 1
124
- },
125
- })
126
-
127
- expect((screen.getByLabelText('Send message') as HTMLButtonElement).disabled).toBe(true)
128
- fireEvent.keyDown(input(), { key: 'Enter' })
129
-
130
- expect(submits).toBe(0)
131
- })
132
-
133
- /**
134
- * The "a failed send does not eat what was typed" guarantee, stated precisely: the clear is
135
- * sequenced STRICTLY AFTER `onSubmit` returns and is not in a `finally`, so a parent that throws
136
- * to reject the send never reaches it.
137
- *
138
- * The throw itself is deliberately NOT exercised here. React 19 does not let an error thrown in
139
- * an event handler propagate out of `dispatchEvent` — it hands it to the global `reportError`,
140
- * captured by `react.development.js` at MODULE EVALUATION (react.development.js:714), so it can
141
- * be neither caught at the `fireEvent` call nor intercepted by stubbing `reportError` afterwards,
142
- * and `bun test` fails the file. What IS observable — and is what actually delivers the
143
- * guarantee — is the ordering.
144
- *
145
- * That ordering must be probed through STATE, not the DOM. `input().value` is worthless here:
146
- * the value comes from `useSyncExternalStore`, and React batches the resulting re-render to the
147
- * end of the discrete click event, so the textarea still reads 'keep me' inside the callback
148
- * whether `store.set('')` ran before `onSubmit` or after it — an assertion that cannot fail.
149
- * The store's localStorage write IS synchronous, so with a `draftKey` the persisted entry flips
150
- * the instant the ordering is wrong.
151
- */
152
- test('the persisted draft is intact while onSubmit runs, and clears only once it returns', () => {
153
- const storageKey = 'basalt:composer-draft:clear-after-onsubmit'
154
- let persistedDuringSubmit: string | null = 'not observed'
155
- renderComposer({
156
- onSubmit: () => {
157
- persistedDuringSubmit = window.localStorage.getItem(storageKey)
158
- },
159
- draftKey: 'clear-after-onsubmit',
160
- })
161
-
162
- type('keep me')
163
- fireEvent.click(screen.getByLabelText('Send message'))
164
-
165
- expect(persistedDuringSubmit).toBe(JSON.stringify({ v: 1, value: 'keep me' }))
166
- expect(window.localStorage.getItem(storageKey)).toBeNull()
167
- expect(input().value).toBe('')
168
- })
169
- })
170
-
171
- describe('async onSubmit — optimistic clear, rejection restores', () => {
172
- /**
173
- * A plain `void`-returning handler — the pre-existing contract — must keep working unchanged.
174
- * This is the backward-compatibility guard for widening `onSubmit` to `void | Promise<void>`.
175
- */
176
- test('a void-returning onSubmit still clears the draft as before', () => {
177
- const calls: ComposerSubmit[] = []
178
- renderComposer({
179
- onSubmit: (payload) => {
180
- calls.push(payload)
181
- },
182
- })
183
-
184
- type('plain handler')
185
- fireEvent.click(screen.getByLabelText('Send message'))
186
-
187
- expect(calls).toEqual([{ text: 'plain handler', attachments: [] }])
188
- expect(input().value).toBe('')
189
- })
190
-
191
- test('a resolving async onSubmit leaves the draft and attachments cleared', async () => {
192
- const storageKey = 'basalt:composer-draft:resolves'
193
- let resolveSubmit: (() => void) | null = null
194
- let reset: readonly ComposerAttachment[] | null = null
195
- renderComposer({
196
- onSubmit: () =>
197
- new Promise<void>((resolve) => {
198
- resolveSubmit = resolve
199
- }),
200
- attachments: [ATTACHMENT],
201
- onAttachmentsChange: (next) => {
202
- reset = next
203
- },
204
- draftKey: 'resolves',
205
- })
206
-
207
- type('will succeed')
208
- fireEvent.click(screen.getByLabelText('Send message'))
209
- expect(window.localStorage.getItem(storageKey)).toBeNull()
210
- expect(widen(reset)).toEqual([])
211
-
212
- if (resolveSubmit === null) throw new Error('expected onSubmit to have been called')
213
- await act(async () => {
214
- resolveSubmit?.()
215
- await Promise.resolve()
216
- })
217
-
218
- expect(window.localStorage.getItem(storageKey)).toBeNull()
219
- expect(input().value).toBe('')
220
- })
221
-
222
- test('a synchronous throw still keeps the draft and attachments (unchanged pre-async behaviour)', () => {
223
- const storageKey = 'basalt:composer-draft:sync-throw'
224
- let reset: readonly ComposerAttachment[] | null = null
225
- renderComposer({
226
- onSubmit: () => {
227
- throw new Error('reject synchronously')
228
- },
229
- attachments: [ATTACHMENT],
230
- onAttachmentsChange: (next) => {
231
- reset = next
232
- },
233
- draftKey: 'sync-throw',
234
- })
235
-
236
- type('keep me on throw')
237
- expect(() => fireEvent.click(screen.getByLabelText('Send message'))).toThrow()
238
-
239
- expect(window.localStorage.getItem(storageKey)).toBe(
240
- JSON.stringify({ v: 1, value: 'keep me on throw' }),
241
- )
242
- expect(reset).toBeNull()
243
- })
244
-
245
- test('a rejecting async onSubmit restores the text and the attachments', async () => {
246
- const storageKey = 'basalt:composer-draft:rejects'
247
- let rejectSubmit: ((reason: unknown) => void) | null = null
248
- let lastAttachmentsChange: readonly ComposerAttachment[] | null = null
249
- const { rerender } = renderComposer({
250
- onSubmit: () =>
251
- new Promise<void>((_resolve, reject) => {
252
- rejectSubmit = reject
253
- }),
254
- attachments: [ATTACHMENT],
255
- onAttachmentsChange: (next) => {
256
- lastAttachmentsChange = next
257
- },
258
- draftKey: 'rejects',
259
- })
260
-
261
- type('network will fail')
262
- fireEvent.click(screen.getByLabelText('Send message'))
263
- // Optimistic clear already happened.
264
- expect(window.localStorage.getItem(storageKey)).toBeNull()
265
- expect(widen(lastAttachmentsChange)).toEqual([])
266
-
267
- // The caller applies the optimistic clear it was asked for — a controlled `attachments` prop
268
- // round-trips back through a render, exactly like a real `useState` setter would.
269
- rerender(
270
- <MantineProvider>
271
- <Composer
272
- onSubmit={noop}
273
- attachments={[]}
274
- onAttachmentsChange={(next) => {
275
- lastAttachmentsChange = next
276
- }}
277
- draftKey="rejects"
278
- />
279
- </MantineProvider>,
280
- )
281
-
282
- if (rejectSubmit === null) throw new Error('expected onSubmit to have been called')
283
- await act(async () => {
284
- rejectSubmit?.(new Error('network error'))
285
- await Promise.resolve()
286
- })
287
-
288
- expect(window.localStorage.getItem(storageKey)).toBe(
289
- JSON.stringify({ v: 1, value: 'network will fail' }),
290
- )
291
- expect(widen(lastAttachmentsChange)).toEqual([ATTACHMENT])
292
- })
293
-
294
- /**
295
- * Finding 1 regression: `onSubmit` is typed `void | Promise<void>`, and a bare structural
296
- * thenable does NOT actually satisfy that type (`PromiseLike<void>` is missing `catch`/
297
- * `finally`/`[Symbol.toStringTag]`, which `Promise<void>` requires — TS2322). What DOES satisfy
298
- * it, and DOES still defeat `result instanceof Promise`, is a promise from ANOTHER REALM: a
299
- * genuine native promise whose `instanceof` reads false because `instanceof` compares prototype
300
- * IDENTITY against THIS realm's `Promise`, not structural shape. `crossRealmPromise` builds
301
- * exactly that. Its rejection must still restore the text and the attachments, exactly like a
302
- * same-realm native `Promise` rejecting does above.
303
- */
304
- test('a cross-realm promise rejecting still restores the text and the attachments', async () => {
305
- const storageKey = 'basalt:composer-draft:cross-realm-rejects'
306
- let rejectSubmit: ((reason: unknown) => void) | null = null
307
- const crossRealm = crossRealmPromise<void>((_resolve, reject) => {
308
- rejectSubmit = reject
309
- })
310
- // The whole point of the fixture: prove it is genuinely NOT of this realm's `Promise` before
311
- // relying on anything else about it — otherwise this test would silently degrade into a re-run
312
- // of the native-promise case above and prove nothing about the regression it targets.
313
- expect(crossRealm instanceof Promise).toBe(false)
314
-
315
- let lastAttachmentsChange: readonly ComposerAttachment[] | null = null
316
- const { rerender } = renderComposer({
317
- onSubmit: () => crossRealm,
318
- attachments: [ATTACHMENT],
319
- onAttachmentsChange: (next) => {
320
- lastAttachmentsChange = next
321
- },
322
- draftKey: 'cross-realm-rejects',
323
- })
324
-
325
- type('a cross-realm send')
326
- fireEvent.click(screen.getByLabelText('Send message'))
327
- expect(window.localStorage.getItem(storageKey)).toBeNull()
328
- expect(widen(lastAttachmentsChange)).toEqual([])
329
-
330
- // Same controlled-prop round trip as the native-Promise version of this test above.
331
- rerender(
332
- <MantineProvider>
333
- <Composer
334
- onSubmit={noop}
335
- attachments={[]}
336
- onAttachmentsChange={(next) => {
337
- lastAttachmentsChange = next
338
- }}
339
- draftKey="cross-realm-rejects"
340
- />
341
- </MantineProvider>,
342
- )
343
-
344
- if (rejectSubmit === null) throw new Error('expected onSubmit to have been called')
345
- await act(async () => {
346
- rejectSubmit?.(new Error('cross-realm rejection'))
347
- await Promise.resolve()
348
- })
349
-
350
- expect(window.localStorage.getItem(storageKey)).toBe(
351
- JSON.stringify({ v: 1, value: 'a cross-realm send' }),
352
- )
353
- expect(widen(lastAttachmentsChange)).toEqual([ATTACHMENT])
354
- })
355
-
356
- test('does not restore text the user already replaced by the time the rejection arrives', async () => {
357
- const storageKey = 'basalt:composer-draft:typed-over'
358
- let rejectSubmit: ((reason: unknown) => void) | null = null
359
- renderComposer({
360
- onSubmit: () =>
361
- new Promise<void>((_resolve, reject) => {
362
- rejectSubmit = reject
363
- }),
364
- draftKey: 'typed-over',
365
- })
366
-
367
- type('the failed message')
368
- fireEvent.click(screen.getByLabelText('Send message'))
369
- expect(window.localStorage.getItem(storageKey)).toBeNull()
370
-
371
- // The user starts a fresh, unrelated message before the rejection lands.
372
- type('a brand new thought')
373
-
374
- if (rejectSubmit === null) throw new Error('expected onSubmit to have been called')
375
- await act(async () => {
376
- rejectSubmit?.(new Error('network error'))
377
- await Promise.resolve()
378
- })
379
-
380
- expect(window.localStorage.getItem(storageKey)).toBe(
381
- JSON.stringify({ v: 1, value: 'a brand new thought' }),
382
- )
383
- })
384
-
385
- test('a first submit rejecting after a second succeeded does not clobber the cleared draft', async () => {
386
- const storageKey = 'basalt:composer-draft:superseded'
387
- const rejections: Array<(reason: unknown) => void> = []
388
- let call = 0
389
- renderComposer({
390
- onSubmit: () =>
391
- new Promise<void>((resolve, reject) => {
392
- call += 1
393
- if (call === 1) rejections.push(reject)
394
- else resolve()
395
- }),
396
- draftKey: 'superseded',
397
- })
398
-
399
- type('first message')
400
- fireEvent.click(screen.getByLabelText('Send message'))
401
- expect(window.localStorage.getItem(storageKey)).toBeNull()
402
-
403
- type('second message')
404
- fireEvent.click(screen.getByLabelText('Send message'))
405
- expect(window.localStorage.getItem(storageKey)).toBeNull()
406
-
407
- const rejectFirst = rejections[0]
408
- if (rejectFirst === undefined) throw new Error('expected the first submit to have rejected')
409
- await act(async () => {
410
- rejectFirst(new Error('first send failed after the second already went through'))
411
- await Promise.resolve()
412
- })
413
-
414
- // The second submit's own (successful) clear must win — the first message does not reappear.
415
- expect(window.localStorage.getItem(storageKey)).toBeNull()
416
- expect(input().value).toBe('')
417
- })
418
-
419
- test('does not restore attachments the caller already changed via the controlled prop', async () => {
420
- let rejectSubmit: ((reason: unknown) => void) | null = null
421
- let lastAttachmentsChange: readonly ComposerAttachment[] | null = null
422
- const { rerender } = renderComposer({
423
- onSubmit: () =>
424
- new Promise<void>((_resolve, reject) => {
425
- rejectSubmit = reject
426
- }),
427
- attachments: [ATTACHMENT],
428
- onAttachmentsChange: (next) => {
429
- lastAttachmentsChange = next
430
- },
431
- })
432
-
433
- fireEvent.click(screen.getByLabelText('Send message'))
434
- expect(widen(lastAttachmentsChange)).toEqual([])
435
-
436
- // The caller applies the optimistic clear, then independently attaches something new —
437
- // NOT via a second Composer submit, just its own unrelated state change.
438
- const replacement: ComposerAttachment = { ...ATTACHMENT, id: 'a2', name: 'other.png' }
439
- rerender(
440
- <MantineProvider>
441
- <Composer
442
- onSubmit={noop}
443
- attachments={[replacement]}
444
- onAttachmentsChange={(next) => {
445
- lastAttachmentsChange = next
446
- }}
447
- />
448
- </MantineProvider>,
449
- )
450
-
451
- if (rejectSubmit === null) throw new Error('expected onSubmit to have been called')
452
- await act(async () => {
453
- rejectSubmit?.(new Error('network error'))
454
- await Promise.resolve()
455
- })
456
-
457
- // The replacement attachment must survive — the failed send's original attachment is dropped.
458
- expect(widen(lastAttachmentsChange)).toEqual([])
459
- })
460
-
461
- test('does not restore attachments into a different thread after draftKey changed', async () => {
462
- let rejectSubmit: ((reason: unknown) => void) | null = null
463
- let lastAttachmentsChange: readonly ComposerAttachment[] | null = null
464
- const { rerender } = renderComposer({
465
- onSubmit: () =>
466
- new Promise<void>((_resolve, reject) => {
467
- rejectSubmit = reject
468
- }),
469
- attachments: [ATTACHMENT],
470
- onAttachmentsChange: (next) => {
471
- lastAttachmentsChange = next
472
- },
473
- draftKey: 'thread-a',
474
- })
475
-
476
- fireEvent.click(screen.getByLabelText('Send message'))
477
- expect(widen(lastAttachmentsChange)).toEqual([])
478
-
479
- // The consumer switches this same Composer instance to a different thread before the
480
- // rejection arrives — a fresh, still-empty attachments slot for that other thread.
481
- rerender(
482
- <MantineProvider>
483
- <Composer
484
- onSubmit={noop}
485
- attachments={[]}
486
- onAttachmentsChange={(next) => {
487
- lastAttachmentsChange = next
488
- }}
489
- draftKey="thread-b"
490
- />
491
- </MantineProvider>,
492
- )
493
-
494
- if (rejectSubmit === null) throw new Error('expected onSubmit to have been called')
495
- await act(async () => {
496
- rejectSubmit?.(new Error('network error'))
497
- await Promise.resolve()
498
- })
499
-
500
- // Thread B's attachments slot must stay untouched by thread A's failed send.
501
- expect(widen(lastAttachmentsChange)).toEqual([])
502
- })
503
- })
504
-
505
- describe('keyboard contract', () => {
506
- test('Enter submits', () => {
507
- let submits = 0
508
- renderComposer({
509
- onSubmit: () => {
510
- submits += 1
511
- },
512
- })
513
-
514
- type('go')
515
- fireEvent.keyDown(input(), { key: 'Enter' })
516
-
517
- expect(submits).toBe(1)
518
- expect(input().value).toBe('')
519
- })
520
-
521
- test('Shift+Enter does not submit', () => {
522
- let submits = 0
523
- renderComposer({
524
- onSubmit: () => {
525
- submits += 1
526
- },
527
- })
528
-
529
- type('go')
530
- fireEvent.keyDown(input(), { key: 'Enter', shiftKey: true })
531
-
532
- expect(submits).toBe(0)
533
- expect(input().value).toBe('go')
534
- })
535
-
536
- test('Enter mid-IME-composition does not submit — it commits the candidate', () => {
537
- let submits = 0
538
- renderComposer({
539
- onSubmit: () => {
540
- submits += 1
541
- },
542
- })
543
-
544
- type('にほん')
545
- fireEvent.keyDown(input(), { key: 'Enter', isComposing: true })
546
-
547
- expect(submits).toBe(0)
548
- expect(input().value).toBe('にほん')
549
- })
550
- })
551
-
552
- describe('streaming gate', () => {
553
- test('streaming without onStop disables the input and keeps the send action', () => {
554
- renderComposer({ onSubmit: noop, streaming: true })
555
-
556
- expect(input().disabled).toBe(true)
557
- expect(screen.getByLabelText('Send message')).toBeDefined()
558
- expect(screen.queryByLabelText('Stop generating')).toBeNull()
559
- })
560
-
561
- test('streaming with onStop swaps send for a Stop action', () => {
562
- let stops = 0
563
- renderComposer({
564
- onSubmit: noop,
565
- streaming: true,
566
- onStop: () => {
567
- stops += 1
568
- },
569
- })
570
-
571
- expect(screen.queryByLabelText('Send message')).toBeNull()
572
- fireEvent.click(screen.getByLabelText('Stop generating'))
573
-
574
- expect(stops).toBe(1)
575
- })
576
-
577
- /**
578
- * The combination `allowSubmitWhileStreaming` exists for: the textarea stays live mid-run, so a
579
- * mouse or touch user needs a Send button as much as a keyboard user needs Enter. Stop replacing
580
- * Send here would make the send action keyboard-only.
581
- */
582
- test('allowSubmitWhileStreaming with onStop renders BOTH the stop and the send action', () => {
583
- const calls: ComposerSubmit[] = []
584
- let stops = 0
585
- renderComposer({
586
- onSubmit: (payload) => {
587
- calls.push(payload)
588
- },
589
- streaming: true,
590
- allowSubmitWhileStreaming: true,
591
- onStop: () => {
592
- stops += 1
593
- },
594
- })
595
-
596
- type('interrupt')
597
- fireEvent.click(screen.getByLabelText('Send message'))
598
- expect(calls).toEqual([{ text: 'interrupt', attachments: [] }])
599
-
600
- fireEvent.click(screen.getByLabelText('Stop generating'))
601
- expect(stops).toBe(1)
602
- })
603
-
604
- test('allowSubmitWhileStreaming permits a submit mid-run', () => {
605
- const calls: ComposerSubmit[] = []
606
- renderComposer({
607
- onSubmit: (payload) => {
608
- calls.push(payload)
609
- },
610
- streaming: true,
611
- allowSubmitWhileStreaming: true,
612
- })
613
-
614
- expect(input().disabled).toBe(false)
615
- type('interrupt')
616
- fireEvent.keyDown(input(), { key: 'Enter' })
617
-
618
- expect(calls).toEqual([{ text: 'interrupt', attachments: [] }])
619
- })
620
- })
621
-
622
- describe('draftKey', () => {
623
- test('a draft survives an unmount and lands in the basalt: envelope', () => {
624
- const { unmount } = renderComposer({ onSubmit: noop, draftKey: 'survives-unmount' })
625
- type('half a thought')
626
- unmount()
627
-
628
- expect(window.localStorage.getItem('basalt:composer-draft:survives-unmount')).toBe(
629
- JSON.stringify({ v: 1, value: 'half a thought' }),
630
- )
631
-
632
- renderComposer({ onSubmit: noop, draftKey: 'survives-unmount' })
633
- expect(input().value).toBe('half a thought')
634
- })
635
-
636
- /**
637
- * The REAL page-reload path, and the only test that exercises it. 'a draft survives an unmount'
638
- * above proves much less than it looks: the store lives in a module-scope Map that outlives
639
- * `cleanup()`, so the remount reuses the same already-hydrated object and never touches storage.
640
- * Here the key has no store yet, so the first `get()` has to go through `readPersistedValue` —
641
- * which is what a fresh process does after a reload. A wrong prefix on the read side survives
642
- * every other test in this file and fails only this one.
643
- */
644
- test('hydrates a draft a previous session left in storage', () => {
645
- window.localStorage.setItem(
646
- 'basalt:composer-draft:hydrated-from-storage',
647
- JSON.stringify({ v: 1, value: 'from a previous session' }),
648
- )
649
-
650
- renderComposer({ onSubmit: noop, draftKey: 'hydrated-from-storage' })
651
-
652
- expect(input().value).toBe('from a previous session')
653
- })
654
-
655
- test('a stale-version envelope is ignored rather than hydrated', () => {
656
- window.localStorage.setItem(
657
- 'basalt:composer-draft:stale-envelope',
658
- JSON.stringify({ v: 0, value: 'written by an older shape' }),
659
- )
660
-
661
- renderComposer({ onSubmit: noop, draftKey: 'stale-envelope' })
662
-
663
- expect(input().value).toBe('')
664
- })
665
-
666
- test("switching draftKey swaps to that key's draft and does not leak the old one", () => {
667
- const { rerender } = renderComposer({ onSubmit: noop, draftKey: 'swap-a' })
668
- type('alpha')
669
-
670
- rerender(
671
- <MantineProvider>
672
- <Composer onSubmit={noop} draftKey="swap-b" />
673
- </MantineProvider>,
674
- )
675
- expect(input().value).toBe('')
676
- type('beta')
677
-
678
- rerender(
679
- <MantineProvider>
680
- <Composer onSubmit={noop} draftKey="swap-a" />
681
- </MantineProvider>,
682
- )
683
- expect(input().value).toBe('alpha')
684
- })
685
-
686
- test('a successful submit removes the persisted entry rather than leaving an empty husk', () => {
687
- renderComposer({ onSubmit: noop, draftKey: 'cleared-on-send' })
688
- type('sent')
689
- fireEvent.click(screen.getByLabelText('Send message'))
690
-
691
- expect(window.localStorage.getItem('basalt:composer-draft:cleared-on-send')).toBeNull()
692
- })
693
-
694
- test('two keyless Composers do not share a draft', () => {
695
- render(
696
- <MantineProvider>
697
- <Composer onSubmit={noop} placeholder="first" />
698
- <Composer onSubmit={noop} placeholder="second" />
699
- </MantineProvider>,
700
- )
701
-
702
- const boxes = screen.getAllByRole('textbox') as HTMLTextAreaElement[]
703
- const [first, second] = [boxes[0], boxes[1]]
704
- if (first === undefined || second === undefined) throw new Error('expected two composers')
705
- fireEvent.change(first, { target: { value: 'only mine' } })
706
-
707
- expect(first.value).toBe('only mine')
708
- expect(second.value).toBe('')
709
- })
710
- })
711
-
712
- describe('slots', () => {
713
- test('leftSection and rightSection render inside the input row', () => {
714
- renderComposer({
715
- onSubmit: noop,
716
- leftSection: <div data-testid="left" />,
717
- rightSection: <div data-testid="right" />,
718
- })
719
-
720
- expect(screen.getByTestId('left')).toBeDefined()
721
- expect(screen.getByTestId('right')).toBeDefined()
722
- })
723
-
724
- test('the default hint row is the Enter/Shift+Enter contract', () => {
725
- renderComposer({ onSubmit: noop })
726
-
727
- expect(screen.getByText('to send')).toBeDefined()
728
- })
729
-
730
- test('hint replaces the whole default row', () => {
731
- renderComposer({ onSubmit: noop, hint: <div data-testid="hint" /> })
732
-
733
- expect(screen.getByTestId('hint')).toBeDefined()
734
- expect(screen.queryByText('to send')).toBeNull()
735
- })
736
-
737
- test('hint={null} drops the row entirely', () => {
738
- renderComposer({ onSubmit: noop, hint: null })
739
-
740
- expect(screen.queryByText('to send')).toBeNull()
741
- })
742
- })
743
-
744
- describe('ComposerHandle (ref)', () => {
745
- test('insertText appends at the end when the textarea has never been focused', () => {
746
- const handle = createRef<ComposerHandle>()
747
- renderComposer({ onSubmit: noop, ref: handle })
748
-
749
- type('partial')
750
- act(() => {
751
- handle.current?.insertText(' more')
752
- })
753
-
754
- expect(input().value).toBe('partial more')
755
- })
756
-
757
- test('insertText inserts at the cursor position, not blindly at the end', () => {
758
- const handle = createRef<ComposerHandle>()
759
- renderComposer({ onSubmit: noop, ref: handle })
760
-
761
- type('hello world')
762
- const el = input()
763
- el.focus()
764
- el.setSelectionRange(5, 5) // caret right after "hello"
765
- act(() => {
766
- handle.current?.insertText(' there')
767
- })
768
-
769
- expect(el.value).toBe('hello there world')
770
- })
771
-
772
- test('insertText replaces a selection rather than inserting alongside it', () => {
773
- const handle = createRef<ComposerHandle>()
774
- renderComposer({ onSubmit: noop, ref: handle })
775
-
776
- type('hello brave world')
777
- const el = input()
778
- el.focus()
779
- el.setSelectionRange(6, 11) // "brave"
780
- act(() => {
781
- handle.current?.insertText('bold')
782
- })
783
-
784
- expect(el.value).toBe('hello bold world')
785
- })
786
-
787
- test('setValue replaces the whole draft value', () => {
788
- const handle = createRef<ComposerHandle>()
789
- renderComposer({ onSubmit: noop, ref: handle })
790
-
791
- type('old text')
792
- act(() => {
793
- handle.current?.setValue('brand new')
794
- })
795
-
796
- expect(input().value).toBe('brand new')
797
- })
798
-
799
- test('focus moves focus to the textarea', () => {
800
- const handle = createRef<ComposerHandle>()
801
- renderComposer({ onSubmit: noop, ref: handle })
802
-
803
- act(() => {
804
- handle.current?.focus()
805
- })
806
-
807
- expect(document.activeElement).toBe(input())
808
- })
809
-
810
- test('an inserted value persists under draftKey and survives remount', () => {
811
- const handle = createRef<ComposerHandle>()
812
- const { unmount } = renderComposer({ onSubmit: noop, draftKey: 'ref-persists', ref: handle })
813
-
814
- act(() => {
815
- handle.current?.insertText('via ref')
816
- })
817
- unmount()
818
-
819
- expect(window.localStorage.getItem('basalt:composer-draft:ref-persists')).toBe(
820
- JSON.stringify({ v: 1, value: 'via ref' }),
821
- )
822
-
823
- renderComposer({ onSubmit: noop, draftKey: 'ref-persists' })
824
- expect(input().value).toBe('via ref')
825
- })
826
-
827
- test('insertText works with no draftKey (the per-instance memory store)', () => {
828
- const handle = createRef<ComposerHandle>()
829
- renderComposer({ onSubmit: noop, ref: handle })
830
-
831
- act(() => {
832
- handle.current?.insertText('memory only')
833
- })
834
-
835
- expect(input().value).toBe('memory only')
836
- })
837
-
838
- test('a submit after an imperative insertText carries the inserted text in its payload', () => {
839
- const calls: ComposerSubmit[] = []
840
- const handle = createRef<ComposerHandle>()
841
- renderComposer({
842
- onSubmit: (payload) => {
843
- calls.push(payload)
844
- },
845
- ref: handle,
846
- })
847
-
848
- act(() => {
849
- handle.current?.insertText('from voice')
850
- })
851
- fireEvent.click(screen.getByLabelText('Send message'))
852
-
853
- expect(calls).toEqual([{ text: 'from voice', attachments: [] }])
854
- })
855
-
856
- /**
857
- * Regression for the stale `pendingCaretRef` bug: `insertText`/`setValue` used to arm the ref
858
- * UNCONDITIONALLY, even when the write was a no-op (didn't change the store's value). Because the
859
- * caret-consuming layout effect is keyed on `[value]`, a no-op write never triggers it — the ref
860
- * stayed armed. The NEXT edit that actually changes the value (ordinary typing through
861
- * `onChange`, which never touches this ref itself) then had that stale, unrelated position
862
- * forced onto it instead of wherever the real edit naturally landed.
863
- *
864
- * Both tests below prove the fix by comparison, not by hard-coding a "natural" DOM/React
865
- * selection-restoration value (which this harness cannot predict independently of the bug):
866
- * the SAME real edit is run once with the no-op priming it and once without, and the resulting
867
- * caret must be identical in both — a no-op write must be invisible to what follows.
868
- */
869
- describe('a no-op write does not arm a stale caret for the next real edit', () => {
870
- test('insertText("") at a collapsed caret', () => {
871
- const primed = createRef<ComposerHandle>()
872
- renderComposer({ onSubmit: noop, ref: primed })
873
- type('hellox')
874
- const primedEl = input()
875
- primedEl.focus()
876
- primedEl.setSelectionRange(5, 5)
877
- act(() => {
878
- primed.current?.insertText('') // no-op: content unchanged
879
- })
880
- fireEvent.change(primedEl, { target: { value: 'hellofoox' } })
881
- const primedCaret = primedEl.selectionStart
882
-
883
- cleanup()
884
-
885
- const control = createRef<ComposerHandle>()
886
- renderComposer({ onSubmit: noop, ref: control })
887
- type('hellox')
888
- const controlEl = input()
889
- controlEl.focus()
890
- controlEl.setSelectionRange(5, 5)
891
- // No insertText('') here — this is the same real edit, unprimed.
892
- fireEvent.change(controlEl, { target: { value: 'hellofoox' } })
893
- const controlCaret = controlEl.selectionStart
894
-
895
- expect(primedCaret).toBe(controlCaret)
896
- })
897
-
898
- test('setValue with the value it already has', () => {
899
- const primed = createRef<ComposerHandle>()
900
- renderComposer({ onSubmit: noop, ref: primed })
901
- type('abcdef')
902
- const primedEl = input()
903
- primedEl.focus()
904
- act(() => {
905
- primed.current?.setValue('abcdef') // no-op: already this value
906
- })
907
- fireEvent.change(primedEl, { target: { value: 'abXcdef' } })
908
- const primedCaret = primedEl.selectionStart
909
-
910
- cleanup()
911
-
912
- const control = createRef<ComposerHandle>()
913
- renderComposer({ onSubmit: noop, ref: control })
914
- type('abcdef')
915
- const controlEl = input()
916
- controlEl.focus()
917
- // No setValue('abcdef') here — this is the same real edit, unprimed.
918
- fireEvent.change(controlEl, { target: { value: 'abXcdef' } })
919
- const controlCaret = controlEl.selectionStart
920
-
921
- expect(primedCaret).toBe(controlCaret)
922
- })
923
- })
924
- })