basalt-ui 1.25.0 → 1.27.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 (476) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +26 -0
  3. package/MIGRATING.md +815 -23
  4. package/README.md +45 -32
  5. package/agent/rules/basalt-batteries.md +205 -0
  6. package/agent/rules/basalt-charts.md +125 -555
  7. package/agent/rules/basalt-controls.md +160 -0
  8. package/agent/rules/basalt-mantine.md +146 -439
  9. package/agent/rules/basalt-state.md +118 -96
  10. package/agent/rules/basalt-tokens.md +126 -288
  11. package/agent/skills/basalt-app/SKILL.md +70 -195
  12. package/agent/skills/basalt-charts/SKILL.md +54 -234
  13. package/agent/skills/basalt-design/SKILL.md +56 -248
  14. package/agent/templates/CLAUDE-block.md.tpl +31 -40
  15. package/agent/templates/DESIGN.md.tpl +22 -51
  16. package/configs/oxlint-plugin.js +1465 -36
  17. package/configs/oxlint-plugin.test.ts +1190 -11
  18. package/configs/oxlint.json +11 -3
  19. package/dist/charts/cursor/resolve.d.ts +18 -0
  20. package/dist/charts/cursor/resolve.d.ts.map +1 -1
  21. package/dist/charts/cursor/resolve.js +15 -6
  22. package/dist/charts/cursor/scope.d.ts +6 -3
  23. package/dist/charts/cursor/scope.d.ts.map +1 -1
  24. package/dist/charts/cursor/store.d.ts +5 -1
  25. package/dist/charts/cursor/store.d.ts.map +1 -1
  26. package/dist/charts/cursor/store.js +4 -4
  27. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  28. package/dist/charts/hooks/useChartCursor.js +9 -8
  29. package/dist/charts/index.d.ts +2 -2
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/primitives/ChartCard.d.ts +34 -9
  32. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  33. package/dist/charts/primitives/ChartCard.js +29 -114
  34. package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
  35. package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
  36. package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
  37. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  38. package/dist/charts/sparklines/BarSparkline.js +18 -4
  39. package/dist/cli/check-coverage.test.d.ts +2 -0
  40. package/dist/cli/check-coverage.test.d.ts.map +1 -0
  41. package/dist/cli/index.d.ts +67 -4
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +225 -7
  44. package/dist/connectivity/connectivity-indicator.d.ts +4 -1
  45. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  46. package/dist/connectivity/connectivity-indicator.js +1 -1
  47. package/dist/content/index.d.ts +5 -3
  48. package/dist/content/index.d.ts.map +1 -1
  49. package/dist/content/index.js +4 -2
  50. package/dist/content/prose.module.css +7 -28
  51. package/dist/controls/actions.d.ts +169 -0
  52. package/dist/controls/actions.d.ts.map +1 -0
  53. package/dist/controls/actions.js +287 -0
  54. package/dist/controls/actions.module.css +62 -0
  55. package/dist/controls/actions.module.css.d.ts +13 -0
  56. package/dist/controls/actions.test.d.ts +2 -0
  57. package/dist/controls/actions.test.d.ts.map +1 -0
  58. package/dist/controls/actions.type-guard.test.d.ts +2 -0
  59. package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
  60. package/dist/controls/compare-filter.d.ts +41 -0
  61. package/dist/controls/compare-filter.d.ts.map +1 -0
  62. package/dist/controls/compare-filter.js +23 -0
  63. package/dist/controls/control-group.d.ts +19 -0
  64. package/dist/controls/control-group.d.ts.map +1 -0
  65. package/dist/controls/control-group.js +20 -0
  66. package/dist/controls/control-group.test.d.ts +2 -0
  67. package/dist/controls/control-group.test.d.ts.map +1 -0
  68. package/dist/controls/controls.module.css +542 -0
  69. package/dist/controls/controls.module.css.d.ts +35 -0
  70. package/dist/controls/controls.router.test.d.ts +2 -0
  71. package/dist/controls/controls.router.test.d.ts.map +1 -0
  72. package/dist/controls/enum-filter.d.ts +39 -0
  73. package/dist/controls/enum-filter.d.ts.map +1 -0
  74. package/dist/controls/enum-filter.js +93 -0
  75. package/dist/controls/filter-context.d.ts +60 -0
  76. package/dist/controls/filter-context.d.ts.map +1 -0
  77. package/dist/controls/filter-context.js +69 -0
  78. package/dist/controls/filter-pill.d.ts +28 -0
  79. package/dist/controls/filter-pill.d.ts.map +1 -0
  80. package/dist/controls/filter-pill.js +70 -0
  81. package/dist/controls/filter-set.d.ts +13 -0
  82. package/dist/controls/filter-set.d.ts.map +1 -0
  83. package/dist/controls/filter-set.js +125 -0
  84. package/dist/controls/filter-set.test.d.ts +2 -0
  85. package/dist/controls/filter-set.test.d.ts.map +1 -0
  86. package/dist/controls/filter-sheet.d.ts +117 -0
  87. package/dist/controls/filter-sheet.d.ts.map +1 -0
  88. package/dist/controls/filter-sheet.js +146 -0
  89. package/dist/controls/glyphs.d.ts +27 -0
  90. package/dist/controls/glyphs.d.ts.map +1 -0
  91. package/dist/controls/glyphs.js +49 -0
  92. package/dist/controls/index.d.ts +20 -0
  93. package/dist/controls/index.d.ts.map +1 -0
  94. package/dist/controls/index.js +46 -0
  95. package/dist/controls/multi-select-filter.d.ts +23 -0
  96. package/dist/controls/multi-select-filter.d.ts.map +1 -0
  97. package/dist/controls/multi-select-filter.js +89 -0
  98. package/dist/controls/number-filter.d.ts +26 -0
  99. package/dist/controls/number-filter.d.ts.map +1 -0
  100. package/dist/controls/number-filter.js +143 -0
  101. package/dist/controls/peer-boundary.test.d.ts +2 -0
  102. package/dist/controls/peer-boundary.test.d.ts.map +1 -0
  103. package/dist/controls/props-contract.test.d.ts +2 -0
  104. package/dist/controls/props-contract.test.d.ts.map +1 -0
  105. package/dist/controls/range-filter.d.ts +43 -0
  106. package/dist/controls/range-filter.d.ts.map +1 -0
  107. package/dist/controls/range-filter.js +135 -0
  108. package/dist/controls/range-filter.type-guard.test.d.ts +2 -0
  109. package/dist/controls/range-filter.type-guard.test.d.ts.map +1 -0
  110. package/dist/controls/search-filter.d.ts +10 -0
  111. package/dist/controls/search-filter.d.ts.map +1 -0
  112. package/dist/controls/search-filter.js +81 -0
  113. package/dist/controls/select-filter.d.ts +74 -0
  114. package/dist/controls/select-filter.d.ts.map +1 -0
  115. package/dist/controls/select-filter.js +10 -0
  116. package/dist/controls/sync-button.d.ts +38 -0
  117. package/dist/controls/sync-button.d.ts.map +1 -0
  118. package/dist/controls/sync-button.js +119 -0
  119. package/dist/controls/sync-button.module.css +63 -0
  120. package/dist/controls/sync-button.module.css.d.ts +11 -0
  121. package/dist/controls/sync-button.test.d.ts +2 -0
  122. package/dist/controls/sync-button.test.d.ts.map +1 -0
  123. package/dist/controls/toggle-filter.d.ts +9 -0
  124. package/dist/controls/toggle-filter.d.ts.map +1 -0
  125. package/dist/controls/toggle-filter.js +42 -0
  126. package/dist/controls/view-tabs.d.ts +23 -0
  127. package/dist/controls/view-tabs.d.ts.map +1 -0
  128. package/dist/controls/view-tabs.js +59 -0
  129. package/dist/controls-dates/date-range-picker.d.ts +4 -0
  130. package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
  131. package/dist/controls-dates/date-range-picker.js +18 -0
  132. package/dist/controls-dates/index.d.ts +9 -0
  133. package/dist/controls-dates/index.d.ts.map +1 -0
  134. package/dist/controls-dates/index.js +4 -0
  135. package/dist/dashboard/delta-badge.d.ts +6 -12
  136. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  137. package/dist/dashboard/delta-badge.js +1 -50
  138. package/dist/dashboard/index.d.ts +1 -1
  139. package/dist/dashboard/index.d.ts.map +1 -1
  140. package/dist/dashboard/index.js +3 -1
  141. package/dist/dashboard/settings-section.d.ts +6 -4
  142. package/dist/dashboard/settings-section.d.ts.map +1 -1
  143. package/dist/dashboard/settings-section.js +23 -16
  144. package/dist/dashboard/settings-section.module.css +10 -0
  145. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  146. package/dist/dashboard/settings-section.test.d.ts +2 -0
  147. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  148. package/dist/dashboard/stat-card.d.ts +95 -9
  149. package/dist/dashboard/stat-card.d.ts.map +1 -1
  150. package/dist/dashboard/stat-card.js +108 -49
  151. package/dist/dashboard/stat-card.module.css +153 -0
  152. package/dist/dashboard/stat-card.module.css.d.ts +15 -0
  153. package/dist/data/data-table.d.ts +37 -15
  154. package/dist/data/data-table.d.ts.map +1 -1
  155. package/dist/data/data-table.js +96 -58
  156. package/dist/guard/grace.test.d.ts +20 -0
  157. package/dist/guard/grace.test.d.ts.map +1 -0
  158. package/dist/guard/index.d.ts +66 -3
  159. package/dist/guard/index.d.ts.map +1 -1
  160. package/dist/guard/index.js +78 -7
  161. package/dist/guard/types.d.ts +3 -3
  162. package/dist/guard/types.d.ts.map +1 -1
  163. package/dist/index.d.ts +13 -3
  164. package/dist/index.d.ts.map +1 -1
  165. package/dist/index.js +13 -10
  166. package/dist/nav/types.d.ts +78 -2
  167. package/dist/nav/types.d.ts.map +1 -1
  168. package/dist/notifications/bell.d.ts +4 -1
  169. package/dist/notifications/bell.d.ts.map +1 -1
  170. package/dist/notifications/index.d.ts +1 -1
  171. package/dist/router-tanstack/field.d.ts +14 -0
  172. package/dist/router-tanstack/field.d.ts.map +1 -0
  173. package/dist/router-tanstack/field.js +4 -0
  174. package/dist/router-tanstack/index.d.ts +6 -2
  175. package/dist/router-tanstack/index.d.ts.map +1 -1
  176. package/dist/router-tanstack/index.js +4 -0
  177. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  178. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  179. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  180. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  181. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  182. package/dist/router-tanstack/search-param-store.js +16 -45
  183. package/dist/router-tanstack/search-store.d.ts +84 -0
  184. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  185. package/dist/router-tanstack/search-store.js +271 -0
  186. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  187. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  188. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  189. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  190. package/dist/router-tanstack/use-nav.d.ts +0 -2
  191. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  192. package/dist/router-tanstack/use-nav.js +34 -12
  193. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  194. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  195. package/dist/section/index.d.ts +2 -0
  196. package/dist/section/index.d.ts.map +1 -0
  197. package/dist/section/index.js +4 -0
  198. package/dist/section/section.d.ts +24 -0
  199. package/dist/section/section.d.ts.map +1 -0
  200. package/dist/section/section.js +126 -0
  201. package/dist/section/section.module.css +104 -0
  202. package/dist/section/section.module.css.d.ts +11 -0
  203. package/dist/section/section.test.d.ts +2 -0
  204. package/dist/section/section.test.d.ts.map +1 -0
  205. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  206. package/dist/shell/app-breadcrumbs.js +18 -15
  207. package/dist/shell/app-header.module.css +77 -37
  208. package/dist/shell/app-header.module.css.d.ts +2 -1
  209. package/dist/shell/app-mobile-nav.d.ts +9 -2
  210. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  211. package/dist/shell/app-mobile-nav.js +98 -11
  212. package/dist/shell/app-mobile-nav.module.css +8 -0
  213. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  214. package/dist/shell/app-sidebar.d.ts +55 -28
  215. package/dist/shell/app-sidebar.d.ts.map +1 -1
  216. package/dist/shell/app-sidebar.js +116 -67
  217. package/dist/shell/app-sidebar.module.css +303 -20
  218. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  219. package/dist/shell/index.d.ts +55 -21
  220. package/dist/shell/index.d.ts.map +1 -1
  221. package/dist/shell/index.js +40 -18
  222. package/dist/shell/mobile-nav-model.d.ts +13 -2
  223. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  224. package/dist/shell/mobile-nav-model.js +15 -1
  225. package/dist/shell/nav-count-badge.d.ts +1 -1
  226. package/dist/shell/page-bar.d.ts +51 -0
  227. package/dist/shell/page-bar.d.ts.map +1 -0
  228. package/dist/shell/page-bar.js +154 -0
  229. package/dist/shell/page-bar.module.css +178 -0
  230. package/dist/shell/page-bar.module.css.d.ts +17 -0
  231. package/dist/shell/page-bar.test.d.ts +2 -0
  232. package/dist/shell/page-bar.test.d.ts.map +1 -0
  233. package/dist/shell/sidebar-block-model.d.ts +48 -0
  234. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  235. package/dist/shell/sidebar-block-model.js +39 -0
  236. package/dist/shell/sidebar-blocks.d.ts +32 -0
  237. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  238. package/dist/shell/sidebar-blocks.js +169 -0
  239. package/dist/shell/sidebar-search.d.ts +13 -1
  240. package/dist/shell/sidebar-search.d.ts.map +1 -1
  241. package/dist/shell/sidebar-search.js +34 -1
  242. package/dist/shell/sidebar-search.module.css +26 -0
  243. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  244. package/dist/state/fields.d.ts +565 -0
  245. package/dist/state/fields.d.ts.map +1 -0
  246. package/dist/state/fields.js +405 -0
  247. package/dist/state/fields.test.d.ts +2 -0
  248. package/dist/state/fields.test.d.ts.map +1 -0
  249. package/dist/state/persisted.d.ts +60 -0
  250. package/dist/state/persisted.d.ts.map +1 -0
  251. package/dist/state/persisted.js +123 -0
  252. package/dist/state/persisted.test.d.ts +2 -0
  253. package/dist/state/persisted.test.d.ts.map +1 -0
  254. package/dist/state.d.ts +11 -56
  255. package/dist/state.d.ts.map +1 -1
  256. package/dist/state.js +14 -120
  257. package/dist/styles.css +13 -0
  258. package/dist/surfaces.d.ts +106 -24
  259. package/dist/surfaces.d.ts.map +1 -1
  260. package/dist/surfaces.js +156 -19
  261. package/dist/theme/controls.module.css +16 -1
  262. package/dist/theme/ctl-theme.d.ts +32 -0
  263. package/dist/theme/ctl-theme.d.ts.map +1 -0
  264. package/dist/theme/ctl-theme.js +54 -0
  265. package/dist/theme/ctl-theme.test.d.ts +2 -0
  266. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  267. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  268. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  269. package/dist/theme/divider-law.test.d.ts +2 -0
  270. package/dist/theme/divider-law.test.d.ts.map +1 -0
  271. package/dist/theme/icon-slot.d.ts +44 -0
  272. package/dist/theme/icon-slot.d.ts.map +1 -0
  273. package/dist/theme/icon-slot.js +21 -0
  274. package/dist/theme/icon-slot.module.css +47 -0
  275. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  276. package/dist/theme/icon-slot.test.d.ts +2 -0
  277. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  278. package/dist/theme/index.d.ts +1 -0
  279. package/dist/theme/index.d.ts.map +1 -1
  280. package/dist/theme/index.js +82 -5
  281. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  282. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  283. package/dist/theme/segmented-control.module.css +20 -0
  284. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  285. package/dist/tokens/index.d.ts +12 -0
  286. package/dist/tokens/index.d.ts.map +1 -1
  287. package/dist/tokens/index.js +38 -8
  288. package/dist/tokens/palette.d.ts +97 -61
  289. package/dist/tokens/palette.d.ts.map +1 -1
  290. package/dist/tokens/palette.js +76 -38
  291. package/dist/tokens.css +10 -2
  292. package/dist/widget-header/delta-badge.d.ts +13 -0
  293. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  294. package/dist/widget-header/delta-badge.js +29 -0
  295. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  296. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  297. package/dist/widget-header/index.d.ts +3 -0
  298. package/dist/widget-header/index.d.ts.map +1 -0
  299. package/dist/widget-header/index.js +6 -0
  300. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  301. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  302. package/dist/widget-header/widget-header.d.ts +67 -0
  303. package/dist/widget-header/widget-header.d.ts.map +1 -0
  304. package/dist/widget-header/widget-header.js +89 -0
  305. package/dist/widget-header/widget-header.module.css +350 -0
  306. package/dist/widget-header/widget-header.module.css.d.ts +25 -0
  307. package/dist/widget-header/widget-header.test.d.ts +2 -0
  308. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  309. package/llms.txt +30 -17
  310. package/package.json +14 -1
  311. package/src/charts/cursor/resolve.test.ts +29 -1
  312. package/src/charts/cursor/resolve.ts +38 -6
  313. package/src/charts/cursor/scope.tsx +6 -3
  314. package/src/charts/cursor/store.test.ts +19 -1
  315. package/src/charts/cursor/store.ts +10 -5
  316. package/src/charts/hooks/useChartCursor.ts +16 -8
  317. package/src/charts/index.ts +2 -2
  318. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  319. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  320. package/src/charts/primitives/ChartCard.tsx +86 -146
  321. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  322. package/src/cli/check-coverage.test.ts +297 -0
  323. package/src/cli/check-theme.test.ts +38 -14
  324. package/src/cli/index.ts +392 -16
  325. package/src/cli/init-sync.test.ts +97 -0
  326. package/src/connectivity/connectivity-indicator.tsx +16 -9
  327. package/src/content/index.ts +16 -3
  328. package/src/content/prose.module.css +7 -28
  329. package/src/content/prose.module.css.test.ts +8 -24
  330. package/src/controls/actions.module.css +62 -0
  331. package/src/controls/actions.module.css.d.ts +13 -0
  332. package/src/controls/actions.test.tsx +252 -0
  333. package/src/controls/actions.tsx +655 -0
  334. package/src/controls/actions.type-guard.test.ts +63 -0
  335. package/src/controls/compare-filter.tsx +64 -0
  336. package/src/controls/control-group.test.tsx +229 -0
  337. package/src/controls/control-group.tsx +63 -0
  338. package/src/controls/controls.module.css +542 -0
  339. package/src/controls/controls.module.css.d.ts +35 -0
  340. package/src/controls/controls.router.test.tsx +1053 -0
  341. package/src/controls/enum-filter.tsx +170 -0
  342. package/src/controls/filter-context.tsx +158 -0
  343. package/src/controls/filter-pill.tsx +120 -0
  344. package/src/controls/filter-set.test.tsx +472 -0
  345. package/src/controls/filter-set.tsx +225 -0
  346. package/src/controls/filter-sheet.tsx +285 -0
  347. package/src/controls/glyphs.tsx +81 -0
  348. package/src/controls/index.ts +49 -0
  349. package/src/controls/multi-select-filter.tsx +140 -0
  350. package/src/controls/number-filter.tsx +291 -0
  351. package/src/controls/peer-boundary.test.ts +85 -0
  352. package/src/controls/props-contract.test.tsx +137 -0
  353. package/src/controls/range-filter.tsx +237 -0
  354. package/src/controls/range-filter.type-guard.test.tsx +122 -0
  355. package/src/controls/search-filter.tsx +123 -0
  356. package/src/controls/select-filter.tsx +89 -0
  357. package/src/controls/sync-button.module.css +63 -0
  358. package/src/controls/sync-button.module.css.d.ts +11 -0
  359. package/src/controls/sync-button.test.tsx +183 -0
  360. package/src/controls/sync-button.tsx +197 -0
  361. package/src/controls/toggle-filter.tsx +60 -0
  362. package/src/controls/view-tabs.tsx +110 -0
  363. package/src/controls-dates/date-range-picker.tsx +42 -0
  364. package/src/controls-dates/index.ts +8 -0
  365. package/src/dashboard/delta-badge.tsx +4 -74
  366. package/src/dashboard/index.ts +6 -1
  367. package/src/dashboard/settings-section.module.css +10 -0
  368. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  369. package/src/dashboard/settings-section.test.tsx +56 -0
  370. package/src/dashboard/settings-section.tsx +33 -30
  371. package/src/dashboard/stat-card.module.css +153 -0
  372. package/src/dashboard/stat-card.module.css.d.ts +15 -0
  373. package/src/dashboard/stat-card.test.tsx +298 -1
  374. package/src/dashboard/stat-card.tsx +289 -67
  375. package/src/data/data-table.test.tsx +96 -11
  376. package/src/data/data-table.tsx +228 -84
  377. package/src/guard/check-source.test.ts +159 -13
  378. package/src/guard/grace.test.ts +158 -0
  379. package/src/guard/index.ts +185 -29
  380. package/src/guard/types.ts +4 -2
  381. package/src/index.ts +51 -5
  382. package/src/nav/types.ts +84 -2
  383. package/src/notifications/bell.tsx +6 -3
  384. package/src/notifications/index.ts +1 -1
  385. package/src/router-tanstack/field.ts +35 -0
  386. package/src/router-tanstack/index.ts +28 -2
  387. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  388. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  389. package/src/router-tanstack/search-param-store.ts +42 -159
  390. package/src/router-tanstack/search-store.router.test.tsx +882 -0
  391. package/src/router-tanstack/search-store.test.ts +578 -0
  392. package/src/router-tanstack/search-store.ts +546 -0
  393. package/src/router-tanstack/use-nav.test.tsx +187 -0
  394. package/src/router-tanstack/use-nav.ts +72 -14
  395. package/src/section/index.ts +1 -0
  396. package/src/section/section.module.css +104 -0
  397. package/src/section/section.module.css.d.ts +11 -0
  398. package/src/section/section.test.tsx +204 -0
  399. package/src/section/section.tsx +220 -0
  400. package/src/shell/app-breadcrumbs.tsx +44 -31
  401. package/src/shell/app-header.module.css +77 -37
  402. package/src/shell/app-header.module.css.d.ts +2 -1
  403. package/src/shell/app-mobile-nav.module.css +8 -0
  404. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  405. package/src/shell/app-mobile-nav.tsx +147 -8
  406. package/src/shell/app-sidebar-account.tsx +2 -2
  407. package/src/shell/app-sidebar.module.css +303 -20
  408. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  409. package/src/shell/app-sidebar.test.tsx +456 -41
  410. package/src/shell/app-sidebar.tsx +276 -140
  411. package/src/shell/index.test.tsx +291 -5
  412. package/src/shell/index.tsx +137 -38
  413. package/src/shell/mobile-nav-model.test.ts +228 -7
  414. package/src/shell/mobile-nav-model.ts +53 -3
  415. package/src/shell/nav-count-badge.tsx +1 -1
  416. package/src/shell/page-bar.module.css +178 -0
  417. package/src/shell/page-bar.module.css.d.ts +17 -0
  418. package/src/shell/page-bar.test.tsx +316 -0
  419. package/src/shell/page-bar.tsx +315 -0
  420. package/src/shell/sidebar-block-model.ts +83 -0
  421. package/src/shell/sidebar-blocks.tsx +301 -0
  422. package/src/shell/sidebar-search.module.css +26 -0
  423. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  424. package/src/shell/sidebar-search.tsx +71 -2
  425. package/src/state/fields.test.tsx +512 -0
  426. package/src/state/fields.ts +1150 -0
  427. package/src/state/persisted.test.ts +91 -0
  428. package/src/state/persisted.ts +261 -0
  429. package/src/state.ts +52 -240
  430. package/src/styles.css +13 -0
  431. package/src/surfaces.ts +204 -36
  432. package/src/theme/controls.module.css +16 -1
  433. package/src/theme/ctl-theme.test.tsx +275 -0
  434. package/src/theme/ctl-theme.tsx +138 -0
  435. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  436. package/src/theme/density-relations.test.ts +106 -51
  437. package/src/theme/divider-law.test.ts +259 -0
  438. package/src/theme/icon-slot.module.css +47 -0
  439. package/src/theme/icon-slot.module.css.d.ts +8 -0
  440. package/src/theme/icon-slot.test.tsx +275 -0
  441. package/src/theme/icon-slot.tsx +59 -0
  442. package/src/theme/index.ts +165 -3
  443. package/src/theme/layout-rhythm-css.test.ts +72 -0
  444. package/src/theme/radius.test.ts +11 -1
  445. package/src/theme/segmented-control.module.css +20 -0
  446. package/src/theme/shadow-surfaces.test.ts +20 -7
  447. package/src/theme/spacing.test.ts +75 -27
  448. package/src/theme/use-basalt-spacing.ts +2 -2
  449. package/src/tokens/density.test.ts +7 -25
  450. package/src/tokens/index.ts +48 -8
  451. package/src/tokens/palette.ts +107 -73
  452. package/src/widget-header/delta-badge.test.tsx +60 -0
  453. package/src/widget-header/delta-badge.tsx +57 -0
  454. package/src/widget-header/index.ts +2 -0
  455. package/src/widget-header/no-mantine.test.ts +33 -0
  456. package/src/widget-header/widget-header.module.css +350 -0
  457. package/src/widget-header/widget-header.module.css.d.ts +25 -0
  458. package/src/widget-header/widget-header.test.tsx +187 -0
  459. package/src/widget-header/widget-header.tsx +213 -0
  460. package/agent/rules/basalt-agent.md +0 -452
  461. package/agent/rules/basalt-app.md +0 -246
  462. package/agent/rules/basalt-commands.md +0 -320
  463. package/agent/rules/basalt-content.md +0 -409
  464. package/agent/rules/basalt-data.md +0 -329
  465. package/agent/rules/basalt-forms.md +0 -186
  466. package/agent/rules/basalt-notifications.md +0 -264
  467. package/agent/rules/basalt-query.md +0 -187
  468. package/agent/rules/basalt-router.md +0 -336
  469. package/dist/content/article-filter-bar.d.ts +0 -40
  470. package/dist/content/article-filter-bar.d.ts.map +0 -1
  471. package/dist/content/article-filter-bar.js +0 -56
  472. package/dist/shell/page-header.d.ts +0 -13
  473. package/dist/shell/page-header.d.ts.map +0 -1
  474. package/dist/shell/page-header.js +0 -23
  475. package/src/content/article-filter-bar.tsx +0 -88
  476. package/src/shell/page-header.tsx +0 -35
