basalt-ui 1.26.0 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1069) hide show
  1. package/AGENTS.md +23 -23
  2. package/CHANGELOG.md +67 -0
  3. package/MIGRATING.md +896 -37
  4. package/README.md +33 -3
  5. package/agent/rules/basalt-batteries.md +25 -10
  6. package/agent/rules/basalt-controls.md +73 -43
  7. package/agent/rules/basalt-mantine.md +17 -6
  8. package/agent/rules/basalt-state.md +5 -0
  9. package/agent/rules/basalt-tokens.md +1 -1
  10. package/configs/oxlint-plugin.js +1286 -73
  11. package/configs/oxlint-plugin.test.ts +1338 -29
  12. package/configs/oxlint.json +12 -5
  13. package/dist/agent/coalesce.d.ts.map +1 -1
  14. package/dist/agent/coalesce.js +2 -1
  15. package/dist/agent/merge.d.ts.map +1 -1
  16. package/dist/agent/merge.js +2 -1
  17. package/dist/agent/part-list.d.ts.map +1 -1
  18. package/dist/agent/part-list.js +2 -0
  19. package/dist/agent/stick-to-bottom.d.ts +3 -6
  20. package/dist/agent/stick-to-bottom.d.ts.map +1 -1
  21. package/dist/agent/stick-to-bottom.js +6 -3
  22. package/dist/agent-chat/composer.d.ts +6 -2
  23. package/dist/agent-chat/composer.d.ts.map +1 -1
  24. package/dist/agent-chat/composer.js +49 -33
  25. package/dist/agent-chat/relative-time.d.ts +0 -9
  26. package/dist/agent-chat/relative-time.d.ts.map +1 -1
  27. package/dist/agent-chat/relative-time.js +2 -1
  28. package/dist/agent-chat/thread-detail-panel.d.ts +3 -2
  29. package/dist/agent-chat/thread-detail-panel.d.ts.map +1 -1
  30. package/dist/agent-chat/thread-detail-panel.js +8 -5
  31. package/dist/agent-chat/thread-feed-row.d.ts +2 -1
  32. package/dist/agent-chat/thread-feed-row.d.ts.map +1 -1
  33. package/dist/agent-chat/thread-feed-row.js +92 -92
  34. package/dist/agent-chat/thread-feed.d.ts +3 -2
  35. package/dist/agent-chat/thread-feed.d.ts.map +1 -1
  36. package/dist/agent-chat/thread-feed.js +8 -3
  37. package/dist/agent-chat/thread-message.d.ts +4 -3
  38. package/dist/agent-chat/thread-message.d.ts.map +1 -1
  39. package/dist/agent-chat/thread-message.js +70 -31
  40. package/dist/agent-chat/thread-outcome-card.d.ts +3 -2
  41. package/dist/agent-chat/thread-outcome-card.d.ts.map +1 -1
  42. package/dist/agent-chat/thread-outcome-card.js +9 -2
  43. package/dist/agent-chat/thread-workspace.d.ts +7 -2
  44. package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
  45. package/dist/agent-chat/thread-workspace.js +62 -29
  46. package/dist/agent-chat/tool-chip.d.ts +3 -2
  47. package/dist/agent-chat/tool-chip.d.ts.map +1 -1
  48. package/dist/agent-chat/tool-chip.js +7 -2
  49. package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
  50. package/dist/charts/hooks/useBandPlot.js +15 -8
  51. package/dist/charts/index.d.ts +8 -7
  52. package/dist/charts/index.d.ts.map +1 -1
  53. package/dist/charts/index.js +45 -5
  54. package/dist/charts/kinds/BandStrip.d.ts +10 -7
  55. package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
  56. package/dist/charts/kinds/BandStrip.js +12 -7
  57. package/dist/charts/kinds/Bars.d.ts +12 -6
  58. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  59. package/dist/charts/kinds/Bars.js +37 -11
  60. package/dist/charts/kinds/Donut.d.ts +6 -5
  61. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  62. package/dist/charts/kinds/Donut.js +19 -2
  63. package/dist/charts/kinds/DualPanel.d.ts +6 -5
  64. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  65. package/dist/charts/kinds/DualPanel.js +36 -8
  66. package/dist/charts/kinds/Heatmap.d.ts +10 -6
  67. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  68. package/dist/charts/kinds/Heatmap.js +58 -31
  69. package/dist/charts/kinds/MirroredBars.d.ts +6 -5
  70. package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
  71. package/dist/charts/kinds/MirroredBars.js +9 -2
  72. package/dist/charts/kinds/MultiLine.d.ts +11 -6
  73. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  74. package/dist/charts/kinds/MultiLine.js +24 -15
  75. package/dist/charts/kinds/StackedArea.d.ts +18 -8
  76. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  77. package/dist/charts/kinds/StackedArea.js +29 -4
  78. package/dist/charts/kinds/ZonedLine.d.ts +12 -8
  79. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  80. package/dist/charts/kinds/ZonedLine.js +33 -23
  81. package/dist/charts/layout/auto-margin.d.ts +65 -8
  82. package/dist/charts/layout/auto-margin.d.ts.map +1 -1
  83. package/dist/charts/layout/auto-margin.js +45 -10
  84. package/dist/charts/layout/log-ticks.d.ts +27 -0
  85. package/dist/charts/layout/log-ticks.d.ts.map +1 -0
  86. package/dist/charts/layout/log-ticks.js +42 -0
  87. package/dist/charts/primitives/Axes.d.ts +11 -2
  88. package/dist/charts/primitives/Axes.d.ts.map +1 -1
  89. package/dist/charts/primitives/Axes.js +15 -5
  90. package/dist/charts/primitives/CartesianChart.d.ts +45 -7
  91. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  92. package/dist/charts/primitives/CartesianChart.js +86 -28
  93. package/dist/charts/primitives/ChartCard.d.ts +27 -2
  94. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  95. package/dist/charts/primitives/ChartCard.js +37 -17
  96. package/dist/charts/primitives/ChartFrame.d.ts +30 -4
  97. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  98. package/dist/charts/primitives/ChartFrame.js +45 -15
  99. package/dist/charts/primitives/ChartLegend.d.ts +13 -2
  100. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  101. package/dist/charts/primitives/ChartLegend.js +33 -13
  102. package/dist/charts/primitives/ChartPending.d.ts +77 -4
  103. package/dist/charts/primitives/ChartPending.d.ts.map +1 -1
  104. package/dist/charts/primitives/ChartPending.js +100 -6
  105. package/dist/charts/primitives/ChartTooltip.d.ts +7 -0
  106. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  107. package/dist/charts/primitives/ChartTooltip.js +18 -5
  108. package/dist/charts/primitives/Crosshair.d.ts +3 -0
  109. package/dist/charts/primitives/Crosshair.d.ts.map +1 -1
  110. package/dist/charts/primitives/Crosshair.js +3 -1
  111. package/dist/charts/primitives/chart-frame-layout.d.ts +95 -0
  112. package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -0
  113. package/dist/charts/primitives/chart-frame-layout.js +89 -0
  114. package/dist/charts/primitives/chart-tier.d.ts +16 -0
  115. package/dist/charts/primitives/chart-tier.d.ts.map +1 -0
  116. package/dist/charts/primitives/chart-tier.js +20 -0
  117. package/dist/charts/series.d.ts +55 -1
  118. package/dist/charts/series.d.ts.map +1 -1
  119. package/dist/charts/series.js +27 -1
  120. package/dist/charts/sparklines/BarSparkline.d.ts +3 -3
  121. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  122. package/dist/charts/sparklines/BarSparkline.js +9 -3
  123. package/dist/charts/sparklines/LineSparkline.d.ts +3 -3
  124. package/dist/charts/sparklines/LineSparkline.d.ts.map +1 -1
  125. package/dist/charts/sparklines/LineSparkline.js +15 -3
  126. package/dist/charts/sparklines/index.d.ts +2 -2
  127. package/dist/charts/sparklines/index.d.ts.map +1 -1
  128. package/dist/charts/utils/format.d.ts +92 -2
  129. package/dist/charts/utils/format.d.ts.map +1 -1
  130. package/dist/charts/utils/format.js +64 -1
  131. package/dist/charts/utils/ticks.d.ts +81 -2
  132. package/dist/charts/utils/ticks.d.ts.map +1 -1
  133. package/dist/charts/utils/ticks.js +54 -4
  134. package/dist/cli/index.d.ts.map +1 -1
  135. package/dist/cli/index.js +2 -2
  136. package/dist/commands/ShortcutsHelp.d.ts +3 -2
  137. package/dist/commands/ShortcutsHelp.d.ts.map +1 -1
  138. package/dist/commands/ShortcutsHelp.js +8 -2
  139. package/dist/commands/define-commands.d.ts.map +1 -1
  140. package/dist/commands/define-commands.js +2 -2
  141. package/dist/commands/define-overlays.d.ts +102 -0
  142. package/dist/commands/define-overlays.d.ts.map +1 -1
  143. package/dist/commands/define-overlays.js +102 -2
  144. package/dist/commands/index.d.ts +10 -1
  145. package/dist/commands/index.d.ts.map +1 -1
  146. package/dist/commands/index.js +3 -1
  147. package/dist/commands/overlays-mount.d.ts +13 -6
  148. package/dist/commands/overlays-mount.d.ts.map +1 -1
  149. package/dist/commands/overlays-mount.js +23 -14
  150. package/dist/common/errors.d.ts +34 -0
  151. package/dist/common/errors.d.ts.map +1 -0
  152. package/dist/common/errors.js +36 -0
  153. package/dist/common/index.d.ts +15 -0
  154. package/dist/common/index.d.ts.map +1 -0
  155. package/dist/common/index.js +29 -0
  156. package/dist/common/props.d.ts +63 -0
  157. package/dist/common/props.d.ts.map +1 -0
  158. package/dist/common/props.js +6 -0
  159. package/dist/common/query-state-like.d.ts +43 -0
  160. package/dist/common/query-state-like.d.ts.map +1 -0
  161. package/dist/common/query-state-like.js +20 -0
  162. package/dist/common/refs.d.ts +29 -0
  163. package/dist/common/refs.d.ts.map +1 -0
  164. package/dist/common/refs.js +29 -0
  165. package/dist/common/scroll-parent.d.ts +23 -0
  166. package/dist/common/scroll-parent.d.ts.map +1 -0
  167. package/dist/common/scroll-parent.js +21 -0
  168. package/dist/common/validate.d.ts +38 -0
  169. package/dist/common/validate.d.ts.map +1 -0
  170. package/dist/common/validate.js +36 -0
  171. package/dist/connectivity/connectivity-indicator.d.ts +3 -1
  172. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  173. package/dist/connectivity/connectivity-indicator.js +3 -1
  174. package/dist/connectivity/connectivity-provider.d.ts.map +1 -1
  175. package/dist/connectivity/connectivity-provider.js +4 -1
  176. package/dist/connectivity/index.d.ts +1 -0
  177. package/dist/connectivity/index.d.ts.map +1 -1
  178. package/dist/connectivity/types.d.ts +2 -2
  179. package/dist/connectivity/types.d.ts.map +1 -1
  180. package/dist/connectivity/use-connectivity.d.ts.map +1 -1
  181. package/dist/content/article-layout.module.css +11 -4
  182. package/dist/content/callout.d.ts +8 -4
  183. package/dist/content/callout.d.ts.map +1 -1
  184. package/dist/content/callout.js +15 -8
  185. package/dist/content/guide.d.ts +5 -4
  186. package/dist/content/guide.d.ts.map +1 -1
  187. package/dist/content/guide.js +13 -4
  188. package/dist/content/heading-anchor.d.ts +24 -2
  189. package/dist/content/heading-anchor.d.ts.map +1 -1
  190. package/dist/content/heading-anchor.js +23 -9
  191. package/dist/content/index.d.ts.map +1 -1
  192. package/dist/content/markdown.d.ts.map +1 -1
  193. package/dist/content/markdown.js +2 -0
  194. package/dist/content/prose.module.css +9 -7
  195. package/dist/content/reading-progress.d.ts +10 -4
  196. package/dist/content/reading-progress.d.ts.map +1 -1
  197. package/dist/content/reading-progress.js +26 -10
  198. package/dist/content/slug.d.ts +4 -0
  199. package/dist/content/slug.d.ts.map +1 -1
  200. package/dist/content/toc.d.ts +11 -0
  201. package/dist/content/toc.d.ts.map +1 -1
  202. package/dist/content/toc.js +23 -10
  203. package/dist/controls/actions.d.ts +49 -6
  204. package/dist/controls/actions.d.ts.map +1 -1
  205. package/dist/controls/actions.js +39 -8
  206. package/dist/controls/compare-filter.d.ts +3 -2
  207. package/dist/controls/compare-filter.d.ts.map +1 -1
  208. package/dist/controls/compare-filter.js +17 -2
  209. package/dist/controls/control-group.d.ts +3 -3
  210. package/dist/controls/control-group.d.ts.map +1 -1
  211. package/dist/controls/control-group.js +9 -2
  212. package/dist/controls/controls.module.css +169 -186
  213. package/dist/controls/controls.module.css.d.ts +9 -11
  214. package/dist/controls/enum-filter.d.ts +13 -3
  215. package/dist/controls/enum-filter.d.ts.map +1 -1
  216. package/dist/controls/enum-filter.js +32 -15
  217. package/dist/controls/filter-context.d.ts +15 -6
  218. package/dist/controls/filter-context.d.ts.map +1 -1
  219. package/dist/controls/filter-pill.d.ts +3 -3
  220. package/dist/controls/filter-pill.d.ts.map +1 -1
  221. package/dist/controls/filter-pill.js +4 -1
  222. package/dist/controls/filter-set.d.ts +3 -2
  223. package/dist/controls/filter-set.d.ts.map +1 -1
  224. package/dist/controls/filter-set.js +66 -41
  225. package/dist/controls/filter-sheet.d.ts +20 -80
  226. package/dist/controls/filter-sheet.d.ts.map +1 -1
  227. package/dist/controls/filter-sheet.js +9 -95
  228. package/dist/controls/index.d.ts +3 -0
  229. package/dist/controls/index.d.ts.map +1 -1
  230. package/dist/controls/index.js +6 -0
  231. package/dist/controls/multi-select-filter.d.ts +18 -2
  232. package/dist/controls/multi-select-filter.d.ts.map +1 -1
  233. package/dist/controls/multi-select-filter.js +124 -22
  234. package/dist/controls/number-filter.d.ts +28 -0
  235. package/dist/controls/number-filter.d.ts.map +1 -0
  236. package/dist/controls/number-filter.js +172 -0
  237. package/dist/controls/panel-row.d.ts +132 -0
  238. package/dist/controls/panel-row.d.ts.map +1 -0
  239. package/dist/controls/panel-row.js +173 -0
  240. package/dist/controls/panel-row.module.css +86 -0
  241. package/dist/controls/panel-row.module.css.d.ts +13 -0
  242. package/dist/controls/range-filter.d.ts +20 -5
  243. package/dist/controls/range-filter.d.ts.map +1 -1
  244. package/dist/controls/range-filter.js +75 -38
  245. package/dist/controls/search-filter.d.ts +3 -2
  246. package/dist/controls/search-filter.d.ts.map +1 -1
  247. package/dist/controls/search-filter.js +26 -11
  248. package/dist/controls/select-filter.d.ts +2 -1
  249. package/dist/controls/select-filter.d.ts.map +1 -1
  250. package/dist/controls/select-filter.js +4 -0
  251. package/dist/controls/slider-control.d.ts +36 -0
  252. package/dist/controls/slider-control.d.ts.map +1 -0
  253. package/dist/controls/slider-control.js +64 -0
  254. package/dist/controls/sync-button.d.ts +3 -2
  255. package/dist/controls/sync-button.d.ts.map +1 -1
  256. package/dist/controls/sync-button.js +16 -3
  257. package/dist/controls/toggle-filter.d.ts +3 -2
  258. package/dist/controls/toggle-filter.d.ts.map +1 -1
  259. package/dist/controls/toggle-filter.js +29 -12
  260. package/dist/controls/view-tabs.d.ts +3 -2
  261. package/dist/controls/view-tabs.d.ts.map +1 -1
  262. package/dist/controls/view-tabs.js +43 -11
  263. package/dist/controls-dates/date-range-picker.d.ts +1 -1
  264. package/dist/controls-dates/date-range-picker.d.ts.map +1 -1
  265. package/dist/controls-dates/date-range-picker.js +6 -1
  266. package/dist/dashboard/delta-badge.d.ts +5 -1
  267. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  268. package/dist/dashboard/empty-state.d.ts +10 -3
  269. package/dist/dashboard/empty-state.d.ts.map +1 -1
  270. package/dist/dashboard/empty-state.js +37 -6
  271. package/dist/dashboard/index.d.ts +6 -4
  272. package/dist/dashboard/index.d.ts.map +1 -1
  273. package/dist/dashboard/index.js +10 -2
  274. package/dist/dashboard/query-state.d.ts +48 -27
  275. package/dist/dashboard/query-state.d.ts.map +1 -1
  276. package/dist/dashboard/query-state.js +111 -57
  277. package/dist/dashboard/settings-section.d.ts +19 -9
  278. package/dist/dashboard/settings-section.d.ts.map +1 -1
  279. package/dist/dashboard/settings-section.js +74 -25
  280. package/dist/dashboard/stat-card.d.ts +76 -19
  281. package/dist/dashboard/stat-card.d.ts.map +1 -1
  282. package/dist/dashboard/stat-card.js +78 -38
  283. package/dist/dashboard/stat-card.module.css +112 -26
  284. package/dist/dashboard/stat-card.module.css.d.ts +5 -0
  285. package/dist/dashboard/stat-group.d.ts +41 -0
  286. package/dist/dashboard/stat-group.d.ts.map +1 -0
  287. package/dist/dashboard/stat-group.js +30 -0
  288. package/dist/dashboard/stat-group.module.css +66 -0
  289. package/dist/dashboard/stat-group.module.css.d.ts +8 -0
  290. package/dist/dashboard/widget-grid.d.ts +27 -0
  291. package/dist/dashboard/widget-grid.d.ts.map +1 -0
  292. package/dist/dashboard/widget-grid.js +41 -0
  293. package/dist/dashboard/widget-grid.module.css +53 -0
  294. package/dist/dashboard/widget-grid.module.css.d.ts +9 -0
  295. package/dist/data/data-table.d.ts +151 -12
  296. package/dist/data/data-table.d.ts.map +1 -1
  297. package/dist/data/data-table.js +392 -138
  298. package/dist/data/data-table.module.css +64 -0
  299. package/dist/data/data-table.module.css.d.ts +11 -0
  300. package/dist/data/index.d.ts +2 -2
  301. package/dist/data/index.d.ts.map +1 -1
  302. package/dist/data/index.js +24 -2
  303. package/dist/data/query-branch.d.ts +28 -0
  304. package/dist/data/query-branch.d.ts.map +1 -0
  305. package/dist/data/query-branch.js +10 -0
  306. package/dist/data/table.d.ts +2 -2
  307. package/dist/data/table.d.ts.map +1 -1
  308. package/dist/data/virtual-list.d.ts +65 -6
  309. package/dist/data/virtual-list.d.ts.map +1 -1
  310. package/dist/data/virtual-list.js +89 -38
  311. package/dist/data/virtual.d.ts +1 -1
  312. package/dist/data/virtual.d.ts.map +1 -1
  313. package/dist/forms/FormErrorSummary.d.ts +4 -3
  314. package/dist/forms/FormErrorSummary.d.ts.map +1 -1
  315. package/dist/forms/FormErrorSummary.js +6 -4
  316. package/dist/forms/create-form.d.ts +2 -2
  317. package/dist/forms/field.d.ts +46 -6
  318. package/dist/forms/field.d.ts.map +1 -1
  319. package/dist/forms/field.js +10 -5
  320. package/dist/forms/form-layout.d.ts +142 -0
  321. package/dist/forms/form-layout.d.ts.map +1 -0
  322. package/dist/forms/form-layout.js +110 -0
  323. package/dist/forms/form-layout.module.css +145 -0
  324. package/dist/forms/form-layout.module.css.d.ts +20 -0
  325. package/dist/forms/form-state.d.ts +42 -0
  326. package/dist/forms/form-state.d.ts.map +1 -0
  327. package/dist/forms/form-state.js +26 -0
  328. package/dist/forms/index.d.ts +22 -6
  329. package/dist/forms/index.d.ts.map +1 -1
  330. package/dist/forms/index.js +19 -3
  331. package/dist/forms/use-field-array.d.ts +60 -0
  332. package/dist/forms/use-field-array.d.ts.map +1 -0
  333. package/dist/forms/use-field-array.js +20 -0
  334. package/dist/forms/use-form-draft.d.ts +25 -10
  335. package/dist/forms/use-form-draft.d.ts.map +1 -1
  336. package/dist/forms/use-form-draft.js +55 -18
  337. package/dist/forms/use-form-submit.d.ts +88 -0
  338. package/dist/forms/use-form-submit.d.ts.map +1 -0
  339. package/dist/forms/use-form-submit.js +63 -0
  340. package/dist/guard/index.d.ts +1 -1
  341. package/dist/guard/index.d.ts.map +1 -1
  342. package/dist/guard/index.js +27 -11
  343. package/dist/index.d.ts +10 -8
  344. package/dist/index.d.ts.map +1 -1
  345. package/dist/index.js +44 -4
  346. package/dist/nav/types.d.ts +15 -6
  347. package/dist/nav/types.d.ts.map +1 -1
  348. package/dist/notifications/bell.d.ts +3 -2
  349. package/dist/notifications/bell.d.ts.map +1 -1
  350. package/dist/notifications/bell.js +73 -56
  351. package/dist/notifications/center.d.ts +15 -4
  352. package/dist/notifications/center.d.ts.map +1 -1
  353. package/dist/notifications/center.js +5 -2
  354. package/dist/notifications/define-notifications.d.ts +56 -14
  355. package/dist/notifications/define-notifications.d.ts.map +1 -1
  356. package/dist/notifications/define-notifications.js +2 -1
  357. package/dist/notifications/index.d.ts +6 -2
  358. package/dist/notifications/index.d.ts.map +1 -1
  359. package/dist/notifications/index.js +5 -1
  360. package/dist/notifications/mount-guard.d.ts +8 -0
  361. package/dist/notifications/mount-guard.d.ts.map +1 -0
  362. package/dist/notifications/mount-guard.js +19 -0
  363. package/dist/notifications/notify.d.ts +58 -1
  364. package/dist/notifications/notify.d.ts.map +1 -1
  365. package/dist/notifications/notify.js +46 -2
  366. package/dist/notifications/overlay.d.ts.map +1 -1
  367. package/dist/notifications/overlay.js +2 -0
  368. package/dist/notifications/store.d.ts +7 -0
  369. package/dist/notifications/store.d.ts.map +1 -1
  370. package/dist/provider/index.d.ts +37 -5
  371. package/dist/provider/index.d.ts.map +1 -1
  372. package/dist/provider/index.js +66 -28
  373. package/dist/provider/lab-theme.d.ts.map +1 -1
  374. package/dist/provider/lab-theme.js +2 -1
  375. package/dist/query/devtools.d.ts.map +1 -1
  376. package/dist/query/devtools.js +1 -0
  377. package/dist/router-tanstack/field.d.ts +1 -1
  378. package/dist/router-tanstack/field.d.ts.map +1 -1
  379. package/dist/router-tanstack/index.d.ts +1 -1
  380. package/dist/router-tanstack/index.d.ts.map +1 -1
  381. package/dist/router-tanstack/search-store.d.ts +4 -1
  382. package/dist/router-tanstack/search-store.d.ts.map +1 -1
  383. package/dist/router-tanstack/search-store.js +92 -8
  384. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  385. package/dist/router-tanstack/use-nav.js +1 -1
  386. package/dist/section/index.d.ts +1 -1
  387. package/dist/section/index.d.ts.map +1 -1
  388. package/dist/section/section.d.ts +36 -2
  389. package/dist/section/section.d.ts.map +1 -1
  390. package/dist/section/section.js +49 -57
  391. package/dist/section/section.module.css +11 -0
  392. package/dist/shell/account-types.d.ts +5 -2
  393. package/dist/shell/account-types.d.ts.map +1 -1
  394. package/dist/shell/app-brand.d.ts +13 -0
  395. package/dist/shell/app-brand.d.ts.map +1 -0
  396. package/dist/shell/app-brand.js +94 -0
  397. package/dist/shell/app-brand.module.css +109 -0
  398. package/dist/shell/app-brand.module.css.d.ts +14 -0
  399. package/dist/shell/app-breadcrumbs.d.ts +4 -2
  400. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  401. package/dist/shell/app-breadcrumbs.js +33 -22
  402. package/dist/shell/app-header.module.css +10 -5
  403. package/dist/shell/app-main.module.css +107 -0
  404. package/dist/shell/app-main.module.css.d.ts +11 -0
  405. package/dist/shell/app-mobile-nav.d.ts +3 -2
  406. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  407. package/dist/shell/app-mobile-nav.js +29 -15
  408. package/dist/shell/app-mobile-nav.module.css +48 -27
  409. package/dist/shell/app-mobile-nav.module.css.d.ts +0 -1
  410. package/dist/shell/app-sidebar-account.d.ts +2 -1
  411. package/dist/shell/app-sidebar-account.d.ts.map +1 -1
  412. package/dist/shell/app-sidebar-account.js +57 -22
  413. package/dist/shell/app-sidebar.d.ts +9 -3
  414. package/dist/shell/app-sidebar.d.ts.map +1 -1
  415. package/dist/shell/app-sidebar.js +55 -109
  416. package/dist/shell/app-sidebar.module.css +53 -96
  417. package/dist/shell/app-sidebar.module.css.d.ts +0 -5
  418. package/dist/shell/index.d.ts +14 -6
  419. package/dist/shell/index.d.ts.map +1 -1
  420. package/dist/shell/index.js +47 -12
  421. package/dist/shell/isomorphic-layout-effect.d.ts +14 -0
  422. package/dist/shell/isomorphic-layout-effect.d.ts.map +1 -0
  423. package/dist/shell/isomorphic-layout-effect.js +5 -0
  424. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  425. package/dist/shell/mobile-nav-model.js +27 -6
  426. package/dist/shell/nav-count-badge.d.ts +5 -3
  427. package/dist/shell/nav-count-badge.d.ts.map +1 -1
  428. package/dist/shell/nav-count-badge.js +3 -1
  429. package/dist/shell/page-aside.d.ts +43 -0
  430. package/dist/shell/page-aside.d.ts.map +1 -0
  431. package/dist/shell/page-aside.js +202 -0
  432. package/dist/shell/page-aside.module.css +174 -0
  433. package/dist/shell/page-aside.module.css.d.ts +14 -0
  434. package/dist/shell/page-bar.d.ts +55 -15
  435. package/dist/shell/page-bar.d.ts.map +1 -1
  436. package/dist/shell/page-bar.js +130 -41
  437. package/dist/shell/page-bar.module.css +107 -13
  438. package/dist/shell/page-bar.module.css.d.ts +4 -1
  439. package/dist/shell/sidebar-search.d.ts +4 -2
  440. package/dist/shell/sidebar-search.d.ts.map +1 -1
  441. package/dist/shell/sidebar-search.js +9 -4
  442. package/dist/shell/sidebar-search.module.css +1 -1
  443. package/dist/state/fields.d.ts +227 -34
  444. package/dist/state/fields.d.ts.map +1 -1
  445. package/dist/state/fields.js +77 -30
  446. package/dist/state/persisted-or-local.d.ts +9 -0
  447. package/dist/state/persisted-or-local.d.ts.map +1 -0
  448. package/dist/state/persisted-or-local.js +24 -0
  449. package/dist/state/persisted.d.ts +12 -0
  450. package/dist/state/persisted.d.ts.map +1 -1
  451. package/dist/state/persisted.js +36 -18
  452. package/dist/state.d.ts +1 -1
  453. package/dist/state.d.ts.map +1 -1
  454. package/dist/state.js +2 -6
  455. package/dist/surfaces.d.ts +12 -11
  456. package/dist/surfaces.d.ts.map +1 -1
  457. package/dist/surfaces.js +43 -12
  458. package/dist/theme/ctl-theme.d.ts.map +1 -1
  459. package/dist/theme/index.d.ts.map +1 -1
  460. package/dist/theme/index.js +102 -17
  461. package/dist/theme/nav-link.module.css +37 -26
  462. package/dist/theme/segmented-control.module.css +22 -0
  463. package/dist/theme/segmented-control.module.css.d.ts +1 -0
  464. package/dist/theme-lab/boot.d.ts +66 -0
  465. package/dist/theme-lab/boot.d.ts.map +1 -0
  466. package/dist/theme-lab/boot.js +82 -0
  467. package/dist/theme-lab/index.d.ts +4 -43
  468. package/dist/theme-lab/index.d.ts.map +1 -1
  469. package/dist/theme-lab/index.js +9 -69
  470. package/dist/theme-toggle/index.d.ts +3 -2
  471. package/dist/theme-toggle/index.d.ts.map +1 -1
  472. package/dist/theme-toggle/index.js +8 -2
  473. package/dist/tokens/index.d.ts +42 -0
  474. package/dist/tokens/index.d.ts.map +1 -1
  475. package/dist/tokens/index.js +35 -6
  476. package/dist/tokens/palette.d.ts +45 -25
  477. package/dist/tokens/palette.d.ts.map +1 -1
  478. package/dist/tokens/palette.js +31 -14
  479. package/dist/tokens.css +4 -5
  480. package/dist/utils/is-dev.d.ts +3 -0
  481. package/dist/utils/is-dev.d.ts.map +1 -0
  482. package/dist/utils/is-dev.js +6 -0
  483. package/dist/widget-header/delta-badge.d.ts +7 -1
  484. package/dist/widget-header/delta-badge.d.ts.map +1 -1
  485. package/dist/widget-header/delta-badge.js +8 -1
  486. package/dist/widget-header/index.d.ts +2 -2
  487. package/dist/widget-header/index.d.ts.map +1 -1
  488. package/dist/widget-header/index.js +3 -1
  489. package/dist/widget-header/widget-header.d.ts +99 -4
  490. package/dist/widget-header/widget-header.d.ts.map +1 -1
  491. package/dist/widget-header/widget-header.js +45 -18
  492. package/dist/widget-header/widget-header.module.css +87 -7
  493. package/dist/widget-header/widget-header.module.css.d.ts +1 -0
  494. package/llms.txt +6 -6
  495. package/package.json +49 -25
  496. package/src/agent/coalesce.ts +2 -1
  497. package/src/agent/merge.ts +2 -1
  498. package/src/agent/part-list.tsx +2 -0
  499. package/src/agent/stick-to-bottom.tsx +9 -11
  500. package/src/agent-chat/composer.tsx +124 -103
  501. package/src/agent-chat/relative-time.ts +2 -1
  502. package/src/agent-chat/thread-detail-panel.tsx +9 -5
  503. package/src/agent-chat/thread-feed-row.tsx +17 -9
  504. package/src/agent-chat/thread-feed.tsx +9 -3
  505. package/src/agent-chat/thread-message.tsx +48 -13
  506. package/src/agent-chat/thread-outcome-card.tsx +9 -1
  507. package/src/agent-chat/thread-workspace.tsx +72 -33
  508. package/src/agent-chat/tool-chip.tsx +8 -2
  509. package/src/charts/hooks/useBandPlot.ts +26 -8
  510. package/src/charts/index.ts +39 -4
  511. package/src/charts/kinds/BandStrip.tsx +36 -14
  512. package/src/charts/kinds/Bars.tsx +67 -16
  513. package/src/charts/kinds/Donut.tsx +30 -3
  514. package/src/charts/kinds/DualPanel.tsx +56 -10
  515. package/src/charts/kinds/Heatmap.tsx +90 -33
  516. package/src/charts/kinds/MirroredBars.tsx +21 -3
  517. package/src/charts/kinds/MultiLine.tsx +61 -18
  518. package/src/charts/kinds/StackedArea.tsx +77 -6
  519. package/src/charts/kinds/ZonedLine.tsx +71 -31
  520. package/src/charts/layout/auto-margin.ts +134 -21
  521. package/src/charts/layout/log-ticks.ts +90 -0
  522. package/src/charts/primitives/Axes.tsx +33 -5
  523. package/src/charts/primitives/CartesianChart.tsx +194 -41
  524. package/src/charts/primitives/ChartCard.tsx +66 -25
  525. package/src/charts/primitives/ChartFrame.tsx +107 -26
  526. package/src/charts/primitives/ChartLegend.tsx +43 -13
  527. package/src/charts/primitives/ChartPending.tsx +185 -7
  528. package/src/charts/primitives/ChartTooltip.tsx +37 -5
  529. package/src/charts/primitives/Crosshair.tsx +6 -3
  530. package/src/charts/primitives/chart-frame-layout.ts +212 -0
  531. package/src/charts/primitives/chart-tier.tsx +37 -0
  532. package/src/charts/series.ts +77 -1
  533. package/src/charts/sparklines/BarSparkline.tsx +11 -3
  534. package/src/charts/sparklines/LineSparkline.tsx +18 -4
  535. package/src/charts/sparklines/index.ts +2 -2
  536. package/src/charts/utils/format.ts +163 -2
  537. package/src/charts/utils/ticks.ts +149 -4
  538. package/src/cli/index.ts +8 -3
  539. package/src/commands/ShortcutsHelp.tsx +10 -3
  540. package/src/commands/define-commands.ts +2 -2
  541. package/src/commands/define-overlays.ts +216 -1
  542. package/src/commands/index.ts +12 -0
  543. package/src/commands/overlays-mount.tsx +60 -26
  544. package/src/common/errors.ts +73 -0
  545. package/src/common/index.ts +21 -0
  546. package/src/common/props.ts +71 -0
  547. package/src/common/query-state-like.ts +64 -0
  548. package/src/common/refs.ts +58 -0
  549. package/src/common/scroll-parent.ts +42 -0
  550. package/src/common/validate.ts +94 -0
  551. package/src/connectivity/connectivity-indicator.tsx +6 -1
  552. package/src/connectivity/connectivity-provider.tsx +4 -1
  553. package/src/connectivity/index.ts +1 -0
  554. package/src/connectivity/types.ts +5 -2
  555. package/src/connectivity/use-connectivity.ts +3 -2
  556. package/src/content/article-layout.module.css +11 -4
  557. package/src/content/callout.tsx +13 -7
  558. package/src/content/guide.tsx +14 -4
  559. package/src/content/heading-anchor.tsx +14 -3
  560. package/src/content/index.ts +2 -0
  561. package/src/content/markdown.tsx +2 -0
  562. package/src/content/prose.module.css +9 -7
  563. package/src/content/reading-progress.tsx +40 -15
  564. package/src/content/slug.ts +4 -0
  565. package/src/content/toc.tsx +47 -10
  566. package/src/controls/actions.tsx +89 -9
  567. package/src/controls/compare-filter.tsx +20 -3
  568. package/src/controls/control-group.tsx +11 -4
  569. package/src/controls/controls.module.css +169 -186
  570. package/src/controls/controls.module.css.d.ts +9 -11
  571. package/src/controls/enum-filter.tsx +46 -19
  572. package/src/controls/filter-context.tsx +21 -8
  573. package/src/controls/filter-pill.tsx +6 -3
  574. package/src/controls/filter-set.tsx +32 -13
  575. package/src/controls/filter-sheet.tsx +44 -194
  576. package/src/controls/index.ts +8 -0
  577. package/src/controls/multi-select-filter.tsx +179 -29
  578. package/src/controls/number-filter.tsx +329 -0
  579. package/src/controls/panel-row.module.css +86 -0
  580. package/src/controls/panel-row.module.css.d.ts +13 -0
  581. package/src/controls/panel-row.tsx +392 -0
  582. package/src/controls/range-filter.tsx +133 -43
  583. package/src/controls/search-filter.tsx +31 -13
  584. package/src/controls/select-filter.tsx +11 -1
  585. package/src/controls/slider-control.tsx +159 -0
  586. package/src/controls/sync-button.tsx +13 -3
  587. package/src/controls/toggle-filter.tsx +37 -17
  588. package/src/controls/view-tabs.tsx +63 -11
  589. package/src/controls-dates/date-range-picker.tsx +8 -1
  590. package/src/dashboard/delta-badge.tsx +5 -1
  591. package/src/dashboard/empty-state.tsx +72 -25
  592. package/src/dashboard/index.ts +19 -2
  593. package/src/dashboard/query-state.tsx +154 -84
  594. package/src/dashboard/settings-section.tsx +87 -31
  595. package/src/dashboard/stat-card.module.css +112 -26
  596. package/src/dashboard/stat-card.module.css.d.ts +5 -0
  597. package/src/dashboard/stat-card.tsx +207 -68
  598. package/src/dashboard/stat-group.module.css +66 -0
  599. package/src/dashboard/stat-group.module.css.d.ts +8 -0
  600. package/src/dashboard/stat-group.tsx +74 -0
  601. package/src/dashboard/widget-grid.module.css +53 -0
  602. package/src/dashboard/widget-grid.module.css.d.ts +9 -0
  603. package/src/dashboard/widget-grid.tsx +127 -0
  604. package/src/data/data-table.module.css +64 -0
  605. package/src/data/data-table.module.css.d.ts +11 -0
  606. package/src/data/data-table.tsx +927 -346
  607. package/src/data/index.ts +30 -2
  608. package/src/data/query-branch.ts +53 -0
  609. package/src/data/table.ts +5 -0
  610. package/src/data/virtual-list.tsx +189 -55
  611. package/src/data/virtual.ts +5 -1
  612. package/src/forms/FormErrorSummary.tsx +13 -6
  613. package/src/forms/create-form.ts +2 -2
  614. package/src/forms/field.ts +60 -11
  615. package/src/forms/form-layout.module.css +145 -0
  616. package/src/forms/form-layout.module.css.d.ts +20 -0
  617. package/src/forms/form-layout.tsx +305 -0
  618. package/src/forms/form-state.tsx +94 -0
  619. package/src/forms/index.ts +38 -7
  620. package/src/forms/use-field-array.ts +94 -0
  621. package/src/forms/use-form-draft.ts +145 -53
  622. package/src/forms/use-form-submit.ts +199 -0
  623. package/src/guard/index.ts +91 -23
  624. package/src/index.ts +79 -10
  625. package/src/nav/types.ts +15 -6
  626. package/src/notifications/bell.tsx +18 -4
  627. package/src/notifications/center.tsx +18 -5
  628. package/src/notifications/define-notifications.ts +64 -18
  629. package/src/notifications/index.ts +12 -1
  630. package/src/notifications/mount-guard.ts +41 -0
  631. package/src/notifications/notify.tsx +122 -3
  632. package/src/notifications/overlay.tsx +2 -0
  633. package/src/notifications/store.ts +7 -0
  634. package/src/provider/index.tsx +101 -13
  635. package/src/provider/lab-theme.ts +2 -1
  636. package/src/query/devtools.tsx +1 -0
  637. package/src/router-tanstack/field.ts +5 -0
  638. package/src/router-tanstack/index.ts +4 -0
  639. package/src/router-tanstack/search-store.ts +171 -12
  640. package/src/router-tanstack/use-nav.ts +18 -13
  641. package/src/section/index.ts +1 -1
  642. package/src/section/section.module.css +11 -0
  643. package/src/section/section.tsx +163 -96
  644. package/src/shell/account-types.ts +5 -2
  645. package/src/shell/app-brand.module.css +109 -0
  646. package/src/shell/app-brand.module.css.d.ts +14 -0
  647. package/src/shell/app-brand.tsx +140 -0
  648. package/src/shell/app-breadcrumbs.tsx +19 -9
  649. package/src/shell/app-header.module.css +10 -5
  650. package/src/shell/app-main.module.css +107 -0
  651. package/src/shell/app-main.module.css.d.ts +11 -0
  652. package/src/shell/app-mobile-nav.module.css +48 -27
  653. package/src/shell/app-mobile-nav.module.css.d.ts +0 -1
  654. package/src/shell/app-mobile-nav.tsx +43 -11
  655. package/src/shell/app-sidebar-account.tsx +29 -6
  656. package/src/shell/app-sidebar.module.css +53 -96
  657. package/src/shell/app-sidebar.module.css.d.ts +0 -5
  658. package/src/shell/app-sidebar.tsx +40 -113
  659. package/src/shell/index.tsx +156 -65
  660. package/src/shell/isomorphic-layout-effect.ts +14 -0
  661. package/src/shell/mobile-nav-model.ts +66 -10
  662. package/src/shell/nav-count-badge.tsx +6 -1
  663. package/src/shell/page-aside.module.css +174 -0
  664. package/src/shell/page-aside.module.css.d.ts +14 -0
  665. package/src/shell/page-aside.tsx +377 -0
  666. package/src/shell/page-bar.module.css +107 -13
  667. package/src/shell/page-bar.module.css.d.ts +4 -1
  668. package/src/shell/page-bar.tsx +294 -94
  669. package/src/shell/sidebar-search.module.css +1 -1
  670. package/src/shell/sidebar-search.tsx +13 -4
  671. package/src/state/fields.ts +360 -69
  672. package/src/state/persisted-or-local.ts +50 -0
  673. package/src/state/persisted.ts +76 -26
  674. package/src/state.ts +4 -7
  675. package/src/surfaces.ts +43 -12
  676. package/src/theme/ctl-theme.tsx +10 -0
  677. package/src/theme/index.ts +129 -16
  678. package/src/theme/nav-link.module.css +37 -26
  679. package/src/theme/segmented-control.module.css +22 -0
  680. package/src/theme/segmented-control.module.css.d.ts +1 -0
  681. package/src/theme-lab/boot.ts +140 -0
  682. package/src/theme-lab/index.tsx +21 -112
  683. package/src/theme-toggle/index.tsx +10 -3
  684. package/src/tokens/index.ts +50 -6
  685. package/src/tokens/palette.ts +58 -30
  686. package/src/utils/is-dev.ts +4 -0
  687. package/src/widget-header/delta-badge.tsx +25 -5
  688. package/src/widget-header/index.ts +10 -2
  689. package/src/widget-header/widget-header.module.css +87 -7
  690. package/src/widget-header/widget-header.module.css.d.ts +1 -0
  691. package/src/widget-header/widget-header.tsx +166 -42
  692. package/dist/agent/adapter.test.d.ts +0 -2
  693. package/dist/agent/adapter.test.d.ts.map +0 -1
  694. package/dist/agent/ai-sdk-transport.test.d.ts +0 -2
  695. package/dist/agent/ai-sdk-transport.test.d.ts.map +0 -1
  696. package/dist/agent/coalesce.test.d.ts +0 -2
  697. package/dist/agent/coalesce.test.d.ts.map +0 -1
  698. package/dist/agent/foreign.test.d.ts +0 -2
  699. package/dist/agent/foreign.test.d.ts.map +0 -1
  700. package/dist/agent/id.test.d.ts +0 -2
  701. package/dist/agent/id.test.d.ts.map +0 -1
  702. package/dist/agent/merge.test.d.ts +0 -2
  703. package/dist/agent/merge.test.d.ts.map +0 -1
  704. package/dist/agent/part-list.test.d.ts +0 -2
  705. package/dist/agent/part-list.test.d.ts.map +0 -1
  706. package/dist/agent/parts.test.d.ts +0 -2
  707. package/dist/agent/parts.test.d.ts.map +0 -1
  708. package/dist/agent/thread.test.d.ts +0 -2
  709. package/dist/agent/thread.test.d.ts.map +0 -1
  710. package/dist/agent/transport.test.d.ts +0 -2
  711. package/dist/agent/transport.test.d.ts.map +0 -1
  712. package/dist/agent/use-agent-stream.test.d.ts +0 -2
  713. package/dist/agent/use-agent-stream.test.d.ts.map +0 -1
  714. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts +0 -2
  715. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts.map +0 -1
  716. package/dist/agent/use-agent-thread-runs.resume.test.d.ts +0 -2
  717. package/dist/agent/use-agent-thread-runs.resume.test.d.ts.map +0 -1
  718. package/dist/agent/use-agent-thread-runs.test.d.ts +0 -2
  719. package/dist/agent/use-agent-thread-runs.test.d.ts.map +0 -1
  720. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts +0 -2
  721. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts.map +0 -1
  722. package/dist/agent-chat/composer.test.d.ts +0 -2
  723. package/dist/agent-chat/composer.test.d.ts.map +0 -1
  724. package/dist/agent-chat/relative-time.test.d.ts +0 -2
  725. package/dist/agent-chat/relative-time.test.d.ts.map +0 -1
  726. package/dist/agent-chat/thread-feed-row.test.d.ts +0 -2
  727. package/dist/agent-chat/thread-feed-row.test.d.ts.map +0 -1
  728. package/dist/agent-chat/thread-feed.test.d.ts +0 -2
  729. package/dist/agent-chat/thread-feed.test.d.ts.map +0 -1
  730. package/dist/agent-chat/thread-message.test.d.ts +0 -2
  731. package/dist/agent-chat/thread-message.test.d.ts.map +0 -1
  732. package/dist/agent-chat/thread-workspace.test.d.ts +0 -2
  733. package/dist/agent-chat/thread-workspace.test.d.ts.map +0 -1
  734. package/dist/agent-chat/tool-chip.test.d.ts +0 -2
  735. package/dist/agent-chat/tool-chip.test.d.ts.map +0 -1
  736. package/dist/agent-chat/virtualize.type-guard.test.d.ts +0 -2
  737. package/dist/agent-chat/virtualize.type-guard.test.d.ts.map +0 -1
  738. package/dist/charts/cursor/resolve.test.d.ts +0 -2
  739. package/dist/charts/cursor/resolve.test.d.ts.map +0 -1
  740. package/dist/charts/cursor/store.test.d.ts +0 -2
  741. package/dist/charts/cursor/store.test.d.ts.map +0 -1
  742. package/dist/charts/kinds/BandStrip.test.d.ts +0 -2
  743. package/dist/charts/kinds/BandStrip.test.d.ts.map +0 -1
  744. package/dist/charts/kinds/Bars.test.d.ts +0 -2
  745. package/dist/charts/kinds/Bars.test.d.ts.map +0 -1
  746. package/dist/charts/kinds/DualPanel.test.d.ts +0 -2
  747. package/dist/charts/kinds/DualPanel.test.d.ts.map +0 -1
  748. package/dist/charts/kinds/MirroredBars.test.d.ts +0 -2
  749. package/dist/charts/kinds/MirroredBars.test.d.ts.map +0 -1
  750. package/dist/charts/kinds/MultiLine.test.d.ts +0 -2
  751. package/dist/charts/kinds/MultiLine.test.d.ts.map +0 -1
  752. package/dist/charts/kinds/StackedArea.test.d.ts +0 -2
  753. package/dist/charts/kinds/StackedArea.test.d.ts.map +0 -1
  754. package/dist/charts/kinds/ZonedLine.test.d.ts +0 -2
  755. package/dist/charts/kinds/ZonedLine.test.d.ts.map +0 -1
  756. package/dist/charts/layout/auto-margin.test.d.ts +0 -2
  757. package/dist/charts/layout/auto-margin.test.d.ts.map +0 -1
  758. package/dist/charts/primitives/CartesianChart.test.d.ts +0 -2
  759. package/dist/charts/primitives/CartesianChart.test.d.ts.map +0 -1
  760. package/dist/charts/primitives/ChartCard.test.d.ts +0 -2
  761. package/dist/charts/primitives/ChartCard.test.d.ts.map +0 -1
  762. package/dist/charts/primitives/ChartFrame.test.d.ts +0 -2
  763. package/dist/charts/primitives/ChartFrame.test.d.ts.map +0 -1
  764. package/dist/charts/primitives/ChartLegend.test.d.ts +0 -2
  765. package/dist/charts/primitives/ChartLegend.test.d.ts.map +0 -1
  766. package/dist/charts/primitives/ChartTooltip.test.d.ts +0 -2
  767. package/dist/charts/primitives/ChartTooltip.test.d.ts.map +0 -1
  768. package/dist/charts/primitives/XZoneRects.test.d.ts +0 -2
  769. package/dist/charts/primitives/XZoneRects.test.d.ts.map +0 -1
  770. package/dist/charts/series.test.d.ts +0 -2
  771. package/dist/charts/series.test.d.ts.map +0 -1
  772. package/dist/charts/utils/domain.test.d.ts +0 -2
  773. package/dist/charts/utils/domain.test.d.ts.map +0 -1
  774. package/dist/charts/utils/measure-text.test.d.ts +0 -2
  775. package/dist/charts/utils/measure-text.test.d.ts.map +0 -1
  776. package/dist/cli/check-coverage.test.d.ts +0 -2
  777. package/dist/cli/check-coverage.test.d.ts.map +0 -1
  778. package/dist/cli/check-theme.test.d.ts +0 -2
  779. package/dist/cli/check-theme.test.d.ts.map +0 -1
  780. package/dist/cli/help.test.d.ts +0 -2
  781. package/dist/cli/help.test.d.ts.map +0 -1
  782. package/dist/cli/init-sync.test.d.ts +0 -2
  783. package/dist/cli/init-sync.test.d.ts.map +0 -1
  784. package/dist/cli/placement-engine.test.d.ts +0 -2
  785. package/dist/cli/placement-engine.test.d.ts.map +0 -1
  786. package/dist/cli/shipped-versions.test.d.ts +0 -2
  787. package/dist/cli/shipped-versions.test.d.ts.map +0 -1
  788. package/dist/cli/tarball-parity.test.d.ts +0 -2
  789. package/dist/cli/tarball-parity.test.d.ts.map +0 -1
  790. package/dist/cli/template-format.test.d.ts +0 -2
  791. package/dist/cli/template-format.test.d.ts.map +0 -1
  792. package/dist/cli/template-ledger.test.d.ts +0 -2
  793. package/dist/cli/template-ledger.test.d.ts.map +0 -1
  794. package/dist/cli/tokens-css.test.d.ts +0 -2
  795. package/dist/cli/tokens-css.test.d.ts.map +0 -1
  796. package/dist/cli/toolchain-wiring.test.d.ts +0 -2
  797. package/dist/cli/toolchain-wiring.test.d.ts.map +0 -1
  798. package/dist/cli/waiver-audit.test.d.ts +0 -2
  799. package/dist/cli/waiver-audit.test.d.ts.map +0 -1
  800. package/dist/content/article-actions.test.d.ts +0 -2
  801. package/dist/content/article-actions.test.d.ts.map +0 -1
  802. package/dist/content/article-model.test.d.ts +0 -2
  803. package/dist/content/article-model.test.d.ts.map +0 -1
  804. package/dist/content/block-split.test.d.ts +0 -2
  805. package/dist/content/block-split.test.d.ts.map +0 -1
  806. package/dist/content/heading-components.test.d.ts +0 -2
  807. package/dist/content/heading-components.test.d.ts.map +0 -1
  808. package/dist/content/markdown.test.d.ts +0 -2
  809. package/dist/content/markdown.test.d.ts.map +0 -1
  810. package/dist/content/prose.module.css.test.d.ts +0 -2
  811. package/dist/content/prose.module.css.test.d.ts.map +0 -1
  812. package/dist/content/sanitize.test.d.ts +0 -2
  813. package/dist/content/sanitize.test.d.ts.map +0 -1
  814. package/dist/content/slug.test.d.ts +0 -2
  815. package/dist/content/slug.test.d.ts.map +0 -1
  816. package/dist/controls/actions.test.d.ts +0 -2
  817. package/dist/controls/actions.test.d.ts.map +0 -1
  818. package/dist/controls/actions.type-guard.test.d.ts +0 -2
  819. package/dist/controls/actions.type-guard.test.d.ts.map +0 -1
  820. package/dist/controls/control-group.test.d.ts +0 -2
  821. package/dist/controls/control-group.test.d.ts.map +0 -1
  822. package/dist/controls/controls.router.test.d.ts +0 -2
  823. package/dist/controls/controls.router.test.d.ts.map +0 -1
  824. package/dist/controls/filter-set.test.d.ts +0 -2
  825. package/dist/controls/filter-set.test.d.ts.map +0 -1
  826. package/dist/controls/peer-boundary.test.d.ts +0 -2
  827. package/dist/controls/peer-boundary.test.d.ts.map +0 -1
  828. package/dist/controls/props-contract.test.d.ts +0 -2
  829. package/dist/controls/props-contract.test.d.ts.map +0 -1
  830. package/dist/controls/sync-button.test.d.ts +0 -2
  831. package/dist/controls/sync-button.test.d.ts.map +0 -1
  832. package/dist/dashboard/query-state.test.d.ts +0 -2
  833. package/dist/dashboard/query-state.test.d.ts.map +0 -1
  834. package/dist/dashboard/settings-section.test.d.ts +0 -2
  835. package/dist/dashboard/settings-section.test.d.ts.map +0 -1
  836. package/dist/dashboard/stat-card.test.d.ts +0 -2
  837. package/dist/dashboard/stat-card.test.d.ts.map +0 -1
  838. package/dist/data/data-table.test.d.ts +0 -2
  839. package/dist/data/data-table.test.d.ts.map +0 -1
  840. package/dist/guard/check-source.test.d.ts +0 -2
  841. package/dist/guard/check-source.test.d.ts.map +0 -1
  842. package/dist/guard/grace.test.d.ts +0 -20
  843. package/dist/guard/grace.test.d.ts.map +0 -1
  844. package/dist/guard/guard-hook.test.d.ts +0 -2
  845. package/dist/guard/guard-hook.test.d.ts.map +0 -1
  846. package/dist/provider/index.test.d.ts +0 -2
  847. package/dist/provider/index.test.d.ts.map +0 -1
  848. package/dist/provider/lab-theme.test.d.ts +0 -2
  849. package/dist/provider/lab-theme.test.d.ts.map +0 -1
  850. package/dist/query/error-message.test.d.ts +0 -2
  851. package/dist/query/error-message.test.d.ts.map +0 -1
  852. package/dist/router-tanstack/multi-search-param-store.test.d.ts +0 -2
  853. package/dist/router-tanstack/multi-search-param-store.test.d.ts.map +0 -1
  854. package/dist/router-tanstack/nav.test.d.ts +0 -2
  855. package/dist/router-tanstack/nav.test.d.ts.map +0 -1
  856. package/dist/router-tanstack/search-param-store.test.d.ts +0 -2
  857. package/dist/router-tanstack/search-param-store.test.d.ts.map +0 -1
  858. package/dist/router-tanstack/search-store.router.test.d.ts +0 -2
  859. package/dist/router-tanstack/search-store.router.test.d.ts.map +0 -1
  860. package/dist/router-tanstack/search-store.test.d.ts +0 -2
  861. package/dist/router-tanstack/search-store.test.d.ts.map +0 -1
  862. package/dist/router-tanstack/use-nav.test.d.ts +0 -2
  863. package/dist/router-tanstack/use-nav.test.d.ts.map +0 -1
  864. package/dist/section/section.test.d.ts +0 -2
  865. package/dist/section/section.test.d.ts.map +0 -1
  866. package/dist/shell/app-mobile-nav.test.d.ts +0 -2
  867. package/dist/shell/app-mobile-nav.test.d.ts.map +0 -1
  868. package/dist/shell/app-sidebar.test.d.ts +0 -2
  869. package/dist/shell/app-sidebar.test.d.ts.map +0 -1
  870. package/dist/shell/index.test.d.ts +0 -2
  871. package/dist/shell/index.test.d.ts.map +0 -1
  872. package/dist/shell/mobile-nav-model.test.d.ts +0 -2
  873. package/dist/shell/mobile-nav-model.test.d.ts.map +0 -1
  874. package/dist/shell/page-bar.test.d.ts +0 -2
  875. package/dist/shell/page-bar.test.d.ts.map +0 -1
  876. package/dist/state/fields.test.d.ts +0 -2
  877. package/dist/state/fields.test.d.ts.map +0 -1
  878. package/dist/styles.floor.test.d.ts +0 -2
  879. package/dist/styles.floor.test.d.ts.map +0 -1
  880. package/dist/theme/border-coverage.test.d.ts +0 -18
  881. package/dist/theme/border-coverage.test.d.ts.map +0 -1
  882. package/dist/theme/contrast.test.d.ts +0 -2
  883. package/dist/theme/contrast.test.d.ts.map +0 -1
  884. package/dist/theme/ctl-theme.test.d.ts +0 -2
  885. package/dist/theme/ctl-theme.test.d.ts.map +0 -1
  886. package/dist/theme/ctl-tier-coverage.test.d.ts +0 -2
  887. package/dist/theme/ctl-tier-coverage.test.d.ts.map +0 -1
  888. package/dist/theme/density-option.test.d.ts +0 -2
  889. package/dist/theme/density-option.test.d.ts.map +0 -1
  890. package/dist/theme/density-relations.test.d.ts +0 -2
  891. package/dist/theme/density-relations.test.d.ts.map +0 -1
  892. package/dist/theme/derive-theme.test.d.ts +0 -2
  893. package/dist/theme/derive-theme.test.d.ts.map +0 -1
  894. package/dist/theme/divider-law.test.d.ts +0 -2
  895. package/dist/theme/divider-law.test.d.ts.map +0 -1
  896. package/dist/theme/fonts-option.test.d.ts +0 -2
  897. package/dist/theme/fonts-option.test.d.ts.map +0 -1
  898. package/dist/theme/icon-slot.test.d.ts +0 -2
  899. package/dist/theme/icon-slot.test.d.ts.map +0 -1
  900. package/dist/theme/layout-rhythm-css.test.d.ts +0 -2
  901. package/dist/theme/layout-rhythm-css.test.d.ts.map +0 -1
  902. package/dist/theme/radius-option.test.d.ts +0 -2
  903. package/dist/theme/radius-option.test.d.ts.map +0 -1
  904. package/dist/theme/radius.test.d.ts +0 -2
  905. package/dist/theme/radius.test.d.ts.map +0 -1
  906. package/dist/theme/shadow-surfaces.test.d.ts +0 -26
  907. package/dist/theme/shadow-surfaces.test.d.ts.map +0 -1
  908. package/dist/theme/spacing.test.d.ts +0 -2
  909. package/dist/theme/spacing.test.d.ts.map +0 -1
  910. package/dist/theme/use-basalt-spacing.test.d.ts +0 -2
  911. package/dist/theme/use-basalt-spacing.test.d.ts.map +0 -1
  912. package/dist/theme-lab/derive-controls.test.d.ts +0 -2
  913. package/dist/theme-lab/derive-controls.test.d.ts.map +0 -1
  914. package/dist/theme-lab/derive-state.test.d.ts +0 -2
  915. package/dist/theme-lab/derive-state.test.d.ts.map +0 -1
  916. package/dist/theme-lab/theme-lab.test.d.ts +0 -2
  917. package/dist/theme-lab/theme-lab.test.d.ts.map +0 -1
  918. package/dist/tokens/build-fonts-css.test.d.ts +0 -2
  919. package/dist/tokens/build-fonts-css.test.d.ts.map +0 -1
  920. package/dist/tokens/build-radius-css.test.d.ts +0 -2
  921. package/dist/tokens/build-radius-css.test.d.ts.map +0 -1
  922. package/dist/tokens/chart-margin.test.d.ts +0 -2
  923. package/dist/tokens/chart-margin.test.d.ts.map +0 -1
  924. package/dist/tokens/density.test.d.ts +0 -2
  925. package/dist/tokens/density.test.d.ts.map +0 -1
  926. package/dist/tokens/derive-radius.test.d.ts +0 -2
  927. package/dist/tokens/derive-radius.test.d.ts.map +0 -1
  928. package/dist/tokens/derive.test.d.ts +0 -2
  929. package/dist/tokens/derive.test.d.ts.map +0 -1
  930. package/dist/tokens/palette.test.d.ts +0 -2
  931. package/dist/tokens/palette.test.d.ts.map +0 -1
  932. package/dist/tokens/text-scale.test.d.ts +0 -2
  933. package/dist/tokens/text-scale.test.d.ts.map +0 -1
  934. package/dist/vite-color-mix.test.d.ts +0 -2
  935. package/dist/vite-color-mix.test.d.ts.map +0 -1
  936. package/dist/vite.test.d.ts +0 -2
  937. package/dist/vite.test.d.ts.map +0 -1
  938. package/dist/widget-header/delta-badge.test.d.ts +0 -2
  939. package/dist/widget-header/delta-badge.test.d.ts.map +0 -1
  940. package/dist/widget-header/no-mantine.test.d.ts +0 -2
  941. package/dist/widget-header/no-mantine.test.d.ts.map +0 -1
  942. package/dist/widget-header/widget-header.test.d.ts +0 -2
  943. package/dist/widget-header/widget-header.test.d.ts.map +0 -1
  944. package/src/agent/adapter.test.ts +0 -1640
  945. package/src/agent/ai-sdk-transport.test.ts +0 -658
  946. package/src/agent/coalesce.test.ts +0 -387
  947. package/src/agent/foreign.test.ts +0 -51
  948. package/src/agent/id.test.ts +0 -169
  949. package/src/agent/merge.test.ts +0 -206
  950. package/src/agent/part-list.test.tsx +0 -112
  951. package/src/agent/parts.test.ts +0 -720
  952. package/src/agent/thread.test.ts +0 -220
  953. package/src/agent/transport.test.ts +0 -70
  954. package/src/agent/use-agent-stream.test.tsx +0 -287
  955. package/src/agent/use-agent-thread-runs.foreign.test.tsx +0 -138
  956. package/src/agent/use-agent-thread-runs.resume.test.tsx +0 -294
  957. package/src/agent/use-agent-thread-runs.test.tsx +0 -1120
  958. package/src/agent/use-agent-thread-runs.wedge.test.tsx +0 -387
  959. package/src/agent-chat/composer.test.tsx +0 -924
  960. package/src/agent-chat/relative-time.test.ts +0 -95
  961. package/src/agent-chat/thread-feed-row.test.tsx +0 -510
  962. package/src/agent-chat/thread-feed.test.tsx +0 -241
  963. package/src/agent-chat/thread-message.test.tsx +0 -1285
  964. package/src/agent-chat/thread-workspace.test.tsx +0 -169
  965. package/src/agent-chat/tool-chip.test.tsx +0 -139
  966. package/src/agent-chat/virtualize.type-guard.test.ts +0 -49
  967. package/src/charts/cursor/resolve.test.ts +0 -151
  968. package/src/charts/cursor/store.test.ts +0 -64
  969. package/src/charts/kinds/BandStrip.test.tsx +0 -282
  970. package/src/charts/kinds/Bars.test.tsx +0 -183
  971. package/src/charts/kinds/DualPanel.test.tsx +0 -527
  972. package/src/charts/kinds/MirroredBars.test.tsx +0 -232
  973. package/src/charts/kinds/MultiLine.test.tsx +0 -218
  974. package/src/charts/kinds/StackedArea.test.tsx +0 -114
  975. package/src/charts/kinds/ZonedLine.test.tsx +0 -147
  976. package/src/charts/layout/auto-margin.test.ts +0 -42
  977. package/src/charts/primitives/CartesianChart.test.tsx +0 -647
  978. package/src/charts/primitives/ChartCard.test.tsx +0 -66
  979. package/src/charts/primitives/ChartFrame.test.tsx +0 -142
  980. package/src/charts/primitives/ChartLegend.test.tsx +0 -78
  981. package/src/charts/primitives/ChartTooltip.test.tsx +0 -44
  982. package/src/charts/primitives/XZoneRects.test.tsx +0 -123
  983. package/src/charts/series.test.ts +0 -112
  984. package/src/charts/utils/domain.test.ts +0 -54
  985. package/src/charts/utils/measure-text.test.ts +0 -36
  986. package/src/cli/check-coverage.test.ts +0 -297
  987. package/src/cli/check-theme.test.ts +0 -837
  988. package/src/cli/help.test.ts +0 -170
  989. package/src/cli/init-sync.test.ts +0 -343
  990. package/src/cli/placement-engine.test.ts +0 -298
  991. package/src/cli/shipped-versions.test.ts +0 -83
  992. package/src/cli/tarball-parity.test.ts +0 -44
  993. package/src/cli/template-format.test.ts +0 -64
  994. package/src/cli/template-ledger.test.ts +0 -219
  995. package/src/cli/tokens-css.test.ts +0 -249
  996. package/src/cli/toolchain-wiring.test.ts +0 -1358
  997. package/src/cli/waiver-audit.test.ts +0 -215
  998. package/src/content/article-actions.test.ts +0 -78
  999. package/src/content/article-model.test.ts +0 -119
  1000. package/src/content/block-split.test.ts +0 -75
  1001. package/src/content/heading-components.test.tsx +0 -65
  1002. package/src/content/markdown.test.tsx +0 -603
  1003. package/src/content/prose.module.css.test.ts +0 -39
  1004. package/src/content/sanitize.test.ts +0 -659
  1005. package/src/content/slug.test.ts +0 -62
  1006. package/src/controls/actions.test.tsx +0 -252
  1007. package/src/controls/actions.type-guard.test.ts +0 -63
  1008. package/src/controls/control-group.test.tsx +0 -229
  1009. package/src/controls/controls.router.test.tsx +0 -637
  1010. package/src/controls/filter-set.test.tsx +0 -472
  1011. package/src/controls/peer-boundary.test.ts +0 -85
  1012. package/src/controls/props-contract.test.tsx +0 -106
  1013. package/src/controls/sync-button.test.tsx +0 -183
  1014. package/src/dashboard/query-state.test.tsx +0 -152
  1015. package/src/dashboard/settings-section.test.tsx +0 -56
  1016. package/src/dashboard/stat-card.test.tsx +0 -154
  1017. package/src/data/data-table.test.tsx +0 -434
  1018. package/src/guard/check-source.test.ts +0 -3355
  1019. package/src/guard/grace.test.ts +0 -149
  1020. package/src/guard/guard-hook.test.ts +0 -203
  1021. package/src/provider/index.test.tsx +0 -101
  1022. package/src/provider/lab-theme.test.ts +0 -147
  1023. package/src/query/error-message.test.ts +0 -67
  1024. package/src/router-tanstack/multi-search-param-store.test.ts +0 -124
  1025. package/src/router-tanstack/nav.test.ts +0 -111
  1026. package/src/router-tanstack/search-param-store.test.ts +0 -135
  1027. package/src/router-tanstack/search-store.router.test.tsx +0 -661
  1028. package/src/router-tanstack/search-store.test.ts +0 -418
  1029. package/src/router-tanstack/use-nav.test.tsx +0 -187
  1030. package/src/section/section.test.tsx +0 -204
  1031. package/src/shell/app-mobile-nav.test.tsx +0 -403
  1032. package/src/shell/app-sidebar.test.tsx +0 -509
  1033. package/src/shell/index.test.tsx +0 -428
  1034. package/src/shell/mobile-nav-model.test.ts +0 -517
  1035. package/src/shell/page-bar.test.tsx +0 -316
  1036. package/src/state/fields.test.tsx +0 -258
  1037. package/src/styles.floor.test.ts +0 -36
  1038. package/src/theme/border-coverage.test.ts +0 -272
  1039. package/src/theme/contrast.test.ts +0 -354
  1040. package/src/theme/ctl-theme.test.tsx +0 -275
  1041. package/src/theme/ctl-tier-coverage.test.ts +0 -170
  1042. package/src/theme/density-option.test.ts +0 -96
  1043. package/src/theme/density-relations.test.ts +0 -506
  1044. package/src/theme/derive-theme.test.ts +0 -101
  1045. package/src/theme/divider-law.test.ts +0 -259
  1046. package/src/theme/fonts-option.test.ts +0 -75
  1047. package/src/theme/icon-slot.test.tsx +0 -275
  1048. package/src/theme/layout-rhythm-css.test.ts +0 -72
  1049. package/src/theme/radius-option.test.ts +0 -86
  1050. package/src/theme/radius.test.ts +0 -142
  1051. package/src/theme/shadow-surfaces.test.ts +0 -488
  1052. package/src/theme/spacing.test.ts +0 -495
  1053. package/src/theme/use-basalt-spacing.test.tsx +0 -55
  1054. package/src/theme-lab/derive-controls.test.ts +0 -94
  1055. package/src/theme-lab/derive-state.test.ts +0 -71
  1056. package/src/theme-lab/theme-lab.test.ts +0 -46
  1057. package/src/tokens/build-fonts-css.test.ts +0 -52
  1058. package/src/tokens/build-radius-css.test.ts +0 -25
  1059. package/src/tokens/chart-margin.test.ts +0 -39
  1060. package/src/tokens/density.test.ts +0 -259
  1061. package/src/tokens/derive-radius.test.ts +0 -62
  1062. package/src/tokens/derive.test.ts +0 -30
  1063. package/src/tokens/palette.test.ts +0 -114
  1064. package/src/tokens/text-scale.test.ts +0 -51
  1065. package/src/vite-color-mix.test.ts +0 -46
  1066. package/src/vite.test.ts +0 -423
  1067. package/src/widget-header/delta-badge.test.tsx +0 -60
  1068. package/src/widget-header/no-mantine.test.ts +0 -33
  1069. package/src/widget-header/widget-header.test.tsx +0 -187
