basalt-ui 1.27.0 → 1.29.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 (1245) hide show
  1. package/AGENTS.md +28 -35
  2. package/CHANGELOG.md +72 -0
  3. package/MIGRATING.md +759 -22
  4. package/README.md +56 -69
  5. package/agent/rules/basalt-batteries.md +60 -168
  6. package/agent/rules/basalt-charts.md +56 -75
  7. package/agent/rules/basalt-controls.md +81 -87
  8. package/agent/rules/basalt-mantine.md +64 -98
  9. package/agent/rules/basalt-state.md +42 -60
  10. package/agent/rules/basalt-tokens.md +46 -67
  11. package/agent/skills/basalt-app/SKILL.md +2 -0
  12. package/agent/templates/CLAUDE-block.md.tpl +14 -14
  13. package/configs/oxlint-plugin.js +964 -33
  14. package/configs/oxlint-plugin.test.ts +902 -2
  15. package/configs/oxlint.json +6 -0
  16. package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
  17. package/dist/agent/ai-sdk-transport.js +7 -4
  18. package/dist/agent/coalesce.d.ts.map +1 -1
  19. package/dist/agent/coalesce.js +2 -1
  20. package/dist/agent/merge.d.ts.map +1 -1
  21. package/dist/agent/merge.js +2 -1
  22. package/dist/agent/part-list.d.ts.map +1 -1
  23. package/dist/agent/part-list.js +2 -0
  24. package/dist/agent/stick-to-bottom.d.ts +3 -6
  25. package/dist/agent/stick-to-bottom.d.ts.map +1 -1
  26. package/dist/agent/stick-to-bottom.js +6 -3
  27. package/dist/agent/transport.d.ts +13 -2
  28. package/dist/agent/transport.d.ts.map +1 -1
  29. package/dist/agent/use-agent-thread-runs.d.ts +12 -1
  30. package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
  31. package/dist/agent/use-agent-thread-runs.js +20 -6
  32. package/dist/agent-chat/composer.d.ts +6 -2
  33. package/dist/agent-chat/composer.d.ts.map +1 -1
  34. package/dist/agent-chat/composer.js +49 -33
  35. package/dist/agent-chat/index.d.ts +1 -1
  36. package/dist/agent-chat/index.d.ts.map +1 -1
  37. package/dist/agent-chat/relative-time.d.ts +0 -9
  38. package/dist/agent-chat/relative-time.d.ts.map +1 -1
  39. package/dist/agent-chat/relative-time.js +2 -1
  40. package/dist/agent-chat/thread-detail-panel.d.ts +3 -2
  41. package/dist/agent-chat/thread-detail-panel.d.ts.map +1 -1
  42. package/dist/agent-chat/thread-detail-panel.js +9 -6
  43. package/dist/agent-chat/thread-feed-row.d.ts +32 -6
  44. package/dist/agent-chat/thread-feed-row.d.ts.map +1 -1
  45. package/dist/agent-chat/thread-feed-row.js +61 -26
  46. package/dist/agent-chat/thread-feed.d.ts +13 -2
  47. package/dist/agent-chat/thread-feed.d.ts.map +1 -1
  48. package/dist/agent-chat/thread-feed.js +16 -7
  49. package/dist/agent-chat/thread-message.d.ts +4 -3
  50. package/dist/agent-chat/thread-message.d.ts.map +1 -1
  51. package/dist/agent-chat/thread-message.js +70 -31
  52. package/dist/agent-chat/thread-outcome-card.d.ts +3 -2
  53. package/dist/agent-chat/thread-outcome-card.d.ts.map +1 -1
  54. package/dist/agent-chat/thread-outcome-card.js +9 -2
  55. package/dist/agent-chat/thread-workspace.d.ts +7 -2
  56. package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
  57. package/dist/agent-chat/thread-workspace.js +62 -29
  58. package/dist/agent-chat/tool-chip.d.ts +3 -2
  59. package/dist/agent-chat/tool-chip.d.ts.map +1 -1
  60. package/dist/agent-chat/tool-chip.js +7 -2
  61. package/dist/agent-chat/virtualize.d.ts +39 -0
  62. package/dist/agent-chat/virtualize.d.ts.map +1 -1
  63. package/dist/agent-chat/virtualize.js +15 -0
  64. package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
  65. package/dist/charts/hooks/useBandPlot.js +15 -8
  66. package/dist/charts/index.d.ts +11 -10
  67. package/dist/charts/index.d.ts.map +1 -1
  68. package/dist/charts/index.js +53 -9
  69. package/dist/charts/kinds/BandStrip.d.ts +10 -7
  70. package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
  71. package/dist/charts/kinds/BandStrip.js +12 -7
  72. package/dist/charts/kinds/Bars.d.ts +13 -9
  73. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  74. package/dist/charts/kinds/Bars.js +37 -11
  75. package/dist/charts/kinds/Donut.d.ts +6 -5
  76. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  77. package/dist/charts/kinds/Donut.js +19 -2
  78. package/dist/charts/kinds/DualPanel.d.ts +9 -6
  79. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  80. package/dist/charts/kinds/DualPanel.js +37 -9
  81. package/dist/charts/kinds/Heatmap.d.ts +10 -6
  82. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  83. package/dist/charts/kinds/Heatmap.js +58 -31
  84. package/dist/charts/kinds/MirroredBars.d.ts +6 -5
  85. package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
  86. package/dist/charts/kinds/MirroredBars.js +9 -2
  87. package/dist/charts/kinds/MultiLine.d.ts +11 -6
  88. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  89. package/dist/charts/kinds/MultiLine.js +24 -15
  90. package/dist/charts/kinds/StackedArea.d.ts +18 -8
  91. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  92. package/dist/charts/kinds/StackedArea.js +29 -4
  93. package/dist/charts/kinds/ZonedLine.d.ts +13 -11
  94. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  95. package/dist/charts/kinds/ZonedLine.js +33 -23
  96. package/dist/charts/layout/auto-margin.d.ts +65 -8
  97. package/dist/charts/layout/auto-margin.d.ts.map +1 -1
  98. package/dist/charts/layout/auto-margin.js +45 -10
  99. package/dist/charts/layout/log-ticks.d.ts +27 -0
  100. package/dist/charts/layout/log-ticks.d.ts.map +1 -0
  101. package/dist/charts/layout/log-ticks.js +42 -0
  102. package/dist/charts/primitives/Axes.d.ts +32 -4
  103. package/dist/charts/primitives/Axes.d.ts.map +1 -1
  104. package/dist/charts/primitives/Axes.js +49 -7
  105. package/dist/charts/primitives/CartesianChart.d.ts +45 -7
  106. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  107. package/dist/charts/primitives/CartesianChart.js +86 -28
  108. package/dist/charts/primitives/ChartCard.d.ts +45 -4
  109. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  110. package/dist/charts/primitives/ChartCard.js +70 -21
  111. package/dist/charts/primitives/ChartFrame.d.ts +30 -4
  112. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  113. package/dist/charts/primitives/ChartFrame.js +52 -15
  114. package/dist/charts/primitives/ChartLegend.d.ts +13 -2
  115. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  116. package/dist/charts/primitives/ChartLegend.js +33 -13
  117. package/dist/charts/primitives/ChartPending.d.ts +85 -6
  118. package/dist/charts/primitives/ChartPending.d.ts.map +1 -1
  119. package/dist/charts/primitives/ChartPending.js +100 -6
  120. package/dist/charts/primitives/ChartTooltip.d.ts +7 -0
  121. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  122. package/dist/charts/primitives/ChartTooltip.js +18 -5
  123. package/dist/charts/primitives/Crosshair.d.ts +3 -0
  124. package/dist/charts/primitives/Crosshair.d.ts.map +1 -1
  125. package/dist/charts/primitives/Crosshair.js +3 -1
  126. package/dist/charts/primitives/chart-frame-layout.d.ts +95 -0
  127. package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -0
  128. package/dist/charts/primitives/chart-frame-layout.js +89 -0
  129. package/dist/charts/primitives/chart-tier.d.ts +16 -0
  130. package/dist/charts/primitives/chart-tier.d.ts.map +1 -0
  131. package/dist/charts/primitives/chart-tier.js +20 -0
  132. package/dist/charts/series.d.ts +55 -1
  133. package/dist/charts/series.d.ts.map +1 -1
  134. package/dist/charts/series.js +27 -1
  135. package/dist/charts/sparklines/BarSparkline.d.ts +3 -3
  136. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  137. package/dist/charts/sparklines/BarSparkline.js +9 -3
  138. package/dist/charts/sparklines/LineSparkline.d.ts +3 -3
  139. package/dist/charts/sparklines/LineSparkline.d.ts.map +1 -1
  140. package/dist/charts/sparklines/LineSparkline.js +15 -3
  141. package/dist/charts/sparklines/index.d.ts +2 -2
  142. package/dist/charts/sparklines/index.d.ts.map +1 -1
  143. package/dist/charts/utils/format.d.ts +7 -4
  144. package/dist/charts/utils/format.d.ts.map +1 -1
  145. package/dist/charts/utils/format.js +17 -24
  146. package/dist/charts/utils/ticks.d.ts +81 -2
  147. package/dist/charts/utils/ticks.d.ts.map +1 -1
  148. package/dist/charts/utils/ticks.js +54 -4
  149. package/dist/cli/audit-allows.d.ts +24 -0
  150. package/dist/cli/audit-allows.d.ts.map +1 -0
  151. package/dist/cli/audit-allows.js +240 -0
  152. package/dist/cli/check-theme.d.ts +10 -0
  153. package/dist/cli/check-theme.d.ts.map +1 -0
  154. package/dist/cli/check-theme.js +139 -0
  155. package/dist/cli/doctor.d.ts +47 -0
  156. package/dist/cli/doctor.d.ts.map +1 -0
  157. package/dist/cli/doctor.js +197 -0
  158. package/dist/cli/guard-hook.d.ts +9 -0
  159. package/dist/cli/guard-hook.d.ts.map +1 -0
  160. package/dist/cli/guard-hook.js +93 -0
  161. package/dist/cli/help.d.ts +9 -0
  162. package/dist/cli/help.d.ts.map +1 -0
  163. package/dist/cli/help.js +53 -0
  164. package/dist/cli/index.d.ts +360 -270
  165. package/dist/cli/index.d.ts.map +1 -1
  166. package/dist/cli/index.js +85 -1901
  167. package/dist/cli/init.d.ts +4 -0
  168. package/dist/cli/init.d.ts.map +1 -0
  169. package/dist/cli/init.js +165 -0
  170. package/dist/cli/sync.d.ts +43 -0
  171. package/dist/cli/sync.d.ts.map +1 -0
  172. package/dist/cli/sync.js +206 -0
  173. package/dist/cli/tokens-css.d.ts +51 -0
  174. package/dist/cli/tokens-css.d.ts.map +1 -0
  175. package/dist/cli/tokens-css.js +165 -0
  176. package/dist/commands/ShortcutsHelp.d.ts +3 -2
  177. package/dist/commands/ShortcutsHelp.d.ts.map +1 -1
  178. package/dist/commands/ShortcutsHelp.js +8 -2
  179. package/dist/commands/define-commands.d.ts.map +1 -1
  180. package/dist/commands/define-commands.js +2 -2
  181. package/dist/commands/define-overlays.d.ts +102 -0
  182. package/dist/commands/define-overlays.d.ts.map +1 -1
  183. package/dist/commands/define-overlays.js +102 -2
  184. package/dist/commands/index.d.ts +12 -2
  185. package/dist/commands/index.d.ts.map +1 -1
  186. package/dist/commands/index.js +9 -1
  187. package/dist/commands/overlays-mount.d.ts +16 -11
  188. package/dist/commands/overlays-mount.d.ts.map +1 -1
  189. package/dist/commands/overlays-mount.js +23 -14
  190. package/dist/commands/shell-bridge.d.ts +57 -0
  191. package/dist/commands/shell-bridge.d.ts.map +1 -0
  192. package/dist/commands/shell-bridge.js +32 -0
  193. package/dist/common/errors.d.ts +69 -0
  194. package/dist/common/errors.d.ts.map +1 -0
  195. package/dist/common/errors.js +64 -0
  196. package/dist/common/index.d.ts +17 -0
  197. package/dist/common/index.d.ts.map +1 -0
  198. package/dist/common/index.js +35 -0
  199. package/dist/common/is-dev.d.ts +3 -0
  200. package/dist/common/is-dev.d.ts.map +1 -0
  201. package/dist/common/is-dev.js +6 -0
  202. package/dist/{motion/index.d.ts → common/motion.d.ts} +1 -1
  203. package/dist/common/motion.d.ts.map +1 -0
  204. package/dist/common/props.d.ts +63 -0
  205. package/dist/common/props.d.ts.map +1 -0
  206. package/dist/common/props.js +6 -0
  207. package/dist/common/query-state-like.d.ts +43 -0
  208. package/dist/common/query-state-like.d.ts.map +1 -0
  209. package/dist/common/query-state-like.js +20 -0
  210. package/dist/common/refs.d.ts +29 -0
  211. package/dist/common/refs.d.ts.map +1 -0
  212. package/dist/common/refs.js +29 -0
  213. package/dist/common/scroll-parent.d.ts +23 -0
  214. package/dist/common/scroll-parent.d.ts.map +1 -0
  215. package/dist/common/scroll-parent.js +21 -0
  216. package/dist/common/validate.d.ts +38 -0
  217. package/dist/common/validate.d.ts.map +1 -0
  218. package/dist/common/validate.js +36 -0
  219. package/dist/content/article-layout.module.css +11 -4
  220. package/dist/content/callout.d.ts +8 -4
  221. package/dist/content/callout.d.ts.map +1 -1
  222. package/dist/content/callout.js +15 -8
  223. package/dist/content/guide.d.ts +5 -4
  224. package/dist/content/guide.d.ts.map +1 -1
  225. package/dist/content/guide.js +13 -4
  226. package/dist/content/heading-anchor.d.ts +24 -2
  227. package/dist/content/heading-anchor.d.ts.map +1 -1
  228. package/dist/content/heading-anchor.js +23 -9
  229. package/dist/content/index.d.ts.map +1 -1
  230. package/dist/content/markdown.d.ts +3 -4
  231. package/dist/content/markdown.d.ts.map +1 -1
  232. package/dist/content/markdown.js +3 -4
  233. package/dist/content/prose.module.css +9 -7
  234. package/dist/content/reading-progress.d.ts +10 -4
  235. package/dist/content/reading-progress.d.ts.map +1 -1
  236. package/dist/content/reading-progress.js +26 -10
  237. package/dist/content/slug.d.ts +4 -0
  238. package/dist/content/slug.d.ts.map +1 -1
  239. package/dist/content/toc.d.ts +11 -0
  240. package/dist/content/toc.d.ts.map +1 -1
  241. package/dist/content/toc.js +23 -10
  242. package/dist/controls/actions.d.ts +50 -7
  243. package/dist/controls/actions.d.ts.map +1 -1
  244. package/dist/controls/actions.js +39 -8
  245. package/dist/controls/compare-filter.d.ts +3 -2
  246. package/dist/controls/compare-filter.d.ts.map +1 -1
  247. package/dist/controls/compare-filter.js +17 -2
  248. package/dist/controls/control-group.d.ts +3 -3
  249. package/dist/controls/control-group.d.ts.map +1 -1
  250. package/dist/controls/control-group.js +9 -2
  251. package/dist/controls/controls.module.css +169 -186
  252. package/dist/controls/controls.module.css.d.ts +9 -11
  253. package/dist/controls/enum-filter.d.ts +3 -2
  254. package/dist/controls/enum-filter.d.ts.map +1 -1
  255. package/dist/controls/enum-filter.js +28 -13
  256. package/dist/controls/filter-context.d.ts +8 -4
  257. package/dist/controls/filter-context.d.ts.map +1 -1
  258. package/dist/controls/filter-pill.d.ts +3 -3
  259. package/dist/controls/filter-pill.d.ts.map +1 -1
  260. package/dist/controls/filter-pill.js +4 -1
  261. package/dist/controls/filter-set.d.ts +3 -2
  262. package/dist/controls/filter-set.d.ts.map +1 -1
  263. package/dist/controls/filter-set.js +66 -41
  264. package/dist/controls/filter-sheet.d.ts +20 -80
  265. package/dist/controls/filter-sheet.d.ts.map +1 -1
  266. package/dist/controls/filter-sheet.js +9 -95
  267. package/dist/controls/index.d.ts +2 -0
  268. package/dist/controls/index.d.ts.map +1 -1
  269. package/dist/controls/index.js +4 -0
  270. package/dist/controls/multi-select-filter.d.ts +18 -2
  271. package/dist/controls/multi-select-filter.d.ts.map +1 -1
  272. package/dist/controls/multi-select-filter.js +122 -20
  273. package/dist/controls/number-filter.d.ts +7 -5
  274. package/dist/controls/number-filter.d.ts.map +1 -1
  275. package/dist/controls/number-filter.js +40 -11
  276. package/dist/controls/panel-row.d.ts +132 -0
  277. package/dist/controls/panel-row.d.ts.map +1 -0
  278. package/dist/controls/panel-row.js +173 -0
  279. package/dist/controls/panel-row.module.css +86 -0
  280. package/dist/controls/panel-row.module.css.d.ts +13 -0
  281. package/dist/controls/range-filter.d.ts +2 -1
  282. package/dist/controls/range-filter.d.ts.map +1 -1
  283. package/dist/controls/range-filter.js +57 -35
  284. package/dist/controls/search-filter.d.ts +3 -2
  285. package/dist/controls/search-filter.d.ts.map +1 -1
  286. package/dist/controls/search-filter.js +25 -10
  287. package/dist/controls/select-filter.d.ts +2 -1
  288. package/dist/controls/select-filter.d.ts.map +1 -1
  289. package/dist/controls/select-filter.js +4 -0
  290. package/dist/controls/slider-control.d.ts +36 -0
  291. package/dist/controls/slider-control.d.ts.map +1 -0
  292. package/dist/controls/slider-control.js +64 -0
  293. package/dist/controls/sync-button.d.ts +3 -2
  294. package/dist/controls/sync-button.d.ts.map +1 -1
  295. package/dist/controls/sync-button.js +16 -3
  296. package/dist/controls/toggle-filter.d.ts +3 -2
  297. package/dist/controls/toggle-filter.d.ts.map +1 -1
  298. package/dist/controls/toggle-filter.js +28 -11
  299. package/dist/controls/view-tabs.d.ts +3 -2
  300. package/dist/controls/view-tabs.d.ts.map +1 -1
  301. package/dist/controls/view-tabs.js +43 -11
  302. package/dist/controls-dates/date-range-picker.d.ts +1 -1
  303. package/dist/controls-dates/date-range-picker.d.ts.map +1 -1
  304. package/dist/controls-dates/date-range-picker.js +6 -1
  305. package/dist/dashboard/delta-badge.d.ts +18 -6
  306. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  307. package/dist/dashboard/delta-badge.js +33 -1
  308. package/dist/dashboard/empty-state.d.ts +8 -4
  309. package/dist/dashboard/empty-state.d.ts.map +1 -1
  310. package/dist/dashboard/empty-state.js +36 -6
  311. package/dist/dashboard/index.d.ts +9 -5
  312. package/dist/dashboard/index.d.ts.map +1 -1
  313. package/dist/dashboard/index.js +13 -1
  314. package/dist/dashboard/query-state.d.ts +39 -27
  315. package/dist/dashboard/query-state.d.ts.map +1 -1
  316. package/dist/dashboard/query-state.js +109 -58
  317. package/dist/dashboard/section.d.ts +58 -0
  318. package/dist/dashboard/section.d.ts.map +1 -0
  319. package/dist/dashboard/section.js +118 -0
  320. package/dist/{section → dashboard}/section.module.css +11 -0
  321. package/dist/dashboard/settings-section.d.ts +19 -9
  322. package/dist/dashboard/settings-section.d.ts.map +1 -1
  323. package/dist/dashboard/settings-section.js +75 -26
  324. package/dist/dashboard/stat-card.d.ts +44 -38
  325. package/dist/dashboard/stat-card.d.ts.map +1 -1
  326. package/dist/dashboard/stat-card.js +56 -43
  327. package/dist/dashboard/stat-card.module.css +46 -31
  328. package/dist/dashboard/stat-card.module.css.d.ts +1 -0
  329. package/dist/dashboard/stat-group.d.ts +41 -0
  330. package/dist/dashboard/stat-group.d.ts.map +1 -0
  331. package/dist/dashboard/stat-group.js +30 -0
  332. package/dist/dashboard/stat-group.module.css +66 -0
  333. package/dist/dashboard/stat-group.module.css.d.ts +8 -0
  334. package/dist/dashboard/widget-grid.d.ts +27 -0
  335. package/dist/dashboard/widget-grid.d.ts.map +1 -0
  336. package/dist/dashboard/widget-grid.js +41 -0
  337. package/dist/dashboard/widget-grid.module.css +53 -0
  338. package/dist/dashboard/widget-grid.module.css.d.ts +9 -0
  339. package/dist/dashboard/widget-header.d.ts +132 -0
  340. package/dist/dashboard/widget-header.d.ts.map +1 -0
  341. package/dist/{widget-header → dashboard}/widget-header.js +42 -27
  342. package/dist/{widget-header → dashboard}/widget-header.module.css +72 -7
  343. package/dist/data/data-table.d.ts +151 -12
  344. package/dist/data/data-table.d.ts.map +1 -1
  345. package/dist/data/data-table.js +385 -139
  346. package/dist/data/data-table.module.css +64 -0
  347. package/dist/data/data-table.module.css.d.ts +11 -0
  348. package/dist/data/query-branch.d.ts +28 -0
  349. package/dist/data/query-branch.d.ts.map +1 -0
  350. package/dist/data/query-branch.js +10 -0
  351. package/dist/data/table.d.ts +2 -2
  352. package/dist/data/table.d.ts.map +1 -1
  353. package/dist/data/table.js +2 -16
  354. package/dist/data/virtual-list.d.ts +65 -6
  355. package/dist/data/virtual-list.d.ts.map +1 -1
  356. package/dist/data/virtual-list.js +89 -38
  357. package/dist/data/virtual.d.ts +4 -4
  358. package/dist/data/virtual.d.ts.map +1 -1
  359. package/dist/dev-dock.d.ts +28 -0
  360. package/dist/dev-dock.d.ts.map +1 -0
  361. package/dist/dev-dock.js +85 -0
  362. package/dist/format/date.d.ts +62 -0
  363. package/dist/format/date.d.ts.map +1 -0
  364. package/dist/format/date.js +106 -0
  365. package/dist/format/index.d.ts +39 -0
  366. package/dist/format/index.d.ts.map +1 -0
  367. package/dist/format/index.js +54 -0
  368. package/dist/format/measure.d.ts +15 -0
  369. package/dist/format/measure.d.ts.map +1 -0
  370. package/dist/format/measure.js +16 -0
  371. package/dist/format/number.d.ts +69 -0
  372. package/dist/format/number.d.ts.map +1 -0
  373. package/dist/format/number.js +62 -0
  374. package/dist/format/shared.d.ts +27 -0
  375. package/dist/format/shared.d.ts.map +1 -0
  376. package/dist/format/shared.js +29 -0
  377. package/dist/forms/FormErrorSummary.d.ts +4 -3
  378. package/dist/forms/FormErrorSummary.d.ts.map +1 -1
  379. package/dist/forms/FormErrorSummary.js +6 -4
  380. package/dist/forms/create-form.d.ts +2 -2
  381. package/dist/forms/field.d.ts +33 -9
  382. package/dist/forms/field.d.ts.map +1 -1
  383. package/dist/forms/field.js +7 -6
  384. package/dist/forms/form-layout.d.ts +142 -0
  385. package/dist/forms/form-layout.d.ts.map +1 -0
  386. package/dist/forms/form-layout.js +110 -0
  387. package/dist/forms/form-layout.module.css +145 -0
  388. package/dist/forms/form-layout.module.css.d.ts +20 -0
  389. package/dist/forms/form-state.d.ts +42 -0
  390. package/dist/forms/form-state.d.ts.map +1 -0
  391. package/dist/forms/form-state.js +26 -0
  392. package/dist/forms/index.d.ts +22 -6
  393. package/dist/forms/index.d.ts.map +1 -1
  394. package/dist/forms/index.js +19 -4
  395. package/dist/forms/use-field-array.d.ts +60 -0
  396. package/dist/forms/use-field-array.d.ts.map +1 -0
  397. package/dist/forms/use-field-array.js +20 -0
  398. package/dist/forms/use-form-draft.d.ts +25 -10
  399. package/dist/forms/use-form-draft.d.ts.map +1 -1
  400. package/dist/forms/use-form-draft.js +55 -18
  401. package/dist/forms/use-form-submit.d.ts +88 -0
  402. package/dist/forms/use-form-submit.d.ts.map +1 -0
  403. package/dist/forms/use-form-submit.js +63 -0
  404. package/dist/guard/index.d.ts +2 -9
  405. package/dist/guard/index.d.ts.map +1 -1
  406. package/dist/guard/index.js +38 -28
  407. package/dist/guard/types.d.ts +8 -10
  408. package/dist/guard/types.d.ts.map +1 -1
  409. package/dist/index.d.ts +21 -14
  410. package/dist/index.d.ts.map +1 -1
  411. package/dist/index.js +40 -6
  412. package/dist/notifications/bell.d.ts +3 -2
  413. package/dist/notifications/bell.d.ts.map +1 -1
  414. package/dist/notifications/bell.js +73 -56
  415. package/dist/notifications/center.d.ts +15 -4
  416. package/dist/notifications/center.d.ts.map +1 -1
  417. package/dist/notifications/center.js +5 -2
  418. package/dist/notifications/define-notifications.d.ts +56 -14
  419. package/dist/notifications/define-notifications.d.ts.map +1 -1
  420. package/dist/notifications/define-notifications.js +2 -1
  421. package/dist/notifications/index.d.ts +11 -7
  422. package/dist/notifications/index.d.ts.map +1 -1
  423. package/dist/notifications/index.js +5 -3
  424. package/dist/notifications/mount-guard.d.ts +8 -0
  425. package/dist/notifications/mount-guard.d.ts.map +1 -0
  426. package/dist/notifications/mount-guard.js +19 -0
  427. package/dist/notifications/notify.d.ts +58 -1
  428. package/dist/notifications/notify.d.ts.map +1 -1
  429. package/dist/notifications/notify.js +46 -2
  430. package/dist/notifications/store.d.ts +7 -0
  431. package/dist/notifications/store.d.ts.map +1 -1
  432. package/dist/provider/connectivity-indicator.d.ts +13 -0
  433. package/dist/provider/connectivity-indicator.d.ts.map +1 -0
  434. package/dist/{connectivity → provider}/connectivity-indicator.js +3 -1
  435. package/dist/{connectivity → provider}/connectivity-provider.d.ts +1 -1
  436. package/dist/provider/connectivity-provider.d.ts.map +1 -0
  437. package/dist/{connectivity → provider}/connectivity-provider.js +4 -1
  438. package/dist/{connectivity/types.d.ts → provider/connectivity-types.d.ts} +3 -3
  439. package/dist/provider/connectivity-types.d.ts.map +1 -0
  440. package/dist/provider/error-fallback.module.css +21 -0
  441. package/dist/provider/error-fallback.module.css.d.ts +9 -0
  442. package/dist/provider/index.d.ts +29 -13
  443. package/dist/provider/index.d.ts.map +1 -1
  444. package/dist/provider/index.js +82 -34
  445. package/dist/provider/lab-theme.d.ts.map +1 -1
  446. package/dist/provider/lab-theme.js +2 -1
  447. package/dist/provider/use-connectivity.d.ts +3 -0
  448. package/dist/provider/use-connectivity.d.ts.map +1 -0
  449. package/dist/query-client.d.ts +63 -0
  450. package/dist/query-client.d.ts.map +1 -0
  451. package/dist/query-client.js +33 -0
  452. package/dist/{query/devtools.d.ts → query-devtools.d.ts} +2 -2
  453. package/dist/query-devtools.d.ts.map +1 -0
  454. package/dist/{query/devtools.js → query-devtools.js} +1 -0
  455. package/dist/register.d.ts +17 -3
  456. package/dist/register.d.ts.map +1 -1
  457. package/dist/router-tanstack/index.d.ts +4 -7
  458. package/dist/router-tanstack/index.d.ts.map +1 -1
  459. package/dist/router-tanstack/index.js +1 -9
  460. package/dist/router-tanstack/nav.d.ts +1 -1
  461. package/dist/router-tanstack/nav.d.ts.map +1 -1
  462. package/dist/router-tanstack/search-store.d.ts +3 -6
  463. package/dist/router-tanstack/search-store.d.ts.map +1 -1
  464. package/dist/router-tanstack/search-store.js +1 -1
  465. package/dist/router-tanstack/use-nav.d.ts +1 -1
  466. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  467. package/dist/router-tanstack/use-nav.js +1 -1
  468. package/dist/shell/account-types.d.ts +5 -2
  469. package/dist/shell/account-types.d.ts.map +1 -1
  470. package/dist/shell/app-brand.d.ts +13 -0
  471. package/dist/shell/app-brand.d.ts.map +1 -0
  472. package/dist/shell/app-brand.js +94 -0
  473. package/dist/shell/app-brand.module.css +109 -0
  474. package/dist/shell/app-brand.module.css.d.ts +14 -0
  475. package/dist/shell/app-breadcrumbs.d.ts +5 -3
  476. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  477. package/dist/shell/app-breadcrumbs.js +33 -22
  478. package/dist/shell/app-header.module.css +10 -5
  479. package/dist/shell/app-main.module.css +118 -0
  480. package/dist/shell/app-main.module.css.d.ts +11 -0
  481. package/dist/shell/app-mobile-nav.d.ts +4 -3
  482. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  483. package/dist/shell/app-mobile-nav.js +53 -17
  484. package/dist/shell/app-mobile-nav.module.css +48 -27
  485. package/dist/shell/app-mobile-nav.module.css.d.ts +0 -1
  486. package/dist/shell/app-sidebar-account.d.ts +2 -1
  487. package/dist/shell/app-sidebar-account.d.ts.map +1 -1
  488. package/dist/shell/app-sidebar-account.js +57 -22
  489. package/dist/shell/app-sidebar.d.ts +10 -4
  490. package/dist/shell/app-sidebar.d.ts.map +1 -1
  491. package/dist/shell/app-sidebar.js +78 -103
  492. package/dist/shell/app-sidebar.module.css +63 -96
  493. package/dist/shell/app-sidebar.module.css.d.ts +0 -5
  494. package/dist/shell/index.d.ts +24 -10
  495. package/dist/shell/index.d.ts.map +1 -1
  496. package/dist/shell/index.js +60 -29
  497. package/dist/shell/isomorphic-layout-effect.d.ts +14 -0
  498. package/dist/shell/isomorphic-layout-effect.d.ts.map +1 -0
  499. package/dist/shell/isomorphic-layout-effect.js +5 -0
  500. package/dist/shell/mobile-nav-model.d.ts +1 -1
  501. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  502. package/dist/shell/mobile-nav-model.js +27 -6
  503. package/dist/shell/nav-count-badge.d.ts +5 -3
  504. package/dist/shell/nav-count-badge.d.ts.map +1 -1
  505. package/dist/shell/nav-count-badge.js +3 -1
  506. package/dist/{nav/types.d.ts → shell/nav-types.d.ts} +16 -7
  507. package/dist/shell/nav-types.d.ts.map +1 -0
  508. package/dist/shell/page-aside.d.ts +43 -0
  509. package/dist/shell/page-aside.d.ts.map +1 -0
  510. package/dist/shell/page-aside.js +175 -0
  511. package/dist/shell/page-aside.module.css +174 -0
  512. package/dist/shell/page-aside.module.css.d.ts +14 -0
  513. package/dist/shell/page-bar.d.ts +55 -15
  514. package/dist/shell/page-bar.d.ts.map +1 -1
  515. package/dist/shell/page-bar.js +130 -41
  516. package/dist/shell/page-bar.module.css +107 -13
  517. package/dist/shell/page-bar.module.css.d.ts +4 -1
  518. package/dist/shell/page-title.d.ts +14 -0
  519. package/dist/shell/page-title.d.ts.map +1 -0
  520. package/dist/shell/page-title.js +25 -0
  521. package/dist/shell/page-title.module.css +33 -0
  522. package/dist/shell/page-title.module.css.d.ts +11 -0
  523. package/dist/shell/sidebar-block-model.d.ts +1 -1
  524. package/dist/shell/sidebar-block-model.d.ts.map +1 -1
  525. package/dist/shell/sidebar-blocks.d.ts +1 -1
  526. package/dist/shell/sidebar-blocks.d.ts.map +1 -1
  527. package/dist/shell/sidebar-search.d.ts +4 -2
  528. package/dist/shell/sidebar-search.d.ts.map +1 -1
  529. package/dist/shell/sidebar-search.js +9 -4
  530. package/dist/shell/sidebar-search.module.css +1 -1
  531. package/dist/shell/use-breakpoint.d.ts +11 -0
  532. package/dist/shell/use-breakpoint.d.ts.map +1 -0
  533. package/dist/shell/use-breakpoint.js +33 -0
  534. package/dist/state/fields.d.ts +26 -22
  535. package/dist/state/fields.d.ts.map +1 -1
  536. package/dist/state/fields.js +19 -18
  537. package/dist/state/persisted-or-local.d.ts +9 -0
  538. package/dist/state/persisted-or-local.d.ts.map +1 -0
  539. package/dist/state/persisted-or-local.js +24 -0
  540. package/dist/state.d.ts +1 -1
  541. package/dist/state.d.ts.map +1 -1
  542. package/dist/state.js +2 -6
  543. package/dist/styles.css +36 -0
  544. package/dist/surfaces.d.ts +25 -47
  545. package/dist/surfaces.d.ts.map +1 -1
  546. package/dist/surfaces.js +72 -55
  547. package/dist/theme/ctl-theme.d.ts.map +1 -1
  548. package/dist/theme/index.d.ts.map +1 -1
  549. package/dist/theme/index.js +102 -17
  550. package/dist/theme/nav-link.module.css +37 -26
  551. package/dist/theme/segmented-control.module.css +22 -0
  552. package/dist/theme/segmented-control.module.css.d.ts +1 -0
  553. package/dist/theme/theme-toggle.d.ts +9 -0
  554. package/dist/theme/theme-toggle.d.ts.map +1 -0
  555. package/dist/{theme-toggle/index.js → theme/theme-toggle.js} +9 -3
  556. package/dist/theme-lab/boot.d.ts +66 -0
  557. package/dist/theme-lab/boot.d.ts.map +1 -0
  558. package/dist/theme-lab/boot.js +82 -0
  559. package/dist/theme-lab/index.d.ts +4 -43
  560. package/dist/theme-lab/index.d.ts.map +1 -1
  561. package/dist/theme-lab/index.js +9 -69
  562. package/dist/tokens/index.d.ts +43 -0
  563. package/dist/tokens/index.d.ts.map +1 -1
  564. package/dist/tokens/index.js +45 -6
  565. package/dist/tokens/palette.d.ts +57 -25
  566. package/dist/tokens/palette.d.ts.map +1 -1
  567. package/dist/tokens/palette.js +44 -15
  568. package/dist/tokens.css +5 -5
  569. package/llms.txt +11 -26
  570. package/package.json +54 -34
  571. package/src/agent/ai-sdk-transport.ts +11 -4
  572. package/src/agent/coalesce.ts +2 -1
  573. package/src/agent/merge.ts +2 -1
  574. package/src/agent/part-list.tsx +2 -0
  575. package/src/agent/stick-to-bottom.tsx +9 -11
  576. package/src/agent/transport.ts +15 -2
  577. package/src/agent/use-agent-thread-runs.ts +65 -15
  578. package/src/agent-chat/composer.tsx +124 -103
  579. package/src/agent-chat/index.ts +6 -4
  580. package/src/agent-chat/relative-time.ts +2 -1
  581. package/src/agent-chat/thread-detail-panel.tsx +10 -6
  582. package/src/agent-chat/thread-feed-row.tsx +117 -34
  583. package/src/agent-chat/thread-feed.tsx +59 -27
  584. package/src/agent-chat/thread-message.tsx +48 -13
  585. package/src/agent-chat/thread-outcome-card.tsx +9 -1
  586. package/src/agent-chat/thread-workspace.tsx +72 -33
  587. package/src/agent-chat/tool-chip.tsx +8 -2
  588. package/src/agent-chat/virtualize.ts +48 -0
  589. package/src/charts/hooks/useBandPlot.ts +26 -8
  590. package/src/charts/index.ts +46 -14
  591. package/src/charts/kinds/BandStrip.tsx +36 -14
  592. package/src/charts/kinds/Bars.tsx +68 -20
  593. package/src/charts/kinds/Donut.tsx +30 -3
  594. package/src/charts/kinds/DualPanel.tsx +60 -13
  595. package/src/charts/kinds/Heatmap.tsx +90 -33
  596. package/src/charts/kinds/MirroredBars.tsx +21 -3
  597. package/src/charts/kinds/MultiLine.tsx +61 -18
  598. package/src/charts/kinds/StackedArea.tsx +77 -6
  599. package/src/charts/kinds/ZonedLine.tsx +72 -35
  600. package/src/charts/layout/auto-margin.ts +134 -21
  601. package/src/charts/layout/log-ticks.ts +90 -0
  602. package/src/charts/primitives/Axes.tsx +82 -5
  603. package/src/charts/primitives/CartesianChart.tsx +194 -41
  604. package/src/charts/primitives/ChartCard.tsx +111 -27
  605. package/src/charts/primitives/ChartFrame.tsx +111 -26
  606. package/src/charts/primitives/ChartLegend.tsx +43 -13
  607. package/src/charts/primitives/ChartPending.tsx +194 -10
  608. package/src/charts/primitives/ChartTooltip.tsx +37 -5
  609. package/src/charts/primitives/Crosshair.tsx +6 -3
  610. package/src/charts/primitives/chart-frame-layout.ts +212 -0
  611. package/src/charts/primitives/chart-tier.tsx +37 -0
  612. package/src/charts/series.ts +77 -1
  613. package/src/charts/sparklines/BarSparkline.tsx +11 -3
  614. package/src/charts/sparklines/LineSparkline.tsx +18 -4
  615. package/src/charts/sparklines/index.ts +2 -2
  616. package/src/charts/utils/format.ts +19 -27
  617. package/src/charts/utils/ticks.ts +149 -4
  618. package/src/cli/audit-allows.ts +371 -0
  619. package/src/cli/check-theme.ts +197 -0
  620. package/src/cli/doctor.ts +331 -0
  621. package/src/cli/guard-hook.ts +115 -0
  622. package/src/cli/help.ts +101 -0
  623. package/src/cli/index.ts +269 -3434
  624. package/src/cli/init.ts +247 -0
  625. package/src/cli/sync.ts +324 -0
  626. package/src/cli/tokens-css.ts +252 -0
  627. package/src/commands/ShortcutsHelp.tsx +10 -3
  628. package/src/commands/define-commands.ts +2 -2
  629. package/src/commands/define-overlays.ts +216 -1
  630. package/src/commands/index.ts +22 -1
  631. package/src/commands/overlays-mount.tsx +63 -31
  632. package/src/commands/shell-bridge.ts +86 -0
  633. package/src/common/errors.ts +143 -0
  634. package/src/common/index.ts +23 -0
  635. package/src/common/is-dev.ts +4 -0
  636. package/src/common/props.ts +71 -0
  637. package/src/common/query-state-like.ts +64 -0
  638. package/src/common/refs.ts +58 -0
  639. package/src/common/scroll-parent.ts +42 -0
  640. package/src/common/validate.ts +94 -0
  641. package/src/content/article-layout.module.css +11 -4
  642. package/src/content/callout.tsx +13 -7
  643. package/src/content/guide.tsx +14 -4
  644. package/src/content/heading-anchor.tsx +14 -3
  645. package/src/content/index.ts +2 -0
  646. package/src/content/markdown.tsx +6 -8
  647. package/src/content/prose.module.css +9 -7
  648. package/src/content/reading-progress.tsx +40 -15
  649. package/src/content/slug.ts +4 -0
  650. package/src/content/toc.tsx +47 -10
  651. package/src/controls/actions.tsx +90 -10
  652. package/src/controls/compare-filter.tsx +20 -3
  653. package/src/controls/control-group.tsx +11 -4
  654. package/src/controls/controls.module.css +169 -186
  655. package/src/controls/controls.module.css.d.ts +9 -11
  656. package/src/controls/enum-filter.tsx +32 -16
  657. package/src/controls/filter-context.tsx +14 -6
  658. package/src/controls/filter-pill.tsx +6 -3
  659. package/src/controls/filter-set.tsx +32 -13
  660. package/src/controls/filter-sheet.tsx +44 -194
  661. package/src/controls/index.ts +7 -0
  662. package/src/controls/multi-select-filter.tsx +177 -27
  663. package/src/controls/number-filter.tsx +61 -23
  664. package/src/controls/panel-row.module.css +86 -0
  665. package/src/controls/panel-row.module.css.d.ts +13 -0
  666. package/src/controls/panel-row.tsx +392 -0
  667. package/src/controls/range-filter.tsx +76 -39
  668. package/src/controls/search-filter.tsx +30 -12
  669. package/src/controls/select-filter.tsx +11 -1
  670. package/src/controls/slider-control.tsx +159 -0
  671. package/src/controls/sync-button.tsx +13 -3
  672. package/src/controls/toggle-filter.tsx +36 -16
  673. package/src/controls/view-tabs.tsx +63 -11
  674. package/src/controls-dates/date-range-picker.tsx +8 -1
  675. package/src/dashboard/delta-badge.tsx +75 -4
  676. package/src/dashboard/empty-state.tsx +67 -24
  677. package/src/dashboard/index.ts +24 -3
  678. package/src/dashboard/query-state.tsx +142 -85
  679. package/src/{section → dashboard}/section.module.css +11 -0
  680. package/src/dashboard/section.tsx +287 -0
  681. package/src/dashboard/settings-section.tsx +88 -32
  682. package/src/dashboard/stat-card.module.css +46 -31
  683. package/src/dashboard/stat-card.module.css.d.ts +1 -0
  684. package/src/dashboard/stat-card.tsx +186 -128
  685. package/src/dashboard/stat-group.module.css +66 -0
  686. package/src/dashboard/stat-group.module.css.d.ts +8 -0
  687. package/src/dashboard/stat-group.tsx +74 -0
  688. package/src/dashboard/widget-grid.module.css +53 -0
  689. package/src/dashboard/widget-grid.module.css.d.ts +9 -0
  690. package/src/dashboard/widget-grid.tsx +127 -0
  691. package/src/{widget-header → dashboard}/widget-header.module.css +72 -7
  692. package/src/dashboard/widget-header.tsx +292 -0
  693. package/src/data/data-table.module.css +64 -0
  694. package/src/data/data-table.module.css.d.ts +11 -0
  695. package/src/data/data-table.tsx +921 -348
  696. package/src/data/query-branch.ts +53 -0
  697. package/src/data/table.ts +9 -23
  698. package/src/data/virtual-list.tsx +190 -56
  699. package/src/data/virtual.ts +8 -4
  700. package/src/dev-dock.tsx +159 -0
  701. package/src/format/date.ts +184 -0
  702. package/src/format/index.ts +69 -0
  703. package/src/format/measure.ts +31 -0
  704. package/src/format/number.ts +129 -0
  705. package/src/format/shared.ts +63 -0
  706. package/src/forms/FormErrorSummary.tsx +13 -6
  707. package/src/forms/create-form.ts +2 -2
  708. package/src/forms/field.ts +42 -13
  709. package/src/forms/form-layout.module.css +145 -0
  710. package/src/forms/form-layout.module.css.d.ts +20 -0
  711. package/src/forms/form-layout.tsx +305 -0
  712. package/src/forms/form-state.tsx +94 -0
  713. package/src/forms/index.ts +37 -7
  714. package/src/forms/use-field-array.ts +94 -0
  715. package/src/forms/use-form-draft.ts +145 -53
  716. package/src/forms/use-form-submit.ts +199 -0
  717. package/src/guard/index.ts +99 -70
  718. package/src/guard/types.ts +7 -10
  719. package/src/index.ts +92 -19
  720. package/src/notifications/bell.tsx +18 -4
  721. package/src/notifications/center.tsx +18 -5
  722. package/src/notifications/define-notifications.ts +64 -18
  723. package/src/notifications/index.ts +17 -8
  724. package/src/notifications/mount-guard.ts +39 -0
  725. package/src/notifications/notify.tsx +122 -3
  726. package/src/notifications/store.ts +7 -0
  727. package/src/{connectivity → provider}/connectivity-indicator.tsx +7 -2
  728. package/src/{connectivity → provider}/connectivity-provider.tsx +5 -2
  729. package/src/{connectivity/types.ts → provider/connectivity-types.ts} +5 -2
  730. package/src/provider/error-fallback.module.css +21 -0
  731. package/src/provider/error-fallback.module.css.d.ts +9 -0
  732. package/src/provider/index.tsx +116 -28
  733. package/src/provider/lab-theme.ts +2 -1
  734. package/src/{connectivity → provider}/use-connectivity.ts +4 -3
  735. package/src/query-client.ts +100 -0
  736. package/src/{query/devtools.tsx → query-devtools.tsx} +6 -4
  737. package/src/register.ts +20 -4
  738. package/src/router-tanstack/index.ts +4 -15
  739. package/src/router-tanstack/nav.ts +1 -1
  740. package/src/router-tanstack/search-store.ts +6 -14
  741. package/src/router-tanstack/use-nav.ts +19 -14
  742. package/src/shell/account-types.ts +5 -2
  743. package/src/shell/app-brand.module.css +109 -0
  744. package/src/shell/app-brand.module.css.d.ts +14 -0
  745. package/src/shell/app-brand.tsx +140 -0
  746. package/src/shell/app-breadcrumbs.tsx +20 -10
  747. package/src/shell/app-header.module.css +10 -5
  748. package/src/shell/app-main.module.css +118 -0
  749. package/src/shell/app-main.module.css.d.ts +11 -0
  750. package/src/shell/app-mobile-nav.module.css +48 -27
  751. package/src/shell/app-mobile-nav.module.css.d.ts +0 -1
  752. package/src/shell/app-mobile-nav.tsx +71 -12
  753. package/src/shell/app-sidebar-account.tsx +29 -6
  754. package/src/shell/app-sidebar.module.css +63 -96
  755. package/src/shell/app-sidebar.module.css.d.ts +0 -5
  756. package/src/shell/app-sidebar.tsx +61 -105
  757. package/src/shell/index.tsx +179 -93
  758. package/src/shell/isomorphic-layout-effect.ts +14 -0
  759. package/src/shell/mobile-nav-model.ts +67 -11
  760. package/src/shell/nav-count-badge.tsx +6 -1
  761. package/src/{nav/types.ts → shell/nav-types.ts} +15 -6
  762. package/src/shell/page-aside.module.css +174 -0
  763. package/src/shell/page-aside.module.css.d.ts +14 -0
  764. package/src/shell/page-aside.tsx +327 -0
  765. package/src/shell/page-bar.module.css +107 -13
  766. package/src/shell/page-bar.module.css.d.ts +4 -1
  767. package/src/shell/page-bar.tsx +294 -94
  768. package/src/shell/page-title.module.css +33 -0
  769. package/src/shell/page-title.module.css.d.ts +11 -0
  770. package/src/shell/page-title.tsx +57 -0
  771. package/src/shell/sidebar-block-model.ts +1 -1
  772. package/src/shell/sidebar-blocks.tsx +1 -1
  773. package/src/shell/sidebar-search.module.css +1 -1
  774. package/src/shell/sidebar-search.tsx +13 -4
  775. package/src/shell/use-breakpoint.ts +73 -0
  776. package/src/state/fields.ts +50 -53
  777. package/src/state/persisted-or-local.ts +50 -0
  778. package/src/state.ts +0 -9
  779. package/src/styles.css +36 -0
  780. package/src/surfaces.ts +72 -57
  781. package/src/theme/ctl-theme.tsx +10 -0
  782. package/src/theme/index.ts +136 -16
  783. package/src/theme/nav-link.module.css +37 -26
  784. package/src/theme/segmented-control.module.css +22 -0
  785. package/src/theme/segmented-control.module.css.d.ts +1 -0
  786. package/src/{theme-toggle/index.tsx → theme/theme-toggle.tsx} +11 -4
  787. package/src/theme-lab/boot.ts +140 -0
  788. package/src/theme-lab/index.tsx +21 -112
  789. package/src/tokens/index.ts +60 -6
  790. package/src/tokens/palette.ts +70 -30
  791. package/dist/agent/adapter.test.d.ts +0 -2
  792. package/dist/agent/adapter.test.d.ts.map +0 -1
  793. package/dist/agent/ai-sdk-transport.test.d.ts +0 -2
  794. package/dist/agent/ai-sdk-transport.test.d.ts.map +0 -1
  795. package/dist/agent/coalesce.test.d.ts +0 -2
  796. package/dist/agent/coalesce.test.d.ts.map +0 -1
  797. package/dist/agent/foreign.test.d.ts +0 -2
  798. package/dist/agent/foreign.test.d.ts.map +0 -1
  799. package/dist/agent/id.test.d.ts +0 -2
  800. package/dist/agent/id.test.d.ts.map +0 -1
  801. package/dist/agent/merge.test.d.ts +0 -2
  802. package/dist/agent/merge.test.d.ts.map +0 -1
  803. package/dist/agent/part-list.test.d.ts +0 -2
  804. package/dist/agent/part-list.test.d.ts.map +0 -1
  805. package/dist/agent/parts.test.d.ts +0 -2
  806. package/dist/agent/parts.test.d.ts.map +0 -1
  807. package/dist/agent/thread.test.d.ts +0 -2
  808. package/dist/agent/thread.test.d.ts.map +0 -1
  809. package/dist/agent/transport.test.d.ts +0 -2
  810. package/dist/agent/transport.test.d.ts.map +0 -1
  811. package/dist/agent/use-agent-stream.test.d.ts +0 -2
  812. package/dist/agent/use-agent-stream.test.d.ts.map +0 -1
  813. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts +0 -2
  814. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts.map +0 -1
  815. package/dist/agent/use-agent-thread-runs.resume.test.d.ts +0 -2
  816. package/dist/agent/use-agent-thread-runs.resume.test.d.ts.map +0 -1
  817. package/dist/agent/use-agent-thread-runs.test.d.ts +0 -2
  818. package/dist/agent/use-agent-thread-runs.test.d.ts.map +0 -1
  819. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts +0 -2
  820. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts.map +0 -1
  821. package/dist/agent-chat/composer.test.d.ts +0 -2
  822. package/dist/agent-chat/composer.test.d.ts.map +0 -1
  823. package/dist/agent-chat/relative-time.test.d.ts +0 -2
  824. package/dist/agent-chat/relative-time.test.d.ts.map +0 -1
  825. package/dist/agent-chat/thread-feed-row.test.d.ts +0 -2
  826. package/dist/agent-chat/thread-feed-row.test.d.ts.map +0 -1
  827. package/dist/agent-chat/thread-feed.test.d.ts +0 -2
  828. package/dist/agent-chat/thread-feed.test.d.ts.map +0 -1
  829. package/dist/agent-chat/thread-message.test.d.ts +0 -2
  830. package/dist/agent-chat/thread-message.test.d.ts.map +0 -1
  831. package/dist/agent-chat/thread-workspace.test.d.ts +0 -2
  832. package/dist/agent-chat/thread-workspace.test.d.ts.map +0 -1
  833. package/dist/agent-chat/tool-chip.test.d.ts +0 -2
  834. package/dist/agent-chat/tool-chip.test.d.ts.map +0 -1
  835. package/dist/agent-chat/virtualize.type-guard.test.d.ts +0 -2
  836. package/dist/agent-chat/virtualize.type-guard.test.d.ts.map +0 -1
  837. package/dist/charts/cursor/resolve.test.d.ts +0 -2
  838. package/dist/charts/cursor/resolve.test.d.ts.map +0 -1
  839. package/dist/charts/cursor/store.test.d.ts +0 -2
  840. package/dist/charts/cursor/store.test.d.ts.map +0 -1
  841. package/dist/charts/kinds/BandStrip.test.d.ts +0 -2
  842. package/dist/charts/kinds/BandStrip.test.d.ts.map +0 -1
  843. package/dist/charts/kinds/Bars.test.d.ts +0 -2
  844. package/dist/charts/kinds/Bars.test.d.ts.map +0 -1
  845. package/dist/charts/kinds/DualPanel.test.d.ts +0 -2
  846. package/dist/charts/kinds/DualPanel.test.d.ts.map +0 -1
  847. package/dist/charts/kinds/MirroredBars.test.d.ts +0 -2
  848. package/dist/charts/kinds/MirroredBars.test.d.ts.map +0 -1
  849. package/dist/charts/kinds/MultiLine.test.d.ts +0 -2
  850. package/dist/charts/kinds/MultiLine.test.d.ts.map +0 -1
  851. package/dist/charts/kinds/StackedArea.test.d.ts +0 -2
  852. package/dist/charts/kinds/StackedArea.test.d.ts.map +0 -1
  853. package/dist/charts/kinds/ZonedLine.test.d.ts +0 -2
  854. package/dist/charts/kinds/ZonedLine.test.d.ts.map +0 -1
  855. package/dist/charts/layout/auto-margin.test.d.ts +0 -2
  856. package/dist/charts/layout/auto-margin.test.d.ts.map +0 -1
  857. package/dist/charts/primitives/CartesianChart.test.d.ts +0 -2
  858. package/dist/charts/primitives/CartesianChart.test.d.ts.map +0 -1
  859. package/dist/charts/primitives/ChartCard.test.d.ts +0 -2
  860. package/dist/charts/primitives/ChartCard.test.d.ts.map +0 -1
  861. package/dist/charts/primitives/ChartFrame.test.d.ts +0 -2
  862. package/dist/charts/primitives/ChartFrame.test.d.ts.map +0 -1
  863. package/dist/charts/primitives/ChartLegend.test.d.ts +0 -2
  864. package/dist/charts/primitives/ChartLegend.test.d.ts.map +0 -1
  865. package/dist/charts/primitives/ChartTooltip.test.d.ts +0 -2
  866. package/dist/charts/primitives/ChartTooltip.test.d.ts.map +0 -1
  867. package/dist/charts/primitives/XZoneRects.test.d.ts +0 -2
  868. package/dist/charts/primitives/XZoneRects.test.d.ts.map +0 -1
  869. package/dist/charts/series.test.d.ts +0 -2
  870. package/dist/charts/series.test.d.ts.map +0 -1
  871. package/dist/charts/utils/domain.test.d.ts +0 -2
  872. package/dist/charts/utils/domain.test.d.ts.map +0 -1
  873. package/dist/charts/utils/measure-text.test.d.ts +0 -2
  874. package/dist/charts/utils/measure-text.test.d.ts.map +0 -1
  875. package/dist/cli/check-coverage.test.d.ts +0 -2
  876. package/dist/cli/check-coverage.test.d.ts.map +0 -1
  877. package/dist/cli/check-theme.test.d.ts +0 -2
  878. package/dist/cli/check-theme.test.d.ts.map +0 -1
  879. package/dist/cli/help.test.d.ts +0 -2
  880. package/dist/cli/help.test.d.ts.map +0 -1
  881. package/dist/cli/init-sync.test.d.ts +0 -2
  882. package/dist/cli/init-sync.test.d.ts.map +0 -1
  883. package/dist/cli/placement-engine.test.d.ts +0 -2
  884. package/dist/cli/placement-engine.test.d.ts.map +0 -1
  885. package/dist/cli/shipped-versions.test.d.ts +0 -2
  886. package/dist/cli/shipped-versions.test.d.ts.map +0 -1
  887. package/dist/cli/tarball-parity.test.d.ts +0 -2
  888. package/dist/cli/tarball-parity.test.d.ts.map +0 -1
  889. package/dist/cli/template-format.test.d.ts +0 -2
  890. package/dist/cli/template-format.test.d.ts.map +0 -1
  891. package/dist/cli/template-ledger.test.d.ts +0 -2
  892. package/dist/cli/template-ledger.test.d.ts.map +0 -1
  893. package/dist/cli/tokens-css.test.d.ts +0 -2
  894. package/dist/cli/tokens-css.test.d.ts.map +0 -1
  895. package/dist/cli/toolchain-wiring.test.d.ts +0 -2
  896. package/dist/cli/toolchain-wiring.test.d.ts.map +0 -1
  897. package/dist/cli/waiver-audit.test.d.ts +0 -2
  898. package/dist/cli/waiver-audit.test.d.ts.map +0 -1
  899. package/dist/connectivity/connectivity-indicator.d.ts +0 -11
  900. package/dist/connectivity/connectivity-indicator.d.ts.map +0 -1
  901. package/dist/connectivity/connectivity-provider.d.ts.map +0 -1
  902. package/dist/connectivity/index.d.ts +0 -13
  903. package/dist/connectivity/index.d.ts.map +0 -1
  904. package/dist/connectivity/index.js +0 -8
  905. package/dist/connectivity/types.d.ts.map +0 -1
  906. package/dist/connectivity/use-connectivity.d.ts +0 -3
  907. package/dist/connectivity/use-connectivity.d.ts.map +0 -1
  908. package/dist/content/article-actions.test.d.ts +0 -2
  909. package/dist/content/article-actions.test.d.ts.map +0 -1
  910. package/dist/content/article-model.test.d.ts +0 -2
  911. package/dist/content/article-model.test.d.ts.map +0 -1
  912. package/dist/content/block-split.test.d.ts +0 -2
  913. package/dist/content/block-split.test.d.ts.map +0 -1
  914. package/dist/content/heading-components.test.d.ts +0 -2
  915. package/dist/content/heading-components.test.d.ts.map +0 -1
  916. package/dist/content/markdown.test.d.ts +0 -2
  917. package/dist/content/markdown.test.d.ts.map +0 -1
  918. package/dist/content/prose.module.css.test.d.ts +0 -2
  919. package/dist/content/prose.module.css.test.d.ts.map +0 -1
  920. package/dist/content/sanitize.test.d.ts +0 -2
  921. package/dist/content/sanitize.test.d.ts.map +0 -1
  922. package/dist/content/slug.test.d.ts +0 -2
  923. package/dist/content/slug.test.d.ts.map +0 -1
  924. package/dist/controls/actions.test.d.ts +0 -2
  925. package/dist/controls/actions.test.d.ts.map +0 -1
  926. package/dist/controls/actions.type-guard.test.d.ts +0 -2
  927. package/dist/controls/actions.type-guard.test.d.ts.map +0 -1
  928. package/dist/controls/control-group.test.d.ts +0 -2
  929. package/dist/controls/control-group.test.d.ts.map +0 -1
  930. package/dist/controls/controls.router.test.d.ts +0 -2
  931. package/dist/controls/controls.router.test.d.ts.map +0 -1
  932. package/dist/controls/filter-set.test.d.ts +0 -2
  933. package/dist/controls/filter-set.test.d.ts.map +0 -1
  934. package/dist/controls/peer-boundary.test.d.ts +0 -2
  935. package/dist/controls/peer-boundary.test.d.ts.map +0 -1
  936. package/dist/controls/props-contract.test.d.ts +0 -2
  937. package/dist/controls/props-contract.test.d.ts.map +0 -1
  938. package/dist/controls/range-filter.type-guard.test.d.ts +0 -2
  939. package/dist/controls/range-filter.type-guard.test.d.ts.map +0 -1
  940. package/dist/controls/sync-button.test.d.ts +0 -2
  941. package/dist/controls/sync-button.test.d.ts.map +0 -1
  942. package/dist/dashboard/query-state.test.d.ts +0 -2
  943. package/dist/dashboard/query-state.test.d.ts.map +0 -1
  944. package/dist/dashboard/settings-section.test.d.ts +0 -2
  945. package/dist/dashboard/settings-section.test.d.ts.map +0 -1
  946. package/dist/dashboard/stat-card.test.d.ts +0 -2
  947. package/dist/dashboard/stat-card.test.d.ts.map +0 -1
  948. package/dist/data/data-table.test.d.ts +0 -2
  949. package/dist/data/data-table.test.d.ts.map +0 -1
  950. package/dist/data/index.d.ts +0 -38
  951. package/dist/data/index.d.ts.map +0 -1
  952. package/dist/data/index.js +0 -2
  953. package/dist/guard/check-source.test.d.ts +0 -2
  954. package/dist/guard/check-source.test.d.ts.map +0 -1
  955. package/dist/guard/grace.test.d.ts +0 -20
  956. package/dist/guard/grace.test.d.ts.map +0 -1
  957. package/dist/guard/guard-hook.test.d.ts +0 -2
  958. package/dist/guard/guard-hook.test.d.ts.map +0 -1
  959. package/dist/motion/index.d.ts.map +0 -1
  960. package/dist/nav/types.d.ts.map +0 -1
  961. package/dist/notifications/overlay.d.ts +0 -20
  962. package/dist/notifications/overlay.d.ts.map +0 -1
  963. package/dist/notifications/overlay.js +0 -12
  964. package/dist/provider/index.test.d.ts +0 -2
  965. package/dist/provider/index.test.d.ts.map +0 -1
  966. package/dist/provider/lab-theme.test.d.ts +0 -2
  967. package/dist/provider/lab-theme.test.d.ts.map +0 -1
  968. package/dist/query/devtools.d.ts.map +0 -1
  969. package/dist/query/error-message.d.ts +0 -41
  970. package/dist/query/error-message.d.ts.map +0 -1
  971. package/dist/query/error-message.js +0 -30
  972. package/dist/query/error-message.test.d.ts +0 -2
  973. package/dist/query/error-message.test.d.ts.map +0 -1
  974. package/dist/query/index.d.ts +0 -47
  975. package/dist/query/index.d.ts.map +0 -1
  976. package/dist/query/index.js +0 -56
  977. package/dist/router-tanstack/field.d.ts +0 -14
  978. package/dist/router-tanstack/field.d.ts.map +0 -1
  979. package/dist/router-tanstack/field.js +0 -4
  980. package/dist/router-tanstack/multi-search-param-store.d.ts +0 -43
  981. package/dist/router-tanstack/multi-search-param-store.d.ts.map +0 -1
  982. package/dist/router-tanstack/multi-search-param-store.js +0 -30
  983. package/dist/router-tanstack/multi-search-param-store.test.d.ts +0 -2
  984. package/dist/router-tanstack/multi-search-param-store.test.d.ts.map +0 -1
  985. package/dist/router-tanstack/nav.test.d.ts +0 -2
  986. package/dist/router-tanstack/nav.test.d.ts.map +0 -1
  987. package/dist/router-tanstack/search-param-store.d.ts +0 -42
  988. package/dist/router-tanstack/search-param-store.d.ts.map +0 -1
  989. package/dist/router-tanstack/search-param-store.js +0 -25
  990. package/dist/router-tanstack/search-param-store.test.d.ts +0 -2
  991. package/dist/router-tanstack/search-param-store.test.d.ts.map +0 -1
  992. package/dist/router-tanstack/search-store.router.test.d.ts +0 -2
  993. package/dist/router-tanstack/search-store.router.test.d.ts.map +0 -1
  994. package/dist/router-tanstack/search-store.test.d.ts +0 -2
  995. package/dist/router-tanstack/search-store.test.d.ts.map +0 -1
  996. package/dist/router-tanstack/use-nav.test.d.ts +0 -2
  997. package/dist/router-tanstack/use-nav.test.d.ts.map +0 -1
  998. package/dist/section/index.d.ts +0 -2
  999. package/dist/section/index.d.ts.map +0 -1
  1000. package/dist/section/index.js +0 -4
  1001. package/dist/section/section.d.ts +0 -24
  1002. package/dist/section/section.d.ts.map +0 -1
  1003. package/dist/section/section.js +0 -126
  1004. package/dist/section/section.test.d.ts +0 -2
  1005. package/dist/section/section.test.d.ts.map +0 -1
  1006. package/dist/shell/app-mobile-nav.test.d.ts +0 -2
  1007. package/dist/shell/app-mobile-nav.test.d.ts.map +0 -1
  1008. package/dist/shell/app-sidebar.test.d.ts +0 -2
  1009. package/dist/shell/app-sidebar.test.d.ts.map +0 -1
  1010. package/dist/shell/index.test.d.ts +0 -2
  1011. package/dist/shell/index.test.d.ts.map +0 -1
  1012. package/dist/shell/mobile-nav-model.test.d.ts +0 -2
  1013. package/dist/shell/mobile-nav-model.test.d.ts.map +0 -1
  1014. package/dist/shell/page-bar.test.d.ts +0 -2
  1015. package/dist/shell/page-bar.test.d.ts.map +0 -1
  1016. package/dist/state/fields.test.d.ts +0 -2
  1017. package/dist/state/fields.test.d.ts.map +0 -1
  1018. package/dist/state/persisted.test.d.ts +0 -2
  1019. package/dist/state/persisted.test.d.ts.map +0 -1
  1020. package/dist/styles.floor.test.d.ts +0 -2
  1021. package/dist/styles.floor.test.d.ts.map +0 -1
  1022. package/dist/theme/border-coverage.test.d.ts +0 -18
  1023. package/dist/theme/border-coverage.test.d.ts.map +0 -1
  1024. package/dist/theme/contrast.test.d.ts +0 -2
  1025. package/dist/theme/contrast.test.d.ts.map +0 -1
  1026. package/dist/theme/ctl-theme.test.d.ts +0 -2
  1027. package/dist/theme/ctl-theme.test.d.ts.map +0 -1
  1028. package/dist/theme/ctl-tier-coverage.test.d.ts +0 -2
  1029. package/dist/theme/ctl-tier-coverage.test.d.ts.map +0 -1
  1030. package/dist/theme/density-option.test.d.ts +0 -2
  1031. package/dist/theme/density-option.test.d.ts.map +0 -1
  1032. package/dist/theme/density-relations.test.d.ts +0 -2
  1033. package/dist/theme/density-relations.test.d.ts.map +0 -1
  1034. package/dist/theme/derive-theme.test.d.ts +0 -2
  1035. package/dist/theme/derive-theme.test.d.ts.map +0 -1
  1036. package/dist/theme/divider-law.test.d.ts +0 -2
  1037. package/dist/theme/divider-law.test.d.ts.map +0 -1
  1038. package/dist/theme/fonts-option.test.d.ts +0 -2
  1039. package/dist/theme/fonts-option.test.d.ts.map +0 -1
  1040. package/dist/theme/icon-slot.test.d.ts +0 -2
  1041. package/dist/theme/icon-slot.test.d.ts.map +0 -1
  1042. package/dist/theme/layout-rhythm-css.test.d.ts +0 -2
  1043. package/dist/theme/layout-rhythm-css.test.d.ts.map +0 -1
  1044. package/dist/theme/radius-option.test.d.ts +0 -2
  1045. package/dist/theme/radius-option.test.d.ts.map +0 -1
  1046. package/dist/theme/radius.test.d.ts +0 -2
  1047. package/dist/theme/radius.test.d.ts.map +0 -1
  1048. package/dist/theme/shadow-surfaces.test.d.ts +0 -26
  1049. package/dist/theme/shadow-surfaces.test.d.ts.map +0 -1
  1050. package/dist/theme/spacing.test.d.ts +0 -2
  1051. package/dist/theme/spacing.test.d.ts.map +0 -1
  1052. package/dist/theme/use-basalt-spacing.test.d.ts +0 -2
  1053. package/dist/theme/use-basalt-spacing.test.d.ts.map +0 -1
  1054. package/dist/theme-lab/derive-controls.test.d.ts +0 -2
  1055. package/dist/theme-lab/derive-controls.test.d.ts.map +0 -1
  1056. package/dist/theme-lab/derive-state.test.d.ts +0 -2
  1057. package/dist/theme-lab/derive-state.test.d.ts.map +0 -1
  1058. package/dist/theme-lab/theme-lab.test.d.ts +0 -2
  1059. package/dist/theme-lab/theme-lab.test.d.ts.map +0 -1
  1060. package/dist/theme-toggle/index.d.ts +0 -8
  1061. package/dist/theme-toggle/index.d.ts.map +0 -1
  1062. package/dist/tokens/build-fonts-css.test.d.ts +0 -2
  1063. package/dist/tokens/build-fonts-css.test.d.ts.map +0 -1
  1064. package/dist/tokens/build-radius-css.test.d.ts +0 -2
  1065. package/dist/tokens/build-radius-css.test.d.ts.map +0 -1
  1066. package/dist/tokens/chart-margin.test.d.ts +0 -2
  1067. package/dist/tokens/chart-margin.test.d.ts.map +0 -1
  1068. package/dist/tokens/density.test.d.ts +0 -2
  1069. package/dist/tokens/density.test.d.ts.map +0 -1
  1070. package/dist/tokens/derive-radius.test.d.ts +0 -2
  1071. package/dist/tokens/derive-radius.test.d.ts.map +0 -1
  1072. package/dist/tokens/derive.test.d.ts +0 -2
  1073. package/dist/tokens/derive.test.d.ts.map +0 -1
  1074. package/dist/tokens/palette.test.d.ts +0 -2
  1075. package/dist/tokens/palette.test.d.ts.map +0 -1
  1076. package/dist/tokens/text-scale.test.d.ts +0 -2
  1077. package/dist/tokens/text-scale.test.d.ts.map +0 -1
  1078. package/dist/vite-color-mix.test.d.ts +0 -2
  1079. package/dist/vite-color-mix.test.d.ts.map +0 -1
  1080. package/dist/vite.test.d.ts +0 -2
  1081. package/dist/vite.test.d.ts.map +0 -1
  1082. package/dist/widget-header/delta-badge.d.ts +0 -13
  1083. package/dist/widget-header/delta-badge.d.ts.map +0 -1
  1084. package/dist/widget-header/delta-badge.js +0 -29
  1085. package/dist/widget-header/delta-badge.test.d.ts +0 -2
  1086. package/dist/widget-header/delta-badge.test.d.ts.map +0 -1
  1087. package/dist/widget-header/index.d.ts +0 -3
  1088. package/dist/widget-header/index.d.ts.map +0 -1
  1089. package/dist/widget-header/index.js +0 -6
  1090. package/dist/widget-header/no-mantine.test.d.ts +0 -2
  1091. package/dist/widget-header/no-mantine.test.d.ts.map +0 -1
  1092. package/dist/widget-header/widget-header.d.ts +0 -67
  1093. package/dist/widget-header/widget-header.d.ts.map +0 -1
  1094. package/dist/widget-header/widget-header.test.d.ts +0 -2
  1095. package/dist/widget-header/widget-header.test.d.ts.map +0 -1
  1096. package/src/agent/adapter.test.ts +0 -1640
  1097. package/src/agent/ai-sdk-transport.test.ts +0 -658
  1098. package/src/agent/coalesce.test.ts +0 -387
  1099. package/src/agent/foreign.test.ts +0 -51
  1100. package/src/agent/id.test.ts +0 -169
  1101. package/src/agent/merge.test.ts +0 -206
  1102. package/src/agent/part-list.test.tsx +0 -112
  1103. package/src/agent/parts.test.ts +0 -720
  1104. package/src/agent/thread.test.ts +0 -220
  1105. package/src/agent/transport.test.ts +0 -70
  1106. package/src/agent/use-agent-stream.test.tsx +0 -287
  1107. package/src/agent/use-agent-thread-runs.foreign.test.tsx +0 -138
  1108. package/src/agent/use-agent-thread-runs.resume.test.tsx +0 -294
  1109. package/src/agent/use-agent-thread-runs.test.tsx +0 -1120
  1110. package/src/agent/use-agent-thread-runs.wedge.test.tsx +0 -387
  1111. package/src/agent-chat/composer.test.tsx +0 -924
  1112. package/src/agent-chat/relative-time.test.ts +0 -95
  1113. package/src/agent-chat/thread-feed-row.test.tsx +0 -510
  1114. package/src/agent-chat/thread-feed.test.tsx +0 -241
  1115. package/src/agent-chat/thread-message.test.tsx +0 -1285
  1116. package/src/agent-chat/thread-workspace.test.tsx +0 -169
  1117. package/src/agent-chat/tool-chip.test.tsx +0 -139
  1118. package/src/agent-chat/virtualize.type-guard.test.ts +0 -49
  1119. package/src/charts/cursor/resolve.test.ts +0 -151
  1120. package/src/charts/cursor/store.test.ts +0 -64
  1121. package/src/charts/kinds/BandStrip.test.tsx +0 -282
  1122. package/src/charts/kinds/Bars.test.tsx +0 -183
  1123. package/src/charts/kinds/DualPanel.test.tsx +0 -527
  1124. package/src/charts/kinds/MirroredBars.test.tsx +0 -232
  1125. package/src/charts/kinds/MultiLine.test.tsx +0 -218
  1126. package/src/charts/kinds/StackedArea.test.tsx +0 -114
  1127. package/src/charts/kinds/ZonedLine.test.tsx +0 -147
  1128. package/src/charts/layout/auto-margin.test.ts +0 -42
  1129. package/src/charts/primitives/CartesianChart.test.tsx +0 -647
  1130. package/src/charts/primitives/ChartCard.test.tsx +0 -66
  1131. package/src/charts/primitives/ChartFrame.test.tsx +0 -142
  1132. package/src/charts/primitives/ChartLegend.test.tsx +0 -78
  1133. package/src/charts/primitives/ChartTooltip.test.tsx +0 -44
  1134. package/src/charts/primitives/XZoneRects.test.tsx +0 -123
  1135. package/src/charts/series.test.ts +0 -112
  1136. package/src/charts/utils/domain.test.ts +0 -54
  1137. package/src/charts/utils/measure-text.test.ts +0 -36
  1138. package/src/cli/check-coverage.test.ts +0 -297
  1139. package/src/cli/check-theme.test.ts +0 -837
  1140. package/src/cli/help.test.ts +0 -170
  1141. package/src/cli/init-sync.test.ts +0 -343
  1142. package/src/cli/placement-engine.test.ts +0 -298
  1143. package/src/cli/shipped-versions.test.ts +0 -83
  1144. package/src/cli/tarball-parity.test.ts +0 -44
  1145. package/src/cli/template-format.test.ts +0 -64
  1146. package/src/cli/template-ledger.test.ts +0 -219
  1147. package/src/cli/tokens-css.test.ts +0 -249
  1148. package/src/cli/toolchain-wiring.test.ts +0 -1358
  1149. package/src/cli/waiver-audit.test.ts +0 -215
  1150. package/src/connectivity/index.ts +0 -18
  1151. package/src/content/article-actions.test.ts +0 -78
  1152. package/src/content/article-model.test.ts +0 -119
  1153. package/src/content/block-split.test.ts +0 -75
  1154. package/src/content/heading-components.test.tsx +0 -65
  1155. package/src/content/markdown.test.tsx +0 -603
  1156. package/src/content/prose.module.css.test.ts +0 -39
  1157. package/src/content/sanitize.test.ts +0 -659
  1158. package/src/content/slug.test.ts +0 -62
  1159. package/src/controls/actions.test.tsx +0 -252
  1160. package/src/controls/actions.type-guard.test.ts +0 -63
  1161. package/src/controls/control-group.test.tsx +0 -229
  1162. package/src/controls/controls.router.test.tsx +0 -1053
  1163. package/src/controls/filter-set.test.tsx +0 -472
  1164. package/src/controls/peer-boundary.test.ts +0 -85
  1165. package/src/controls/props-contract.test.tsx +0 -137
  1166. package/src/controls/range-filter.type-guard.test.tsx +0 -122
  1167. package/src/controls/sync-button.test.tsx +0 -183
  1168. package/src/dashboard/query-state.test.tsx +0 -152
  1169. package/src/dashboard/settings-section.test.tsx +0 -56
  1170. package/src/dashboard/stat-card.test.tsx +0 -385
  1171. package/src/data/data-table.test.tsx +0 -434
  1172. package/src/data/index.ts +0 -41
  1173. package/src/guard/check-source.test.ts +0 -3388
  1174. package/src/guard/grace.test.ts +0 -158
  1175. package/src/guard/guard-hook.test.ts +0 -203
  1176. package/src/notifications/overlay.tsx +0 -64
  1177. package/src/provider/index.test.tsx +0 -101
  1178. package/src/provider/lab-theme.test.ts +0 -147
  1179. package/src/query/error-message.test.ts +0 -67
  1180. package/src/query/error-message.ts +0 -70
  1181. package/src/query/index.ts +0 -97
  1182. package/src/router-tanstack/field.ts +0 -35
  1183. package/src/router-tanstack/multi-search-param-store.test.ts +0 -124
  1184. package/src/router-tanstack/multi-search-param-store.ts +0 -106
  1185. package/src/router-tanstack/nav.test.ts +0 -111
  1186. package/src/router-tanstack/search-param-store.test.ts +0 -135
  1187. package/src/router-tanstack/search-param-store.ts +0 -100
  1188. package/src/router-tanstack/search-store.router.test.tsx +0 -882
  1189. package/src/router-tanstack/search-store.test.ts +0 -578
  1190. package/src/router-tanstack/use-nav.test.tsx +0 -187
  1191. package/src/section/index.ts +0 -1
  1192. package/src/section/section.test.tsx +0 -204
  1193. package/src/section/section.tsx +0 -220
  1194. package/src/shell/app-mobile-nav.test.tsx +0 -403
  1195. package/src/shell/app-sidebar.test.tsx +0 -509
  1196. package/src/shell/index.test.tsx +0 -428
  1197. package/src/shell/mobile-nav-model.test.ts +0 -517
  1198. package/src/shell/page-bar.test.tsx +0 -316
  1199. package/src/state/fields.test.tsx +0 -512
  1200. package/src/state/persisted.test.ts +0 -91
  1201. package/src/styles.floor.test.ts +0 -36
  1202. package/src/theme/border-coverage.test.ts +0 -272
  1203. package/src/theme/contrast.test.ts +0 -354
  1204. package/src/theme/ctl-theme.test.tsx +0 -275
  1205. package/src/theme/ctl-tier-coverage.test.ts +0 -170
  1206. package/src/theme/density-option.test.ts +0 -96
  1207. package/src/theme/density-relations.test.ts +0 -506
  1208. package/src/theme/derive-theme.test.ts +0 -101
  1209. package/src/theme/divider-law.test.ts +0 -259
  1210. package/src/theme/fonts-option.test.ts +0 -75
  1211. package/src/theme/icon-slot.test.tsx +0 -275
  1212. package/src/theme/layout-rhythm-css.test.ts +0 -72
  1213. package/src/theme/radius-option.test.ts +0 -86
  1214. package/src/theme/radius.test.ts +0 -142
  1215. package/src/theme/shadow-surfaces.test.ts +0 -488
  1216. package/src/theme/spacing.test.ts +0 -495
  1217. package/src/theme/use-basalt-spacing.test.tsx +0 -55
  1218. package/src/theme-lab/derive-controls.test.ts +0 -94
  1219. package/src/theme-lab/derive-state.test.ts +0 -71
  1220. package/src/theme-lab/theme-lab.test.ts +0 -46
  1221. package/src/tokens/build-fonts-css.test.ts +0 -52
  1222. package/src/tokens/build-radius-css.test.ts +0 -25
  1223. package/src/tokens/chart-margin.test.ts +0 -39
  1224. package/src/tokens/density.test.ts +0 -259
  1225. package/src/tokens/derive-radius.test.ts +0 -62
  1226. package/src/tokens/derive.test.ts +0 -30
  1227. package/src/tokens/palette.test.ts +0 -114
  1228. package/src/tokens/text-scale.test.ts +0 -51
  1229. package/src/vite-color-mix.test.ts +0 -46
  1230. package/src/vite.test.ts +0 -423
  1231. package/src/widget-header/delta-badge.test.tsx +0 -60
  1232. package/src/widget-header/delta-badge.tsx +0 -57
  1233. package/src/widget-header/index.ts +0 -2
  1234. package/src/widget-header/no-mantine.test.ts +0 -33
  1235. package/src/widget-header/widget-header.test.tsx +0 -187
  1236. package/src/widget-header/widget-header.tsx +0 -213
  1237. /package/dist/{motion/index.js → common/motion.js} +0 -0
  1238. /package/dist/{section → dashboard}/section.module.css.d.ts +0 -0
  1239. /package/dist/{widget-header → dashboard}/widget-header.module.css.d.ts +0 -0
  1240. /package/dist/{connectivity/types.js → provider/connectivity-types.js} +0 -0
  1241. /package/dist/{connectivity → provider}/use-connectivity.js +0 -0
  1242. /package/dist/{nav/types.js → shell/nav-types.js} +0 -0
  1243. /package/src/{motion/index.ts → common/motion.ts} +0 -0
  1244. /package/src/{section → dashboard}/section.module.css.d.ts +0 -0
  1245. /package/src/{widget-header → dashboard}/widget-header.module.css.d.ts +0 -0