package/src/surfaces.ts CHANGED
@@ -9,26 +9,61 @@ import type { GuardKind } from './guard/types'
9
9
 
10
10
  // ── Scalar types ──────────────────────────────────────────────────────────────────────────────────
11
11
 
12
- /** The 13 on-disk rule names (agent/rules/basalt-{name}.md — the set-equality target).
12
+ /** The 6 on-disk rule names (agent/rules/basalt-{name}.md — the set-equality target).
13
+ *
14
+ * One rule file per DOCTRINE, not per subpath: `state` also owns the router bridge's placement law
15
+ * (the two were one argument split across two files), and `batteries` owns every adapter surface
16
+ * whose doctrine is "use the shipped thing" — query, forms, notifications, commands, data, content,
17
+ * agent and the app-global layer. Thirteen files carried 4,177 lines, 55% of it with no guard
18
+ * behind it and the identity paragraph restated six times (`docs/CONTROLS-SPEC.md` §7).
13
19
  *
14
20
  * @example
15
21
  * const r: RuleName = 'tokens' // ok
16
22
  * // const bad: RuleName = 'overlays' // tsc error — not in the union
17
23
  */
18
- export type RuleName =
19
- | 'tokens'
20
- | 'charts'
21
- | 'mantine'
22
- | 'router'
23
- | 'query'
24
- | 'state'
25
- | 'forms'
26
- | 'notifications'
27
- | 'commands'
28
- | 'data'
29
- | 'agent'
30
- | 'content'
31
- | 'app'
24
+ export type RuleName = 'tokens' | 'charts' | 'mantine' | 'state' | 'controls' | 'batteries'
25
+
26
+ /**
27
+ * Every rule the shipped oxlint JS plugin registers (`configs/oxlint-plugin.js` → `rules`), as one
28
+ * literal union AND one runtime list — the type and the list are the same declaration, so they
29
+ * cannot drift from each other. What they CAN drift from is the plugin itself, which this module
30
+ * must not import (it is a standalone `.js` loaded through `jsPlugins` out of a consumer's
31
+ * node_modules, and `surfaces.ts` is dependency-free by contract). `oxlint-plugin.test.ts` closes
32
+ * that gap: it asserts this list is EXACTLY the plugin's own rule keys, and `check-coverage`
33
+ * asserts every id here maps to exactly one surface.
34
+ *
35
+ * @example
36
+ * const id: PluginRuleId = 'hand-rolled-filter' // ok
37
+ * // const bad: PluginRuleId = 'hand-rolled-filters' // tsc error
38
+ */
39
+ export const PLUGIN_RULE_ID_LIST = [
40
+ 'no-raw-font-size',
41
+ 'raw-size-literal',
42
+ 'card-inset',
43
+ 'chart-in-raw-surface',
44
+ 'hand-rolled-plot',
45
+ 'chart-legend-literal',
46
+ 'shadow-basalt-export',
47
+ 'hand-rolled-shell',
48
+ 'raw-scroll-container',
49
+ 'hand-rolled-filter',
50
+ 'control-outside-home',
51
+ 'control-size-literal',
52
+ 'page-bar-budget',
53
+ 'in-body-page-title',
54
+ 'responsive-twin',
55
+ 'search-literal-link',
56
+ 'use-search-from-literal',
57
+ 'visx-boundary',
58
+ 'visx-tooltip',
59
+ 'token-layer-boundary',
60
+ 'agent-resume-guard',
61
+ 'agent-no-raw-usechat',
62
+ 'ai-sdk-major',
63
+ ] as const
64
+
65
+ /** One registered oxlint plugin rule id — the literal union of {@link PLUGIN_RULE_ID_LIST}. */
66
+ export type PluginRuleId = (typeof PLUGIN_RULE_ID_LIST)[number]
32
67
 