package/MIGRATING.md CHANGED
@@ -32,6 +32,855 @@ at 1.3.0, `./agent-chat` at 1.10.0.
32
32
 
33
33
  ## Unreleased
34
34
 
35
+ **Nothing removed, and ONE thing changed shape: `inputProps` no longer returns `key`** — a silent
36
+ behaviour change at every field call site, caught by the new `basalt/forms-field-key` and described
37
+ in its own § below. Read that one before upgrading; the rest of the minor is additive, with
38
+ enforcement tightened: five oxlint rules plus one guard
39
+ kind became `error`, and new rule ids shipped at `warn`. The store half of this minor also
40
+ fixed four inference and notification defects consumers hit while porting, plus two ways a fallback
41
+ got pinned into localStorage — see § Stores below. Every level change honours the `theme-allow`
42
+ grammar unchanged; only the severity of an unwaived finding moves.
43
+
44
+ **One behaviour change with a blast radius past this package: `AppShell.Main` is now the
45
+ scrollport, and the WINDOW no longer scrolls inside a `BasaltShell`.** Nothing was removed or
46
+ renamed, but four things a consumer may be doing stop working, and all four have a one-line remedy —
47
+ see § The shell scrollport below.
48
+
49
+ What is new, one line each — nothing here renames or removes anything:
50
+
51
+ - **`scrollParentOf` / `SCROLLPORT_ATTRIBUTE`** (`basalt-ui`) — resolve which box actually scrolls an
52
+ element (`null` = the document). The seam behind the shell scrollport change below.
53
+ - **`PageAside`** (`basalt-ui`) — the right-hand aside REGION a route claims, with the panel filter
54
+ surface in its body and a mobile projection into `PageBar` row 2 (`docs/ASIDE-SPEC.md`).
55
+ - **`PanelRow`** (`basalt-ui/controls`) — the aside's inspector/facet row: label above, control
56
+ below, `end` for a control that rides the label line. Every bound control renders one on the
57
+ `panel` surface.
58
+ - **`SliderControl`** (`basalt-ui/controls`) — a `field.number`-bound track for a weight or a
59
+ threshold. It always renders a `PanelRow`, so unlike the pill filters a `Section` body is a
60
+ legitimate home for it and `basalt/bound-control-outside-home` does not police it.
61
+ - **`MultiSelectFilter.counts` / `.max`** — per-option counts (a mono number plus a proportional
62
+ bar) and the panel facet cut-off before the `Show more` fold. Both optional; omitted is today's
63
+ rendering.
64
+ - **`field.number({ step })`** — the declared grain, republished on the handle. `field.number`
65
+ resolves `1` for an `int: true` field that declared none, so `NumberFilter` and `SliderControl`
66
+ cannot answer it differently.
67
+ - **`basalt/bound-control-outside-home`** — a NEW rule id at `warn` (grace to 1.30.0): a bound
68
+ basalt control written into a `Section` body or a page stack renders as a stray pill.
69
+ `control-outside-home` matches raw Mantine tags only, so this half of law C1 was unguarded. Homes
70
+ are the slot props plus the `FilterSet` / `PageAside` / `PanelRow` subtrees; the escape is
71
+ `theme-allow bound-control-outside-home — <why>`.
72
+ - **The chart phone tier** (`basalt-ui/charts`) — `resolveChartTier` / `chartTierMetrics` /
73
+ `useChartTier` / `useChartTierMetrics`, plus `VX.phoneChartWidth` (480). Below that MEASURED
74
+ container width (never a media query — a chart in a narrow grid cell on a desktop is as narrow as
75
+ one on a phone) a chart drops the legend and tick fonts one type step, tightens the `VX.margin`
76
+ FLOORS to 0.75×, shrinks the crosshair dot, narrows the tooltip 140 → 110, and caps the legend at
77
+ two entries with a `+N more` rollup. **This moves rendering, but only below 480px of measured
78
+ width** — nothing at desktop width changes. Opt back out per chart with an explicit
79
+ `legend.maxRows` / `margin` override. `docs/CHARTS-SPEC.md` §8.
80
+ - **`xLabelRotate` accepts `0`**, and left UNSET now auto-rotates to 45 at the phone tier when the
81
+ measured labels cannot fit three ticks side by side. `0` is the documented opt-out. Also on every
82
+ rotated axis, at every width: the LEFT margin now clears the rotated label's leftward projection
83
+ (`width × cos(angle)`), which previously ran the first label off the plot, and `smartTicks` drops
84
+ the grid tick before an appended final one when the two would overlap. Both are corrections to
85
+ §1's measured-gutter law, so a rotated or a densely-labelled axis will lay out slightly
86
+ differently; an explicit `margin` / `xTicks` / `xTickValues` still wins.
87
+ - **`SeriesStyle.curve`** (`basalt-ui/charts`) — `'monotone' | 'linear' | 'step' | 'stepAfter' |
88
+ 'stepBefore'`, plus the `curveFor` mapper and the newly re-exported `curveStep`/`curveStepBefore`.
89
+ Default `'monotone'`, which is what every kind hard-coded, so an omitted `curve` moves nothing.
90
+ Honoured by `MultiLine`, `DualPanel`, `ZonedLine`, `StackedArea` and `BarsLine.curve`. Two of
91
+ those read ONE curve for a whole shape rather than one per series: `ZonedLine` takes the PRIMARY
92
+ series' (its band, area and line are one reading), and `StackedArea` takes the TOPMOST VISIBLE
93
+ band that declares one — the last such entry in your `series` array (`AreaStack`'s bands share
94
+ their boundaries, so two curves would leave gaps between them). A hidden band's curve is ignored
95
+ either way, so a legend toggle can hand a stack to the next declarer down.
96
+ - **Single-series legend suppressed by default** (`basalt-ui/charts`) — since commit `5e0ade1`, a
97
+ chart with exactly one visible series and no `legend` prop renders no legend. Pass `legend={{}}`
98
+ (or any explicit config) to keep it.
99
+ - **`Bars`' baseline is now the axis' domain floor**, not `yScale(0)` — previously a y domain that
100
+ did not reach 0 drew the bar off the bottom of the plot; now it stops at the domain's own floor,
101
+ which also makes a log y-axis usable on `Bars`. A domain that includes 0 is unchanged.
102
+ - **Number formatters** (`basalt-ui/charts`) — `fmtCompact`, `fmtPercent`, `fmtCurrency`, `fmtInt`
103
+ and the `formatters` bundle, all `Intl`-backed with an explicit `locale` defaulting to the
104
+ runtime's. `utils/format.ts` shipped only two DATE formatters, so every call site hand-rolled
105
+ `` `$${v}k` ``. **Every formatter — the two date ones included — now returns an em dash for
106
+ non-finite input** (`NaN`, `±Infinity`, an `Invalid Date`) instead of `Intl`'s own `NaN` / `∞`.
107
+ A collapsed domain therefore stops painting its arithmetic accident as a tick label.
108
+ - **`ChartEmpty` / `ChartError` / `state` / `resolveChartState`** (`basalt-ui/charts`) — the chart
109
+ layer's own three-state placeholder set. `state?: { pending?, error?, empty? }` on
110
+ `CartesianChart` / `ChartFrame` / every kind resolves pending → error → empty, so a consumer no
111
+ longer needs the Mantine-coupled `QueryState` (unreachable from `./charts`) to tell a 500 apart
112
+ from an empty result. **`isPending` still works and is not deprecated** — it is an alias for
113
+ `state={{ pending: true }}`. `ChartError` announces as `role="alert"`; `ChartPending` and
114
+ `ChartEmpty` as `role="status"`, so a screen reader hears the resolution and not only the failure.
115
+ - **`BasaltProps` on every exported chart component** — `className` and `style` now reach the root
116
+ element of every kind, sparkline and primitive; `ChartCard` also takes
117
+ `classNames={{ root, header, body }}`. Additive: a component that ignored `className` now honours
118
+ it, so a stylesheet that previously had no effect starts having one.
119
+ - **`ChartTooltipFloat` server-renders.** It was the one `renderToString` casualty in the package
120
+ (an unconditional `createPortal`); it now emits nothing on the server and is unchanged in the
121
+ browser. Consumers carrying an SSR workaround around a chart tooltip can drop it.
122
+ - **`BREAKPOINTS`** (`basalt-ui/tokens`) — `{ article: 1200 }`. The one `@media` literal in the
123
+ package that was not derived from anything now has a named, testable counterpart; a `@media`
124
+ condition cannot read a custom property, so the literal stays and a test holds the two together.
125
+ - **`theme-lab/boot.ts`** — `applyOverrides`/`loadOverrides`/`readVar` moved into their own
126
+ Mantine-free, SSR-safe module (no-op / empty result with no `document`, instead of throwing).
127
+ `basalt-ui/theme-lab` still resolves all three, unchanged, alongside `ThemeLabControls` and
128
+ `DeriveControls` — nothing renamed or removed, and no new subpath was added.
129
+ - **`basalt/raw-size-literal` / `basalt/no-raw-font-size`** — no longer exempt a consumer's own
130
+ `src/tokens/` directory. The exemption was basalt-internal (the derive engine's own hardcoded
131
+ scale values) and leaked to any package with a same-named directory. Consumers with raw sizes
132
+ there move them to `--vx-*` tokens, or add a `theme-allow` waiver.
133
+ - **`BasaltProvider connectivity` prop** — `Omit<ConnectivityProviderProps, 'children'>`, threaded
134
+ straight through to the auto-mounted `ConnectivityProvider`. Reaches `override` for the first
135
+ time (the only prior route was mounting a second, shadowing `ConnectivityProvider`) — see
136
+ § BasaltProvider below.
137
+ - **Six new oxlint rule ids, all `warn`** — `basalt/provider-above-router`,
138
+ `basalt/duplicate-notifications-mount`, `basalt/query-dual-import`, `basalt/query-fn-unwrap`,
139
+ `basalt/forms-field-key` (grace to 1.30.0) and `basalt/deprecated-export` (permanently
140
+ advisory). `forms-field-key` is the one that matters most in this minor — it is the only thing
141
+ that reports the `inputProps` call sites below, which a compiler cannot see; see § `inputProps`.
142
+ The first four guard
143
+ four doctrines that shipped stated-but-unguarded under a `not guarded:` banner — the provider/
144
+ router mount order, the `BasaltOverlays` XOR `BasaltNotifications` double-mount, the
145
+ `basalt-ui/query` import seam, and `queryFn` wrapping in `unwrap()`. Each is honest about its
146
+ reach in its own message and its grace entry: all four read ONE file, and the two query rules
147
+ carry a documented text heuristic. Escape with `theme-allow <rule> — <why>` as usual. Nothing
148
+ fails a build: every one is `warn` in the shipped preset.
149
+ - **`basalt/deprecated-export`** — nudges an import or a prop basalt has deprecated but still ships,
150
+ reading the `DEPRECATED_EXPORTS` ledger beside the plugin. Today that is `field` from
151
+ `basalt-ui/forms` (autofixed to `inputProps as field`, so no call site moves) and the three
152
+ flattened `BasaltProvider` connectivity props. Permanently `warn` — a deprecation is a schedule,
153
+ not a defect. **On the `field` row, take the two fixes in order**: that rename lands the new
154
+ `inputProps` shape, which has no `key`, and `basalt/forms-field-key` then reports the same line
155
+ and inserts the `key`. Two `--fix` passes, or one manual edit writing both.
156
+ - **Dev-only duplicate-mount warnings** — `BasaltProvider` now warns when a second instance mounts
157
+ while the first is still mounted; `<BasaltOverlays notifications />` and `<BasaltNotifications />`
158
+ now warn when BOTH are mounted at once (previously prose-only in both cases). Both are
159
+ `console.warn`, dev builds only, no behavior change.
160
+ - **`WidgetGrid` + `WidgetGrid.Item` and `StatGroup`** (`basalt-ui`) — the dashboard column law,
161
+ owned once. `cols` is the DESKTOP count and the only number you state: `WidgetGrid` resolves
162
+ `base 1 → sm min(cols, 2) → lg cols`; `StatGroup` (the KPI row) resolves
163
+ `base 2 → sm min(cols, 3) → lg cols`. `<WidgetGrid.Item span={2}>` widens one cell and is clamped
164
+ to the live count at each breakpoint. `StatGroup divided` swaps the column gap for a hairline rail. **These two are the only
165
+ place `lg` exists in the package** — `sm` remains the only breakpoint a consumer writes
166
+ (`docs/CONTROLS-SPEC.md` §2). Replaces hand-written `SimpleGrid cols={{ base, md }}` /
167
+ `Grid.Col span={{ base: 12, md: 8 }}`; Mantine's responsive `cols` object is deliberately not a
168
+ prop.
169
+ - **`StatCard.query`** — `QueryStateLike`, the same structural subset `QueryState` takes. Supplied,
170
+ the card renders its pending / error / empty branch through `QueryState` at the `'section'` tier
171
+ INSIDE the body, directly under the header, which stays put. Omitted, nothing changes. `value` is
172
+ still yours to format — pass a placeholder while the query is pending.
173
+ - **`WidgetHeaderTitleProps` / `WidgetHeaderMetricProps` / `WidgetHeaderDeltaProps`** (`basalt-ui`)
174
+ — the named `Pick` slices of `WidgetHeaderProps` its composers share, cut along the three rows the
175
+ header paints. `StatCardProps` and `SettingsSectionProps` now compose them instead of re-declaring
176
+ props one at a time; **both gained props as a result** — `SettingsSection`/`DangerZone` now accept
177
+ and FORWARD `icon` and `info`. Nothing was removed from either (pinned by
178
+ `src/dashboard/stat-card-props.test.ts`). `Section` and `ChartCard` still cut their own subsets.
179
+ - **`tier` replaces `variant` on `QueryState` / `LoadingState` / `ErrorState` / `EmptyState`** —
180
+ `tier` is the package's word for "how loud is this" (`WidgetHeader`, `CtlSlot`), and `'section'`
181
+ meant two different things across the two spellings. `variant` is `@deprecated` and still
182
+ honoured; resolution is `tier ?? variant ?? 'page'`, so nothing moves until you change the prop.
183
+ `QueryStateTier` is the new type name; `QueryStateVariant` stays exported as an alias. No removal
184
+ is scheduled — this is a preferred spelling, not a sunset.
185
+ - **`WidgetHeader` and `QueryState` gained slot classes** — `classNames={{ root, title, metric,
186
+ icon }}` on the first, `classNames={{ root }}` on the second (the branch that is live). Additive.
187
+ `Section` now OMITS `classNames` from the `WidgetHeaderProps` it re-publishes, so its slot union
188
+ stays exactly `root | header | body` — a `<Section classNames={{ metric: … }}>` that used to
189
+ type-check (and do nothing) is now a compile error. Reach the header's own slots on a
190
+ `WidgetHeader`.
191
+ - **`Callout` extends `BasaltProps`** — `className`/`style` were already accepted and are now the
192
+ shared members, so `exactOptionalPropertyTypes` consumers can forward an `string | undefined`
193
+ through. No rendering change.
194
+ - **`PageAside` throws on a missing `title`** — it is both the header text and the region's
195
+ `aria-label`, so an omitted one shipped a nameless landmark rather than a crash. Now
196
+ `[basalt] PageAside: prop "title" is required — …`, in every build. `title` was never optional in
197
+ the type; this only reaches untyped JS and runtime-built props objects.
198
+ - **`BasaltDataTable` warns (dev only) when `stickyHeaderOffset` meets `maxHeight`/`minWidth`** —
199
+ the offset has always been DROPPED there (the sticky `<thead>` anchors to the scroll container's
200
+ own top edge, not the window). The behaviour is unchanged; it just says so now.
201
+ - **`query` on `Section`, `BasaltDataTable` and `BasaltVirtualList`** — law C3's uniform container
202
+ contract. Pass a `QueryStateLike` (a TanStack `UseQueryResult` is one) and the container resolves
203
+ the body: pending → its own placeholder (`QueryState`'s section-tier spinner / the table's
204
+ skeleton rows / the list's skeleton rows), error with no data → an `ErrorState` with the query's
205
+ own `refetch` behind Retry, anything else → the content, or the empty branch. **The table and the
206
+ list had NO error branch at all**, so a 500 rendered _No data to display._ / a blank box — the
207
+ false claim `QueryState` exists to delete. Additive: omit `query` and nothing changes.
208
+ `Section` also gains `empty?: QueryEmptyCopy` (copy for that branch, `QueryState`'s own type) and
209
+ `BasaltVirtualList` gains `emptyState?: ReactNode` (which works with or without `query`).
210
+ `isLoading` still works on both containers and is NOT deprecated; passing it beside `query` lets
211
+ `query` win and warns once in dev. The chrome stays drawn through every branch — a table keeps its
212
+ `<thead>`, toolbar and pagination bar, a `Section` keeps its header, chevron and `summary` — which
213
+ is why this is not `QueryState` wrapped around the container. A refetch that fails over rows
214
+ already on screen keeps the rows with no banner; compose `QueryState` AROUND the container for
215
+ that.
216
+ - **`BasaltDataTable.onRowActivate`** — `(row: T) => void`, fired by a click on the row and by Enter
217
+ on a focused one. The row → detail hook that pairs with `PageAside`. The row keeps its native
218
+ `<tr>` semantics and takes `tabIndex=0` + `data-activatable` rather than `role="button"` (a role
219
+ swap costs the column-header announcement that is the reason the data is in a table); the cursor
220
+ and focus ring come from a module class. A click originating inside the selection checkbox does
221
+ not activate.
222
+ - **`BasaltDataTable` row selection** — `enableRowSelection` prepends a checkbox column (header =
223
+ select-all on the page) and arms TanStack's row-selection feature; `rowSelection` /
224
+ `onRowSelectionChange` are the controlled pair over TanStack's own `RowSelectionState` (now
225
+ re-exported from `basalt-ui/data` and `basalt-ui/data/table`), and `getRowId` makes a selection
226
+ survive a re-sort or a refetch (the default id is the row INDEX). `bulkActions` renders a bar
227
+ above the table while ≥1 row is selected — the count, then the actions, through the SAME
228
+ `BarAction[]` vocabulary `PageBar` uses, so the ≤3-inline fold and the mobile kebab are basalt's
229
+ here too. **Its signature is `(rows: T[]) => BarAction[]`, a function and not a bare array**, and
230
+ deliberately: `BarActionItem.onClick` stays `() => void` (one unforked vocabulary across every
231
+ home), so the selected rows reach a handler through the closure you write — and an UNCONTROLLED
232
+ table can offer bulk actions at all, since the selection lives inside it.
233
+ - **`Section.actions` and `BasaltDataTable.actions` accept `BarAction[] | ReactNode`** — law C15.
234
+ Handed typed data, basalt owns the C6 budget and the C7 overflow fold (≤3 inline, the rest behind
235
+ `More`, one kebab below `sm`) exactly as `PageBar` does; handed a node, rendering is byte-for-byte
236
+ what it was. Every entry lands in `secondary` — a `BarAction[]` carries no primary marker, and C6's
237
+ "exactly one primary" stays the page bar's rule. `Section`'s ≤3 dev warning now counts an array
238
+ EXACTLY (`Children.count` read one as a single child). Both slots were `ReactNode` before, so no
239
+ call site moves.
240
+ - **`overlays.confirm` / `overlays.confirmDelete`** (`basalt-ui/commands`) — the two-button dialog
241
+ every consumer was registering as a `defineOverlays` entry with its own button pair, now
242
+ registry-free: `confirm({ title, body?, confirmLabel?, cancelLabel?, danger?, onConfirm,
243
+ onCancel? })` resolves `true`/`false` and fires exactly one callback, whichever exit the user takes
244
+ (cancel, escape, click-outside and the close button all count as `false`). `danger` paints the
245
+ confirm button in the theme's derived destructive family. `confirmDelete({ subject, count?,
246
+ onConfirm })` is the counted one-liner over it ("Delete 3 items?"). Both REJECT with a named
247
+ `[basalt] overlays.confirm: …` error when the `@mantine/modals` peer is absent, or when no
248
+ `ModalsProvider` is live — previously either returned a promise nothing would ever settle, because
249
+ the imperative API is a window event bus with nothing subscribed. `@mantine/modals` stays lazily
250
+ imported, so the peer is still optional. Additive: `defineOverlays` and `overlays.open` are
251
+ unchanged.
252
+ - **`registerModalsProvider()`** (`basalt-ui/commands`) — how a consumer who mounts
253
+ `<ModalsProvider>` THEMSELVES (with `<BasaltOverlays modals={false}>`, or with no `BasaltOverlays`
254
+ at all) tells `overlays.confirm` the layer is up. `<BasaltOverlays>` registers its own provider, so
255
+ the default path needs nothing. Call it from an effect and return its result as the cleanup:
256
+
257
+ ```tsx
258
+ useEffect(() => registerModalsProvider(), [])
259
+ ```
260
+
261
+ Availability is a COUNTER of live providers, not a flag, so two overlapping mounts (a route swap
262
+ mounting the next shell before unmounting the last) never report "no layer" while one is serving.
263
+ There is no way to detect the provider from outside React — its context is only readable through
264
+ `useModals()`, and `confirm` is an imperative call with no component to read it from — so the
265
+ registration is explicit and unregistered is indistinguishable from absent.
266
+
267
+ - **`notifyUndo` / `notifyUndoable`** (`basalt-ui/notifications`) — an undo window on top of the
268
+ existing toast machinery: `notifyUndo({ message, onUndo, window = 6000, onExpire })` shows a toast
269
+ carrying an `Undo` button and returns `{ id, dismiss() }`. The affordance and the grace period are
270
+ ONE number (`autoClose === window`), and exactly one of `onUndo` / `onExpire` runs, exactly once —
271
+ `onExpire` is where the optimistic mutation COMMITS. `dismiss()` settles the window immediately
272
+ (running `onExpire`), so a dismissed toast can never strand an uncommitted change.
273
+ `notifyUndoable(mutate, undo, opts)` is the same thing spelled for the optimistic shape. No new
274
+ store — the toast is recorded to the notification history like any other.
275
+ - **`NotifyOptions.historyMessage`** — what the persisted history records when `message` is a
276
+ composed ReactNode the store cannot stringify (`String(<Group/>)` is `[object Object]`). Defaults
277
+ to today's `String(message)`, so nothing moves unless you pass it. `notifyUndo` uses it.
278
+ - **`missingLayer`** (`basalt-ui`) — a message builder beside `requiredProp`/`oneOf`/`duplicateMount`
279
+ for "this call needs a layer that is not mounted or not installed". Additive.
280
+ - **`emit`'s payload now narrows per kind** (`basalt-ui/notifications`) — `NotificationSpec<P>` and
281
+ `NotificationAction<P>` are generic, `P` inferred from `toMessage`/`action.run`'s parameter, the
282
+ same mechanism `Overlay<P>` uses for `render`. Annotate the payload shape and `emit('kind', …)`
283
+ requires it, a wrong shape is a tsc error. A kind that annotates neither function keeps today's
284
+ `payload?: unknown` — additive, no existing `defineNotifications` call needs a change.
285
+ - **The form layer** (`basalt-ui/forms`) — `./forms` was an adapter with no layout, no submit
286
+ lifecycle, no array helper and no disabled propagation (audit B #5). Seven new exports, all
287
+ additive; `useBasaltForm`, `FormErrorSummary` and `useFormDraft`'s existing signature are
288
+ untouched. `inputProps` is the one exception, and it is not additive — see § `inputProps` no
289
+ longer returns `key` below.
290
+ - **`FormSection` / `FormRow` / `FormGroup` / `FormActions`** — the layout. `FormRow` IS law C1's
291
+ third home, the form row: label left / control right, label above below `sm`, the swap in CSS
292
+ (law C9). It does NOT replace `SettingsRow` — that stays the settings-page variant, and both
293
+ keep Mantine's `md` tier rather than mounting a `CtlSlot`. `FormActions` takes the same
294
+ `BarAction[] | ReactNode` union law C15 gave `Section.actions`.
295
+ - **`useFormSubmit(form, handler, options?)`** — `{ submit, isSubmitting, submitError, reset }`.
296
+ Catches the handler's throw and decodes it through `toErrorMessage` (override with `mapError`),
297
+ routes a thrown `{ fieldErrors }` envelope onto the fields via `form.setErrors`, and focuses the
298
+ first errored field on BOTH failure paths. `isSubmitting` is Mantine's own `form.submitting`,
299
+ not a second flag beside it.
300
+ - **`options.validateAsync`** — the async validation rule `useBasaltForm` has no room for:
301
+ `schemaResolver(schema, { sync: true })` is still hard-coded, so `validate()`/`isValid()` stay
302
+ synchronous, and the async rule runs at submit time after the schema and before the handler.
303
+ - **`FormStateProvider` / `useFormState`** — `{ disabled, submitting }`. The layout primitives
304
+ read it and disable their control region through a native `<fieldset disabled>`, so nothing has
305
+ to be threaded to each input and no child is cloned. Nesting ORs; it cannot re-enable.
306
+ - **`useFieldArray(form, path)`** — `{ items, append, remove, move, key }` over
307
+ `insertListItem`/`removeListItem`/`reorderListItem` + `form.key`. `key(index)` is POSITIONAL
308
+ (Mantine's list actions do not rotate the form key generation) — a user-reorderable list still
309
+ wants an id on the item.
310
+ - **`useFormDraft(form, { autosave })`** — `true`, or `{ debounceMs }`. The hook now owns its own
311
+ `form.watch` subscription, replacing the documented `saveDraftRef` + `onValuesChange` dance. It
312
+ is OFF by default and READ ONCE at mount (the watch set is a hook count), so every existing call
313
+ site behaves exactly as before. `DEFAULT_AUTOSAVE_DEBOUNCE_MS` is exported.
314
+ - **Still subpath-only.** The root barrel re-exports none of this, so a consumer without
315
+ `@mantine/form` is unaffected by importing `basalt-ui`.
316
+ - **Mobile "More" sheet rows now 40px with the sidebar's own insets** — `--vx-space-mobile-nav-row-
317
+ height` 44 → 40 (WCAG 2.5.5 AA touch floor, not the AAA figure the bar's own `mobileNavBarHeight`
318
+ still holds to). The rows themselves no longer force a bespoke touch padding over the theme's
319
+ `NavLink` styling; they now render with the SAME `--vx-space-row-inset-y`/`-x` padding and
320
+ `VX.text.md` the desktop sidebar row does. A nested (child) row gains the sidebar's own child
321
+ indent — a 1px `--vx-divider` left guide. Purely visual; no prop, export or behaviour changed.
322
+ - **`NotificationCenter` defaults to `width: '100%'`, not a hardcoded `320`** — it now sizes to its
323
+ host instead of assuming it always sits inside its own 320px popover. `NotificationBell` fixes the
324
+ 320px width at the `Popover` itself (`<Popover width={320}>`) instead, so nothing changes for that
325
+ call path; a consumer mounting `NotificationCenter` directly (a Drawer, an inline card) now gets a
326
+ component that shrinks to its container rather than overflowing it. `style` still merges over the
327
+ default, so `style={{ width: 280 }}` still wins.
328
+
329
+ ### `inputProps` no longer returns `key` — pair it with the new `fieldKey`
330
+
331
+ **This is the one thing in the minor that needs a code change at every call site, and the compiler
332
+ will not tell you about a single one of them.** It is a one-line-per-field edit:
333
+
334
+ ```tsx
335
+ /* before (1.27) */ <TextInput {...inputProps(form, 'email')} />
336
+ /* after */ <TextInput key={fieldKey(form, 'email')} {...inputProps(form, 'email')} />
337
+ ```
338
+
339
+ **Exactly what changed.** In 1.27 `inputProps(form, path)` returned `form.getInputProps(path)` **and
340
+ `key`** in one object, so a single spread covered both. It now returns `form.getInputProps(path)`
341
+ and nothing else; `fieldKey(form, path)` is the other half, written as a real JSX `key` attribute.
342
+ The bundle was basalt's own invention, and React 19 logs `A props object containing a "key" prop is
343
+ being spread into JSX` on every render of it — three fields, three warnings per page.
344
+ `key={form.key(path)} {...form.getInputProps(path)}` is Mantine's own documented idiom; `fieldKey` +
345
+ `inputProps` is that idiom with basalt's names.
346
+
347
+ **What breaks if you only upgrade and do nothing.** Nothing type-checks differently, nothing throws
348
+ and no warning is logged — the 1.27 call site is still a valid spread of a valid object. The element
349
+ simply has no `key` any more, so React reuses the DOM node and an uncontrolled input keeps its old
350
+ text through `form.reset()`, `form.setValues()` and a removed list row. That is the entire failure
351
+ mode: a form that stops resetting, with no signal anywhere.
352
+
353
+ **What catches it: `basalt/forms-field-key`**, new in this minor at `warn` (grace to 1.30.0). It
354
+ reports every `{...inputProps(…)}` spread whose element carries no sibling `key`, and it
355
+ **autofixes** — inserting `key={fieldKey(<same args>)}` and adding `fieldKey` to the existing
356
+ `basalt-ui/forms` import. Run `oxlint --fix` over the app and read the diff; the escape hatch for an
357
+ element genuinely remounted by its parent is `theme-allow forms-field-key — <why>`.
358
+
359
+ **The deprecated `field` alias did NOT follow `inputProps` here.** It is no longer
360
+ `export const field = inputProps`: it keeps the 1.27 return shape, `key` included, so
361
+ `{...field(form, 'x')}` behaves in 1.28.0 byte for byte as it did in 1.27 — including the React 19
362
+ key-spread warning. An alias would have changed what every existing call site DOES without changing
363
+ whether it compiles, and a deprecation is a schedule, not a behaviour change. `forms-field-key`
364
+ reports the `field(` spread too, with its own message pointing here; the alias itself is removed in
365
+ `1.29.0`.
366
+
367
+ ### `BasaltDataTable` — the root is a `<div>`, not a Fragment
368
+
369
+ **One structural change, no prop change.** The component returned a Fragment, so the header row, the
370
+ table (or its scroll container) and the pagination bar were DIRECT CHILDREN of whatever the caller
371
+ rendered it into. It now always renders a wrapper:
372
+
373
+ ```html
374
+ <div class="{classNames.root} {className}" style="{style}">
375
+ …header row… …table / Table.ScrollContainer… …pagination bar…
376
+ </div>
377
+ ```
378
+
379
+ A `className` needs a root to live on (`common/props.ts`), and a Fragment has none — so the wrapper
380
+ is what `className`, `style` and `classNames.root` all resolve against. It is unconditional: a table
381
+ with no title, no toolbar and no pagination still renders it.
382
+
383
+ **What breaks:** any CSS or test selector written against the OLD flat structure. A
384
+ `.page-grid > table`, a `:first-child` rule aimed at the table, an `:nth-child` counting the header
385
+ row and the pagination bar as siblings of your own content, or a flex/grid parent that laid those
386
+ three boxes out itself — all now see one child. Target the wrapper instead, and give it the layout:
387
+
388
+ ```css
389
+ /* before */ .page-grid > table { … }
390
+ /* after */ .page-grid > div > table { … } /* or: <BasaltDataTable classNames={{ root: 'my-table' }} /> */
391
+ ```
392
+
393
+ Pinned by `src/data/data-table.test.tsx` ("the root is one div carrying classNames.root, wrapping
394
+ the table and the pagination bar"), so it cannot revert silently.
395
+
396
+ ### BasaltProvider — a `connectivity` object replaces three flattened props
397
+
398
+ `sseUrl` / `healthUrl` / `healthIntervalMs` on `BasaltProvider` are now `@deprecated` aliases for
399
+ the corresponding fields on the new `connectivity` prop — **removed in 1.29.0**. `connectivity`
400
+ wins WHOLESALE once supplied, not per-key: passing `connectivity` at all makes basalt ignore the
401
+ three deprecated props entirely, even ones `connectivity` itself leaves unset. Supplying both logs a
402
+ dev-only warning. Move every deprecated field to `connectivity` in one pass, not one field at a time.
403
+
404
+ | Removed / deprecated | Replacement |
405
+ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
406
+ | `<BasaltProvider sseUrl={…} />` | `<BasaltProvider connectivity={{ sseUrl: … }} />` — `@deprecated`, removed 1.29.0 |
407
+ | `<BasaltProvider healthUrl={…} />` | `<BasaltProvider connectivity={{ healthUrl: … }} />` — `@deprecated`, removed 1.29.0 |
408
+ | `<BasaltProvider healthIntervalMs={…} />` | `<BasaltProvider connectivity={{ healthIntervalMs: … }} />` — `@deprecated`, removed 1.29.0 |
409
+ | (unreachable before this minor) | `<BasaltProvider connectivity={{ override: {…} }} />` — simulate a signal for testing/demo, no second `ConnectivityProvider` needed |
410
+
411
+ **`cssVariablesResolver` can no longer reach `BasaltProvider` through the passthrough
412
+ `MantineProviderProps` rest** — it was silently spreadable and would override basalt's own resolver
413
+ (the one thing the provider exists to install). `Omit`ted from the accepted prop type; pass any
414
+ palette customization through `createBasaltTheme(overrides, { derive, fonts, radius, density })`
415
+ instead.
416
+
417
+ ### `basalt-ui/connectivity` — `queryOnline` narrowed to `boolean`
418
+
419
+ `ConnectivitySnapshot['details'].queryOnline` and `ConnectivityOverride.queryOnline` drop the
420
+ `| null` arm. The JSDoc used to claim null meant "QueryClient not mounted", but `onlineManager` is a
421
+ module-level singleton read with no such check — the only null this field ever carried was the
422
+ pre-hydration SSR snapshot, which now reports an optimistic `true` (matching `browserOnline`'s own
423
+ SSR default) instead. Code branching on `queryOnline === null` was already dead; delete it.
424
+
425
+ ### `basalt-ui/state` — eight internal symbols dropped from the barrel
426
+
427
+ | Removed / renamed | Replacement | Note |
428
+ | ---------------------------------------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
429
+ | `createStoreCore`, `resolveFieldCodec` (`basalt-ui/state`) | none | Both were `@internal` — the seam `createSearchStore` is built on, not a consumer API — and were never meant to be a public contract. No replacement needed. |
430
+ | `FieldCodec`, `StoreEntry`, `StoreCoreOptions`, `StoreCore`, `FieldUse`, `FieldWrite` (types, `basalt-ui/state`) | none | Also `@internal` — the supporting types behind `createStoreCore`/`resolveFieldCodec`, dropped alongside the two functions for the same reason. No replacement needed. |
431
+
432
+ ### `basalt-ui/forms` — `field` renamed to `inputProps`
433
+
434
+ Two unrelated exports shared the name `field`: the forms adapter's `getInputProps`+`key` bundler and
435
+ the `field.enum/multi/range/number/boolean/string` store-field builder in `basalt-ui/state` and
436
+ `basalt-ui/router-tanstack`. A page combining a form and a filter store had to alias one on import.
437
+
438
+ | Removed / renamed | Replacement | Note |
439
+ | --------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
440
+ | `field` (`basalt-ui/forms`) | `inputProps` | Same signature — `inputProps(form, path)`. `field` still resolves from `basalt-ui/forms` as a `@deprecated` alias, so nothing breaks; the alias goes away the next time the forms surface changes, and that removal ships as a plain `feat:`, never a major. **Since the Unreleased minor the two are no longer the same function**: `inputProps` dropped `key` from its return and `field` did not — see § `inputProps` no longer returns `key`. |
441
+
442
+ The `field.*` store builder (`basalt-ui/state`, `basalt-ui/router-tanstack`) is untouched.
443
+
444
+ ### Shell — region seams, and what moved with them
445
+
446
+ **Every `AppShell` region now ends in a real, themed 1px `--vx-divider` line on its Main-facing
447
+ edge, with no opt-out** (`docs/DESIGN-SPEC.md` §5 "Region seams", §8 inversion #12): sidebar|main
448
+ (full height, under `layout="alt"` it owns the top-left corner), header|main (main column only),
449
+ main|aside (full height, absent when the aside is unclaimed), main|mobile-nav (below `sm`). Mantine
450
+ draws all four itself through `[data-with-border]`; the colour is one theme var,
451
+ `AppShell.extend({ vars: () => ({ root: { '--app-shell-border-color': 'var(--vx-divider)' } }) })`.
452
+ The aside's shell-form header also carries its own `border-bottom: 1px solid var(--vx-divider)`, so
453
+ the top belt's seam closes across the panel instead of dead-ending at the aside column.
454
+ **Delete any hand-drawn twin**: a `border-left` on your own aside-shaped panel, a `border-bottom`
455
+ under `PageBar` via `className`, a `withBorder` or a `--app-shell-border-color` override on your own
456
+ `AppShell` sections — the theme only pins the COLOUR; `withBorder` still adds or removes the edge
457
+ per section (Mantine gates the border CSS on `[data-with-border]`), so a leftover `withBorder={false}`
458
+ still suppresses the seam and must be deleted to get it, and a leftover `withBorder={true}` is
459
+ redundant (Mantine's own default).
460
+
461
+ - **`--vx-divider` moved.** Light is now `rgba(<derived ink>, 0.09)` (was
462
+ `color-mix(in srgb, #e5e5e5 65%, transparent)`) — visibly darker on light, tracks a non-default
463
+ `neutral`/`lightLevel` where the old hex did not, and never flips polarity at `lightLevel <= -3`
464
+ the way the fixed hex did. Dark moved too: `color-mix(in srgb, #ffffff 6%, transparent)` →
465
+ `rgba(255, 255, 255, 0.08)`.
466
+ - **The header's horizontal inset moved from `md` to `sm`**, so the breadcrumb's left edge and the
467
+ global actions' right edge land on the card column's edges. The sidebar brand row and the aside's
468
+ header now sit at the SAME `appShellHeaderHeight` band, so their centrelines meet the header's
469
+ across the seams. `SPACE_STEP.sidebarBrandInsetTop` / `--vx-space-sidebar-brand-inset-top` are
470
+ deleted with no replacement — the row IS the band now, not an inset inside it.
471
+ `PageAside`'s title renders the head-font `--vx-text-md`/550/ink title treatment, not the old mono
472
+ micro-label.
473
+ - **[S2] `PageBar` row 2 below `sm`** is two declared lines — the tabs, then the pill row — instead
474
+ of one overflowing `nowrap` line. `--basalt-page-bar-h` grows to match, and the `className` JSDoc
475
+ no longer suggests drawing a hairline under the bar.
476
+ - **[S3] `DeltaBadge.polarity` and `deltaPolarity`** on `WidgetHeader`/`StatCard`/`ChartCard` —
477
+ `'up-good'` (default, today's tone), `'up-bad'`, `'neutral'`. And `WidgetHeader tier="group"` — a
478
+ `Section` mounted inside `PageAside` now resolves it automatically and renders a mono micro-label
479
+ `h3` with a zero-gap row body; outside an aside nothing changes.
480
+ - **[S4] `AxisConfig.scale: 'log'`** on `CartesianChart` — 1-2-5 mantissa ticks, `autoMinCeil`
481
+ ignored on a log axis. Honoured by every kind whose domain isn't anchored at zero (`MultiLine`,
482
+ `ZonedLine`, un-stacked `Bars`); `StackedArea` and stacked `Bars` install a domain function with a
483
+ hard `0` floor and their marks anchor at the baseline, so a log scale on either yields `NaN`
484
+ geometry — don't opt in there. Additive; omitted stays linear.
485
+
486
+ ### Stores — the `custom` flag, patched writes, lazy fallbacks, derived windows
487
+
488
+ **Additive, except one type that got narrower on purpose.** `field.range({ presets, fallback })`
489
+ written INLINE inside `createSearchStore({ fields })` used to infer `custom` as `boolean` — the
490
+ contextual `AnyField` return type won over the argument — so every value and search type carried a
491
+ `'custom'` preset the field itself rejects at runtime, and `RangeFilter field={…}` needed a cast or
492
+ an explicit `custom: false`. `field.range` is now three overloads: an omitted or `false` flag is
493
+ `false`, a literal `true` is `true`, a value typed `boolean` stays widened.
494
+
495
+ - **Delete the workaround, not the field.** An explicit `custom: false` still works (same
496
+ overload); a cast on `<RangeFilter field={…} />` can go, and `RangeFilterProps<P, C>` is generic
497
+ over the flag so a preset-only handle binds directly.
498
+ - **What can newly type-error:** code that read `'custom'` out of a range whose field never declared
499
+ it — `if (search.range === 'custom')` on a preset-only field, or a `RangeValue<P | 'custom'>`
500
+ annotation over one. That branch was always dead at runtime; the type just stopped pretending.
501
+ - **…and one more, in a consumer's own WRAPPER.** A prop typed `FieldHandle<RangeField<P>>` — `C`
502
+ defaulting to `boolean` — no longer accepts a handle from a `field.range` without `custom: true`,
503
+ because that handle's setter is now `RangeValue<P>`-typed and a setter is contravariant. Pin
504
+ `FieldHandle<RangeField<P, false>>` for the preset-only shape, `RangeField<P, true>` for a
505
+ picker-backed one, or make the wrapper generic over the flag
506
+ (`<P extends string, C extends boolean>` + `FieldHandle<RangeField<P, C>>`), which is what
507
+ `RangeFilter` does. An existing `as FieldHandle<RangeField<P>>` cast keeps compiling.
508
+
509
+ **`toWindow()` resolves derived presets.** `field.range({ window: { '3m': (now) => ({ from, to }) } })`
510
+ makes `toWindow({ preset: '3m' })` return `{ from, to }` (ISO dates, resolved at call time with the
511
+ current `Date`) while `3m` stays one preset in the URL. A preset with no resolver keeps
512
+ `{ window: preset }`, in the same field — which is what retires the last hand-rolled
513
+ `presetToParams` a consumer kept beside the store for `3m` / `6m` / `1y` / `ytd`.
514
+
515
+ **The resolved presets are gone from the return TYPE too**, which is the half that decides whether
516
+ the switch is deleted or the cast just moves: the field now carries its resolver keys (`RangeField`'s
517
+ fifth type parameter, inferred from `window`), and `toWindow` returns
518
+ `{ window: Exclude<P, W> } | { from; to }`. A `resolveWindow` whose target is
519
+ `{ window: '7d' | '30d' | '90d' | 'all' } | { from; to }` becomes a one-line delegation with no
520
+ `as`. One guard survives on a `custom: true` field: a `'custom'` preset that arrives WITHOUT dates
521
+ resolves to `{ window: 'custom' }`, so `'custom'` stays in the union by construction.
522
+
523
+ **A field setter takes an optional second argument.** `set(next, { patch })` merges extra search
524
+ params into the SAME navigate — URL lane only, for keys the store does not own (`{ patch: {
525
+ detailDate: undefined } }` clears a sibling param the page put there). The field's own params always
526
+ win over the patch, so it cannot corrupt the value being set. A local/memory-lane write has no
527
+ navigate to merge into and ignores it.
528
+
529
+ Two limits are now enforced rather than documented. A patch key **another field of the same store
530
+ owns** is refused — it would reach the URL while the store's own write path never touched the mirror,
531
+ so the next paramless visit and every `linkSearch` link resolve the OLD value: `createSearchStore`
532
+ throws in dev, and in production keeps the write and logs it once. Write that field through its own
533
+ setter instead. And a patched write **from a route that does not validate the field** persists (A1)
534
+ but drops the patch with it — there is no navigate to merge into — which now warns once per field in
535
+ dev instead of doing nothing.
536
+
537
+ **A fallback may be a thunk.** `field.*({ fallback: () => T })` (every kind — `field.number`'s codec
538
+ froze its thunk at store definition in the first cut of this minor and does not now) is resolved at
539
+ READ time and re-resolved while nothing is written, and is never persisted on its own — a write
540
+ stores what the control produced, as before. **Local and memory lanes only:** `createSearchStore` now
541
+ THROWS at definition for a thunk on a URL-lane field, because `validateSearch` would evaluate it on
542
+ every navigation and pin the result into the URL. Move the field off the URL lane (`{ url: false }`,
543
+ or `createLocalStore`) or pass a value.
544
+
545
+ **`FieldHandle` gained `clear()`, and a reset now UNSETS.** `useReset()` used to write
546
+ `entry.codec.fallback` into the mirror for every persisted field, and every control's reset called
547
+ `setValue(field.fallback)` — both of which pin a RESOLVED thunk: pressing `Reset all` over
548
+ `field.string({ fallback: () => todayIso() }, { url: false })` stored today's date, so tomorrow the
549
+ field read a value nobody chose and counted as active in `Filters (n)`. Both paths now delete the
550
+ key instead (the memory lane always did), and `clear()` is the per-field door: the persist lane
551
+ deletes its key, the memory lane drops its value, the URL lane navigates back to the fallback
552
+ params.
553
+
554
+ Nothing to change at a call site — every shipped control's reset calls it. A consumer that
555
+ hand-rolled a reset should swap `setValue(field.fallback)` for `field.clear()`; a hand-built
556
+ `FieldHandle` (a facet column, a test double) must add the member, which is the one type-error this
557
+ adds.
558
+
559
+ **`createPersistedState` notifies across instances (bug fix).** Two instances over one key — a
560
+ page's store and a widget's own state naming the same key, or a store re-exported from two modules —
561
+ kept per-instance listener sets, so a write through one left the other's components rendering the
562
+ stale value **in the same tab** while the cross-tab `storage` path worked. Subscribers are now
563
+ registered per storage key. No API change; if you were re-rendering something by hand to work around
564
+ this, delete it.
565
+
566
+ **`RangeFilter` warns once in dev** when the field declares `custom: true` and no `customPicker` was
567
+ injected: the custom window is then unreachable (no popover picker, no `Custom range…` row in the
568
+ sheet). Preset-only remains a legal configuration — the picker is injected precisely because
569
+ `@mantine/dates` may be absent — so this is a warning, not a type error.
570
+
571
+ ### Controls — `NumberFilter`, the `field.number` lane
572
+
573
+ **One export added, nothing removed.** `field.number` has existed since the store landed and had no
574
+ control, so both consumers that needed one wrote around it in opposite directions: linewatch kept a
575
+ raw `SegmentedControl` over `minDuration` (a `control-outside-home` warn, law C1), and argo widened
576
+ `nights` into a string enum — which puts `'3'` in the URL and makes every downstream comparison a
577
+ parse.
578
+
579
+ ```tsx
580
+ import { NumberFilter } from 'basalt-ui/controls'
581
+
582
+ // A preset set — a pill plus a radio list, the same body every enum filter renders.
583
+ <NumberFilter field={booking.field.nights} label="Nights"
584
+ options={[{ value: 1, label: '1 night' }, { value: 7, label: 'A week' }]} />
585
+
586
+ // No `options` → a pill whose popover holds a `ctl` NumberInput; the sheet renders it full-width.
587
+ <NumberFilter field={lines.field.minDuration} label="Min duration" step={30} />
588
+ ```
589
+
590
+ Three properties worth knowing before porting a call site:
591
+
592
+ - **The URL keeps a NUMBER.** `options` takes `{ value: number; label: string }`, and a numeral
593
+ STRING there is a type error — the whole reason to stop widening a threshold into an enum.
594
+ - **The stepper applies on blur or Enter, never per keystroke.** A number is typed digit by digit, so
595
+ a live write would navigate on `4`, `42`, `420` — three loader runs for one intended threshold,
596
+ two of them values nobody meant. `SearchFilter` debounces; a number has an explicit commit point.
597
+ - **`min`/`max`/`int` come off the HANDLE, not the call site.** They are deliberately not props: the
598
+ field is what validates the URL, and a second copy at the call site is a second answer to the same
599
+ question. The number handle republishes all three (see § Stores — the number handle below), so the
600
+ stepper stops at the field's limit and an `int` field refuses decimals outright. A TYPED value is
601
+ clamped to those bounds when it commits, so the box reads what the store holds even when the store
602
+ does not move — a second out-of-range `9999` against `max: 600` over a stored `600` writes nothing,
603
+ and the readout still corrects itself to `600`. The codec's clamp stays the backstop for a value
604
+ that never came through the box at all — a hand-typed URL, a stale deep link.
605
+
606
+ ### Stores — the number handle republishes its bounds
607
+
608
+ **Three members added to `FieldHandle<NumberField>`, nothing removed.** `min`, `max` and `int` now
609
+ sit on the handle, filled from the field declaration (`NumberHandleExtras`, the shape
610
+ `RangeHandleExtras`/`toWindow` already had). A control never sees the field descriptor — only the
611
+ handle — so before this the only way one could learn its own limits was for the call site to pass
612
+ them a second time.
613
+
614
+ ```ts
615
+ const nights = store.field.nights // field.number({ fallback: 2, min: 1, max: 14, int: true })
616
+ nights.min // 1 — `undefined` when the field declared none
617
+ nights.max // 14
618
+ nights.int // true — `false`, never `undefined`, on a number handle
619
+ ```
620
+
621
+ Non-number handles carry all three as `undefined`, the same way `toWindow` is `undefined` off a
622
+ range handle. That is a type-level fact, not just a runtime one, and it matters for one collision in
623
+ particular: `StringField` has its own `max`, which is NOT republished — a string handle's `max` is
624
+ `undefined`.
625
+
626
+ Nothing about the clamp changed. The codec has always clamped a number to the declared bounds on
627
+ write, and still does; the handle is a readout of that law, which is what lets `NumberFilter` bound
628
+ its stepper instead of letting the user watch the correction happen.
629
+
630
+ ### `StatCard` — `unit`, `breakdown` and the delta's own format
631
+
632
+ **Four props added, nothing removed or renamed; every one defaults to today's rendering when
633
+ omitted.**
634
+ `value` is a pre-formatted `string`, so a card had exactly one text channel under the hero row
635
+ (`subtitle`) and three consumers wanting a unit AND a basis AND a split hand-rolled the card instead
636
+ — which is the fork `shadow-basalt-export` reports as a `HeroCard`.
637
+
638
+ | Prop | Type | Renders |
639
+ | ------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
640
+ | `unit` | `string` | after the value on the hero row — muted, mono, `--vx-text-sm` |
641
+ | `breakdown` | `readonly { label: string; value: string; tone?: StatCardTone }[]` | compact rows under the hero, one `controlHeightTag` line each |
642
+ | `deltaFormat` | `(delta: number) => string` | the delta chip's label. Default unchanged: `Math.abs(delta).toFixed(1) + '%'` |
643
+ | `deltaGlyph` | `boolean` | the chip's ▲/▼. Default `true`; a zero delta never shows one |
644
+
645
+ `unit` is forwarded to `WidgetHeader`, which gained the same prop, so `ChartCard` and `Section`
646
+ headers get it too. It is NOT the basis: `412` + `TSS` is the pair, and `7-day rolling` is still
647
+ `subtitle`. A thousands separator, a currency symbol or a `%` still belong in `value`.
648
+
649
+ `breakdown` draws **no hairlines** — §2.1 puts a horizontal rule between OPTION rows and nowhere
650
+ else, and `theme/divider-law.test.ts` inventories every one basalt draws. The rows separate by
651
+ weight (muted label, mono ink value) instead. Keep it to two or three: past that the card is a
652
+ table, and a table is `BasaltDataTable` in a `Section`. A row's `tone` reads the same per-scheme
653
+ `--vx-status-*` solid the card's own rail does, and omitting it is untinted, never `'good'`.
654
+
655
+ `deltaFormat` exists because **a delta is not always a percentage, and `StatCard` claimed it was**:
656
+ `delta={0.3}` on a pace or speed card rendered `▲0.3%`, a wrong unit on a KPI — worse than no chip —
657
+ so the one consumer that needed it kept the card hand-rolled and took the `shadow-basalt-export` warn
658
+ instead. `DeltaBadge` had `format` all along; neither `WidgetHeader` nor `StatCard` forwarded it. Both now do.
659
+ `ChartCard`/`Section` are unchanged: each declares its own header props and forwards them one by
660
+ one, so a delta-bearing `ChartCard` or `Section` still prints the percentage — ask if you need it
661
+ there.
662
+
663
+ ```tsx
664
+ // A percentage, unchanged — no prop, no move.
665
+ <StatCard title="Volume" value="1,204" delta={4.2} deltaPeriod="WoW" />
666
+
667
+ // An absolute delta. The formatter gets the SIGNED number, so it may print the sign itself;
668
+ // `deltaGlyph={false}` then stops the ▼ saying the same thing twice. No `deltaLabel` prop exists —
669
+ // the function IS the escape hatch, and `delta` stays the number that drives the tone.
670
+ <StatCard title="Pace" value="5:31" unit="/km" delta={-12} deltaGlyph={false}
671
+ deltaFormat={(s) => `${s < 0 ? '−' : '+'}0:${String(Math.abs(s)).padStart(2, '0')} /km`} />
672
+ ```
673
+
674
+ ### Guards — the 1.27.0 promotions, one narrowing and one new exemption
675
+
676
+ **Five plugin rules and one guard kind became `error` in the shipped preset.** Their
677
+ `PLUGIN_RULE_GRACE` / `GRACE_PERIOD_KINDS` entries are deleted, which IS the promotion (C16). A
678
+ `theme-allow` written against any of them still works unchanged; only the severity of an UNWAIVED
679
+ finding moves.
680
+
681
+ | Promoted to `error` | Waiver, if the finding is deliberate |
682
+ | -------------------------------- | --------------------------------------------------------------------------------------------- |
683
+ | `basalt/control-size-literal` | `theme-allow control-size-literal — <why>` above the node, or `theme-allow-file` for the file |
684
+ | `basalt/in-body-page-title` | `theme-allow in-body-page-title — <why>` — ONE id, so it waives the guard kind's lane too |
685
+ | `in-body-page-title` (kind) | the same annotation; the two lanes share the id by construction |
686
+ | `basalt/responsive-twin` | `theme-allow responsive-twin — <why>`; the real fix is deleting one mount |
687
+ | `basalt/search-literal-link` | `theme-allow search-literal-link — <why>`; the real fix is `search: <store>.linkSearch` |
688
+ | `basalt/use-search-from-literal` | `theme-allow use-search-from-literal — <why>`; the real fix is the param as a prop |
689
+
690
+ The commonest in-body-title case that is NOT a defect: **a shell-less surface naming itself** — an
691
+ auth gate, an error boundary, a print view. There is no breadcrumb to carry the name, so write
692
+ `theme-allow in-body-page-title — shell-less surface`.
693
+
694
+ **`control-size-literal` also gained one exemption in the same minor, and it removes a waiver rather
695
+ than adding one: a slot owned by `ChartCard` no longer reports.** `ChartCard` lives inside the
696
+ Mantine-free chart layer, so its `actions` slot writes `data-basalt-tier` by hand and cannot mount
697
+ the tier theme at all — a `Switch` there with no `size` renders at Mantine's default, not at `ctl`.
698
+ The rule's message ("the HOME sets the tier, drop the prop") was simply false in that one slot, and
699
+ because the rule fires on the `size` ATTRIBUTE it could not tell a correct `size="ctl"` from the
700
+ `size="xs"` it exists to catch. Every other home still reports, and the owner test reads the
701
+ IMPORTED name so `ChartCard as Card` is exempt too. `hand-rolled-filter` is deliberately NOT
702
+ exempted there: a raw `Select` in `ChartCard.actions` is still a filter that should take a `field`.
703
+ A HOISTED binding is exempt only when EVERY basalt home it was handed to is `ChartCard` — one
704
+ `const acts = <Button size="xs"/>` given to both `<ChartCard actions>` and `<Section actions>` still
705
+ renders in a tiered slot, and keying the exemption on a single owner made the verdict depend on which
706
+ attribute came later in the file.
707
+
708
+ **`basalt/control-outside-home` and `raw-selection-control` did NOT promote. They are re-dated to
709
+ 1.30.0, and the reason is a measurement rather than caution:** the wave-7 consumer run left 9 warns
710
+ in argo, and every one is a control inside a modal/form module whose `<Modal>` is rendered by the
711
+ PARENT route — law C1's cross-file case, which is advisory by declaration because no scan of one
712
+ file can see it. Both lanes now exempt a file whose BASENAME matches
713
+ `*-{modal,drawer,popover,panel,form}.tsx`, which is the convention those nine already follow.
714
+ Outside the convention, an overlay body declares itself:
715
+ `theme-allow-file control-outside-home — overlay`. The trade is stated plainly: a whole file goes
716
+ unscanned on a naming convention, so a `Select` that genuinely belongs in a page bar goes unreported
717
+ if it lives in `filters-panel.tsx` — the same bargain the `@mantine/form` exemption already buys,
718
+ and a smaller one than promoting a rule with 9 known false positives.
719
+
720
+ **`basalt/shadow-basalt-export` narrowed, and it stays a permanent advisory `warn`.** An ALIAS hit is
721
+ now skipped when the file imports the basalt export it renames **and REFERENCES that binding as a
722
+ value**: a `HeroCard` that composes `StatCard` is a wrapper, not a fork, and it was already following
723
+ the advice the message gives. Composition, not import — three shapes stay reported, and they are the
724
+ ones a fork actually writes: a type-only `import type { StatCard }` (or an inline `type` specifier)
725
+ feeding `ComponentProps<typeof StatCard>`, a VALUE import used only in a type position, and a dead
726
+ import left behind after the body was re-rolled. None of them render anything. A reference does not
727
+ have to be a JSX tag — `component={StatCard}` and `createElement(StatCard)` compose it too. The
728
+ provenance test reads the IMPORTED name, so `import { StatCard as Base }` counts. The name-COLLISION
729
+ half is deliberately NOT exempted the same way — a local `StatCard` beside an
730
+ `import { StatCard as Base }` kept the name AND a piece of the original, which is the fork shape this
731
+ rule most wants to see.
732
+
733
+ ### `basalt-ui/data` — a typed facet id, and a required `getItemKey`
734
+
735
+ **`BasaltDataTable`: a mistyped `facets[].columnId` used to render no pill, silently — `if
736
+ (!column) return null`.** It now throws in dev, naming the id and every known column id
737
+ (`facet columnId "…" matches no column`); production still degrades to no pill for that one facet.
738
+ `DataTableFacet` also gained a type parameter — `DataTableFacet<T = unknown>` — so `columnId` is
739
+ typed off the row shape wherever TanStack's `accessor`/manual `id` inference allows it
740
+ (`Extract<keyof T, string> | (string & {})`); the default keeps a bare `DataTableFacet[]`
741
+ compiling unchanged. `BasaltDataTableProps.facets` is now `DataTableFacet<T>[]`, and
742
+ `initialColumnPinning` moved from TanStack's own `ColumnPinningState` to a new, `T`-derived
743
+ `DataTableColumnPinning<T>` (structurally assignable to `ColumnPinningState`, so no cast is
744
+ needed at either end). Both are additive for existing call sites — an id that was already a real
745
+ column keeps compiling and rendering exactly as before.
746
+
747
+ **`BasaltVirtualList.getItemKey` is now required.** The index fallback hid stale rows across a
748
+ mutation: an insert/delete shifts every index below it, so the OLD item kept rendering at each
749
+ shifted index until an unrelated re-render happened to catch it up — in the one component whose
750
+ job is a long mutable list. Pass a real per-item key (`(item) => item.id`); there is no
751
+ replacement for the index fallback.
752
+
753
+ **`BasaltVirtualList` gained a ref.** `ref?: Ref<BasaltVirtualListHandle>` exposes
754
+ `scrollToIndex`/`scrollToOffset`/`scrollToEnd` (delegating to the internal
755
+ `@tanstack/react-virtual` instance) plus `getVirtualizer()` as the full escape hatch — mirrors
756
+ `./data/table`'s `useReactTable` re-export as the table's own. Additive; omitting `ref` changes
757
+ nothing.
758
+
759
+ ### `basalt-ui/commands` — `BasaltOverlays` no longer WRAPS your app in `ModalsProvider`
760
+
761
+ **Same props, same imperative API, one structural change: every layer — `ModalsProvider`
762
+ included — now renders as a SIBLING of `children` instead of an ancestor.** The old shape put the
763
+ whole app under a `React.lazy` Suspense boundary (`<Suspense fallback={<>{app}</>}>
764
+ <LazyModalsProvider>{app}</LazyModalsProvider></Suspense>`), and `React.lazy` suspends on its first
765
+ render even when the module is already warm. Two consequences, both real: the app's FIRST COMMIT was
766
+ deferred past a microtask — long enough for TanStack Router's async `loadMatches` to `setState` onto
767
+ a not-yet-mounted fiber ("Can't perform a React state update on a component that hasn't mounted
768
+ yet", once per full page load) — and the app tree mounted TWICE, once inside the fallback and once
769
+ resolved, so every effect in the app ran twice on boot.
770
+
771
+ Nothing to change for the overlay APIs. `modals.open` / `modals.openConfirmModal` (and so basalt's
772
+ own `overlays.open` / `overlays.close`) go through `@mantine/modals`' window CustomEvent bus, which
773
+ `ModalsProvider` subscribes to — being inside it was never a requirement, and the provider renders
774
+ its own managed `Modal` regardless of where it sits.
775
+
776
+ **The one thing a sibling cannot serve is React CONTEXT**: `useModals()` from `@mantine/modals`
777
+ called inside your app now throws its "was called outside of context" error. If you use it (or
778
+ `openContextModal`, which needs the provider's `modals` map that `BasaltOverlays` never passed
779
+ anyway), mount your own provider and turn basalt's off — two providers would both answer the event
780
+ bus and open every modal twice:
781
+
782
+ ```tsx
783
+ <BasaltOverlays modals={false}>
784
+ <ModalsProvider modals={CONTEXT_MODALS}>
785
+ <App />
786
+ </ModalsProvider>
787
+ </BasaltOverlays>
788
+ ```
789
+
790
+ ### `basalt-ui/controls` — the mobile `Filters (n)` sheet renders panel rows, not its own list
791
+
792
+ **The sheet's `SheetOptionList` (44px rows, a hairline between them, one per option) is gone.**
793
+ `useFilterSurface() === 'sheet'` now resolves to the SAME `PanelRow`/`PanelChoice`/facet-list body
794
+ `'panel'` already renders (`docs/CONTROLS-SPEC.md` §3: "sheet = panel rows inside a Drawer") — label
795
+ above, full-width control below, folding past `PANEL_TRACK_MAX` (3, a `Select`) or `max` (a
796
+ `MultiSelectFilter`'s `Show N more`) the same way the aside already did. A set with many options no
797
+ longer grows the sheet to a full column of option rows. `RangeFilter`'s sheet form is the panel's one
798
+ `Select` (presets plus a `Custom range…` row revealing the injected picker) instead of a
799
+ `SheetOptionList` with the picker behind a separate disclosure row.
800
+
801
+ Also fixed in the same pass: a `fullWidth` `SegmentedControl` (`PanelChoice`'s ≤3-option form, and
802
+ `ViewTabs`) now splits its options EVENLY — Mantine's own `.control` carries no `min-width`
803
+ override, so the initial `auto` floored each option at its content width and a `fullWidth` track
804
+ split unevenly; a long label now ellipsizes inside its equal share instead of forcing the column
805
+ wide. The active segment's fill is a second ink-mix step
806
+ (`color-mix(in srgb, var(--vx-ink) 12%, var(--vx-surface-panel))`), not a flat `--vx-surface-panel`,
807
+ so it stays visible against its own track when the whole control sits on a panel-coloured surface (a
808
+ `PanelRow`), where a flat panel fill read as barely-there next to the 6%-tinted track around it.
809
+
810
+ | Removed | Replacement | Note |
811
+ | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
812
+ | `SheetOptionList`, `SheetField`, `SheetDisclosure` (`basalt-ui/controls`) | `PanelRow` / `PanelChoice` (already exported) | Were never part of the public `./controls` barrel — internal to `filter-sheet.tsx` — so this only breaks a consumer test that imported them directly out of `basalt-ui/controls/filter-sheet` or hooked a `role="radio"` `<fieldset>` DOM shape inside the sheet (now a `PanelChoice` `SegmentedControl`, `role="radiogroup"`, or a `Select`). |
813
+ | `SheetRow`, `sheetRowClassNames` (`basalt-ui/controls`) | none | Also internal-only. `ToggleFilter`'s sheet form is now a `PanelRow` whose `Switch` rides the label line, the same shape the panel surface always used. |
814
+ | `.sheetOption` / `.sheetList` / `.sheetField` / `.sheetRow` / `.sheetDisclosureBody` / `.sheetLabel` (`controls.module.css`) | `panel-row.module.css`'s `.row`/`.head`/`.control`/`.label` | CSS-module classes were never a public contract; named for a consumer that vendored a copy of the sheet's old CSS. |
815
+
816
+ ### The shell scrollport — Main scrolls, not the window
817
+
818
+ `BasaltShell` dropped Mantine's `layout="alt"` (the header now spans the full viewport width, above
819
+ the sidebar and the aside, instead of being inset beside the sidebar) and gave `AppShell.Main` a
820
+ bounded height with `overflow: auto`. Main is therefore THE scrolling element: the document's
821
+ `scrollHeight` equals the viewport height, the browser scrollbar sits on Main's own right edge
822
+ (inside the aside, where the content is) instead of at the far edge of the window, and
823
+ `window.scrollY` is pinned at 0.
824
+
825
+ Main carries two handles: `data-basalt-scrollport` (basalt's own, and what `scrollParentOf` looks
826
+ for) and `data-scroll-restoration-id="basalt-main"` (the attribute `@tanstack/router-core`'s scroll
827
+ restoration reads, so a router-driven app restores this element rather than the window).
828
+
829
+ What to change in a consumer:
830
+
831
+ | If your app… | Do this |
832
+ | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
833
+ | reads `window.scrollY` / listens for `scroll` on `window` | `const port = scrollParentOf(el)` and read `port.scrollTop` / listen on `port` — `null` still means the window, so shell-less code is unchanged |
834
+ | scrolls with `window.scrollTo` / `document.scrollingElement` | target `document.querySelector('[data-basalt-scrollport]')` first, with the document as the fallback |
835
+ | uses window-based scroll restoration | point it at `[data-scroll-restoration-id="basalt-main"]`; TanStack Router picks it up with no config |
836
+ | overrides `--vx-space-sticky-header-clearance` | the token NO LONGER includes the app-shell header height — it is `anchors.stackMd` (12px at level 0), pure breathing room, because BOTH the header and the page-bar band are regions outside the scrollport |
837
+ | passes `BasaltDataTable stickyHeaderOffset` inside a shell | drop it entirely. Nothing overhangs the scrollport any more, so a sticky table head wants `top: 0`, which is the default |
838
+ | computes a viewport-filling body from `--basalt-page-bar-h` | inside a shell that is just `100%` now — the band is outside Main rather than scrolling with it. The var is still published |
839
+ | passes `mobileNav.getScrollElement` only to reach the page scroller | you can drop it — the default resolves the scrollport now |
840
+
841
+ Three rendering changes ride along, all visible without any code change:
842
+
843
+ - **The brand moved out of the sidebar and into the header.** The header spans the full viewport
844
+ width now, so the sidebar's `appShellHeaderHeight` brand row painted as a SECOND 48px band under
845
+ the header seam. It is the header's leading zone instead — `--app-shell-navbar-offset` wide, so it
846
+ still tracks the rail collapse and still ends on the sidebar\|main seam — and the sidebar column
847
+ starts at `SidebarSearch`. `brand`, `brand.menu` and the collapse toggle are unchanged; only the
848
+ markup's home moved. Two consequences for a consumer mounting `AppSidebar` DIRECTLY (rather than
849
+ through `BasaltShell`, which is the supported path): it no longer paints a brand row, and its
850
+ `onToggleCollapse` prop is no longer read there — `BasaltShell` feeds it to the header instead.
851
+ - **`PageBar` row 2 is a shell-owned BAND**, not a row in the page: it portals into an outlet
852
+ `BasaltShell` renders between the header and the scrollport, exactly as row 1 portals into the
853
+ header. It spans Main's width, carries `--vx-surface-bg` and one `--vx-divider` hairline, and is a
854
+ zero-height, seam-less box on a route with no `PageBar`. It is no longer sticky and no longer
855
+ depends on where you wrote `<PageBar>` — nested in a `Stack` or written as Main's direct child now
856
+ produce identical geometry. Its padding-block tightened from `--vx-space-stack-sm` to
857
+ `--vx-space-stack-xs`, so the bar is 8px shorter.
858
+ - **`BasaltDataTable` contains itself horizontally by default.** A table with neither `maxHeight`
859
+ nor `minWidth` used to render a bare `<table>`, which sizes to its own min-content — a five-column
860
+ table measured 448px inside a 390px viewport and dragged the whole page sideways. Every table now
861
+ renders inside `Table.ScrollContainer type="native"` (`minWidth: 0` when you set none), so a wide
862
+ table scrolls inside its own card. `stickyHeader` with neither prop cannot take that container —
863
+ an `overflow-x` box computes `overflow-y` to `auto` and becomes the header's scrollport, and with
864
+ no height cap it has no scroll range, so the header would go inert — so it takes a wrapper whose
865
+ overflow is **measured** instead: while the table fits its container the wrapper declares no
866
+ overflow at all and the page-sticky header sticks exactly as before; once the table is wider the
867
+ wrapper flips to `overflow-x: auto`, the columns stay reachable, and the header is inert at that
868
+ width. The flip is a live `ResizeObserver` reading in both directions, so the wrapper goes back to
869
+ bare when the space returns (the window widened, the sidebar collapsed, the aside closed), and it
870
+ publishes `data-contained="true|false"`. The dev warning on that shape now states the trade rather
871
+ than a defect and points at `maxHeight` for a table that must both scroll and stick. Pinning takes
872
+ the same wrapper: the `overflow-x: auto` `Box` a pinned, uncapped, sticky table used to get is
873
+ gone, because it was the inert-header shape it was meant to avoid. **The TOOLBAR was the other
874
+ half of the same defect and is fixed with it**: the toolbar `Group` is the header row's flex item
875
+ (`CtlSlot` between them is `display: contents`) and carried `flex: 0 0 auto`, so a 220px search
876
+ beside a 230px facet pill row was an unshrinkable 461px box in a 302px column — `AppShell.Main`
877
+ measured `scrollWidth` 505 against `clientWidth` 390 at 390x844. It is now `flex: 0 1 auto` with
878
+ `min-width: 0`, and the search's `w={220}` became a flex BASIS (`flex: 0 1 220px`, floored at
879
+ `12ch`, capped at `100%`). The field still resolves to 220px wherever 220 fits and still never
880
+ grows with the table; below that the search and the pills wrap onto their own lines.
881
+
882
+ ## 1.26.0 — the control tier, the page bar and the store
883
+
35
884
  **One export removed and two deprecated — see § Stores below; two shell PROPS removed — see
36
885
  § Sidebar. Two other behaviour changes: the grace ledgers changed shape, and nine
37
886
  long-stale entries (D4, `docs/archive/CONTROLS-SYNTHESIS.md`) are promoted.** C16
@@ -48,7 +897,8 @@ happened to the nine rows below.
48
897
  (the table below) or moved to the new, permanent `PLUGIN_RULE_ADVISORY` ledger
49
898
  (`shadow-basalt-export`). Both ledgers now hold only the wave-6 control guards listed under
50
899
  § Guards — six plugin rules and two guard kinds, each `{ since: '1.26.0', promote: '1.27.0' }` —
51
- which the C16 gate forces to promote or be deleted at 1.27.0. A theme-allow escape written against
900
+ which the C16 gate forced to promote or be deleted at 1.27.0. Five rules and one kind promoted there;
901
+ the C1 pair was re-dated to 1.30.0 against a measurement (see `## Unreleased` above). A theme-allow escape written against
52
902
  any of these still works unchanged; only the SEVERITY of an unwaived finding moves from `warn` to
53
903
  `error`.
54
904
 
@@ -357,22 +1207,23 @@ A consumer that uses `DateRangePicker` installs `@mantine/dates` and imports
357
1207
 
358
1208
  ### Guards — the control rules (docs/CONTROLS-SPEC.md §6)
359
1209
 
360
- **Nothing removed. Eight new oxlint rules and two new guard kinds; two of the ten land `error`, the
361
- rest `warn` under a dated C16 grace entry.** Every one honours `theme-allow <id> — <why>` on the
1210
+ **Nothing removed. Eight new oxlint rules and two new guard kinds. `Ships` below is the level as of
1211
+ THIS minor** — eight land `error`, and the C1 pair (`control-outside-home` and its text lane) is the
1212
+ only grace left, re-dated to 1.30.0 on a measurement (see § Guards — the promotions).\*\* Every one honours `theme-allow <id> — <why>` on the
362
1213
  node and `theme-allow-file <id> — <why>` on the file, the same grammar as the rest.
363
1214
 
364
- | Rule / kind | Fires on | Ships |
365
- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
366
- | `basalt/hand-rolled-filter` | A raw Mantine `Select`/`SegmentedControl`/… handed to a TIERED home slot (`actions`/`filters`/`tabs`/`sync`/`filtersEnd` on `PageBar`/`Section`/`WidgetHeader`/`ChartCard`/`StatCard`/`BasaltDataTable`/`SettingsSection`/`FilterSet`) | error |
367
- | `basalt/page-bar-budget` | A second `PageBar` in the SAME returned tree, >4 `actions.secondary`, >3 `Section` actions, a second filled `Button`/`ActionIcon` in one slot (outside an overlay) | error |
368
- | `basalt/control-outside-home` | The same raw control with no home at all — exempt under a settings row / overlay / composer, in an `@mantine/form` file, or in a file that DEFINES a basalt control | warn → 1.27.0 |
369
- | `basalt/control-size-literal` | `size`/`w`/`fullWidth`/`visibleFrom`/`hiddenFrom` on anything inside a home slot (the slot sets the tier) | warn → 1.27.0 |
370
- | `basalt/in-body-page-title` | `<Title order={1\|2}>` outside prose/overlay context and outside a `content/` path | warn → 1.27.0 |
371
- | `basalt/responsive-twin` | The same control mounted twice, one `visibleFrom="X"` and one `hiddenFrom="X"` | warn → 1.27.0 |
372
- | `basalt/search-literal-link` | A `search:` object literal in a `linkOptions()` inside `defineNav()`/`navGroup()` | warn → 1.27.0 |
373
- | `basalt/use-search-from-literal` | `useSearch({ from: '<route>' })` | warn → 1.27.0 |
374
- | `in-body-page-title` (guard kind) | The text lane of the same law — SAME id, so one annotation waives both lanes | warn → 1.27.0 |
375
- | `raw-selection-control` (guard kind) | The text lane of `control-outside-home`, approximated by a 12-line host-tag window | warn → 1.27.0 |
1215
+ | Rule / kind | Fires on | Ships |
1216
+ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- |
1217
+ | `basalt/hand-rolled-filter` | A raw Mantine `Select`/`SegmentedControl`/… handed to a TIERED home slot (`actions`/`filters`/`tabs`/`sync`/`filtersEnd` on `PageBar`/`Section`/`WidgetHeader`/`ChartCard`/`StatCard`/`BasaltDataTable`/`SettingsSection`/`FilterSet`) | error |
1218
+ | `basalt/page-bar-budget` | A second `PageBar` in the SAME returned tree, >4 `actions.secondary`, >3 `Section` actions, a second filled `Button`/`ActionIcon` in one slot (outside an overlay) | error |
1219
+ | `basalt/control-outside-home` | The same raw control with no home at all — exempt under a settings row / overlay / composer, in an `@mantine/form` file, in a file that DEFINES a basalt control (and imports no `basalt-ui`), or in a file basenamed `*-{modal,drawer,popover,panel,form}.tsx` / `<Subject>{Modal,Drawer,Popover,Panel,Form}.tsx` | warn → 1.30.0 |
1220
+ | `basalt/control-size-literal` | `size`/`w`/`fullWidth`/`visibleFrom`/`hiddenFrom` on anything inside a home slot (the slot sets the tier) | **error** |
1221
+ | `basalt/in-body-page-title` | `<Title order={1\|2}>` outside prose/overlay context and outside a `content/` path | **error** |
1222
+ | `basalt/responsive-twin` | The same control mounted twice, one `visibleFrom="X"` and one `hiddenFrom="X"` | **error** |
1223
+ | `basalt/search-literal-link` | A `search:` object literal in a `linkOptions()` inside `defineNav()`/`navGroup()` | **error** |
1224
+ | `basalt/use-search-from-literal` | `useSearch({ from: '<route>' })` | **error** |
1225
+ | `in-body-page-title` (guard kind) | The text lane of the same law — SAME id, so one annotation waives both lanes | **error** |
1226
+ | `raw-selection-control` (guard kind) | The text lane of `control-outside-home`, approximated by a 12-line host-tag window, plus the same overlay-basename exemption | warn → 1.30.0 |
376
1227
 
377
1228
  **A `SettingsRow`'s `control` is not a tiered slot, so nothing in this table fires inside one.**
378
1229
  It is law C1's third home — the form row — and a form keeps Mantine's `md` tier
@@ -741,7 +1592,7 @@ instead of dumping help and letting the dump read like a choice.
741
1592
  - **`tokens:css --check` blanks the provenance line (line 2) before comparing**, so a version bump
742
1593
  alone no longer forces a no-op commit in a tokens-only consumer, where the gate is byte-equality.
743
1594
  A stale provenance line is now a note on an otherwise-passing check. The `@generated` header is
744
- still emitted byte-identical — the line stays, it just stops gating. **Superseded in `Unreleased`:
1595
+ still emitted byte-identical — the line stays, it just stops gating. **Superseded at 1.26.0:
745
1596
  blanking the WHOLE line also stopped gating the regeneration command it carries. Only the version
746
1597
  token is neutralized now.**
747
1598
 
@@ -912,7 +1763,7 @@ icons — it is not yet **sufficient**. If you went hybrid over the 404s, droppi
912
1763
  half here costs you every icon unless your `public/` matches basalt's six filenames: at this
913
1764
  release `icons` was still `false | { dir?: string }` over those six, so an app whose icon is
914
1765
  `favicon.svg` could pick between a manifest naming two PNGs it never builds and a manifest with no
915
- `icons` member at all. The array form that fixes it is in `## Unreleased`. The option's JSDoc said
1766
+ `icons` member at all. The array form that fixes it is in `## 1.26.0`. The option's JSDoc said
916
1767
  "skips the head `<link>` icons"; it now says what the option does.
917
1768
 
918
1769
  **Two `theme-allow` comment shapes were silently broken and now work.** Plainly: this is the fourth
@@ -920,7 +1771,7 @@ hole found in this one contract in three rounds. A thirteen-shape matrix — eve
920
1771
  had been SEEN to write — is pinned in both halves, `src/guard/check-source.test.ts` and
921
1772
  `configs/oxlint-plugin.test.ts`. **The claim that followed it here, that the two parsers could
922
1773
  therefore no longer disagree, was false when it was written**: a list of collected anecdotes cannot
923
- close a contract. Three more holes were open at this release. See `## Unreleased`.
1774
+ close a contract. Three more holes were open at this release. See `## 1.26.0`.
924
1775
 
925
1776
  | Shape | What it did | Now |
926
1777
  | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
@@ -1207,35 +2058,43 @@ live in `configs/oxlint.json`, and since 1.20.0 their grace is tracked by `PLUGI
1207
2058
  named export beside the plugin — a test asserts it against the shipped preset in both directions, so
1208
2059
  deleting an entry forces the level flip in the same commit.
1209
2060
 
1210
- | Rule | Landed | Became `error` |
1211
- | -------------------------------------- | ---------------- | --------------------------------------------------------------------- |
1212
- | `mantine-shade-index` (guard kind) | 1.7.0 as `warn` | **1.11.0** |
1213
- | `basalt/raw-scroll-container` | ≤1.2.0 as `off` | `warn` 1.10.0 → **`error` 1.13.0** |
1214
- | `basalt/ai-sdk-major` | 1.10.0 as `warn` | **1.13.0** |
1215
- | `basalt/agent-no-raw-usechat` | 1.10.0 as `warn` | **1.13.0** |
1216
- | `basalt/agent-resume-guard` | 1.10.0 as `warn` | **1.13.0** |
1217
- | `basalt/raw-size-literal` | 1.7.0 as `warn` | **1.20.0** |
1218
- | `basalt/hand-rolled-plot` | 1.21.0 as `warn` | **Unreleased** (grace had restarted at the 1.21.0 widening) |
1219
- | `basalt/chart-legend-literal` | 1.20.0 as `warn` | **Unreleased** |
1220
- | `basalt/shadow-basalt-export` | 1.20.0 as `warn` | ADVISORY — permanent `warn`, see below, never subject to the C16 gate |
1221
- | `basalt/hand-rolled-shell` | 1.20.0 as `warn` | **Unreleased** |
1222
- | `theme-allow-unscoped` (guard kind) | 1.20.0 as `warn` | **Unreleased** |
1223
- | `surface-shadow-override` (guard kind) | 1.20.0 as `warn` | **Unreleased** |
1224
- | `css-raw-surface` (guard kind) | 1.20.0 as `warn` | **Unreleased** |
1225
- | `inline-font-size` (guard kind) | 1.20.0 as `warn` | **Unreleased** |
1226
- | `hidden-inline-style` (guard kind) | 1.20.0 as `warn` | **Unreleased** |
2061
+ | Rule | Landed | Became `error` |
2062
+ | -------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------- |
2063
+ | `mantine-shade-index` (guard kind) | 1.7.0 as `warn` | **1.11.0** |
2064
+ | `basalt/raw-scroll-container` | ≤1.2.0 as `off` | `warn` 1.10.0 → **`error` 1.13.0** |
2065
+ | `basalt/ai-sdk-major` | 1.10.0 as `warn` | **1.13.0** |
2066
+ | `basalt/agent-no-raw-usechat` | 1.10.0 as `warn` | **1.13.0** |
2067
+ | `basalt/agent-resume-guard` | 1.10.0 as `warn` | **1.13.0** |
2068
+ | `basalt/raw-size-literal` | 1.7.0 as `warn` | **1.20.0** |
2069
+ | `basalt/hand-rolled-plot` | 1.21.0 as `warn` | **1.26.0** (grace had restarted at the 1.21.0 widening) |
2070
+ | `basalt/chart-legend-literal` | 1.20.0 as `warn` | **1.26.0** |
2071
+ | `basalt/shadow-basalt-export` | 1.20.0 as `warn` | ADVISORY — permanent `warn`, see below, never subject to the C16 gate |
2072
+ | `basalt/hand-rolled-shell` | 1.20.0 as `warn` | **1.26.0** |
2073
+ | `theme-allow-unscoped` (guard kind) | 1.20.0 as `warn` | **1.26.0** |
2074
+ | `surface-shadow-override` (guard kind) | 1.20.0 as `warn` | **1.26.0** |
2075
+ | `css-raw-surface` (guard kind) | 1.20.0 as `warn` | **1.26.0** |
2076
+ | `inline-font-size` (guard kind) | 1.20.0 as `warn` | **1.26.0** |
2077
+ | `hidden-inline-style` (guard kind) | 1.20.0 as `warn` | **1.26.0** |
2078
+ | `basalt/control-size-literal` | 1.26.0 as `warn` | **1.27.0** |
2079
+ | `basalt/in-body-page-title` | 1.26.0 as `warn` | **1.27.0** |
2080
+ | `in-body-page-title` (guard kind) | 1.26.0 as `warn` | **1.27.0** — same id as the rule above, so one waiver covers both |
2081
+ | `basalt/responsive-twin` | 1.26.0 as `warn` | **1.27.0** |
2082
+ | `basalt/search-literal-link` | 1.26.0 as `warn` | **1.27.0** |
2083
+ | `basalt/use-search-from-literal` | 1.26.0 as `warn` | **1.27.0** |
2084
+ | `basalt/control-outside-home` | 1.26.0 as `warn` | still `warn`, `promote: '1.30.0'` — re-dated on 9 measured cross-file overlay warns in argo |
2085
+ | `raw-selection-control` (guard kind) | 1.26.0 as `warn` | still `warn`, `promote: '1.30.0'` — the text lane of the row above |
1227
2086
 
1228
2087
  `card-with-border`, `inline-display`, `raw-html-layout`, `raw-form-control`, `raw-font-family` and
1229
2088
  the other original guard kinds have been `error` since before 1.2.0 — they never had a grace minor.
1230
2089
  Guard findings only gained a severity field at all in 1.4.0; before that every finding was fatal.
1231
2090
 
1232
- **Both ledgers changed shape in the Unreleased minor (C16, `docs/CONTROLS-SPEC.md` §1).** An entry
2091
+ **Both ledgers changed shape at 1.26.0 (C16, `docs/CONTROLS-SPEC.md` §1).** An entry
1233
2092
  used to be a bare promotion-note string with no expiry a machine could check — which is exactly how
1234
2093
  the nine rows above sat at `warn` for five minors although the doctrine below says "one minor". Both
1235
2094
  `PLUGIN_RULE_GRACE` (`configs/oxlint-plugin.js`) and `GRACE_PERIOD_KINDS` (`src/guard/index.ts`) now
1236
2095
  hold `{ since, promote, why }` (semver strings), and a version-gated test fails the build once
1237
2096
  `package.json`'s version reaches an entry's `promote` while the entry is still there — see
1238
- "Unreleased" above for the full change. `shadow-basalt-export` moved to a sibling ledger,
2097
+ "1.26.0" above for the full change. `shadow-basalt-export` moved to a sibling ledger,
1239
2098
  `PLUGIN_RULE_ADVISORY`, which the gate never checks: it was already documented as a possible
1240
2099
  permanent `warn`, and the version-gate doctrine only fits an entry that has an honest promotion
1241
2100
  date.