@@ -1,1053 +0,0 @@
1
- /**
2
- * Every control, bound to a REAL `createSearchStore` under a REAL memory-history router — the half
3
- * that cannot be tested headlessly. What each control reads on a deep link, what its pill says, and
4
- * what lands on the URL when it is operated (C2/C3: there is no `value`/`onChange` to stub, so
5
- * nothing here can pass against a control that only pretends to write).
6
- *
7
- * The harness is the one from `router-tanstack/search-store.router.test.tsx`, narrowed to one route
8
- * (the position questions are that file's job) and wrapped in a `MantineProvider`.
9
- */
10
- import { beforeEach, describe, expect, test } from 'bun:test'
11
- import type { ReactNode } from 'react'
12
- import { MantineProvider } from '@mantine/core'
13
- import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
14
- import {
15
- Outlet,
16
- RouterProvider,
17
- createMemoryHistory,
18
- createRootRoute,
19
- createRoute,
20
- createRouter,
21
- } from '@tanstack/react-router'
22
- import { field } from '../router-tanstack/field'
23
- import { createSearchStore } from '../router-tanstack/search-store'
24
- import { COMPARE_LABELS, COMPARE_VALUES, CompareFilter } from './compare-filter'
25
- import { FilterSet } from './filter-set'
26
- import { MultiSelectFilter } from './multi-select-filter'
27
- import { NumberFilter } from './number-filter'
28
- import { RangeFilter } from './range-filter'
29
- import type { RangeCustomPickerProps } from './range-filter'
30
- import { SearchFilter } from './search-filter'
31
- import { SelectFilter } from './select-filter'
32
- import { ToggleFilter } from './toggle-filter'
33
- import { ViewTabs } from './view-tabs'
34
-
35
- type Router = { state: { status: string; location: { search: unknown } } }
36
-
37
- async function mountPage(input: {
38
- validateSearch: (raw: Record<string, unknown>) => Record<string, unknown>
39
- entry: string
40
- Page: () => ReactNode
41
- }): Promise<Router> {
42
- const rootRoute = createRootRoute({ component: () => <Outlet /> })
43
- const pageRoute = createRoute({
44
- getParentRoute: () => rootRoute,
45
- path: '/dashboard',
46
- validateSearch: input.validateSearch,
47
- component: input.Page,
48
- })
49
- const router = createRouter({
50
- routeTree: rootRoute.addChildren([pageRoute]),
51
- history: createMemoryHistory({ initialEntries: [input.entry] }),
52
- })
53
-
54
- render(
55
- <MantineProvider>
56
- <RouterProvider router={router} />
57
- </MantineProvider>,
58
- )
59
- await waitFor(() => {
60
- expect(router.state.status).toBe('idle')
61
- })
62
- return router as unknown as Router
63
- }
64
-
65
- function search(router: Router): Record<string, unknown> {
66
- return router.state.location.search as Record<string, unknown>
67
- }
68
-
69
- /**
70
- * Press a pill and let its popover mount. The `act` wrapper is load-bearing: Mantine's `Popover` is
71
- * a controlled surface behind a `Transition`, so the dropdown appears one flushed effect cycle after
72
- * the click — a bare `fireEvent.click` leaves the body holding the button and nothing else.
73
- *
74
- * Queries INSIDE a dropdown pass `{ hidden: true }`: the dropdown is a portal still carrying its
75
- * transition styles, which Testing Library reads as outside the accessibility tree. That is a
76
- * harness fact, not a claim about the control — where visibility IS the assertion (`ViewTabs`'
77
- * CSS swap below) the bare query is used deliberately.
78
- */
79
- async function openPill(name: string | RegExp): Promise<void> {
80
- await act(async () => {
81
- fireEvent.click(screen.getByRole('button', { name }))
82
- })
83
- }
84
-
85
- /** A stand-in for `basalt-ui/controls-dates`' `DateRangePicker` — the injected-seam contract, and
86
- * nothing else, so the range tests need no `@mantine/dates` at all. */
87
- function Picker({ onChange }: RangeCustomPickerProps): ReactNode {
88
- return (
89
- <button
90
- type="button"
91
- onClick={() => {
92
- onChange({ from: '2026-03-01', to: '2026-03-14' })
93
- }}
94
- >
95
- pick march
96
- </button>
97
- )
98
- }
99
-
100
- beforeEach(() => {
101
- localStorage.clear()
102
- })
103
-
104
- describe('SelectFilter', () => {
105
- const store = createSearchStore({
106
- key: 'c-select',
107
- fields: { currency: field.enum(['USD', 'EUR'], 'USD') },
108
- })
109
-
110
- test('reads the SELECTED option at the fallback too, and writes the URL', async () => {
111
- // The pill is a READOUT, so it prints the value at every value — including the field's default.
112
- // It used to print the filter's NAME while `isDefault` held, which is how the playground's bar
113
- // read `Compare` over a field holding `'previous'`. `data-active` is the channel that says
114
- // "touched", and it is still the only one.
115
- const router = await mountPage({
116
- validateSearch: store.validateSearch,
117
- entry: '/dashboard',
118
- Page: () => <SelectFilter field={store.field.currency} label="Currency" />,
119
- })
120
-
121
- const pill = screen.getByRole('button', { name: 'USD' })
122
- expect(pill.hasAttribute('data-active')).toBe(false)
123
- expect(screen.queryByRole('button', { name: 'Currency' })).toBeNull()
124
-
125
- await openPill('USD')
126
- fireEvent.click(screen.getByRole('radio', { name: 'EUR', hidden: true }))
127
-
128
- await waitFor(() => {
129
- expect(search(router)['currency']).toBe('EUR')
130
- })
131
- const active = screen.getByRole('button', { name: 'EUR' })
132
- expect(active.hasAttribute('data-active')).toBe(true)
133
- })
134
-
135
- test('the popover names the FILTER, not the value the pill happens to read', async () => {
136
- // Without this the dropdown announces 'EUR, radio button, 1 of 2' and never the word Currency:
137
- // the pill's visible text is the VALUE once set, so it cannot be the group's name.
138
- await mountPage({
139
- validateSearch: store.validateSearch,
140
- entry: '/dashboard?currency=EUR',
141
- Page: () => <SelectFilter field={store.field.currency} label="Currency" />,
142
- })
143
- await openPill('EUR')
144
- expect(screen.getByRole('radiogroup', { name: 'Currency', hidden: true })).toBeDefined()
145
- })
146
-
147
- test('a deep link is what the pill reads, with no interaction at all', async () => {
148
- await mountPage({
149
- validateSearch: store.validateSearch,
150
- entry: '/dashboard?currency=EUR',
151
- Page: () => <SelectFilter field={store.field.currency} label="Currency" />,
152
- })
153
- expect(screen.getByRole('button', { name: 'EUR' })).toBeDefined()
154
- })
155
- })
156
-
157
- /**
158
- * Every reset a control owns — the `FilterSet` registration and the popover's own `Clear` — calls
159
- * `field.clear()`, never `setValue(field.fallback)`.
160
- *
161
- * On the URL lane the two look identical (`clear()` navigates back to the fallback params, so the
162
- * search object reads the same either way) and they differ in the MIRROR: writing the fallback
163
- * PERSISTS it, as if the user had chosen the default, which then outranks a later change to the
164
- * field's own default and pins a thunk fallback outright. Clearing removes the key.
165
- */
166
- describe('a control resets by CLEARING its field, never by writing the fallback back', () => {
167
- const store = createSearchStore({
168
- key: 'c-reset-clear',
169
- fields: {
170
- currency: field.enum(['USD', 'EUR'], 'USD'),
171
- // A SECOND child, so one folds on mobile and `FilterSet` renders the sheet that owns
172
- // `Reset all` at all — with a single child there is nothing to fold and no sheet.
173
- region: field.enum(['all', 'eu'], 'all'),
174
- },
175
- })
176
-
177
- /** Did the mirror keep a value for `currency`? */
178
- function mirrored(): boolean {
179
- return Object.hasOwn(store.readStored(), 'currency')
180
- }
181
-
182
- test("Reset all drops the mirror key and leaves the URL on the field's fallback", async () => {
183
- const router = await mountPage({
184
- validateSearch: store.validateSearch,
185
- entry: '/dashboard',
186
- Page: () => (
187
- <FilterSet>
188
- <SelectFilter field={store.field.currency} label="Currency" />
189
- <SelectFilter field={store.field.region} label="Region" />
190
- </FilterSet>
191
- ),
192
- })
193
-
194
- await openPill('USD')
195
- await act(async () => {
196
- fireEvent.click(screen.getByRole('radio', { name: 'EUR', hidden: true }))
197
- })
198
- await waitFor(() => {
199
- expect(search(router)['currency']).toBe('EUR')
200
- })
201
- // The write persisted it — which is what makes the reset's effect on the mirror observable.
202
- expect(mirrored()).toBe(true)
203
-
204
- // `hidden: true` — the currency popover is still open, and its portal leaves the rest of the
205
- // document out of the accessibility tree (the same harness fact every in-dropdown query here
206
- // carries). The count is registry state, so it lands a flush after the write.
207
- const sheetPill = await waitFor(() =>
208
- screen.getByRole('button', { name: 'Filters (1)', hidden: true }),
209
- )
210
- await act(async () => {
211
- fireEvent.click(sheetPill)
212
- })
213
- await act(async () => {
214
- fireEvent.click(screen.getByRole('button', { name: 'Reset all', hidden: true }))
215
- })
216
-
217
- await waitFor(() => {
218
- expect(search(router)['currency']).toBe('USD')
219
- })
220
- expect(mirrored()).toBe(false)
221
- })
222
-
223
- test("the popover's own Clear action clears too", async () => {
224
- const router = await mountPage({
225
- validateSearch: store.validateSearch,
226
- entry: '/dashboard',
227
- Page: () => <SelectFilter field={store.field.currency} label="Currency" clearable />,
228
- })
229
-
230
- await openPill('USD')
231
- fireEvent.click(screen.getByRole('radio', { name: 'EUR', hidden: true }))
232
- await waitFor(() => {
233
- expect(mirrored()).toBe(true)
234
- })
235
-
236
- await openPill('EUR')
237
- await act(async () => {
238
- fireEvent.click(screen.getByRole('button', { name: 'Clear', hidden: true }))
239
- })
240
-
241
- await waitFor(() => {
242
- expect(search(router)['currency']).toBe('USD')
243
- })
244
- expect(mirrored()).toBe(false)
245
- })
246
- })
247
-
248
- /**
249
- * The runtime-catalogue shape: `options` overrides the field's own rows, and a `StringField` handle
250
- * becomes legal once it does. Both halves are asserted through a REAL `createSearchStore`, because
251
- * the string lane is where a hand-rolled `value`/`onChange` would look identical and write nothing.
252
- */
253
- describe('SelectFilter — options as a runtime catalogue', () => {
254
- const store = createSearchStore({
255
- key: 'c-select-options',
256
- fields: { currency: field.enum(['USD', 'EUR'], 'USD'), projectId: field.string() },
257
- })
258
-
259
- const RATES = [
260
- { value: 'USD', label: 'USD · 1.00' },
261
- { value: 'EUR', label: 'EUR · 1.08' },
262
- ]
263
- const PROJECTS = [
264
- { value: 'argo', label: 'Argo' },
265
- { value: 'linewatch', label: 'Linewatch' },
266
- { value: 'retired', label: 'Retired app', disabled: true },
267
- ]
268
-
269
- test('the rows and their labels come from the prop, and the field is still what is written', async () => {
270
- const router = await mountPage({
271
- validateSearch: store.validateSearch,
272
- entry: '/dashboard',
273
- Page: () => <SelectFilter field={store.field.currency} label="Currency" options={RATES} />,
274
- })
275
-
276
- // The pill reads the catalogue's label for the CURRENT value, fallback included — `USD · 1.00`,
277
- // never `Currency`.
278
- await openPill('USD · 1.00')
279
- // The override is whole, not a merge: the field's own bare `EUR` row is GONE, which is the
280
- // property a merge would silently break.
281
- expect(screen.queryByRole('radio', { name: 'EUR', hidden: true })).toBeNull()
282
- fireEvent.click(screen.getByRole('radio', { name: 'EUR · 1.08', hidden: true }))
283
-
284
- await waitFor(() => {
285
- expect(search(router)['currency']).toBe('EUR')
286
- })
287
- expect(screen.getByRole('button', { name: 'EUR · 1.08' })).toBeDefined()
288
- })
289
-
290
- test('a string field plus options round-trips an id no enum could have declared', async () => {
291
- const router = await mountPage({
292
- validateSearch: store.validateSearch,
293
- entry: '/dashboard',
294
- Page: () => <SelectFilter field={store.field.projectId} label="Project" options={PROJECTS} />,
295
- })
296
-
297
- expect(screen.getByRole('button', { name: 'Project' })).toBeDefined()
298
- await openPill('Project')
299
- fireEvent.click(screen.getByRole('radio', { name: 'Argo', hidden: true }))
300
-
301
- await waitFor(() => {
302
- expect(search(router)['projectId']).toBe('argo')
303
- })
304
- expect(screen.getByRole('button', { name: 'Argo' })).toBeDefined()
305
- })
306
-
307
- test('a deep link into that id set is read with no interaction at all', async () => {
308
- await mountPage({
309
- validateSearch: store.validateSearch,
310
- entry: '/dashboard?projectId=linewatch',
311
- Page: () => <SelectFilter field={store.field.projectId} label="Project" options={PROJECTS} />,
312
- })
313
- expect(screen.getByRole('button', { name: 'Linewatch' })).toBeDefined()
314
- })
315
-
316
- test('a disabled catalogue row is offered and refused — it still labels the value it names', async () => {
317
- await mountPage({
318
- validateSearch: store.validateSearch,
319
- entry: '/dashboard?projectId=retired',
320
- Page: () => <SelectFilter field={store.field.projectId} label="Project" options={PROJECTS} />,
321
- })
322
- // A live catalogue cannot express "archived" by omitting the row — the URL may already point at
323
- // it, and a missing row would read as the filter's default.
324
- expect(screen.getByRole('button', { name: 'Retired app' })).toBeDefined()
325
- await openPill('Retired app')
326
- expect(
327
- screen.getByRole('radio', { name: 'Retired app', hidden: true }).hasAttribute('disabled'),
328
- ).toBe(true)
329
- })
330
- })
331
-
332
- describe('CompareFilter', () => {
333
- const store = createSearchStore({
334
- key: 'c-compare',
335
- fields: { compare: field.enum(['none', 'previous', 'year'], 'none') },
336
- }).labels({ compare: { none: 'No comparison', previous: 'Previous period', year: 'Last year' } })
337
-
338
- test('reads the selected basis at the fallback too, and writes the chosen one', async () => {
339
- const router = await mountPage({
340
- validateSearch: store.validateSearch,
341
- entry: '/dashboard',
342
- Page: () => <CompareFilter field={store.field.compare} />,
343
- })
344
-
345
- // At the fallback the pill reads `No comparison`, not `Compare` — the exact regression the
346
- // playground's bar showed, where the pill said `Compare` while the URL said `previous`.
347
- await openPill('No comparison')
348
- fireEvent.click(screen.getByRole('radio', { name: 'Previous period', hidden: true }))
349
-
350
- await waitFor(() => {
351
- expect(search(router)['compare']).toBe('previous')
352
- })
353
- expect(screen.getByRole('button', { name: 'Previous period' })).toBeDefined()
354
- expect(screen.queryByRole('button', { name: 'Compare' })).toBeNull()
355
- })
356
-
357
- test('a consumer label wins over basalt’s default for the same value', async () => {
358
- // This store labels `year` as `Last year`. basalt's default is `Same period last year`; a
359
- // deliberate label is not something the framework overrides.
360
- await mountPage({
361
- validateSearch: store.validateSearch,
362
- entry: '/dashboard?compare=year',
363
- Page: () => <CompareFilter field={store.field.compare} />,
364
- })
365
- expect(screen.getByRole('button', { name: 'Last year' })).toBeDefined()
366
- })
367
-
368
- test('label= still names the POPOVER, which is the only thing it names now', async () => {
369
- await mountPage({
370
- validateSearch: store.validateSearch,
371
- entry: '/dashboard',
372
- Page: () => <CompareFilter field={store.field.compare} />,
373
- })
374
- await openPill('No comparison')
375
- expect(screen.getByRole('radiogroup', { name: 'Compare', hidden: true })).toBeDefined()
376
- })
377
- })
378
-
379
- describe('CompareFilter — the three option labels are basalt’s, not each app’s', () => {
380
- // An UNLABELLED field: `field.enum` labels each option with the raw value until `store.labels()`
381
- // is called, so this popover used to read `none` / `previous` / `year` and every consumer wrote
382
- // the same three strings itself. `COMPARE_LABELS` is the default the control now supplies.
383
- const store = createSearchStore({
384
- key: 'c-compare-unlabelled',
385
- fields: { compare: field.enum(['none', 'previous', 'year'], 'none') },
386
- })
387
-
388
- test('an unlabelled field still renders the three spec’d strings', async () => {
389
- await mountPage({
390
- validateSearch: store.validateSearch,
391
- entry: '/dashboard',
392
- Page: () => <CompareFilter field={store.field.compare} />,
393
- })
394
-
395
- expect(COMPARE_LABELS).toEqual({
396
- none: 'No comparison',
397
- previous: 'Previous period',
398
- year: 'Same period last year',
399
- })
400
-
401
- await openPill('No comparison')
402
- for (const value of COMPARE_VALUES) {
403
- expect(screen.getByRole('radio', { name: COMPARE_LABELS[value], hidden: true })).toBeDefined()
404
- }
405
- expect(screen.queryByRole('radio', { name: 'previous', hidden: true })).toBeNull()
406
- })
407
- })
408
-
409
- describe('MultiSelectFilter', () => {
410
- const store = createSearchStore({
411
- key: 'c-multi',
412
- fields: { channels: field.multi(['web', 'email', 'social'], []) },
413
- })
414
-
415
- test('reads the label while the selection carries no information, then a count', async () => {
416
- const router = await mountPage({
417
- validateSearch: store.validateSearch,
418
- entry: '/dashboard',
419
- Page: () => (
420
- <MultiSelectFilter field={store.field.channels} label="All channels" noun="channels" />
421
- ),
422
- })
423
-
424
- expect(screen.getByRole('button', { name: 'All channels' })).toBeDefined()
425
-
426
- await openPill('All channels')
427
- fireEvent.click(screen.getByRole('checkbox', { name: 'web', hidden: true }))
428
-
429
- await waitFor(() => {
430
- expect(search(router)['channels']).toEqual(['web'])
431
- })
432
- expect(screen.getByRole('button', { name: '1 channels' })).toBeDefined()
433
- })
434
-
435
- test('every option selected is the same information as none — the label comes back', async () => {
436
- await mountPage({
437
- validateSearch: store.validateSearch,
438
- entry: '/dashboard?channels=web&channels=email&channels=social',
439
- Page: () => <MultiSelectFilter field={store.field.channels} label="All channels" />,
440
- })
441
- expect(screen.getByRole('button', { name: 'All channels' })).toBeDefined()
442
- })
443
-
444
- test("options relabels the boxes while the values stay the field's own", async () => {
445
- const router = await mountPage({
446
- validateSearch: store.validateSearch,
447
- entry: '/dashboard',
448
- Page: () => (
449
- <MultiSelectFilter
450
- field={store.field.channels}
451
- label="All channels"
452
- noun="channels"
453
- options={[
454
- { value: 'web', label: 'web · 1.2k' },
455
- { value: 'email', label: 'email · 340' },
456
- ]}
457
- />
458
- ),
459
- })
460
-
461
- await openPill('All channels')
462
- fireEvent.click(screen.getByRole('checkbox', { name: 'web · 1.2k', hidden: true }))
463
-
464
- await waitFor(() => {
465
- expect(search(router)['channels']).toEqual(['web'])
466
- })
467
- // Two rows shown, not the field's three — so `1 channels` is counted against what is offered.
468
- expect(screen.getByRole('button', { name: '1 channels' })).toBeDefined()
469
- })
470
- })
471
-
472
- describe('ToggleFilter', () => {
473
- const store = createSearchStore({
474
- key: 'c-toggle',
475
- fields: { errorsOnly: field.boolean(false) },
476
- })
477
-
478
- test('the pill IS the control — one press writes the field, no popover', async () => {
479
- const router = await mountPage({
480
- validateSearch: store.validateSearch,
481
- entry: '/dashboard',
482
- Page: () => <ToggleFilter field={store.field.errorsOnly} label="Errors only" />,
483
- })
484
-
485
- fireEvent.click(screen.getByRole('button', { name: 'Errors only' }))
486
- await waitFor(() => {
487
- expect(search(router)['errorsOnly']).toBe(true)
488
- })
489
- expect(screen.getByRole('button', { name: 'Errors only' }).hasAttribute('data-active')).toBe(
490
- true,
491
- )
492
- })
493
-
494
- test('the on/off state is announced, not only drawn as a border colour', async () => {
495
- const router = await mountPage({
496
- validateSearch: store.validateSearch,
497
- entry: '/dashboard',
498
- Page: () => <ToggleFilter field={store.field.errorsOnly} label="Errors only" />,
499
- })
500
-
501
- expect(screen.getByRole('button', { name: 'Errors only', pressed: false })).toBeDefined()
502
- fireEvent.click(screen.getByRole('button', { name: 'Errors only' }))
503
- await waitFor(() => {
504
- expect(search(router)['errorsOnly']).toBe(true)
505
- })
506
- expect(screen.getByRole('button', { name: 'Errors only', pressed: true })).toBeDefined()
507
- })
508
- })
509
-
510
- describe('RangeFilter', () => {
511
- const store = createSearchStore({
512
- key: 'c-range',
513
- fields: {
514
- range: field.range({ presets: ['7d', '30d', '90d'], fallback: '30d', custom: true }),
515
- },
516
- })
517
-
518
- test('the pill reads the preset label and a press writes the preset param', async () => {
519
- const router = await mountPage({
520
- validateSearch: store.validateSearch,
521
- entry: '/dashboard',
522
- Page: () => <RangeFilter field={store.field.range} customPicker={Picker} />,
523
- })
524
-
525
- expect(screen.getByRole('button', { name: '30d' })).toBeDefined()
526
-
527
- await openPill('30d')
528
- fireEvent.click(screen.getByRole('radio', { name: '7d', hidden: true }))
529
-
530
- await waitFor(() => {
531
- expect(search(router)['range']).toBe('7d')
532
- })
533
- })
534
-
535
- test('the pill never reads `label`, at the fallback or anywhere else', async () => {
536
- // Same law as `SelectFilter`/`CompareFilter`: `label` is the popover heading and the accessible
537
- // name, and the pill text is the value. `30d` here IS the fallback.
538
- await mountPage({
539
- validateSearch: store.validateSearch,
540
- entry: '/dashboard',
541
- Page: () => <RangeFilter field={store.field.range} label="Window" customPicker={Picker} />,
542
- })
543
- expect(screen.getByRole('button', { name: '30d' })).toBeDefined()
544
- expect(screen.queryByRole('button', { name: 'Window' })).toBeNull()
545
- })
546
-
547
- test('the preset track carries the filter name — a radiogroup must not be anonymous', async () => {
548
- await mountPage({
549
- validateSearch: store.validateSearch,
550
- entry: '/dashboard',
551
- Page: () => <RangeFilter field={store.field.range} label="Window" />,
552
- })
553
- await openPill('30d')
554
- expect(screen.getByRole('radiogroup', { name: 'Window', hidden: true })).toBeDefined()
555
- })
556
-
557
- test('numeric preset labels get the mono treatment via data-numeric', async () => {
558
- await mountPage({
559
- validateSearch: store.validateSearch,
560
- entry: '/dashboard',
561
- Page: () => <RangeFilter field={store.field.range} />,
562
- })
563
- expect(screen.getByRole('button', { name: '30d' }).hasAttribute('data-numeric')).toBe(true)
564
- })
565
-
566
- test('the injected picker writes preset=custom plus both ISO dates, and the pill reads the span', async () => {
567
- const router = await mountPage({
568
- validateSearch: store.validateSearch,
569
- entry: '/dashboard',
570
- Page: () => <RangeFilter field={store.field.range} customPicker={Picker} />,
571
- })
572
-
573
- await openPill('30d')
574
- fireEvent.click(screen.getByRole('button', { name: 'pick march', hidden: true }))
575
-
576
- await waitFor(() => {
577
- expect(search(router)['range']).toBe('custom')
578
- })
579
- expect(search(router)['from']).toBe('2026-03-01')
580
- expect(search(router)['to']).toBe('2026-03-14')
581
- expect(screen.getByRole('button', { name: /Mar 1/ }).textContent).toContain('Mar 1 – Mar 14')
582
- })
583
-
584
- test('no picker is injected → no custom affordance, presets only', async () => {
585
- await mountPage({
586
- validateSearch: store.validateSearch,
587
- entry: '/dashboard',
588
- Page: () => <RangeFilter field={store.field.range} />,
589
- })
590
- await openPill('30d')
591
- expect(screen.queryByRole('button', { name: 'pick march', hidden: true })).toBeNull()
592
- expect(screen.queryByRole('radio', { name: 'custom', hidden: true })).toBeNull()
593
- })
594
- })
595
-
596
- describe('SearchFilter', () => {
597
- const store = createSearchStore({
598
- key: 'c-search',
599
- fields: { q: field.string({ max: 40 }) },
600
- })
601
-
602
- test('keystrokes land on the field only after the debounce', async () => {
603
- const router = await mountPage({
604
- validateSearch: store.validateSearch,
605
- entry: '/dashboard',
606
- Page: () => <SearchFilter field={store.field.q} placeholder="Search" />,
607
- })
608
-
609
- fireEvent.change(screen.getByPlaceholderText('Search'), { target: { value: 'invo' } })
610
- // Same tick: the box shows it, the URL does not.
611
- expect((screen.getByPlaceholderText('Search') as HTMLInputElement).value).toBe('invo')
612
- expect(search(router)['q']).toBe('')
613
-
614
- await waitFor(() => {
615
- expect(search(router)['q']).toBe('invo')
616
- })
617
- })
618
-
619
- test('the box is named even with no placeholder — and stays named while typing', async () => {
620
- await mountPage({
621
- validateSearch: store.validateSearch,
622
- entry: '/dashboard',
623
- Page: () => <SearchFilter field={store.field.q} />,
624
- })
625
- expect(screen.getByRole('textbox', { name: 'Search' })).toBeDefined()
626
- })
627
- })
628
-
629
- /**
630
- * `ViewTabs` — the CSS swap, asserted rather than assumed. `MantineProvider` injects the
631
- * `visible-from`/`hidden-from` media rules as a real stylesheet, and happy-dom evaluates them at its
632
- * default (desktop) viewport — so the DESKTOP form is in the accessibility tree and the phone form is
633
- * `display: none`. `{ hidden: true }` counts what is MOUNTED, the bare query counts what is
634
- * REACHABLE, and the pair is what proves "one mount each, switched by CSS" (C9).
635
- */
636
- describe('ViewTabs', () => {
637
- test('three options or fewer: both forms are tracks, one mounted each, only one reachable', async () => {
638
- const store = createSearchStore({
639
- key: 'c-tabs-3',
640
- fields: { tab: field.enum(['a', 'b', 'c'], 'a') },
641
- })
642
- const router = await mountPage({
643
- validateSearch: store.validateSearch,
644
- entry: '/dashboard',
645
- Page: () => <ViewTabs field={store.field.tab} />,
646
- })
647
-
648
- expect(screen.getAllByRole('radio', { name: 'b', hidden: true })).toHaveLength(2)
649
- expect(screen.getAllByRole('radio', { name: 'b' })).toHaveLength(1)
650
- expect(screen.queryByRole('combobox', { hidden: true })).toBeNull()
651
-
652
- fireEvent.click(screen.getByRole('radio', { name: 'b' }))
653
- await waitFor(() => {
654
- expect(search(router)['tab']).toBe('b')
655
- })
656
- })
657
-
658
- test('past three options the phone form becomes a Select, not a track', async () => {
659
- const store = createSearchStore({
660
- key: 'c-tabs-4',
661
- fields: { tab: field.enum(['a', 'b', 'c', 'd'], 'a') },
662
- })
663
- await mountPage({
664
- validateSearch: store.validateSearch,
665
- entry: '/dashboard',
666
- Page: () => <ViewTabs field={store.field.tab} />,
667
- })
668
-
669
- expect(screen.getAllByRole('radio', { name: 'b', hidden: true })).toHaveLength(1)
670
- expect(screen.getByRole('combobox', { hidden: true })).toBeDefined()
671
- })
672
-
673
- test('both forms carry an accessible name — neither is an anonymous radiogroup/combobox', async () => {
674
- const store = createSearchStore({
675
- key: 'c-tabs-named',
676
- fields: { tab: field.enum(['a', 'b', 'c', 'd'], 'a') },
677
- })
678
- await mountPage({
679
- validateSearch: store.validateSearch,
680
- entry: '/dashboard',
681
- Page: () => <ViewTabs field={store.field.tab} label="Section" />,
682
- })
683
-
684
- expect(screen.getByRole('radiogroup', { name: 'Section', hidden: true })).toBeDefined()
685
- expect(screen.getByRole('combobox', { name: 'Section', hidden: true })).toBeDefined()
686
- })
687
-
688
- test("the name defaults to 'View' rather than being absent", async () => {
689
- const store = createSearchStore({
690
- key: 'c-tabs-default-name',
691
- fields: { tab: field.enum(['a', 'b'], 'a') },
692
- })
693
- await mountPage({
694
- validateSearch: store.validateSearch,
695
- entry: '/dashboard',
696
- Page: () => <ViewTabs field={store.field.tab} />,
697
- })
698
- // Read off the attribute rather than through the accessible-name query: the phone twin is
699
- // `display: none` on this viewport, and `dom-accessibility-api` declines to compute a name for a
700
- // hidden `radiogroup` — which would make the assertion pass for the wrong reason.
701
- const groups = screen.getAllByRole('radiogroup', { hidden: true })
702
- expect(groups).toHaveLength(2)
703
- expect(groups.map((group) => group.getAttribute('aria-label'))).toEqual(['View', 'View'])
704
- })
705
-
706
- test("only: 'sm-down' keeps an option off the desktop form entirely", async () => {
707
- const store = createSearchStore({
708
- key: 'c-tabs-only',
709
- fields: { tab: field.enum(['overview', 'train'], 'overview') },
710
- })
711
- await mountPage({
712
- validateSearch: store.validateSearch,
713
- entry: '/dashboard',
714
- Page: () => (
715
- <ViewTabs
716
- field={store.field.tab}
717
- options={[
718
- { value: 'overview', label: 'Overview' },
719
- { value: 'train', label: 'Train', only: 'sm-down' },
720
- ]}
721
- />
722
- ),
723
- })
724
-
725
- expect(screen.getAllByRole('radio', { name: 'Overview', hidden: true })).toHaveLength(2)
726
- expect(screen.getAllByRole('radio', { name: 'Train', hidden: true })).toHaveLength(1)
727
- // And the one that exists is the PHONE one — unreachable on this viewport.
728
- expect(screen.queryAllByRole('radio', { name: 'Train' })).toHaveLength(0)
729
- })
730
- })
731
-
732
- /**
733
- * `NumberFilter` — the numeric lane, and the one control whose two forms are genuinely different
734
- * components. `options` renders the same radio body every enum filter renders (through
735
- * `EnumFilter`'s `ChoiceHandle`), so what needs asserting there is that the URL still holds a
736
- * NUMBER; without `options` it is a stepper, and what needs asserting is WHEN a keystroke becomes a
737
- * navigation.
738
- */
739
- describe('NumberFilter — the options form', () => {
740
- const store = createSearchStore({
741
- key: 'c-number-options',
742
- fields: { nights: field.number({ fallback: 2, min: 1, max: 14, int: true }) },
743
- })
744
-
745
- const NIGHTS = [
746
- { value: 1, label: '1 night' },
747
- { value: 2, label: '2 nights' },
748
- { value: 7, label: 'A week' },
749
- ]
750
-
751
- test('the pill reads the selected label and the URL keeps a NUMBER, not a numeral string', async () => {
752
- const router = await mountPage({
753
- validateSearch: store.validateSearch,
754
- entry: '/dashboard',
755
- Page: () => <NumberFilter field={store.field.nights} label="Nights" options={NIGHTS} />,
756
- })
757
-
758
- const pill = screen.getByRole('button', { name: '2 nights' })
759
- expect(pill.hasAttribute('data-active')).toBe(false)
760
- // Mono, because the values ARE numbers — the same law a numeric SegmentedControl label follows.
761
- expect(pill.hasAttribute('data-numeric')).toBe(true)
762
-
763
- await openPill('2 nights')
764
- fireEvent.click(screen.getByRole('radio', { name: 'A week', hidden: true }))
765
-
766
- await waitFor(() => {
767
- // The whole reason this control exists: argo widened `nights` into a string enum and got
768
- // `'7'` in the URL, which makes every downstream comparison a parse.
769
- expect(search(router)['nights']).toBe(7)
770
- })
771
- expect(typeof search(router)['nights']).toBe('number')
772
- expect(screen.getByRole('button', { name: 'A week' }).hasAttribute('data-active')).toBe(true)
773
- })
774
-
775
- test('the popover names the FILTER — the pill text is the value', async () => {
776
- await mountPage({
777
- validateSearch: store.validateSearch,
778
- entry: '/dashboard?nights=7',
779
- Page: () => <NumberFilter field={store.field.nights} label="Nights" options={NIGHTS} />,
780
- })
781
- await openPill('A week')
782
- expect(screen.getByRole('radiogroup', { name: 'Nights', hidden: true })).toBeDefined()
783
- })
784
-
785
- test('a deep link is what the pill reads, with no interaction', async () => {
786
- await mountPage({
787
- validateSearch: store.validateSearch,
788
- entry: '/dashboard?nights=1',
789
- Page: () => <NumberFilter field={store.field.nights} label="Nights" options={NIGHTS} />,
790
- })
791
- expect(screen.getByRole('button', { name: '1 night' })).toBeDefined()
792
- })
793
- })
794
-
795
- describe('NumberFilter — the stepper form', () => {
796
- const store = createSearchStore({
797
- key: 'c-number-stepper',
798
- fields: { minDuration: field.number({ fallback: 0, min: 0, max: 600 }) },
799
- })
800
-
801
- function box(): HTMLInputElement {
802
- return screen.getByRole('textbox', { name: 'Min duration', hidden: true }) as HTMLInputElement
803
- }
804
-
805
- test('typing does not navigate; blur is what commits', async () => {
806
- const router = await mountPage({
807
- validateSearch: store.validateSearch,
808
- entry: '/dashboard',
809
- Page: () => <NumberFilter field={store.field.minDuration} label="Min duration" step={30} />,
810
- })
811
-
812
- await openPill('0')
813
- fireEvent.change(box(), { target: { value: '120' } })
814
- // Same tick: the box shows it, the URL does not. An un-gated write would have navigated on
815
- // `1`, `12` and `120` — three loader runs for one intended threshold, two of them values the
816
- // user never meant.
817
- expect(box().value).toBe('120')
818
- expect(search(router)['minDuration']).toBe(0)
819
-
820
- fireEvent.blur(box())
821
- await waitFor(() => {
822
- expect(search(router)['minDuration']).toBe(120)
823
- })
824
- })
825
-
826
- test('Enter commits without leaving the field', async () => {
827
- const router = await mountPage({
828
- validateSearch: store.validateSearch,
829
- entry: '/dashboard',
830
- Page: () => <NumberFilter field={store.field.minDuration} label="Min duration" />,
831
- })
832
-
833
- await openPill('0')
834
- fireEvent.change(box(), { target: { value: '45' } })
835
- fireEvent.keyDown(box(), { key: 'Enter' })
836
- await waitFor(() => {
837
- expect(search(router)['minDuration']).toBe(45)
838
- })
839
- })
840
-
841
- test('the field CLAMPS and the box follows it down — never a readout over a different URL', async () => {
842
- const router = await mountPage({
843
- validateSearch: store.validateSearch,
844
- entry: '/dashboard',
845
- Page: () => <NumberFilter field={store.field.minDuration} label="Min duration" />,
846
- })
847
-
848
- await openPill('0')
849
- fireEvent.change(box(), { target: { value: '9999' } })
850
- fireEvent.blur(box())
851
-
852
- await waitFor(() => {
853
- expect(search(router)['minDuration']).toBe(600)
854
- })
855
- // The bound is the FIELD's, applied on write — so the input has to accept the correction rather
856
- // than keep displaying a value the app is not filtering by.
857
- await waitFor(() => {
858
- expect(box().value).toBe('600')
859
- })
860
- })
861
-
862
- /**
863
- * The SECOND out-of-range commit is the one that used to desync, and it desynced because nothing
864
- * moved: the store already held `600`, so the write clamped to the value it was already at, the
865
- * `[value]` effect never fired, and the box went on reading `8888` over a URL filtering at 600 —
866
- * the exact readout `useCommittedNumber` exists to prevent. `commit` now clamps locally first, so
867
- * the draft follows the bound whether or not the store value changes.
868
- */
869
- test('a REPEATED out-of-range Enter still leaves the box on the clamped value', async () => {
870
- const router = await mountPage({
871
- validateSearch: store.validateSearch,
872
- entry: '/dashboard',
873
- Page: () => <NumberFilter field={store.field.minDuration} label="Min duration" />,
874
- })
875
-
876
- await openPill('0')
877
- fireEvent.change(box(), { target: { value: '9999' } })
878
- fireEvent.keyDown(box(), { key: 'Enter' })
879
- await waitFor(() => {
880
- expect(search(router)['minDuration']).toBe(600)
881
- })
882
- await waitFor(() => {
883
- expect(box().value).toBe('600')
884
- })
885
-
886
- // An intermediate in-range keystroke, then a second out-of-range one: react-number-format dedups
887
- // an identical string, so re-firing `9999` in happy-dom would not register as a change at all.
888
- // A real browser needs no such help — the digits arrive one keystroke at a time.
889
- fireEvent.change(box(), { target: { value: '500' } })
890
- fireEvent.change(box(), { target: { value: '8888' } })
891
- fireEvent.keyDown(box(), { key: 'Enter' })
892
-
893
- expect(box().value).toBe('600')
894
- expect(search(router)['minDuration']).toBe(600)
895
- })
896
-
897
- test('an unparseable draft restores the field rather than committing NaN', async () => {
898
- const router = await mountPage({
899
- validateSearch: store.validateSearch,
900
- entry: '/dashboard?minDuration=90',
901
- Page: () => <NumberFilter field={store.field.minDuration} label="Min duration" />,
902
- })
903
-
904
- await openPill('90')
905
- fireEvent.change(box(), { target: { value: '' } })
906
- fireEvent.blur(box())
907
- expect(box().value).toBe('90')
908
- expect(search(router)['minDuration']).toBe(90)
909
- })
910
-
911
- /**
912
- * The bounds now come off the HANDLE (`NumberHandleExtras`), so the STEPPER stops at the field's
913
- * limit instead of letting the codec correct the value one commit later. Asserted through the
914
- * arrow keys rather than a `min`/`max` DOM attribute: Mantine's `NumberInput` is a text input
915
- * (`inputmode=decimal`) and clamps in JS, so the attributes do not exist to read — the observable
916
- * is that stepping cannot leave the range.
917
- *
918
- * The clamp test above still stands. It is the backstop for a value that never came through this
919
- * box at all (a hand-typed URL, a stale deep link).
920
- */
921
- test('the field s min stops the stepper — no `min` prop reached the input before this', async () => {
922
- await mountPage({
923
- validateSearch: store.validateSearch,
924
- entry: '/dashboard',
925
- Page: () => <NumberFilter field={store.field.minDuration} label="Min duration" step={30} />,
926
- })
927
- await openPill('0')
928
- // At the fallback 0 against `min: 0`. Without the handle's bound this would read `-30`, and a
929
- // negative threshold is a value the codec would then clamp back — visibly, after the fact.
930
- fireEvent.keyDown(box(), { key: 'ArrowDown' })
931
- expect(box().value).toBe(String(store.field.minDuration.min))
932
- })
933
-
934
- test('the field s max stops it at the other end', async () => {
935
- await mountPage({
936
- validateSearch: store.validateSearch,
937
- entry: '/dashboard?minDuration=590',
938
- Page: () => <NumberFilter field={store.field.minDuration} label="Min duration" step={30} />,
939
- })
940
- await openPill('590')
941
- // 590 + 30 would be 620; the field declares `max: 600`.
942
- fireEvent.keyDown(box(), { key: 'ArrowUp' })
943
- expect(box().value).toBe(String(store.field.minDuration.max))
944
- })
945
-
946
- test('the explicit step is the one the stepper uses', async () => {
947
- await mountPage({
948
- validateSearch: store.validateSearch,
949
- entry: '/dashboard',
950
- Page: () => <NumberFilter field={store.field.minDuration} label="Min duration" step={30} />,
951
- })
952
- await openPill('0')
953
- fireEvent.keyDown(box(), { key: 'ArrowUp' })
954
- expect(box().value).toBe('30')
955
- })
956
-
957
- test('an int field refuses a decimal and steps by 1 with no step prop at all', async () => {
958
- const ints = createSearchStore({
959
- key: 'c-number-int',
960
- fields: { nights: field.number({ fallback: 2, min: 1, max: 14, int: true }) },
961
- })
962
- const router = await mountPage({
963
- validateSearch: ints.validateSearch,
964
- entry: '/dashboard',
965
- Page: () => <NumberFilter field={ints.field.nights} label="Nights" />,
966
- })
967
-
968
- const input = () =>
969
- screen.getByRole('textbox', { name: 'Nights', hidden: true }) as HTMLInputElement
970
- await openPill('2')
971
-
972
- // `int` also picks the GRAIN: no `step` prop, and the field's own declaration produces 1.
973
- fireEvent.keyDown(input(), { key: 'ArrowUp' })
974
- expect(input().value).toBe('3')
975
-
976
- // `allowDecimal={false}` — Mantine drops the separator rather than accepting a value the codec
977
- // would decode to `null`, which would silently resurrect the fallback.
978
- fireEvent.change(input(), { target: { value: '3.5' } })
979
- expect(input().value).not.toContain('.')
980
- fireEvent.blur(input())
981
- await waitFor(() => {
982
- expect(Number.isInteger(search(router)['nights'])).toBe(true)
983
- })
984
- })
985
-
986
- test('the pill reads the value and marks itself active off the fallback', async () => {
987
- await mountPage({
988
- validateSearch: store.validateSearch,
989
- entry: '/dashboard?minDuration=90',
990
- Page: () => <NumberFilter field={store.field.minDuration} label="Min duration" />,
991
- })
992
- const pill = screen.getByRole('button', { name: '90' })
993
- expect(pill.hasAttribute('data-active')).toBe(true)
994
- expect(pill.hasAttribute('data-numeric')).toBe(true)
995
- })
996
- })
997
-
998
- /**
999
- * The SHEET form of both branches, through a real `FilterSet` — the surface is chosen by context,
1000
- * never by a media query (C9), so this is the only way to reach it.
1001
- */
1002
- describe('NumberFilter — the sheet form', () => {
1003
- const store = createSearchStore({
1004
- key: 'c-number-sheet',
1005
- fields: {
1006
- nights: field.number({ fallback: 2, min: 1, max: 14, int: true }),
1007
- minDuration: field.number({ fallback: 0, min: 0, max: 600 }),
1008
- },
1009
- })
1010
-
1011
- async function openSheet(): Promise<void> {
1012
- await mountPage({
1013
- validateSearch: store.validateSearch,
1014
- entry: '/dashboard',
1015
- Page: () => (
1016
- <FilterSet>
1017
- <NumberFilter
1018
- field={store.field.nights}
1019
- label="Nights"
1020
- options={[
1021
- { value: 1, label: '1 night' },
1022
- { value: 2, label: '2 nights' },
1023
- ]}
1024
- />
1025
- <NumberFilter field={store.field.minDuration} label="Min duration" />
1026
- </FilterSet>
1027
- ),
1028
- })
1029
- await act(async () => {
1030
- fireEvent.click(screen.getByRole('button', { name: 'Filters' }))
1031
- })
1032
- }
1033
-
1034
- test('the options form is a named option list, the stepper form a named input row', async () => {
1035
- await openSheet()
1036
- // `group`, not `radiogroup`: the sheet form is a `<fieldset>` of native radios
1037
- // (`SheetOptionList`), pointed at its own visible `SheetField` heading.
1038
- expect(screen.getByRole('group', { name: 'Nights', hidden: true })).toBeDefined()
1039
- // The stepper is the input itself in a `SheetField` — no pill, no popover, one full-width row.
1040
- // Exactly ONE input in the document: `FilterSet` keeps the bar-row copy of every child mounted
1041
- // (hidden slots are `display: none`, never unmounted), but the bar copy is a PILL whose popover
1042
- // is closed, so the only rendered box is the sheet's.
1043
- expect(screen.getAllByRole('textbox', { name: 'Min duration', hidden: true })).toHaveLength(1)
1044
- })
1045
-
1046
- test('a sheet edit writes the field immediately — there is no Apply', async () => {
1047
- await openSheet()
1048
- fireEvent.click(screen.getByRole('radio', { name: '1 night', hidden: true }))
1049
- await waitFor(() => {
1050
- expect(store.field.nights.isDefault(1)).toBe(false)
1051
- })
1052
- })
1053
- })