33
68
  /** The 3 shipped skill names (agent/skills/basalt-{name}/SKILL.md, placed into a consumer's
34
69
  * .claude/skills/ by `basalt-ui init`/`sync` — the same managed path the rules take).
@@ -112,15 +147,33 @@ type BaseSurface = {
112
147
  *
113
148
  * @example
114
149
  * const s: DoctrineSpec = { kind: 'doctrine', layer: 'headless', rule: 'tokens',
115
- * skill: ['basalt-design'], guardKinds: ['raw-hex'], forbiddenImports: [] }
150
+ * skill: ['basalt-design'], guardKinds: ['raw-hex'], pluginRules: ['no-raw-font-size'],
151
+ * forbiddenImports: [] }
116
152
  */
117
153
  export type DoctrineSpec = BaseSurface & {
118
154
  readonly kind: 'doctrine'
119
155
  readonly rule: RuleName
120
156
  /** A LIST (skill↔surface is many-to-one — basalt-design covers tokens AND mantine AND state). */
121
157
  readonly skill: readonly SkillName[]
122
- /** Required, but [] is legal for advisory surfaces (router/query: rule only, no guard). */
158
+ /** Required, but [] is legal for advisory surfaces (the batteries: rule only, no guard). */
123
159
  readonly guardKinds: readonly GuardKind[]
160
+ /**
161
+ * The oxlint plugin rules this surface's doctrine is enforced by — the AST half of the same seam
162
+ * `guardKinds` covers with text scans. Required (`[]` is legal, and says "no AST rule guards
163
+ * this"), because the ABSENCE of the field is what let the generated coverage headers claim a
164
+ * rule was "backed by" a guard kind while the plugin rule doing the real work went unnamed (D8).
165
+ *
166
+ * Every id in {@link PLUGIN_RULE_ID_LIST} maps to EXACTLY ONE surface — asserted by
167
+ * `check-coverage` and by `oxlint-plugin.test.ts`, so a new plugin rule cannot ship without a
168
+ * home, and one rule cannot be counted as coverage for two surfaces.
169
+ */
170
+ readonly pluginRules: readonly PluginRuleId[]
171
+ /**
172
+ * Laws this surface states but nothing enforces — printed under `not guarded:` in the generated
173
+ * `<!-- basalt:coverage -->` header. The honest half of the block: claiming coverage that does
174
+ * not exist is D8, and the only way a reader can tell is if the doc says so itself.
175
+ */
176
+ readonly advisoryLaws?: readonly string[]
124
177
  }
