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
@@ -14,9 +14,11 @@ import { dirname, resolve } from 'node:path'
14
14
  // eslint-disable-next-line -- the plugin is plain JS; the ledger + id set are named exports beside it
15
15
  import basaltPlugin, {
16
16
  CTL_THEME_TAGS,
17
+ DEPRECATED_EXPORTS,
17
18
  KNOWN_RULE_IDS,
18
19
  PLUGIN_RULE_ADVISORY,
19
20
  PLUGIN_RULE_GRACE,
21
+ RETIRED_RULE_IDS,
20
22
  } from './oxlint-plugin.js'
21
23
  import { CTL_THEME } from '../src/theme/ctl-theme'
22
24
  import { GUARD_RULES, PLUGIN_RULE_IDS } from '../src/guard/index.ts'
@@ -35,7 +37,13 @@ beforeEach(() => {
35
37
  plugins: [],
36
38
  jsPlugins: [PLUGIN_PATH],
37
39
  rules: {
40
+ 'basalt/provider-above-router': 'error',
41
+ 'basalt/query-dual-import': 'error',
42
+ 'basalt/query-fn-unwrap': 'error',
43
+ 'basalt/deprecated-export': 'error',
44
+ 'basalt/forms-field-key': 'error',
38
45
  'basalt/no-raw-font-size': 'error',
46
+ 'basalt/raw-size-literal': 'error',
39
47
  'basalt/card-inset': 'error',
40
48
  'basalt/chart-in-raw-surface': 'error',
41
49
  'basalt/hand-rolled-plot': 'error',
@@ -45,6 +53,7 @@ beforeEach(() => {
45
53
  'basalt/raw-scroll-container': 'error',
46
54
  'basalt/hand-rolled-filter': 'error',
47
55
  'basalt/control-outside-home': 'error',
56
+ 'basalt/bound-control-outside-home': 'error',
48
57
  'basalt/control-size-literal': 'error',
49
58
  'basalt/page-bar-budget': 'error',
50
59
  'basalt/in-body-page-title': 'error',
@@ -111,6 +120,76 @@ describe('basalt/no-raw-font-size', () => {
111
120
  expect(code).toBe(0)
112
121
  expect(rules).not.toContain('no-raw-font-size')
113
122
  })
123
+
124
+ // F25: symmetric with raw-size-literal's own test-file skip below — the doctrine is about
125
+ // shipped UI, not fixtures.
126
+ it('does NOT flag a numeric fz inside a *.test.tsx file', () => {
127
+ const { code, rules } = run(`export const C = () => <Text fz={10}>a</Text>\n`, 'lib.test.tsx')
128
+ expect(code).toBe(0)
129
+ expect(rules).not.toContain('no-raw-font-size')
130
+ })
131
+ })
132
+
133
+ // ── raw-size-literal ─────────────────────────────────────────────────────────
134
+
135
+ describe('basalt/raw-size-literal', () => {
136
+ it('flags a CSS-length string on size', () => {
137
+ const { code, rules } = run(`export const C = () => <ThemeIcon size="2rem" />\n`)
138
+ expect(code).toBe(1)
139
+ expect(rules).toContain('raw-size-literal')
140
+ })
141
+
142
+ it('flags a CSS-length string on fz', () => {
143
+ const { code, rules } = run(`export const C = () => <Text fz="0.8rem">a</Text>\n`)
144
+ expect(code).toBe(1)
145
+ expect(rules).toContain('raw-size-literal')
146
+ })
147
+
148
+ it('does NOT flag a scale token on size', () => {
149
+ const { code, rules } = run(`export const C = () => <ThemeIcon size="md" />\n`)
150
+ expect(code).toBe(0)
151
+ expect(rules).not.toContain('raw-size-literal')
152
+ })
153
+
154
+ it('does NOT flag a numeric size (the documented icon-dimension idiom)', () => {
155
+ const { code, rules } = run(`export const C = () => <ThemeIcon size={32} />\n`)
156
+ expect(code).toBe(0)
157
+ expect(rules).not.toContain('raw-size-literal')
158
+ })
159
+
160
+ it('does NOT flag with a same-line theme-allow comment', () => {
161
+ const { code, rules } = run(
162
+ `export const C = () => <Text fz="0.8rem" /* theme-allow: legacy */>a</Text>\n`,
163
+ )
164
+ expect(code).toBe(0)
165
+ expect(rules).not.toContain('raw-size-literal')
166
+ })
167
+
168
+ // F25: was the asymmetric half — no-raw-font-size already skipped test files, this rule did not.
169
+ it('does NOT flag a CSS-length string inside a *.test.tsx file', () => {
170
+ const { code, rules } = run(
171
+ `export const C = () => <Text fz="0.8rem">a</Text>\n`,
172
+ 'lib.test.tsx',
173
+ )
174
+ expect(code).toBe(0)
175
+ expect(rules).not.toContain('raw-size-literal')
176
+ })
177
+
178
+ // A3: the /src/tokens/ exemption used to be a bare path-substring check, then a check gated on
179
+ // `isBasaltScopedFile` — but that predicate tests "does the nearest package.json depend on
180
+ // basalt-ui", not "is this file basalt's own", so a CONSUMER package that merely depends on
181
+ // basalt-ui could still put raw literals under its own `src/tokens/` and silence the rule. The
182
+ // exemption is deleted outright rather than re-gated on package identity: both rules only ever
183
+ // visit JSX, and basalt's real `src/tokens/**` is pure `.ts` data with none, so no gate was ever
184
+ // load-bearing there. A `src/tokens/` path — basalt's own or a consumer's — now always fires.
185
+ it('DOES flag a CSS-length string inside a src/tokens/ file (no path exemption)', () => {
186
+ const { code, rules } = run(
187
+ `export const C = () => <ThemeIcon size="2rem" />\n`,
188
+ 'src/tokens/palette.tsx',
189
+ )
190
+ expect(code).toBe(1)
191
+ expect(rules).toContain('raw-size-literal')
192
+ })
114
193
  })
115
194
 
116
195
  // ── card-inset ───────────────────────────────────────────────────────────────
@@ -1296,6 +1375,30 @@ describe('PLUGIN_RULE_GRACE', () => {
1296
1375
  it('passes the C16 gate against the real ledger and package version', () => {
1297
1376
  expect(() => assertGraceLedger(Object.fromEntries(graceEntries), pkgVersion)).not.toThrow()
1298
1377
  })
1378
+
1379
+ // F27: a grace entry is written for a rule that lands in the NEXT release, so its `since` can sit
1380
+ // one minor ahead of `package.json`'s current version (`bound-control-outside-home` at 1.28.0
1381
+ // while the published version still reads 1.27.0, exactly like `control-outside-home` before it).
1382
+ // What this catches is a `since` that is malformed or planted further out than that — a value
1383
+ // static analysis has no way to verify is "the version this actually ships in", so the bound this
1384
+ // test enforces is the one thing that IS checkable: well-formed semver, no more than one minor
1385
+ // past what is currently published.
1386
+ it("every grace entry's since is valid semver, at most one minor past the published version", () => {
1387
+ const SEMVER = /^\d+\.\d+\.\d+$/
1388
+ const [major, minor] = pkgVersion.split('.').map(Number)
1389
+ const nextMinorCeiling = `${major}.${minor + 1}.0`
1390
+ for (const [id, entry] of graceEntries) {
1391
+ if (!SEMVER.test(entry.since)) {
1392
+ throw new Error(`${id}: \`since\` (${entry.since}) is not valid semver.`)
1393
+ }
1394
+ if (compareSemver(entry.since, nextMinorCeiling) > 0) {
1395
+ throw new Error(
1396
+ `${id}: \`since\` (${entry.since}) is more than one minor past the published version ` +
1397
+ `${pkgVersion}.`,
1398
+ )
1399
+ }
1400
+ }
1401
+ })
1299
1402
  })
1300
1403
 
1301
1404
  // ── chart-legend-literal: the .map() half ────────────────────────────────────
@@ -1408,9 +1511,15 @@ describe('KNOWN_RULE_IDS', () => {
1408
1511
  // Deduped, because ONE id is deliberately in both registries: `in-body-page-title` is a plugin
1409
1512
  // rule AND a guard kind (law C8 has an AST half and a text half), so one `theme-allow
1410
1513
  // in-body-page-title — <why>` waives both lanes rather than needing a different word per lane.
1411
- it('contains nothing else — it is exactly the union of the two registries', () => {
1514
+ // `RETIRED_RULE_IDS` adds a third component: an id that used to be a guard kind or a plugin
1515
+ // rule, is in neither live registry, and must still parse as a (dead) waiver rather than a typo.
1516
+ it('contains nothing else — it is exactly the union of the two registries plus retired ids', () => {
1412
1517
  const expected = [
1413
- ...new Set([...Object.keys(basaltPlugin.rules), ...Object.keys(GUARD_RULES)]),
1518
+ ...new Set([
1519
+ ...Object.keys(basaltPlugin.rules),
1520
+ ...Object.keys(GUARD_RULES),
1521
+ ...RETIRED_RULE_IDS,
1522
+ ]),
1414
1523
  ].toSorted()
1415
1524
  expect([...KNOWN_RULE_IDS].toSorted()).toEqual(expected)
1416
1525
  })
@@ -1428,6 +1537,15 @@ describe('KNOWN_RULE_IDS', () => {
1428
1537
  expect([id, PLUGIN_RULE_IDS.has(id) || Object.hasOwn(GUARD_RULES, id)]).toEqual([id, true])
1429
1538
  }
1430
1539
  })
1540
+
1541
+ // A retired id has to be classifiable too, on both sides — `RETIRED_RULE_IDS` is this plugin's
1542
+ // copy, `PLUGIN_RULE_IDS` (src/guard) is the guard's. Drift between them would mean
1543
+ // `--audit-allows` classifies a retired id one way from `check-theme` and another from oxlint.
1544
+ it('every retired id is in the guard’s PLUGIN_RULE_IDS too', () => {
1545
+ for (const id of RETIRED_RULE_IDS) {
1546
+ expect([id, PLUGIN_RULE_IDS.has(id)]).toEqual([id, true])
1547
+ }
1548
+ })
1431
1549
  })
1432
1550
 
1433
1551
  // ── theme-allow fails closed on a typo ─────────────────────────────────────────────────────────
@@ -1681,6 +1799,28 @@ describe('basalt/control-outside-home', () => {
1681
1799
  },
1682
1800
  )
1683
1801
 
1802
+ // FormRow/FormGroup (`basalt-ui/forms`) are law C1's third home written for a `<form>` rather
1803
+ // than a settings page — the same non-home treatment SettingsRow gets, via the same
1804
+ // CONTROL_HOST_TAGS ancestry walk.
1805
+ it.each(['FormRow', 'FormGroup'])(
1806
+ 'does NOT flag a raw Select nested inside a %s imported from basalt-ui/forms',
1807
+ (host) => {
1808
+ const { code, rules } = run(
1809
+ `import { ${host} } from 'basalt-ui/forms'\n${MANTINE_IMPORT}` +
1810
+ `export const C = () => <${host} label="L"><Select data={[]} /></${host}>\n`,
1811
+ )
1812
+ expect(code).toBe(0)
1813
+ expect(rules).not.toContain('control-outside-home')
1814
+ },
1815
+ )
1816
+
1817
+ it('still flags a bare Select in a page body with no FormRow/FormGroup around it', () => {
1818
+ const { rules } = run(
1819
+ `${MANTINE_IMPORT}export const C = () => <div><Select data={[]} /></div>\n`,
1820
+ )
1821
+ expect(rules).toContain('control-outside-home')
1822
+ })
1823
+
1684
1824
  it('does NOT flag one under a member-expression non-home (Menu.Dropdown)', () => {
1685
1825
  const { rules } = run(
1686
1826
  `${MANTINE_IMPORT}export const C = () => <Menu.Dropdown><Select data={[]} /></Menu.Dropdown>\n`,
@@ -1708,6 +1848,58 @@ describe('basalt/control-outside-home', () => {
1708
1848
  expect(rules).not.toContain('control-outside-home')
1709
1849
  })
1710
1850
 
1851
+ /**
1852
+ * The exemption's SECOND half. `CONTROL_OWNER_NAMES` carries generic names (`PanelRow`,
1853
+ * `EnumFilter`, `SliderControl`), so a bare-name match let one local helper switch this rule —
1854
+ * and `bound-control-outside-home` and `responsive-twin` with it — off for a whole consumer file.
1855
+ * A file that imports `basalt-ui*` is CONSUMING basalt; basalt's own control sources import each
1856
+ * other relatively and never name the package.
1857
+ */
1858
+ it('DOES flag a file that declares its own PanelRow but imports basalt-ui', () => {
1859
+ const { rules } = run(
1860
+ `import { Section } from 'basalt-ui'\n${MANTINE_IMPORT}` +
1861
+ `function PanelRow({ children }) {\n return <div>{children}</div>\n}\n` +
1862
+ `export const C = () => (\n <Section title="x">\n <Select value={v} onChange={set} data={[]} />\n </Section>\n)\n`,
1863
+ )
1864
+ expect(rules).toContain('control-outside-home')
1865
+ })
1866
+
1867
+ /**
1868
+ * The basalt SUBTREE homes, which this rule did not know: `bound-control-outside-home` has read
1869
+ * `FilterSet` / `PageAside` / `PanelRow` as homes since it shipped, so a `<Select>` inside a
1870
+ * `<PageAside><PanelRow>` reported while the bound control beside it did not — two rules
1871
+ * answering one question two ways.
1872
+ */
1873
+ it('does NOT flag one inside a PageAside > PanelRow subtree', () => {
1874
+ const { code, rules } = run(
1875
+ `import { PageAside } from 'basalt-ui'\nimport { PanelRow } from 'basalt-ui/controls'\n${MANTINE_IMPORT}` +
1876
+ `export const C = () => (\n <PageAside title="Filters">\n <PanelRow label="Scale">\n <Select data={[]} />\n </PanelRow>\n </PageAside>\n)\n`,
1877
+ 'src/scale-page.tsx',
1878
+ )
1879
+ expect(code).toBe(0)
1880
+ expect(rules).not.toContain('control-outside-home')
1881
+ })
1882
+
1883
+ it('does NOT flag one inside a FilterSet subtree', () => {
1884
+ const { rules } = run(
1885
+ `import { FilterSet } from 'basalt-ui/controls'\n${MANTINE_IMPORT}` +
1886
+ `export const C = () => (\n <FilterSet>\n <Select data={[]} />\n </FilterSet>\n)\n`,
1887
+ 'src/scale-page.tsx',
1888
+ )
1889
+ expect(rules).not.toContain('control-outside-home')
1890
+ })
1891
+
1892
+ // Provenance, the test `SLOT_OWNER_TAGS` already applies: a consumer's own `PanelRow` is a layout
1893
+ // helper, not a basalt home, so it exempts nothing.
1894
+ it("still flags one inside a consumer's OWN PanelRow", () => {
1895
+ const { rules } = run(
1896
+ `import { PanelRow } from './panel-row'\n${MANTINE_IMPORT}` +
1897
+ `export const C = () => (\n <PanelRow label="Scale">\n <Select data={[]} />\n </PanelRow>\n)\n`,
1898
+ 'src/scale-page.tsx',
1899
+ )
1900
+ expect(rules).toContain('control-outside-home')
1901
+ })
1902
+
1711
1903
  it('flags an ALIASED Mantine import with no home', () => {
1712
1904
  const { code, rules } = run(
1713
1905
  `import { Select as MantineSelect } from '@mantine/core'\n` +
@@ -1770,6 +1962,252 @@ describe('basalt/control-outside-home', () => {
1770
1962
  const { rules } = run(SOURCE, 'src/modal.tsx')
1771
1963
  expect(rules).toContain('control-outside-home')
1772
1964
  })
1965
+
1966
+ // The SECOND dialect: a repo mandating `PascalCase.tsx` for component files (basalt's own root
1967
+ // CLAUDE.md included) can never write `foo-panel.tsx`, so the kebab form alone exempted nothing
1968
+ // there — which is how the CBBI panel collected four warns for a `<PageAside>` in its parent.
1969
+ it.each(['src/EditSessionModal.tsx', 'src/FiltersDrawer.tsx', 'src/CbbiPanel.tsx'])(
1970
+ 'does NOT flag %s — the PascalCase dialect of the same convention',
1971
+ (filename) => {
1972
+ const { code, rules } = run(SOURCE, filename)
1973
+ expect(code).toBe(0)
1974
+ expect(rules).not.toContain('control-outside-home')
1975
+ },
1976
+ )
1977
+
1978
+ /**
1979
+ * The trade, stated as a test rather than only as prose: a `FooPanel.tsx` that renders NO page
1980
+ * bar is exempt WHOLE-FILE, so a control that genuinely belongs in a bar goes unreported there.
1981
+ * Measured before keeping it — across argo, linewatch, image-share, rb and image-gen the
1982
+ * PascalCase dialect matches 9 files, all in image-gen, and NONE of them renders a `<PageBar>`
1983
+ * or sits under a `routes/` directory. Nine overlay bodies, zero pages, so the dialect stays
1984
+ * whole-file rather than growing a "does it render a page bar" predicate (CONTROLS-SPEC §6).
1985
+ */
1986
+ it('exempts a FooPanel.tsx that renders no page bar — the measured trade', () => {
1987
+ const { code, rules } = run(SOURCE, 'src/components/refine/CropPanel.tsx')
1988
+ expect(code).toBe(0)
1989
+ expect(rules).not.toContain('control-outside-home')
1990
+ })
1991
+
1992
+ it('needs the leading subject in the PascalCase dialect too', () => {
1993
+ const { rules } = run(SOURCE, 'src/Panel.tsx')
1994
+ expect(rules).toContain('control-outside-home')
1995
+ })
1996
+
1997
+ it('is CASE-sensitive — foopanel.tsx is neither dialect', () => {
1998
+ const { rules } = run(SOURCE, 'src/foopanel.tsx')
1999
+ expect(rules).toContain('control-outside-home')
2000
+ })
2001
+ })
2002
+ })
2003
+
2004
+ /**
2005
+ * Ledger G5 (`docs/ASIDE-SPEC.md` §2) — the basalt half of law C1. Mirrors the
2006
+ * `control-outside-home` block above case for case, because the two rules answer the same question
2007
+ * about a different tag set: that one matches raw Mantine, this one matches a bound basalt control.
2008
+ */
2009
+ describe('basalt/bound-control-outside-home', () => {
2010
+ const CONTROLS_IMPORT = `import { FilterSet, PanelRow, SelectFilter, SliderControl } from 'basalt-ui/controls'\n`
2011
+ const ASIDE_IMPORT = `import { PageAside, PageBar, Section } from 'basalt-ui'\n`
2012
+
2013
+ it("flags a bound control in a Section's BODY — the stray pill G5 named", () => {
2014
+ const { code, rules } = run(
2015
+ `${ASIDE_IMPORT}${CONTROLS_IMPORT}export const C = () => (\n <Section title="Composition">\n <SelectFilter field={f} />\n </Section>\n)\n`,
2016
+ )
2017
+ expect(code).toBe(1)
2018
+ expect(rules).toContain('bound-control-outside-home')
2019
+ })
2020
+
2021
+ it('flags one in a bare page Stack', () => {
2022
+ const { rules } = run(
2023
+ `import { Stack } from '@mantine/core'\n${CONTROLS_IMPORT}` +
2024
+ `export const C = () => (\n <Stack>\n <SelectFilter field={f} />\n </Stack>\n)\n`,
2025
+ )
2026
+ expect(rules).toContain('bound-control-outside-home')
2027
+ })
2028
+
2029
+ it('does NOT flag one in a PageBar filters slot', () => {
2030
+ const { code, rules } = run(
2031
+ `${ASIDE_IMPORT}${CONTROLS_IMPORT}export const C = () => <PageBar filters={<SelectFilter field={f} />} />\n`,
2032
+ )
2033
+ expect(code).toBe(0)
2034
+ expect(rules).not.toContain('bound-control-outside-home')
2035
+ })
2036
+
2037
+ it('does NOT flag one inside a FilterSet subtree', () => {
2038
+ const { code, rules } = run(
2039
+ `${CONTROLS_IMPORT}export const C = () => (\n <FilterSet>\n <SelectFilter field={f} />\n </FilterSet>\n)\n`,
2040
+ )
2041
+ expect(code).toBe(0)
2042
+ expect(rules).not.toContain('bound-control-outside-home')
2043
+ })
2044
+
2045
+ // The aside is a shell REGION whose body IS a home — it scopes its children to the `panel`
2046
+ // surface, where the same control renders as a row rather than a pill (docs/ASIDE-SPEC.md §3).
2047
+ it('does NOT flag one inside a PageAside subtree, Section and all', () => {
2048
+ const { code, rules } = run(
2049
+ `${ASIDE_IMPORT}${CONTROLS_IMPORT}export const C = () => (\n <PageAside title="Filters">\n <Section title="Origin">\n <SelectFilter field={f} />\n </Section>\n </PageAside>\n)\n`,
2050
+ )
2051
+ expect(code).toBe(0)
2052
+ expect(rules).not.toContain('bound-control-outside-home')
2053
+ })
2054
+
2055
+ it("does NOT flag one as a PanelRow's `end` — the attribute hangs off PanelRow itself", () => {
2056
+ const { code, rules } = run(
2057
+ `${CONTROLS_IMPORT}export const C = () => <PanelRow label="Weight" end={<SelectFilter field={f} />} />\n`,
2058
+ )
2059
+ expect(code).toBe(0)
2060
+ expect(rules).not.toContain('bound-control-outside-home')
2061
+ })
2062
+
2063
+ it("does NOT flag one in a PanelRow's children — the row IS the home", () => {
2064
+ const { rules } = run(
2065
+ `${CONTROLS_IMPORT}export const C = () => (\n <PanelRow label="Origin">\n <SelectFilter field={f} />\n </PanelRow>\n)\n`,
2066
+ )
2067
+ expect(rules).not.toContain('bound-control-outside-home')
2068
+ })
2069
+
2070
+ it('does NOT flag one inside an overlay host (Modal)', () => {
2071
+ const { rules } = run(
2072
+ `import { Modal } from '@mantine/core'\n${CONTROLS_IMPORT}` +
2073
+ `export const C = () => (\n <Modal opened>\n <SelectFilter field={f} />\n </Modal>\n)\n`,
2074
+ )
2075
+ expect(rules).not.toContain('bound-control-outside-home')
2076
+ })
2077
+
2078
+ // FormRow/FormGroup are the same declared non-home as SettingsRow — shared CONTROL_HOST_TAGS walk.
2079
+ it.each(['FormRow', 'FormGroup'])('does NOT flag one inside a %s', (host) => {
2080
+ const { code, rules } = run(
2081
+ `import { ${host} } from 'basalt-ui/forms'\n${CONTROLS_IMPORT}` +
2082
+ `export const C = () => <${host} label="L"><SelectFilter field={f} /></${host}>\n`,
2083
+ )
2084
+ expect(code).toBe(0)
2085
+ expect(rules).not.toContain('bound-control-outside-home')
2086
+ })
2087
+
2088
+ it.each(['src/cbbi-panel.tsx', 'src/CbbiPanel.tsx'])(
2089
+ 'does NOT flag %s — a panel body whose host lives in the parent, in either dialect',
2090
+ (filename) => {
2091
+ const { code, rules } = run(
2092
+ `${CONTROLS_IMPORT}export const C = () => <div><SelectFilter field={f} /></div>\n`,
2093
+ filename,
2094
+ )
2095
+ expect(code).toBe(0)
2096
+ expect(rules).not.toContain('bound-control-outside-home')
2097
+ },
2098
+ )
2099
+
2100
+ // Both dialects need the leading SUBJECT, and neither is case-insensitive — `foopanel.tsx` is a
2101
+ // page module spelled without a separator, not a declaration.
2102
+ it.each(['src/foopanel.tsx', 'src/Panel.tsx'])('still flags %s', (filename) => {
2103
+ const { rules } = run(
2104
+ `${CONTROLS_IMPORT}export const C = () => <div><SelectFilter field={f} /></div>\n`,
2105
+ filename,
2106
+ )
2107
+ expect(rules).toContain('bound-control-outside-home')
2108
+ })
2109
+
2110
+ it('honors a scoped theme-allow', () => {
2111
+ const { rules } = run(
2112
+ `${CONTROLS_IMPORT}export const C = () => (\n <div>\n {/* theme-allow bound-control-outside-home — a one-off embedded picker */}\n <SelectFilter field={f} />\n </div>\n)\n`,
2113
+ )
2114
+ expect(rules).not.toContain('bound-control-outside-home')
2115
+ })
2116
+
2117
+ it('honors a theme-allow-file declaration', () => {
2118
+ const { rules } = run(
2119
+ `// theme-allow-file bound-control-outside-home — a legacy page, migrating next sprint\n` +
2120
+ `${CONTROLS_IMPORT}export const C = () => <div><SelectFilter field={f} /></div>\n`,
2121
+ )
2122
+ expect(rules).not.toContain('bound-control-outside-home')
2123
+ })
2124
+
2125
+ // Provenance, the same test the raw-filter rules apply the other way round: a consumer's own
2126
+ // `SelectFilter` is not basalt's, and basalt has no claim over where it is written.
2127
+ it('does NOT flag a locally-defined SelectFilter', () => {
2128
+ const { code, rules } = run(
2129
+ `import { SelectFilter } from './my-filters'\n` +
2130
+ `export const C = () => <div><SelectFilter field={f} /></div>\n`,
2131
+ )
2132
+ expect(code).toBe(0)
2133
+ expect(rules).not.toContain('bound-control-outside-home')
2134
+ })
2135
+
2136
+ it('flags an ALIASED basalt import with no home', () => {
2137
+ const { rules } = run(
2138
+ `import { SelectFilter as Picker } from 'basalt-ui/controls'\n` +
2139
+ `export const C = () => <div><Picker field={f} /></div>\n`,
2140
+ )
2141
+ expect(rules).toContain('bound-control-outside-home')
2142
+ })
2143
+
2144
+ /**
2145
+ * The owner exemption needs the file to import NOTHING from `basalt-ui*` — see
2146
+ * `control-outside-home`'s copy for why. For THIS rule that makes the exemption reachable only
2147
+ * inside basalt's own `src/`, which is exactly where it is meant to apply: provenance already
2148
+ * requires a basalt import (or a relative one inside that tree), so a consumer file can never be
2149
+ * both the owner and a place this rule could fire.
2150
+ */
2151
+ it('DOES flag a consumer file that declares its own PanelRow', () => {
2152
+ const { rules } = run(
2153
+ `${CONTROLS_IMPORT}function PanelRow({ children }) {\n return <div>{children}</div>\n}\n` +
2154
+ `export const C = () => <div><SelectFilter field={f} /></div>\n`,
2155
+ )
2156
+ expect(rules).toContain('bound-control-outside-home')
2157
+ })
2158
+
2159
+ // Provenance on the HOME too, the same test `control-outside-home` applies: a consumer's own
2160
+ // `PanelRow` is a layout helper, not the aside's row primitive.
2161
+ it("still flags one inside a consumer's OWN PanelRow", () => {
2162
+ const { rules } = run(
2163
+ `import { SelectFilter } from 'basalt-ui/controls'\nimport { PanelRow } from './panel-row'\n` +
2164
+ `export const C = () => (\n <PanelRow label="Scale">\n <SelectFilter field={f} />\n </PanelRow>\n)\n`,
2165
+ )
2166
+ expect(rules).toContain('bound-control-outside-home')
2167
+ })
2168
+
2169
+ /**
2170
+ * `SliderControl` is NOT policed: it renders its own `PanelRow` and has no pill form at all
2171
+ * (`docs/ASIDE-SPEC.md` §3), so "this renders as a stray pill" is false for it and a `Section`
2172
+ * body is a legitimate place to write one. The plugin has no per-tag home set, so the honest fix
2173
+ * is dropping it from the policed set rather than giving it a private exemption.
2174
+ */
2175
+ it('does NOT flag a SliderControl in a Section body — it has no pill form', () => {
2176
+ const { code, rules } = run(
2177
+ `${ASIDE_IMPORT}${CONTROLS_IMPORT}export const C = () => (\n <Section title="Weights">\n <SliderControl field={f} label="Pi" />\n </Section>\n)\n`,
2178
+ )
2179
+ expect(code).toBe(0)
2180
+ expect(rules).not.toContain('bound-control-outside-home')
2181
+ })
2182
+
2183
+ it('resolves a hoisted binding handed to a slot, like its siblings do', () => {
2184
+ const { rules } = run(
2185
+ `${ASIDE_IMPORT}${CONTROLS_IMPORT}const pills = <SelectFilter field={f} />\n` +
2186
+ `export const C = () => <PageBar filters={pills} />\n`,
2187
+ )
2188
+ expect(rules).not.toContain('bound-control-outside-home')
2189
+ })
2190
+
2191
+ /**
2192
+ * …and one handed to a subtree home as its CHILDREN, which the ancestry walk cannot see: at the
2193
+ * `const` there is no `PageAside` above the node at all. The same `Program:exit` deferral the
2194
+ * slot lane uses, over the home's `{expr}` children instead of its attributes.
2195
+ */
2196
+ it('resolves a hoisted binding rendered as a PageAside CHILD', () => {
2197
+ const { code, rules } = run(
2198
+ `${ASIDE_IMPORT}${CONTROLS_IMPORT}const rows = <SelectFilter field={f} />\n` +
2199
+ `export const C = () => <PageAside title="Filters">{rows}</PageAside>\n`,
2200
+ )
2201
+ expect(code).toBe(0)
2202
+ expect(rules).not.toContain('bound-control-outside-home')
2203
+ })
2204
+
2205
+ it('still flags the same hoisted binding under a bare Stack', () => {
2206
+ const { rules } = run(
2207
+ `import { Stack } from '@mantine/core'\n${CONTROLS_IMPORT}const rows = <SelectFilter field={f} />\n` +
2208
+ `export const C = () => <Stack>{rows}</Stack>\n`,
2209
+ )
2210
+ expect(rules).toContain('bound-control-outside-home')
1773
2211
  })
1774
2212
  })
1775
2213
 
@@ -2159,6 +2597,17 @@ describe('basalt/responsive-twin', () => {
2159
2597
  expect(rules).not.toContain('responsive-twin')
2160
2598
  })
2161
2599
 
2600
+ // The owner exemption's second half, the same predicate its two siblings apply: a consumer file
2601
+ // that names a helper `PanelRow` and imports basalt is consuming basalt, not defining a control.
2602
+ it('DOES flag a file that declares its own PanelRow but imports basalt-ui', () => {
2603
+ const { rules } = run(
2604
+ `import { Section } from 'basalt-ui'\n` +
2605
+ `function PanelRow({ children }) {\n return <div>{children}</div>\n}\n` +
2606
+ `export const C = () => (\n <div>\n <SegmentedControl visibleFrom="sm" data={[]} />\n <SegmentedControl hiddenFrom="sm" data={[]} />\n </div>\n)\n`,
2607
+ )
2608
+ expect(rules).toContain('responsive-twin')
2609
+ })
2610
+
2162
2611
  it('honors a scoped theme-allow', () => {
2163
2612
  const { rules } = run(
2164
2613
  `export const C = () => (\n <div>\n <SegmentedControl visibleFrom="sm" data={[]} />\n {/* theme-allow responsive-twin — two genuinely different data sets */}\n <SegmentedControl hiddenFrom="sm" data={[]} />\n </div>\n)\n`,
@@ -2507,6 +2956,7 @@ describe('PLUGIN_RULE_ID_LIST ↔ the plugin', () => {
2507
2956
 
2508
2957
  it('gives ./controls the control-tier rules', () => {
2509
2958
  expect([...SURFACES['./controls'].pluginRules].toSorted()).toEqual([
2959
+ 'bound-control-outside-home',
2510
2960
  'control-outside-home',
2511
2961
  'control-size-literal',
2512
2962
  'hand-rolled-filter',
@@ -2514,3 +2964,453 @@ describe('PLUGIN_RULE_ID_LIST ↔ the plugin', () => {
2514
2964
  ])
2515
2965
  })
2516
2966
  })
2967
+
2968
+ // ── provider-above-router (F5) ───────────────────────────────────────────────
2969
+
2970
+ describe('basalt/provider-above-router', () => {
2971
+ const IMPORTS =
2972
+ `import { BasaltProvider } from 'basalt-ui'\n` +
2973
+ `import { RouterProvider } from '@tanstack/react-router'\n`
2974
+
2975
+ it('flags a BasaltProvider rendered inside a RouterProvider', () => {
2976
+ const { code, rules } = run(
2977
+ `${IMPORTS}export const App = () => (\n` +
2978
+ ` <RouterProvider router={r}>\n <BasaltProvider>{null}</BasaltProvider>\n` +
2979
+ ` </RouterProvider>\n)\n`,
2980
+ )
2981
+ expect(code).toBe(1)
2982
+ expect(rules).toContain('provider-above-router')
2983
+ })
2984
+
2985
+ it('does NOT flag the correct order — provider above router', () => {
2986
+ const { code, rules } = run(
2987
+ `${IMPORTS}export const App = () => (\n` +
2988
+ ` <BasaltProvider>\n <RouterProvider router={r} />\n </BasaltProvider>\n)\n`,
2989
+ )
2990
+ expect(code).toBe(0)
2991
+ expect(rules).not.toContain('provider-above-router')
2992
+ })
2993
+
2994
+ // Provenance both ways: the tag names alone are not the finding.
2995
+ it("does NOT flag a consumer's own BasaltProvider (no basalt import)", () => {
2996
+ const { code, rules } = run(
2997
+ `import { RouterProvider } from '@tanstack/react-router'\n` +
2998
+ `import { BasaltProvider } from './my-provider'\n` +
2999
+ `export const App = () => (\n <RouterProvider router={r}>\n` +
3000
+ ` <BasaltProvider>{null}</BasaltProvider>\n </RouterProvider>\n)\n`,
3001
+ )
3002
+ expect(code).toBe(0)
3003
+ expect(rules).not.toContain('provider-above-router')
3004
+ })
3005
+
3006
+ it('does NOT flag a RouterProvider that is not TanStack s', () => {
3007
+ const { code, rules } = run(
3008
+ `import { BasaltProvider } from 'basalt-ui'\n` +
3009
+ `import { RouterProvider } from 'other-router'\n` +
3010
+ `export const App = () => (\n <RouterProvider router={r}>\n` +
3011
+ ` <BasaltProvider>{null}</BasaltProvider>\n </RouterProvider>\n)\n`,
3012
+ )
3013
+ expect(code).toBe(0)
3014
+ expect(rules).not.toContain('provider-above-router')
3015
+ })
3016
+
3017
+ // The alias is why the ancestry is read by BINDING and not by written tag name.
3018
+ it('flags through an aliased RouterProvider import', () => {
3019
+ const { code, rules } = run(
3020
+ `import { BasaltProvider } from 'basalt-ui'\n` +
3021
+ `import { RouterProvider as Router } from '@tanstack/react-router'\n` +
3022
+ `export const App = () => (\n <Router router={r}>\n` +
3023
+ ` <BasaltProvider>{null}</BasaltProvider>\n </Router>\n)\n`,
3024
+ )
3025
+ expect(code).toBe(1)
3026
+ expect(rules).toContain('provider-above-router')
3027
+ })
3028
+
3029
+ it('honours a theme-allow naming the rule', () => {
3030
+ const { code, rules } = run(
3031
+ `${IMPORTS}export const App = () => (\n <RouterProvider router={r}>\n` +
3032
+ ` {/* theme-allow provider-above-router — legacy bootstrap, ported next minor */}\n` +
3033
+ ` <BasaltProvider>{null}</BasaltProvider>\n </RouterProvider>\n)\n`,
3034
+ )
3035
+ expect(code).toBe(0)
3036
+ expect(rules).not.toContain('provider-above-router')
3037
+ })
3038
+
3039
+ // The documented limit of the heuristic — stated in the rule's own JSDoc, pinned here so a
3040
+ // future widening has to move this test rather than discover the gap.
3041
+ it('does NOT see a cross-file composition (static JSX ancestry only)', () => {
3042
+ const { code, rules } = run(
3043
+ `import { BasaltProvider } from 'basalt-ui'\n` +
3044
+ `import { Routes } from './routes'\n` +
3045
+ `export const App = () => (\n <Routes>\n <BasaltProvider>{null}</BasaltProvider>\n` +
3046
+ ` </Routes>\n)\n`,
3047
+ )
3048
+ expect(code).toBe(0)
3049
+ expect(rules).not.toContain('provider-above-router')
3050
+ })
3051
+ })
3052
+
3053
+ // ── query-dual-import (F5) ───────────────────────────────────────────────────
3054
+
3055
+ describe('basalt/query-dual-import', () => {
3056
+ it('flags a file importing from both @tanstack/react-query and basalt-ui/query', () => {
3057
+ const { code, rules, output } = run(
3058
+ `import { useQuery } from '@tanstack/react-query'\n` +
3059
+ `import { unwrap } from 'basalt-ui/query'\n` +
3060
+ `export const useThing = () => useQuery({ queryKey: ['t'], queryFn: () => unwrap(get()) })\n`,
3061
+ )
3062
+ expect(code).toBe(1)
3063
+ expect(rules).toContain('query-dual-import')
3064
+ expect(output).toContain('Dual query import')
3065
+ })
3066
+
3067
+ // The softer half — the one that actually catches the drift, and why the rule is `warn`.
3068
+ it('flags a raw @tanstack/react-query import beside a basalt root import', () => {
3069
+ const { code, rules, output } = run(
3070
+ `import { useQuery } from '@tanstack/react-query'\n` +
3071
+ `import { QueryState } from 'basalt-ui'\n` +
3072
+ `export const useThing = () => useQuery({ queryKey: ['t'], queryFn: get })\n`,
3073
+ )
3074
+ expect(code).toBe(1)
3075
+ expect(rules).toContain('query-dual-import')
3076
+ expect(output).toContain('Raw @tanstack/react-query import')
3077
+ })
3078
+
3079
+ it('does NOT flag basalt-ui/query on its own', () => {
3080
+ const { code, rules } = run(
3081
+ `import { useQuery, unwrap } from 'basalt-ui/query'\n` +
3082
+ `export const useThing = () => useQuery({ queryKey: ['t'], queryFn: () => unwrap(get()) })\n`,
3083
+ )
3084
+ expect(code).toBe(0)
3085
+ expect(rules).not.toContain('query-dual-import')
3086
+ })
3087
+
3088
+ // `import type` is erased at compile time — there is no runtime import to route through the
3089
+ // seam, and typing against the library's own result type is what `QueryStateLike` documents as
3090
+ // legitimate. Same skip `agent-no-raw-usechat` and `ai-sdk-major` take.
3091
+ it('does NOT flag a type-only @tanstack/react-query import beside basalt-ui/query', () => {
3092
+ const { code, rules } = run(
3093
+ `import type { UseQueryResult } from '@tanstack/react-query'\n` +
3094
+ `import { unwrap } from 'basalt-ui/query'\n` +
3095
+ `export const pick = (q: UseQueryResult<number>) => unwrap(q.data)\n`,
3096
+ )
3097
+ expect(code).toBe(0)
3098
+ expect(rules).not.toContain('query-dual-import')
3099
+ })
3100
+
3101
+ it('does NOT flag @tanstack/react-query in a file with no basalt import', () => {
3102
+ const { code, rules } = run(
3103
+ `import { useQuery } from '@tanstack/react-query'\n` +
3104
+ `export const useThing = () => useQuery({ queryKey: ['t'], queryFn: get })\n`,
3105
+ )
3106
+ expect(code).toBe(0)
3107
+ expect(rules).not.toContain('query-dual-import')
3108
+ })
3109
+
3110
+ // The devtools package is a different package, not a subpath.
3111
+ it('does NOT flag @tanstack/react-query-devtools', () => {
3112
+ const { code, rules } = run(
3113
+ `import { ReactQueryDevtools } from '@tanstack/react-query-devtools'\n` +
3114
+ `import { unwrap } from 'basalt-ui/query'\n` +
3115
+ `export const D = () => <ReactQueryDevtools />\n`,
3116
+ )
3117
+ expect(code).toBe(0)
3118
+ expect(rules).not.toContain('query-dual-import')
3119
+ })
3120
+
3121
+ it('honours a theme-allow naming the rule', () => {
3122
+ const { code, rules } = run(
3123
+ `// theme-allow query-dual-import — the persister API has no basalt re-export yet\n` +
3124
+ `import { useQuery } from '@tanstack/react-query'\n` +
3125
+ `import { unwrap } from 'basalt-ui/query'\n` +
3126
+ `export const useThing = () => useQuery({ queryKey: ['t'], queryFn: () => unwrap(g()) })\n`,
3127
+ )
3128
+ expect(code).toBe(0)
3129
+ expect(rules).not.toContain('query-dual-import')
3130
+ })
3131
+ })
3132
+
3133
+ // ── query-fn-unwrap (F5) ─────────────────────────────────────────────────────
3134
+
3135
+ describe('basalt/query-fn-unwrap', () => {
3136
+ const IMPORT = `import { unwrap } from 'basalt-ui/query'\n`
3137
+
3138
+ it('flags a queryFn that fetches with no unwrap', () => {
3139
+ const { code, rules } = run(
3140
+ `${IMPORT}export const o = { queryKey: ['t'], queryFn: () => fetch('/api/t') }\n`,
3141
+ )
3142
+ expect(code).toBe(1)
3143
+ expect(rules).toContain('query-fn-unwrap')
3144
+ })
3145
+
3146
+ it('flags the async .json() form', () => {
3147
+ const { code, rules } = run(
3148
+ `${IMPORT}export const o = {\n queryKey: ['t'],\n` +
3149
+ ` queryFn: async () => (await fetch('/api/t')).json(),\n}\n`,
3150
+ )
3151
+ expect(code).toBe(1)
3152
+ expect(rules).toContain('query-fn-unwrap')
3153
+ })
3154
+
3155
+ it('does NOT flag a queryFn that wraps the call', () => {
3156
+ const { code, rules } = run(
3157
+ `${IMPORT}export const o = { queryKey: ['t'], queryFn: () => unwrap(fetch('/api/t')) }\n`,
3158
+ )
3159
+ expect(code).toBe(0)
3160
+ expect(rules).not.toContain('query-fn-unwrap')
3161
+ })
3162
+
3163
+ it('does NOT flag a queryFn with no fetch in it', () => {
3164
+ const { code, rules } = run(
3165
+ `${IMPORT}export const o = { queryKey: ['t'], queryFn: () => client.t.get() }\n`,
3166
+ )
3167
+ expect(code).toBe(0)
3168
+ expect(rules).not.toContain('query-fn-unwrap')
3169
+ })
3170
+
3171
+ // The scope gate: with no basalt-ui/query import this is an opinion about `fetch`, which basalt
3172
+ // does not have.
3173
+ it('does NOT flag a file that never imports basalt-ui/query', () => {
3174
+ const { code, rules } = run(
3175
+ `export const o = { queryKey: ['t'], queryFn: () => fetch('/api/t') }\n`,
3176
+ )
3177
+ expect(code).toBe(0)
3178
+ expect(rules).not.toContain('query-fn-unwrap')
3179
+ })
3180
+
3181
+ // The documented blind spot — a reference, not a function literal. Pinned so a widening moves it.
3182
+ it('does NOT see a queryFn passed as a reference', () => {
3183
+ const { code, rules } = run(
3184
+ `${IMPORT}const load = () => fetch('/api/t')\n` +
3185
+ `export const o = { queryKey: ['t'], queryFn: load }\n`,
3186
+ )
3187
+ expect(code).toBe(0)
3188
+ expect(rules).not.toContain('query-fn-unwrap')
3189
+ })
3190
+
3191
+ it('honours a theme-allow naming the rule', () => {
3192
+ const { code, rules } = run(
3193
+ `${IMPORT}export const o = {\n queryKey: ['t'],\n` +
3194
+ ` // theme-allow query-fn-unwrap — raw Response is the point, this reads a blob\n` +
3195
+ ` queryFn: () => fetch('/api/t'),\n}\n`,
3196
+ )
3197
+ expect(code).toBe(0)
3198
+ expect(rules).not.toContain('query-fn-unwrap')
3199
+ })
3200
+ })
3201
+
3202
+ // ── deprecated-export (B4) ───────────────────────────────────────────────────
3203
+
3204
+ // deprecated-export's behaviour tests were removed with the last ledger row in 1.29.0; the
3205
+ // ledger-shape and barrel-scan assertions below keep the mechanism honest until the next row.
3206
+
3207
+ // ── forms-field-key ──────────────────────────────────────────────────────────
3208
+
3209
+ describe('basalt/forms-field-key', () => {
3210
+ const FORMS = `import { inputProps } from 'basalt-ui/forms'\n`
3211
+
3212
+ it('flags a spread inputProps() with no sibling key', () => {
3213
+ const { code, rules, output } = run(
3214
+ `${FORMS}export const C = () => <TextInput {...inputProps(form, 'email')} />\n`,
3215
+ )
3216
+ expect(code).toBe(1)
3217
+ expect(rules).toContain('forms-field-key')
3218
+ expect(output).toContain('no sibling `key`')
3219
+ })
3220
+
3221
+ it('does NOT flag the documented two-call idiom', () => {
3222
+ const { code, rules } = run(
3223
+ `import { fieldKey, inputProps } from 'basalt-ui/forms'\n` +
3224
+ `export const C = () => <TextInput key={fieldKey(form, 'e')} {...inputProps(form, 'e')} />\n`,
3225
+ )
3226
+ expect(code).toBe(0)
3227
+ expect(rules).not.toContain('forms-field-key')
3228
+ })
3229
+
3230
+ // An `inputProps` a consumer wrote themselves is not basalt's — the same provenance gate the
3231
+ // control and chart rules take, one lane over.
3232
+ it('does NOT flag a same-named helper imported from somewhere else', () => {
3233
+ const { code, rules } = run(
3234
+ `import { inputProps } from './my-helpers'\n` +
3235
+ `export const C = () => <TextInput {...inputProps(form, 'email')} />\n`,
3236
+ )
3237
+ expect(code).toBe(0)
3238
+ expect(rules).not.toContain('forms-field-key')
3239
+ })
3240
+
3241
+ it('resolves an aliased import — `inputProps as f` is still inputProps', () => {
3242
+ const { code, rules } = run(
3243
+ `import { inputProps as f } from 'basalt-ui/forms'\n` +
3244
+ `export const C = () => <TextInput {...f(form, 'email')} />\n`,
3245
+ )
3246
+ expect(code).toBe(1)
3247
+ expect(rules).toContain('forms-field-key')
3248
+ })
3249
+
3250
+ // The second message: `field` still RETURNS `key`, so it is reported whether or not the element
3251
+ // carries one — the remedy is the rename, never an added attribute.
3252
+ it('flags a spread field() even when the element already has a key', () => {
3253
+ const { code, rules, output } = run(
3254
+ `import { field } from 'basalt-ui/forms'\n` +
3255
+ `export const C = () => <TextInput key="e" {...field(form, 'email')} />\n`,
3256
+ )
3257
+ expect(code).toBe(1)
3258
+ expect(rules).toContain('forms-field-key')
3259
+ expect(output).toContain('@deprecated 1.27 alias')
3260
+ })
3261
+
3262
+ it('honours a theme-allow naming the rule', () => {
3263
+ const { code, rules } = run(
3264
+ `${FORMS}export const C = () => (\n` +
3265
+ ` // theme-allow forms-field-key — this input is remounted by its parent\n` +
3266
+ ` <TextInput {...inputProps(form, 'email')} />\n)\n`,
3267
+ )
3268
+ expect(code).toBe(0)
3269
+ expect(rules).not.toContain('forms-field-key')
3270
+ })
3271
+
3272
+ // The autofix has to produce COMPILING code: an inserted `fieldKey(…)` with no import is a worse
3273
+ // outcome than the missing key it replaced.
3274
+ it('autofixes the key in, with the same arguments, and adds fieldKey to the import once', () => {
3275
+ const source =
3276
+ `${FORMS}export const C = () => (\n` +
3277
+ ` <form>\n` +
3278
+ ` <TextInput {...inputProps(form, 'email')} label="Email" />\n` +
3279
+ ` <TextInput {...inputProps(form, 'name')} />\n` +
3280
+ ` </form>\n)\n`
3281
+ writeFileSync(resolve(dir, 'fixture.tsx'), source)
3282
+ Bun.spawnSync([OXLINT_BIN, '-c', '.oxlintrc.json', '--fix', 'fixture.tsx'], { cwd: dir })
3283
+ const fixed = readFileSync(resolve(dir, 'fixture.tsx'), 'utf8')
3284
+ expect(fixed).toContain(`import { inputProps, fieldKey } from 'basalt-ui/forms'`)
3285
+ expect(fixed).toContain(`key={fieldKey(form, 'email')} {...inputProps(form, 'email')}`)
3286
+ expect(fixed).toContain(`key={fieldKey(form, 'name')} {...inputProps(form, 'name')}`)
3287
+ // Once, not twice: two overlapping edits to one import declaration would drop one of them.
3288
+ expect(fixed.match(/fieldKey,|, fieldKey/g)).toHaveLength(1)
3289
+ })
3290
+
3291
+ it('does not re-add fieldKey when it is already imported', () => {
3292
+ const source =
3293
+ `import { fieldKey, inputProps } from 'basalt-ui/forms'\n` +
3294
+ `export const C = () => <TextInput {...inputProps(form, 'email')} />\n`
3295
+ writeFileSync(resolve(dir, 'fixture.tsx'), source)
3296
+ Bun.spawnSync([OXLINT_BIN, '-c', '.oxlintrc.json', '--fix', 'fixture.tsx'], { cwd: dir })
3297
+ const fixed = readFileSync(resolve(dir, 'fixture.tsx'), 'utf8')
3298
+ expect(fixed).toContain(`import { fieldKey, inputProps } from 'basalt-ui/forms'`)
3299
+ expect(fixed).toContain(`key={fieldKey(form, 'email')}`)
3300
+ })
3301
+ })
3302
+
3303
+ // ── the deprecation ledger (B4) ──────────────────────────────────────────────
3304
+
3305
+ describe('DEPRECATED_EXPORTS', () => {
3306
+ const pkgVersion = (
3307
+ JSON.parse(readFileSync(resolve(import.meta.dirname, '..', 'package.json'), 'utf8')) as {
3308
+ version: string
3309
+ }
3310
+ ).version
3311
+
3312
+ it('carries a replacement and a removeIn at least one minor out on every row', () => {
3313
+ const [major, minor] = pkgVersion.split('.').map(Number)
3314
+ const floor = `${major}.${(minor as number) + 1}.0`
3315
+ for (const row of DEPRECATED_EXPORTS) {
3316
+ expect([row.name, row.replacement.length > 0]).toEqual([row.name, true])
3317
+ expect([row.name, /^\d+\.\d+\.\d+$/.test(row.removeIn)]).toEqual([row.name, true])
3318
+ expect([row.name, compareSemver(row.removeIn, floor) >= 0]).toEqual([row.name, true])
3319
+ }
3320
+ })
3321
+
3322
+ it('names a real published subpath on every row', () => {
3323
+ const exports = Object.keys(
3324
+ (
3325
+ JSON.parse(readFileSync(resolve(import.meta.dirname, '..', 'package.json'), 'utf8')) as {
3326
+ exports: Record<string, unknown>
3327
+ }
3328
+ ).exports,
3329
+ )
3330
+ for (const row of DEPRECATED_EXPORTS) {
3331
+ const subpath = row.subpath === 'basalt-ui' ? '.' : row.subpath.replace('basalt-ui', '.')
3332
+ expect([row.subpath, exports.includes(subpath)]).toEqual([row.subpath, true])
3333
+ }
3334
+ })
3335
+
3336
+ /**
3337
+ * The barrel half of the lifecycle: a JSDoc `@deprecated` on a published barrel with no ledger
3338
+ * row is an export nothing nudges anyone off. Deliberately scoped to `src/**' + '/index.ts(x)` —
3339
+ * the files a consumer's import resolves through. To add one: write the `@deprecated` JSDoc, add
3340
+ * the `DEPRECATED_EXPORTS` row, add the `MIGRATING.md` row.
3341
+ */
3342
+ it('has a row for every @deprecated JSDoc tag on a published barrel', () => {
3343
+ const barrels = [
3344
+ ...new Bun.Glob('src/**/index.{ts,tsx}').scanSync({
3345
+ cwd: resolve(import.meta.dirname, '..'),
3346
+ absolute: true,
3347
+ }),
3348
+ ]
3349
+ const names = new Set(DEPRECATED_EXPORTS.map((row) => row.prop ?? row.name))
3350
+ for (const file of barrels) {
3351
+ const lines = readFileSync(file, 'utf8').split('\n')
3352
+ for (const [index, line] of lines.entries()) {
3353
+ if (!/^\s*\*\s*@deprecated\b/.test(line)) continue
3354
+ const declared = lines
3355
+ .slice(index, index + 12)
3356
+ .map((l) => /^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\??\s*[:(]/.exec(l)?.[1])
3357
+ .find((name) => name !== undefined)
3358
+ expect([file, declared, declared !== undefined && names.has(declared)]).toEqual([
3359
+ file,
3360
+ declared,
3361
+ true,
3362
+ ])
3363
+ }
3364
+ }
3365
+ })
3366
+ })
3367
+
3368
+ // ── the `Ships:` claim above each rule (F26) ─────────────────────────────────
3369
+
3370
+ /**
3371
+ * F26 was a rule's own doc comment claiming a level it did not ship at, with nothing able to catch
3372
+ * it: the C16 ledger is asserted against the preset, and a COMMENT is outside the ledger. So the
3373
+ * claim is now one normalised line — `// Ships: <level>` directly above each rule's `const` — and
3374
+ * this is the assertion that makes it as checkable as the ledger it restates.
3375
+ */
3376
+ describe('the `Ships:` line above each rule', () => {
3377
+ const source = readFileSync(resolve(import.meta.dirname, 'oxlint-plugin.js'), 'utf8')
3378
+ const lines = source.split('\n')
3379
+ const shipped = JSON.parse(readFileSync(resolve(import.meta.dirname, 'oxlint.json'), 'utf8')) as {
3380
+ rules: Record<string, unknown>
3381
+ }
3382
+ // The registry block maps rule id → the `const` implementing it.
3383
+ const registry = [
3384
+ ...source.slice(source.indexOf('export default {')).matchAll(/^ {4}'([\w-]+)': (\w+),$/gm),
3385
+ ].map(([, id, binding]) => ({ id: id as string, binding: binding as string }))
3386
+
3387
+ it('covers every registered rule', () => {
3388
+ expect(registry.map((r) => r.id).toSorted()).toEqual(Object.keys(basaltPlugin.rules).toSorted())
3389
+ })
3390
+
3391
+ for (const { id, binding } of registry) {
3392
+ it(`${id}: the claim matches oxlint.json`, () => {
3393
+ const at = lines.indexOf(`const ${binding} = {`)
3394
+ expect([id, at]).not.toEqual([id, -1])
3395
+ const claim = /^\/\/ Ships: (.+)$/.exec(lines[at - 1] as string)?.[1]
3396
+ const level = shipped.rules[`basalt/${id}`]
3397
+ if (level === undefined) {
3398
+ expect([id, claim]).toEqual([id, 'repo-local only'])
3399
+ return
3400
+ }
3401
+ if (Object.hasOwn(PLUGIN_RULE_GRACE, id)) {
3402
+ const { promote } = PLUGIN_RULE_GRACE[id] as GraceEntry
3403
+ expect([id, claim]).toEqual([id, `warn (grace → ${promote})`])
3404
+ expect([id, level]).toEqual([id, 'warn'])
3405
+ return
3406
+ }
3407
+ if (Object.hasOwn(PLUGIN_RULE_ADVISORY, id)) {
3408
+ expect([id, claim]).toEqual([id, 'warn (advisory)'])
3409
+ expect([id, level]).toEqual([id, 'warn'])
3410
+ return
3411
+ }
3412
+ expect([id, claim]).toEqual([id, 'error'])
3413
+ expect([id, level]).toEqual([id, 'error'])
3414
+ })
3415
+ }
3416
+ })