125
178
 
126
179
  /**
@@ -136,6 +189,8 @@ export type ToolingSpec = BaseSurface & {
136
189
  readonly rule?: never
137
190
  readonly skill?: never
138
191
  readonly guardKinds?: never
192
+ readonly pluginRules?: never
193
+ readonly advisoryLaws?: never
139
194
  }
140
195
 
141
196
  /** The discriminated union — the SURFACES registry value type. */
@@ -192,9 +247,29 @@ export const SURFACES = {
192
247
  'raw-form-control',
193
248
  'sub-16-input-font',
194
249
  'mantine-shade-index',
250
+ // The text lane of the kinds whose remedy IS a Mantine primitive or a Mantine-owned prop:
251
+ // C8's page title (same id as the plugin rule, so one annotation waives both lanes), the
252
+ // spacing props, and the layout primitives an inline `display`/raw element replaces.
253
+ 'in-body-page-title',
254
+ 'raw-spacing',
255
+ 'inline-spacing',
256
+ 'inline-display',
257
+ 'raw-html-layout',
258
+ 'hidden-inline-style',
259
+ ],
260
+ // The chrome half of the plugin: the shell, the card idiom, the page title and the scroll
261
+ // doctrine. `hand-rolled-shell` sits here rather than on a shell-shaped surface because
262
+ // `BasaltShell` IS the root barrel's promise (docs/CONTROLS-SPEC.md §6 wave 6).
263
+ pluginRules: [
264
+ 'hand-rolled-shell',
265
+ 'card-inset',
266
+ 'in-body-page-title',
267
+ 'raw-scroll-container',
268
+ 'page-bar-budget',
269
+ 'shadow-basalt-export',
195
270
  ],
196
271
  description:
197
- 'BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, QueryState/LoadingState/ErrorState, SettingsSection/SettingsRow/DangerZone)',
272
+ 'BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, PageBar, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, WidgetHeader, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, QueryState/LoadingState/ErrorState, SettingsSection/SettingsRow/DangerZone)',
198
273
  optionalPeers: [
199
274
  'react-markdown',
200
275
  'remark-gfm',
@@ -210,7 +285,20 @@ export const SURFACES = {
210
285
  layer: 'headless',
211
286
  rule: 'charts',
212
287
  skill: ['basalt-charts'],
213
- guardKinds: ['raw-hex', 'raw-color-fn', 'raw-visx-axis', 'unframed-chart'],
288
+ guardKinds: [
289
+ 'raw-hex',
290
+ 'raw-color-fn',
291
+ 'raw-visx-axis',
292
+ 'unframed-chart',
293
+ 'chart-missing-aria-label',
294
+ ],
295
+ pluginRules: [
296
+ 'hand-rolled-plot',
297
+ 'chart-legend-literal',
298
+ 'chart-in-raw-surface',
299
+ 'visx-boundary',
300
+ 'visx-tooltip',
301
+ ],
214
302
  description:
215
303
  'CartesianChart + visx chart primitives, kinds, sparklines, hooks, and token re-exports (Mantine-free)',
216
304
  globs: {
@@ -229,7 +317,28 @@ export const SURFACES = {
229
317
  layer: 'headless',
230
318
  rule: 'tokens',
231
319
  skill: ['basalt-design', 'basalt-charts'],
232
- guardKinds: ['raw-hex', 'raw-color-fn', 'off-identity-accent', 'off-system-surface-var'],
320
+ guardKinds: [
321
+ 'raw-hex',
322
+ 'raw-color-fn',
323
+ 'off-identity-accent',
324
+ 'off-system-surface-var',
325
+ // The rest of the value lane: every kind whose remedy is a `--vx-*` token or a theme knob,
326
+ // in TSX and in kebab CSS alike.
327
+ 'raw-font-family',
328
+ 'raw-radius',
329
+ 'raw-surface',
330
+ 'inline-font-size',
331
+ 'css-raw-surface',
332
+ 'surface-shadow-override',
333
+ // Not a token kind — the kind that judges the ANNOTATION rather than the code. It lands here
334
+ // because `agent/rules/basalt-tokens.md` is where the `theme-allow` grammar is documented
335
+ // (said once, on purpose), so this is the header that must name its enforcement.
336
+ 'theme-allow-unscoped',
337
+ ],
338
+ // The type-scale rules live with the tokens, not with `.`: both police a value that left the
339
+ // `--vx-*` system, which is this surface's whole subject. `token-layer-boundary` is the
340
+ // repo-local-only rule that keeps the layer upstream of Mantine (see below).
341
+ pluginRules: ['no-raw-font-size', 'raw-size-literal', 'token-layer-boundary'],
233
342
  description:
234
343
  'VX token refs, buildPaletteCss, defineSeries, seriesTokens, groupTokens, alpha (Mantine-free)',
235
344
  globs: {
@@ -269,9 +378,10 @@ export const SURFACES = {
269
378
  './query': {
270
379
  kind: 'doctrine',
271
380
  layer: 'headless',
272
- rule: 'query',
381
+ rule: 'batteries',
273
382
  skill: ['basalt-app'],
274
383
  guardKinds: [],
384
+ pluginRules: [],
275
385
  description: 'createBasaltQueryClient, transport-agnostic unwrap, lazy BasaltQueryDevtools',
276
386
  optionalPeers: ['@tanstack/react-query-devtools'],
277
387
  globs: {
@@ -284,11 +394,13 @@ export const SURFACES = {
284
394
  './router-tanstack': {
285
395
  kind: 'doctrine',
286
396
  layer: 'headless',
287
- rule: 'router',
397
+ rule: 'state',
288
398
  skill: ['basalt-app'],
289
399
  guardKinds: [],
400
+ // Law C10 — a nav link's `search` and a reader's `from` are both this bridge's contract.
401
+ pluginRules: ['search-literal-link', 'use-search-from-literal'],
290
402
  description:
291
- 'TanStack Router bridge: defineNav/navGroup/navTarget (one typed nav definition) + useNav (sections + mobileNav, spread onto BasaltShell) + useBasaltNav (active route) + useRouterBreadcrumbs + createSearchParamStore (single-select URL-state store) + createMultiSearchParamStore (multi-select URL-state store)',
403
+ 'TanStack Router bridge: defineNav/navGroup/navTarget (one typed nav definition) + useNav (sections + mobileNav, spread onto BasaltShell) + useBasaltNav (active route) + useRouterBreadcrumbs + createSearchStore (typed URL > localStorage > fallback store over field.enum/multi/range/number/boolean/string; deprecated createSearchParamStore/createMultiSearchParamStore wrappers until 1.29.0)',
292
404
  optionalPeers: ['@tanstack/react-router'],
293
405
  globs: {
294
406
  shipped: [],
@@ -300,9 +412,10 @@ export const SURFACES = {
300
412
  './forms': {
301
413
  kind: 'doctrine',
302
414
  layer: 'mantine-coupled',
303
- rule: 'forms',
415
+ rule: 'batteries',
304
416
  skill: ['basalt-design'],
305
417
  guardKinds: [],
418
+ pluginRules: [],
306
419
  description:
307
420
  'Mantine form adapter: useBasaltForm, field, FormErrorSummary, useFormDraft (Standard Schema)',
308
421
  optionalPeers: ['@mantine/form'],
@@ -311,9 +424,10 @@ export const SURFACES = {
311
424
  './notifications': {
312
425
  kind: 'doctrine',
313
426
  layer: 'mantine-coupled',
314
- rule: 'notifications',
427
+ rule: 'batteries',
315
428
  skill: ['basalt-app'],
316
429
  guardKinds: [],
430
+ pluginRules: [],
317
431
  description:
318
432
  'Mantine notifications: notify helpers, typed registry, persisted history, NotificationBell, NotificationCenter',
319
433
  optionalPeers: ['@mantine/notifications'],
@@ -322,9 +436,10 @@ export const SURFACES = {
322
436
  './commands': {
323
437
  kind: 'doctrine',
324
438
  layer: 'mantine-coupled',
325
- rule: 'commands',
439
+ rule: 'batteries',
326
440
  skill: ['basalt-app'],
327
441
  guardKinds: [],
442
+ pluginRules: [],
328
443
  description:
329
444
  'typed command bus + overlay controller, toSpotlightActions, ShortcutsHelp, BasaltOverlays',
330
445
  optionalPeers: ['@mantine/spotlight', '@mantine/modals', '@tanstack/react-hotkeys'],
@@ -333,9 +448,10 @@ export const SURFACES = {
333
448
  './data': {
334
449
  kind: 'doctrine',
335
450
  layer: 'mantine-coupled',
336
- rule: 'data',
451
+ rule: 'batteries',
337
452
  skill: ['basalt-design'],
338
453
  guardKinds: [],
454
+ pluginRules: [],
339
455
  description:
340
456
  'Convenience barrel pulling both TanStack Table + Virtual peer groups: BasaltDataTable, BasaltVirtualList (Mantine-rendered) — prefer ./data/table or ./data/virtual for per-feature opt-in',
341
457
  optionalPeers: ['@tanstack/react-table', '@tanstack/react-virtual'],
@@ -344,9 +460,10 @@ export const SURFACES = {
344
460
  './data/table': {
345
461
  kind: 'doctrine',
346
462
  layer: 'mantine-coupled',
347
- rule: 'data',
463
+ rule: 'batteries',
348
464
  skill: ['basalt-design'],
349
465
  guardKinds: [],
466
+ pluginRules: [],
350
467
  description:
351
468
  'BasaltDataTable: a sortable data table over TanStack Table, rendered with Mantine (Mantine-rendered)',
352
469
  optionalPeers: ['@tanstack/react-table'],
@@ -355,9 +472,10 @@ export const SURFACES = {
355
472
  './data/virtual': {
356
473
  kind: 'doctrine',
357
474
  layer: 'mantine-coupled',
358
- rule: 'data',
475
+ rule: 'batteries',
359
476
  skill: ['basalt-design'],
360
477
  guardKinds: [],
478
+ pluginRules: [],
361
479
  description:
362
480
  'BasaltVirtualList: a windowed virtual list over TanStack Virtual, rendered with Mantine (Mantine-rendered)',
363
481
  optionalPeers: ['@tanstack/react-virtual'],
@@ -366,9 +484,11 @@ export const SURFACES = {
366
484
  './agent': {
367
485
  kind: 'doctrine',
368
486
  layer: 'headless',
369
- rule: 'agent',
487
+ rule: 'batteries',
370
488
  skill: ['basalt-app'],
371
489
  guardKinds: [],
490
+ // The three agent-chat correctness rules — they honour `basalt-agent-allow`, not `theme-allow`.
491
+ pluginRules: ['agent-resume-guard', 'agent-no-raw-usechat', 'ai-sdk-major'],
372
492
  description:
373
493
  'Headless streaming-chat layer: useAgentStream, aiSdkTransport (recommended default) + edenTransport, isResumable/ResumableAgentTransport (stream-resumption seam), PartList, coalesceParts, the ForeignPart/definePartRenderers/narrowAgentPart open part-registry seam, plus the multi-thread createThreadsStore + useAgentThreadRuns + outcome-resolver seam (Mantine-free)',
374
494
  optionalPeers: ['ai', 'use-stick-to-bottom'],
@@ -382,9 +502,10 @@ export const SURFACES = {
382
502
  './agent-chat': {
383
503
  kind: 'doctrine',
384
504
  layer: 'mantine-coupled',
385
- rule: 'agent',
505
+ rule: 'batteries',
386
506
  skill: ['basalt-app'],
387
507
  guardKinds: [],
508
+ pluginRules: [],
388
509
  description:
389
510
  'Mantine-styled thread-chat components over basalt-ui/agent: ThreadWorkspace, ThreadFeed (variant/renderRow), ThreadFeedRow (inline-expanding Slack row, lazily mounted + kept mounted), ThreadOutcomeCard, ThreadDetailPanel, Composer, ThreadTranscript (open part-renderer registry via its renderers/fallbackRenderer props, per-message MessageAffordances, groupConsecutive, and an optional virtualize/height windowing mode whose VirtualizeOptions carry overscan/estimateSize/initialScroll — a virtualized transcript opens scrolled to the newest message unless initialScroll is "start"), threadPartRenderers, ToolChip (Mantine-coupled). motion is required, not optional — ThreadFeed/ThreadDetailPanel import motion/react eagerly, so this subpath fails to resolve without it installed even though peerDependenciesMeta marks it optional (npm has no per-subpath optionality). remend is genuinely optional here — ThreadTranscript reaches it only through the lazy dynamic import() inside content/markdown.tsx, and @tanstack/react-virtual the same way through the lazy import() behind ThreadTranscript virtualize (absent peer degrades to an unwindowed, height-bound pane).',
390
511
  optionalPeers: [
@@ -405,11 +526,12 @@ export const SURFACES = {
405
526
  './content': {
406
527
  kind: 'doctrine',
407
528
  layer: 'mantine-coupled',
408
- rule: 'content',
529
+ rule: 'batteries',
409
530
  skill: ['basalt-design'],
410
531
  guardKinds: [],
532
+ pluginRules: [],
411
533
  description:
412
- 'Prose (article/chat typography), CodeBlock (shiki, optional peer), Callout, TableOfContents, ReadingProgress, Markdown (react-markdown + remark-gfm, optional peers; `streaming` is a rendering mode ONLY — `contentTrust` is the independent security input, and any surface rendering agent/model output must pin `contentTrust="untrusted"`, the sole input to the image-origin allowlist; a `fenceRenderers` registry — settledOnly/FenceRenderer/FenceRenderers/FenceRenderContext; `sanitizeSchema`, an additions-only SanitizeSchemaExtension merged over BASALT_SANITIZE_SCHEMA via mergeSanitizeSchema; the remend streaming-repair pass is now a lazy optional peer), MermaidDiagram (beautiful-mermaid, optional peer), mdxComponents/createMdxComponents, ArticleLayout (docs-page frame), ArticleCard/ArticleGrid (overview cards), Article model (sortArticles/filterArticles/formatArticleDate), ArticleFilterBar (category/tags filter UI), toArticleActions (Spotlight projector, @mantine/spotlight type-only), GuideLink/GuideDrawer (contextual-help drawer) — the content/prose surface',
534
+ 'Prose (article/chat typography), CodeBlock (shiki, optional peer), Callout, TableOfContents, ReadingProgress, Markdown (react-markdown + remark-gfm, optional peers; `streaming` is a rendering mode ONLY — `contentTrust` is the independent security input, and any surface rendering agent/model output must pin `contentTrust="untrusted"`, the sole input to the image-origin allowlist; a `fenceRenderers` registry — settledOnly/FenceRenderer/FenceRenderers/FenceRenderContext; `sanitizeSchema`, an additions-only SanitizeSchemaExtension merged over BASALT_SANITIZE_SCHEMA via mergeSanitizeSchema; the remend streaming-repair pass is now a lazy optional peer), MermaidDiagram (beautiful-mermaid, optional peer), mdxComponents/createMdxComponents, ArticleLayout (docs-page frame), ArticleCard/ArticleGrid (overview cards), Article model (sortArticles/filterArticles/formatArticleDate), FilterSet/ViewTabs/MultiSelectFilter re-exported from ./controls (they replaced the controlled ArticleFilterBar), toArticleActions (Spotlight projector, @mantine/spotlight type-only), GuideLink/GuideDrawer (contextual-help drawer) — the content/prose surface',
413
535
  optionalPeers: [
414
536
  'shiki',
415
537
  '@shikijs/langs',
@@ -423,14 +545,58 @@ export const SURFACES = {
423
545
  ],
424
546
  forbiddenImports: [],
425
547
  },
548
+ './controls': {
549
+ kind: 'doctrine',
550
+ layer: 'mantine-coupled',
551
+ rule: 'controls',
552
+ skill: ['basalt-design'],
553
+ // The text lane of law C1 — the plugin's `control-outside-home` seen through a 12-line host-tag
554
+ // window, since a regex scan has no ancestry. Same law, same wave, same promotion.
555
+ guardKinds: ['raw-selection-control'],
556
+ // The control tier itself (laws C1/C3/C5/C9). `hand-rolled-filter`, `control-size-literal` and
557
+ // `responsive-twin` all ship `error`; `control-outside-home` is the one grace entry left in
558
+ // `PLUGIN_RULE_GRACE`, re-dated to 1.28.0 against the wave-7 measurement (see its `why`).
559
+ // The budget rules live on `.` with `PageBar` itself.
560
+ pluginRules: [
561
+ 'hand-rolled-filter',
562
+ 'control-outside-home',
563
+ 'control-size-literal',
564
+ 'responsive-twin',
565
+ ],
566
+ // docs/CONTROLS-SPEC.md §6 "Honest coverage" — verbatim, because a generated header claiming
567
+ // otherwise is D8 again.
568
+ advisoryLaws: [
569
+ 'C1 as a cross-file law (a control placed in one file, its home declared in another)',
570
+ 'hand-rolled section headings (argo writes `<Text fw={600} size="sm">` + children, which no AST heuristic matches without false positives)',
571
+ 'C11 — a table/list stating its count when it is not a BasaltDataTable',
572
+ 'C12 — one shape for refresh/sync (SyncButton); only the alias table sees a renamed copy',
573
+ ],
574
+ description:
575
+ 'The control tier (docs/CONTROLS-SPEC.md §3): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter/CompareFilter/SelectFilter/MultiSelectFilter/NumberFilter/SearchFilter/ToggleFilter (each bound to a FieldHandle — no value/onChange/size, law C2/C5; NumberFilter is the field.number lane, a radio list over `options` or a stepper without), ViewTabs, and the action/sync family (ActionGroup, OverflowMenu, SyncButton, BarAction/GlobalAction). Every control owns its own desktop/mobile swap in CSS (C9) and renders size="ctl" internally. Resolves and renders with NO @mantine/dates installed — the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.',
576
+ optionalPeers: [],
577
+ forbiddenImports: [],
578
+ },
579
+ './controls-dates': {
580
+ kind: 'doctrine',
581
+ layer: 'mantine-coupled',
582
+ rule: 'controls',
583
+ skill: ['basalt-design'],
584
+ guardKinds: [],
585
+ pluginRules: [],
586
+ description:
587
+ "DateRangePicker — the @mantine/dates implementation of RangeFilter's customPicker seam. Its own subpath because @mantine/dates is an optional peer and basaltViteConfig pre-bundles the whole @mantine scope, so a consumer without the peer (linewatch) must never resolve it: nothing under src/controls may import it, statically or lazily (docs/CONTROLS-SPEC.md §3).",
588
+ optionalPeers: ['@mantine/dates'],
589
+ forbiddenImports: [],
590
+ },
426
591
  './state': {
427
592
  kind: 'doctrine',
428
593
  layer: 'headless',
429
594
  rule: 'state',
430
595
  skill: ['basalt-design'],
431
596
  guardKinds: ['localstorage-theme'],
597
+ pluginRules: [],
432
598
  description:
433
- 'createPersistedState (versioned localStorage) + useOnlineStatus — Mantine-free state primitives',
599
+ 'createPersistedState (versioned localStorage) + the store field vocabulary (field.enum/multi/range/number/boolean/string, FieldHandle, lanes) + createLocalStore, the router-free store — Mantine-free state primitives',
434
600
  optionalPeers: [],
435
601
  // Consumers import basalt-ui/state from node_modules (lint-ignored), so there is nothing to
436
602
  // enforce consumer-side, and a shipped **/state* glob would wrongly hit consumers' own
@@ -449,6 +615,7 @@ export const SURFACES = {
449
615
  rule: 'mantine',
450
616
  skill: ['basalt-app'],
451
617
  guardKinds: [],
618
+ pluginRules: [],
452
619
  description:
453
620
  'ConnectivityProvider (aggregates browser online/offline, React Query onlineManager, SSE, and health-check pings into one status), useConnectivity, and ConnectivityIndicator — auto-mounted by BasaltProvider',
454
621
  optionalPeers: [],
@@ -490,9 +657,10 @@ export const SURFACES = {
490
657
  // synthetic global app-wide ban layer — the src/**+app/** glob
491
658
  kind: 'doctrine',
492
659
  layer: 'app-global',
493
- rule: 'app',
660
+ rule: 'batteries',
494
661
  skill: ['basalt-app'],
495
662
  guardKinds: [],
663
+ pluginRules: [],
496
664
  globs: {
497
665
  // Shipped is a catch-all (not just src/**+app/**) so consumer code under components/, lib/,
498
666
  // features/, etc. is also covered. The @visx/*-only-in-charts and Mantine-free charts/tokens
@@ -523,7 +691,7 @@ export const SURFACES = {
523
691
 
524
692
  /**
525
693
  * Derived, deduped set of doctrine rule names. Projection 1 of SURFACES.
526
- * → ['mantine', 'charts', 'tokens', 'query', 'router', 'forms', 'notifications', 'commands', 'data', 'agent', 'content', 'state', 'app'] (order is insertion order of Set)
694
+ * → ['mantine', 'charts', 'tokens', 'batteries', 'state', 'controls'] (order is insertion order of Set)
527
695
  *
528
696
  * @example
529
697
  * RULE_NAMES.includes('tokens') // true
@@ -204,7 +204,8 @@ html[data-mantine-color-scheme] .inputWrapper[data-variant='default']:not([data-
204
204
  /*
205
205
  * Reduced motion drops the fade, never the shadow: a static shadow is not motion, and removing it
206
206
  * would strip the very hierarchy this pass exists to establish from the users who asked for less
207
- * animation. `subtle`'s hover depth still lands — it just lands instantly.
207
+ * animation. `default`/`filled`/`light`/`outline`'s hover depth still lands — it just lands
208
+ * instantly. `subtle` stays flat regardless, as it does in every other state (see above).
208
209
  */
209
210
  @media (prefers-reduced-motion: reduce) {
210
211
  .buttonRoot,
@@ -282,3 +283,17 @@ html[data-mantine-color-scheme] .tabsRoot {
282
283
  html .tableRoot {
283
284
  --table-border-color: var(--vx-surface-hairline);
284
285
  }
286
+
287
+ /*
288
+ * The LAST row's separator is dropped — exactly the rule (and exactly the reasoning) the Accordion
289
+ * above already carries: a separator BETWEEN items is information, and the same line drawn after the
290
+ * final item is an outer border pretending to be one. It mattered more once `BasaltDataTable`'s
291
+ * `withTableBorder` went to `false` by default, because the two used to sit on top of each other and
292
+ * the doubling hid which of them was drawing the line.
293
+ *
294
+ * Scoped to `tbody`: a `tfoot` (a totals row) is separated FROM the body by the rule above it, which
295
+ * is a between rule and stays.
296
+ */
297
+ html .tableRoot tbody tr:last-of-type {
298
+ border-bottom: none;
299
+ }