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
@@ -0,0 +1,350 @@
1
+ /*
2
+ * WidgetHeader — the one heading primitive behind every section/card/table title
3
+ * (docs/CONTROLS-SPEC.md §2.2). Plain elements only — no `@mantine/*` anywhere in this folder —
4
+ * so `ChartCard` (inside the `charts/` boundary) can compose it without tripping
5
+ * `basalt/token-layer-boundary` / `check-dist-layering.mjs`.
6
+ *
7
+ * `--vx-space-section-header-height` / `--vx-space-widget-header-height` / `--vx-space-control-gap`
8
+ * are density-tracking anchors landing alongside this wave (docs/CONTROLS-SPEC.md §5) — referenced
9
+ * by name with rem fallbacks at their level-0 px value (36/28/6), same idiom as ChartCard's
10
+ * `--mantine-spacing-*` fallbacks.
11
+ *
12
+ * Layout: the title row holds title/icon/info/count/actions only. `value` + `delta` render on
13
+ * their own hero-metric row directly beneath it — for BOTH tiers, never inline with the title.
14
+ */
15
+
16
+ .root {
17
+ display: flex;
18
+ flex-direction: column;
19
+ }
20
+
21
+ .titleRow {
22
+ display: flex;
23
+ align-items: center;
24
+ gap: var(--vx-space-control-gap, 0.375rem);
25
+ }
26
+
27
+ /*
28
+ * Below `sm` the title row WRAPS, which is the safety net for an `actions` slot too wide to sit
29
+ * beside the title. `flex-wrap` is the right tool precisely because it is conditional: a 30px kebab
30
+ * fits and stays on the row, while a control whose hypothetical size does not fit takes its own line
31
+ * instead of squeezing the heading to an ellipsis. No breakpoint arithmetic, no max-width fraction,
32
+ * and no prop — the row measures the actual content.
33
+ *
34
+ * `row-gap` is the 8px rhythm step, so a wrapped slot reads as the line under the title rather than
35
+ * as a second header. `.actions` keeps its `margin-inline-start: auto`, which on a wrapped line makes
36
+ * it right-aligned — the same edge it holds when it is on the row.
37
+ */
38
+ @media (max-width: 47.99375em) {
39
+ .titleRow {
40
+ flex-wrap: wrap;
41
+ row-gap: var(--vx-space-stack-sm);
42
+ }
43
+ }
44
+
45
+ .root[data-tier='section'] .titleRow {
46
+ min-height: var(--vx-space-section-header-height, 2.25rem);
47
+ }
48
+
49
+ .root[data-tier='widget'] .titleRow {
50
+ min-height: var(--vx-space-widget-header-height, 1.75rem);
51
+ }
52
+
53
+ /*
54
+ * Section → h2, widget → h3, and the two tiers now read DIFFERENTLY on purpose — they did not, and
55
+ * that is what flattened the page.
56
+ *
57
+ * A `section` heading names a region of the page: 15px, weight 600, INK. A `widget` heading labels a
58
+ * number that is itself the thing being read (a KPI card's hero value, a chart card's total), so it
59
+ * is the quieter of the pair — one step down and MUTED, which is what lets the 24px mono value under
60
+ * it carry the card. Both keep the shared head-font 88% stretch.
61
+ *
62
+ * Rendered identically (15px ink both) the hero value competed with its own label, four cards in a
63
+ * row read as four headings, and the section that grouped them read as one more of the same.
64
+ */
65
+ .heading {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ gap: 5px;
69
+ margin: 0;
70
+ min-width: 0;
71
+ font-family: var(--basalt-font-head);
72
+ font-stretch: 88%;
73
+ font-weight: 550;
74
+ font-size: var(--vx-text-md);
75
+ color: var(--vx-ink);
76
+ }
77
+
78
+ .root[data-tier='section'] .heading {
79
+ font-weight: 600;
80
+ }
81
+
82
+ .root[data-tier='widget'] .heading {
83
+ font-size: var(--vx-text-sm);
84
+ color: var(--vx-muted);
85
+ }
86
+
87
+ .titleText {
88
+ overflow: hidden;
89
+ text-overflow: ellipsis;
90
+ white-space: nowrap;
91
+ }
92
+
93
+ /*
94
+ * The icon slot PINS its glyph's box. A consumer's icon arrives at whatever size its own component
95
+ * chose (the playground's is 18px), and an 18px mark beside a 13.5px title is a bullet, not a label
96
+ * icon — so the tier states the size here rather than asking every call site to pass one.
97
+ */
98
+ /*
99
+ * The heading's icon box carries COLOUR ONLY — geometry belongs to `theme/icon-slot.module.css`,
100
+ * which this class is composed onto (`IconSlot className={classes.icon}`). The per-tier SIZE is set
101
+ * as the slot's own box var below rather than as a `> svg` rule, so it survives whatever the caller's
102
+ * glyph declares for itself: a `width`/`height` attribute is a presentation attribute and loses to
103
+ * the slot's CSS, while a `> svg` rule scoped to one component only ever fixed that component.
104
+ */
105
+ .icon {
106
+ color: var(--vx-faint);
107
+ }
108
+
109
+ .root[data-tier='widget'] .icon {
110
+ --vx-space-icon-size: 0.875rem;
111
+ }
112
+
113
+ .root[data-tier='section'] .icon {
114
+ --vx-space-icon-size: 1rem;
115
+ }
116
+
117
+ /* Mono count tag after the title (C11 — every table/list states its count in its header). Its box is
118
+ * the `tag` tier (20px), so it never grows the header row it sits in. */
119
+ .count {
120
+ display: inline-flex;
121
+ flex-shrink: 0;
122
+ align-items: center;
123
+ /* Exactly the `tag` tier (20px), not a floor: the mono `VX.text.xs` line box plus
124
+ * `badgeInset`-y is 23px on its own, so a `min-height` changed nothing and the tag rendered 3px
125
+ * taller than the tier it is named for — enough to grow a 36px section-header row. `line-height: 1`
126
+ * is what lets the box be the stated height instead of the text's. */
127
+ /* `--mantine-scale`-aware, same reconstruction every other ctl/tag-tier box uses (`theme/index.ts`'s
128
+ * `ctlSizeVars`) — read raw, this box stayed fixed while the Badge/Button beside it in the same
129
+ * header scaled with `theme.scale`. */
130
+ height: calc(var(--vx-space-control-height-tag, 1.25rem) * var(--mantine-scale, 1));
131
+ line-height: 1;
132
+ border-radius: var(--vx-radius-ctrl);
133
+ background: var(--vx-surface-subtle);
134
+ padding: var(--vx-space-badge-inset-y) var(--vx-space-badge-inset-x);
135
+ font-family: var(--basalt-font-mono);
136
+ font-size: var(--vx-text-xs);
137
+ font-weight: 500;
138
+ color: var(--vx-faint);
139
+ }
140
+
141
+ /*
142
+ * The hero-metric row — value + delta, directly under the title row, for both tiers. The delta rides
143
+ * 4px off the value, not 8: it is a suffix ON the number (`$168,920 +47%`), and at 8px it read as a
144
+ * second, unrelated chip in the row. `baseline` alignment is what keeps the mono numeral and the
145
+ * badge's own smaller mono text sitting on one line.
146
+ */
147
+ /*
148
+ * The hero-metric row: the value, then its `DeltaBadge`.
149
+ *
150
+ * The gap is the 8px vertical-rhythm step (`--vx-space-stack-sm`), not the 4px it shipped as. 4px is
151
+ * the rhythm's SMALLEST step — the distance between a label and the thing it labels — and at that
152
+ * distance the badge read as a superscript hanging off the numeral rather than as a second fact
153
+ * beside it. It is a token and not a literal so it tracks the density knob with every other spacing
154
+ * in the header (`deriveSpacing`, `tokens/palette.ts`); `theme/layout-rhythm-css.test.ts`'s sibling
155
+ * assertion in `widget-header.test.tsx` pins it against exactly the literal it used to be.
156
+ *
157
+ * `align-items: center`, not `baseline`. The badge is a BOX with its own padding and a mono 11.5px
158
+ * label, so aligning its text baseline to a 24px KPI numeral's baseline put the box itself low —
159
+ * its top edge sat below the numeral's x-height and its bottom edge hung under the numeral's
160
+ * baseline. A chip beside a numeral is centred on the numeral, the same law the `count` tag in the
161
+ * title row above already follows.
162
+ */
163
+ .metrics {
164
+ display: flex;
165
+ flex-wrap: nowrap;
166
+ align-items: center;
167
+ gap: var(--vx-space-stack-sm);
168
+ margin-top: 2px;
169
+ min-width: 0;
170
+ }
171
+
172
+ /*
173
+ * Below `sm` the metric row WRAPS, so the badge drops under the value instead of ellipsizing it.
174
+ * `nowrap` plus the `.value` clamp below is right on desktop — a `+18.9%` cannot ellipsize and read,
175
+ * a numeral can — but a KPI's numeral IS the content, and in a 2-up card grid at 390px
176
+ * `$1294.9k ▲18.9%` rendered as `$129… ▲18.9%`. Truncating the number to protect the delta about the
177
+ * number is the wrong way round. Conditional like the title row above it: a short value keeps the
178
+ * badge beside it, and only a value that would otherwise clip gives up the line.
179
+ */
180
+ @media (max-width: 47.99375em) {
181
+ .metrics {
182
+ flex-wrap: wrap;
183
+ row-gap: 2px;
184
+ }
185
+ }
186
+
187
+ /* The value is the row's shrinkable half and the delta badge is not: a pre-formatted numeral can
188
+ * ellipsize and still read, a `+18.9%` cannot. Without the clamp the badge grew past the header box
189
+ * and painted over `StatCard`'s 72px sparkline slot beside it. */
190
+ .value {
191
+ min-width: 0;
192
+ overflow: hidden;
193
+ text-overflow: ellipsis;
194
+ white-space: nowrap;
195
+ font-family: var(--basalt-font-mono);
196
+ font-size: var(--vx-text-kpi);
197
+ font-weight: 600;
198
+ letter-spacing: -0.02em;
199
+ color: var(--vx-ink);
200
+ }
201
+
202
+ /*
203
+ * The unit beside the hero value. Mono like the number it qualifies (so `412 TSS` sits on one
204
+ * baseline grid), `--vx-text-sm` and `--vx-muted` so it never competes with the numeral — the whole
205
+ * point is that a unit is the SMALL half of the pair. `flex-shrink: 0`: the value is the row's
206
+ * shrinkable half (see `.value`), and a unit that ellipsized to `TS…` would state nothing.
207
+ */
208
+ .unit {
209
+ flex-shrink: 0;
210
+ align-self: baseline;
211
+ font-family: var(--basalt-font-mono);
212
+ font-size: var(--vx-text-sm);
213
+ font-weight: 500;
214
+ color: var(--vx-muted);
215
+ }
216
+
217
+ /* The C1/C5 home for header actions — always pinned right, regardless of what else is present. */
218
+ .actions {
219
+ display: inline-flex;
220
+ flex-shrink: 0;
221
+ align-items: center;
222
+ gap: var(--vx-space-control-gap, 0.375rem);
223
+ margin-inline-start: auto;
224
+ }
225
+
226
+ /* Directly under the title row, 4px off it — 1px put it inside the title's own line box, so a
227
+ * section with both read as a two-line heading rather than a heading plus a note. */
228
+ .subtitle {
229
+ margin-top: 4px;
230
+ font-size: var(--vx-text-sm);
231
+ font-weight: 400;
232
+ color: var(--vx-muted);
233
+ }
234
+
235
+ /* `data-placement` is read by wave-3 composers wanting a `bleed` layout (docs/CONTROLS-SPEC.md
236
+ * §2.2's `sparklinePlacement` composer mapping) — this primitive only ever sets `right`. */
237
+ .sparkline {
238
+ margin-top: 4px;
239
+ }
240
+
241
+ /* ── info glyph ─────────────────────────────────────────────────────────────────────────────── */
242
+
243
+ .info {
244
+ position: relative;
245
+ display: inline-flex;
246
+ flex-shrink: 0;
247
+ align-items: center;
248
+ }
249
+
250
+ .infoTrigger {
251
+ display: inline-flex;
252
+ align-items: center;
253
+ appearance: none;
254
+ margin: 0;
255
+ padding: 0;
256
+ border: none;
257
+ background: none;
258
+ color: var(--vx-faint);
259
+ cursor: help;
260
+ }
261
+
262
+ .infoTrigger:hover,
263
+ .infoTrigger:focus-visible,
264
+ .infoTrigger[data-open='true'] {
265
+ color: var(--vx-muted);
266
+ }
267
+
268
+ /*
269
+ * The tooltip bubble reads like every other floating surface — panel bg + `shadow-card` + card
270
+ * radius (docs/DESIGN-SPEC.md §5). It opens DOWNWARD by convention, into whatever sits below the
271
+ * header. `pointer-events: none` so it can never swallow the pointer on its way back to the
272
+ * trigger's `mouseleave`. Mantine-free by design (this whole folder is), so it is a plain
273
+ * state-driven bubble rather than a Mantine Tooltip — which is also what makes it composable from
274
+ * inside the `charts/` boundary.
275
+ */
276
+ .infoBubble {
277
+ position: absolute;
278
+ top: calc(100% + 7px);
279
+ left: 0;
280
+ z-index: 10;
281
+ width: max-content;
282
+ max-width: 260px;
283
+ padding: 7px 10px;
284
+ border-radius: var(--vx-radius-card);
285
+ background-color: var(--vx-surface-panel);
286
+ box-shadow: var(--vx-shadow-card);
287
+ color: var(--vx-muted);
288
+ font-size: var(--vx-text-sm);
289
+ font-weight: 400;
290
+ line-height: 1.4;
291
+ white-space: normal;
292
+ pointer-events: none;
293
+ }
294
+
295
+ /* ── DeltaBadge ─────────────────────────────────────────────────────────────────────────────── */
296
+
297
+ .deltaBadge {
298
+ display: inline-flex;
299
+ flex: none;
300
+ white-space: nowrap;
301
+ align-items: center;
302
+ gap: 3px;
303
+ border-radius: var(--vx-radius-ctrl);
304
+ padding: var(--vx-space-badge-inset-y) var(--vx-space-badge-inset-x);
305
+ font-family: var(--basalt-font-mono);
306
+ font-size: var(--vx-text-xs);
307
+ font-weight: 600;
308
+ line-height: 1;
309
+ }
310
+
311
+ .deltaGlyph {
312
+ font-size: 0.8em;
313
+ }
314
+
315
+ .deltaPeriod {
316
+ margin-inline-start: 4px;
317
+ font-weight: 500;
318
+ opacity: 0.6;
319
+ }
320
+
321
+ /*
322
+ * Law C15 — the `actions` slot is the tier-2/tier-3 home, and its controls are 30px (`ctl`) or 24px
323
+ * (`icon`). Below `sm` an `::after` overlay lifts the HIT area to
324
+ * `--vx-space-touch-control-height` (36px at level 0) without moving the visible box, the same
325
+ * overlay `shell/page-bar.module.css` draws for the bar. `overflow: visible` is load-bearing:
326
+ * Mantine's Button/ActionIcon roots are `overflow: hidden`, which clips an overlay that by
327
+ * definition extends past the box — the clipping only exists for their loading overlay, painted
328
+ * under `[data-loading]`, which is excluded here.
329
+ */
330
+ @media (max-width: 47.99375em) {
331
+ .actions :is(button, a) {
332
+ position: relative;
333
+ }
334
+
335
+ .actions :is(button, a):not([data-loading]) {
336
+ overflow: visible;
337
+ }
338
+
339
+ .actions :is(button, a)::after {
340
+ content: '';
341
+ position: absolute;
342
+ top: 50%;
343
+ left: 50%;
344
+ translate: -50% -50%;
345
+ width: 100%;
346
+ height: 100%;
347
+ min-width: var(--vx-space-touch-control-height, 2.25rem);
348
+ min-height: var(--vx-space-touch-control-height, 2.25rem);
349
+ }
350
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Typed CSS-module declaration (the S4 CSS-modules path). Named string props — not an index
3
+ * signature — so dot access stays verbatim under the package's strict flags.
4
+ */
5
+ declare const classes: {
6
+ readonly root: string
7
+ readonly titleRow: string
8
+ readonly heading: string
9
+ readonly titleText: string
10
+ readonly icon: string
11
+ readonly count: string
12
+ readonly metrics: string
13
+ readonly value: string
14
+ readonly unit: string
15
+ readonly actions: string
16
+ readonly subtitle: string
17
+ readonly sparkline: string
18
+ readonly info: string
19
+ readonly infoTrigger: string
20
+ readonly infoBubble: string
21
+ readonly deltaBadge: string
22
+ readonly deltaGlyph: string
23
+ readonly deltaPeriod: string
24
+ }
25
+ export default classes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=widget-header.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-header.test.d.ts","sourceRoot":"","sources":["../../src/widget-header/widget-header.test.tsx"],"names":[],"mappings":""}
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # basalt-ui v1.25.0
1
+ # basalt-ui v1.27.0
2
2
  # Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
3
3
  #
4
4
  # basalt-ui is an opinionated framework for Mantine-based React apps.
@@ -19,11 +19,11 @@
19
19
  # ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
20
20
 
21
21
  Package: basalt-ui
22
- Version: 1.25.0
22
+ Version: 1.27.0
23
23
 
24
24
  ## basalt-ui
25
25
  Import: import { ... } from 'basalt-ui'
26
- Description: 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)
26
+ Description: 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)
27
27
  Layer: mantine-coupled
28
28
  Rule: basalt-mantine
29
29
  OptionalPeers: react-markdown@^10.1.0, remark-gfm@^4.0.1, rehype-sanitize@^6.0.0, remend@1.3.0, shiki@^4.3.0, beautiful-mermaid@^1.1.0
@@ -60,82 +60,95 @@ Layer: headless
60
60
  Import: import { ... } from 'basalt-ui/query'
61
61
  Description: createBasaltQueryClient, transport-agnostic unwrap, lazy BasaltQueryDevtools
62
62
  Layer: headless
63
- Rule: basalt-query
63
+ Rule: basalt-batteries
64
64
  OptionalPeers: @tanstack/react-query-devtools@^5.101.0
65
65
 
66
66
  ## basalt-ui/router-tanstack
67
67
  Import: import { ... } from 'basalt-ui/router-tanstack'
68
- Description: 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)
68
+ Description: 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)
69
69
  Layer: headless
70
- Rule: basalt-router
70
+ Rule: basalt-state
71
71
  OptionalPeers: @tanstack/react-router@^1.170.0
72
72
 
73
73
  ## basalt-ui/forms
74
74
  Import: import { ... } from 'basalt-ui/forms'
75
75
  Description: Mantine form adapter: useBasaltForm, field, FormErrorSummary, useFormDraft (Standard Schema)
76
76
  Layer: mantine-coupled
77
- Rule: basalt-forms
77
+ Rule: basalt-batteries
78
78
  OptionalPeers: @mantine/form@^9.3.0
79
79
 
80
80
  ## basalt-ui/notifications
81
81
  Import: import { ... } from 'basalt-ui/notifications'
82
82
  Description: Mantine notifications: notify helpers, typed registry, persisted history, NotificationBell, NotificationCenter
83
83
  Layer: mantine-coupled
84
- Rule: basalt-notifications
84
+ Rule: basalt-batteries
85
85
  OptionalPeers: @mantine/notifications@^9.3.0
86
86
 
87
87
  ## basalt-ui/commands
88
88
  Import: import { ... } from 'basalt-ui/commands'
89
89
  Description: typed command bus + overlay controller, toSpotlightActions, ShortcutsHelp, BasaltOverlays
90
90
  Layer: mantine-coupled
91
- Rule: basalt-commands
91
+ Rule: basalt-batteries
92
92
  OptionalPeers: @mantine/spotlight@^9.3.0, @mantine/modals@^9.3.0, @tanstack/react-hotkeys@0.10.0
93
93
 
94
94
  ## basalt-ui/data
95
95
  Import: import { ... } from 'basalt-ui/data'
96
96
  Description: Convenience barrel pulling both TanStack Table + Virtual peer groups: BasaltDataTable, BasaltVirtualList (Mantine-rendered) — prefer ./data/table or ./data/virtual for per-feature opt-in
97
97
  Layer: mantine-coupled
98
- Rule: basalt-data
98
+ Rule: basalt-batteries
99
99
  OptionalPeers: @tanstack/react-table@>=8 <9, @tanstack/react-virtual@>=3.13.26 <4
100
100
 
101
101
  ## basalt-ui/data/table
102
102
  Import: import { ... } from 'basalt-ui/data/table'
103
103
  Description: BasaltDataTable: a sortable data table over TanStack Table, rendered with Mantine (Mantine-rendered)
104
104
  Layer: mantine-coupled
105
- Rule: basalt-data
105
+ Rule: basalt-batteries
106
106
  OptionalPeers: @tanstack/react-table@>=8 <9
107
107
 
108
108
  ## basalt-ui/data/virtual
109
109
  Import: import { ... } from 'basalt-ui/data/virtual'
110
110
  Description: BasaltVirtualList: a windowed virtual list over TanStack Virtual, rendered with Mantine (Mantine-rendered)
111
111
  Layer: mantine-coupled
112
- Rule: basalt-data
112
+ Rule: basalt-batteries
113
113
  OptionalPeers: @tanstack/react-virtual@>=3.13.26 <4
114
114
 
115
115
  ## basalt-ui/agent
116
116
  Import: import { ... } from 'basalt-ui/agent'
117
117
  Description: 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)
118
118
  Layer: headless
119
- Rule: basalt-agent
119
+ Rule: basalt-batteries
120
120
  OptionalPeers: ai@^7.0.15, use-stick-to-bottom@^1.1.6
121
121
 
122
122
  ## basalt-ui/agent-chat
123
123
  Import: import { ... } from 'basalt-ui/agent-chat'
124
124
  Description: 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).
125
125
  Layer: mantine-coupled
126
- Rule: basalt-agent
126
+ Rule: basalt-batteries
127
127
  OptionalPeers: ai@^7.0.15, motion@12.42.0, remend@1.3.0, use-stick-to-bottom@^1.1.6, react-markdown@^10.1.0, remark-gfm@^4.0.1, shiki@^4.3.0, @shikijs/langs@^4.3.1, @shikijs/themes@^4.3.1, beautiful-mermaid@^1.1.0, @tanstack/react-virtual@>=3.13.26 <4
128
128
 
129
129
  ## basalt-ui/content
130
130
  Import: import { ... } from 'basalt-ui/content'
131
- Description: 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
131
+ Description: 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
132
132
  Layer: mantine-coupled
133
- Rule: basalt-content
133
+ Rule: basalt-batteries
134
134
  OptionalPeers: shiki@^4.3.0, @shikijs/langs@^4.3.1, @shikijs/themes@^4.3.1, beautiful-mermaid@^1.1.0, react-markdown@^10.1.0, remark-gfm@^4.0.1, rehype-sanitize@^6.0.0, remend@1.3.0, @mantine/spotlight@^9.3.0
135
135
 
136
+ ## basalt-ui/controls
137
+ Import: import { ... } from 'basalt-ui/controls'
138
+ Description: 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.
139
+ Layer: mantine-coupled
140
+ Rule: basalt-controls
141
+
142
+ ## basalt-ui/controls-dates
143
+ Import: import { ... } from 'basalt-ui/controls-dates'
144
+ Description: 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).
145
+ Layer: mantine-coupled
146
+ Rule: basalt-controls
147
+ OptionalPeers: @mantine/dates@^9.3.0
148
+
136
149
  ## basalt-ui/state
137
150
  Import: import { ... } from 'basalt-ui/state'
138
- Description: createPersistedState (versioned localStorage) + useOnlineStatus — Mantine-free state primitives
151
+ Description: 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
139
152
  Layer: headless
140
153
  Rule: basalt-state
141
154
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basalt-ui",
3
- "version": "1.25.0",
3
+ "version": "1.27.0",
4
4
  "description": "Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets",
5
5
  "keywords": [
6
6
  "charts",
@@ -113,6 +113,14 @@
113
113
  "types": "./dist/content/index.d.ts",
114
114
  "import": "./dist/content/index.js"
115
115
  },
116
+ "./controls": {
117
+ "types": "./dist/controls/index.d.ts",
118
+ "import": "./dist/controls/index.js"
119
+ },
120
+ "./controls-dates": {
121
+ "types": "./dist/controls-dates/index.d.ts",
122
+ "import": "./dist/controls-dates/index.js"
123
+ },
116
124
  "./state": {
117
125
  "types": "./dist/state.d.ts",
118
126
  "import": "./dist/state.js"
@@ -143,6 +151,7 @@
143
151
  "@fontsource-variable/hubot-sans": "5.2.8",
144
152
  "@fontsource-variable/jetbrains-mono": "5.2.8",
145
153
  "@fontsource-variable/nunito-sans": "5.2.7",
154
+ "@mantine/dates": "9.3.0",
146
155
  "@shikijs/langs": "^4.3.1",
147
156
  "@shikijs/themes": "^4.3.1",
148
157
  "@tanstack/react-hotkeys": "0.10.0",
@@ -179,6 +188,7 @@
179
188
  "@fontsource-variable/jetbrains-mono": "5.2.8",
180
189
  "@fontsource-variable/nunito-sans": "5.2.7",
181
190
  "@mantine/core": "^9.3.0",
191
+ "@mantine/dates": "^9.3.0",
182
192
  "@mantine/form": "^9.3.0",
183
193
  "@mantine/hooks": "^9.3.0",
184
194
  "@mantine/modals": "^9.3.0",
@@ -228,6 +238,9 @@
228
238
  "@mantine/core": {
229
239
  "optional": true
230
240
  },
241
+ "@mantine/dates": {
242
+ "optional": true
243
+ },
231
244
  "@mantine/form": {
232
245
  "optional": true
233
246
  },
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { buildDomainIndex, parseKey, resolveCursorPoint } from './resolve'
2
+ import { buildDomainIndex, classifyDomain, parseKey, resolveCursorPoint } from './resolve'
3
3
 
4
4
  type Point = { date: string }
5
5
 
@@ -121,3 +121,31 @@ describe("resolveCursorPoint — 'leading' resolution (bucket-keyed domains)", (
121
121
  expect(resolveCursorPoint(i, '2026-08-05', 'leading')?.date).toBe('2026-08-01')
122
122
  })
123
123
  })
124
+
125
+ describe('classifyDomain', () => {
126
+ test('an ISO date domain classifies as time', () => {
127
+ expect(classifyDomain(['2026-08-01', '2026-08-02', '2026-08-03'])).toBe('time')
128
+ })
129
+
130
+ test("a bare-year domain classifies as linear, matching parseKey's numeric-first precedence", () => {
131
+ // "2026" passes the numeric test before it ever reaches the date fallback — same ordering
132
+ // `parseKey` applies to a single key, applied here across the whole domain.
133
+ expect(classifyDomain(['2024', '2025', '2026'])).toBe('linear')
134
+ })
135
+
136
+ test('a numeric bucket domain classifies as linear', () => {
137
+ expect(classifyDomain(['0', '1', '2', '3'])).toBe('linear')
138
+ })
139
+
140
+ test('a category domain classifies as band', () => {
141
+ expect(classifyDomain(['Visited', 'Added to cart', 'Purchased'])).toBe('band')
142
+ })
143
+
144
+ test('a mixed domain falls back to band', () => {
145
+ expect(classifyDomain(['2026-08-01', 'Direct'])).toBe('band')
146
+ })
147
+
148
+ test('an empty domain classifies as band', () => {
149
+ expect(classifyDomain([])).toBe('band')
150
+ })
151
+ })
@@ -8,18 +8,50 @@
8
8
  * PARSED domain instead makes that automatic (`docs/CHARTS-SPEC.md` §3).
9
9
  */
10
10
 
11
+ /** Numeric-string test — shared by `parseKey` and `classifyDomain` so a key can never be numeric
12
+ * to one and not the other. */
13
+ function isNumericString(key: string): boolean {
14
+ return /^[-+]?\d*\.?\d+$/.test(key) && Number.isFinite(Number(key))
15
+ }
16
+
17
+ /** `Date.parse`-able test — shared by `parseKey` and `classifyDomain`, same reason. */
18
+ function isDateParseableString(key: string): boolean {
19
+ return !Number.isNaN(Date.parse(key))
20
+ }
21
+
11
22
  /**
12
23
  * A key as a comparable number: numeric-looking strings as numbers, everything else as a date.
13
24
  * Neither (a plain category like "Direct") → null, which degrades to exact-match only — correct,
14
25
  * since "nearest category" is meaningless.
15
26
  */
16
27
  export function parseKey(key: string): number | null {
17
- if (/^[-+]?\d*\.?\d+$/.test(key)) {
18
- const n = Number(key)
19
- return Number.isFinite(n) ? n : null
20
- }
21
- const t = Date.parse(key)
22
- return Number.isNaN(t) ? null : t
28
+ if (isNumericString(key)) return Number(key)
29
+ return isDateParseableString(key) ? Date.parse(key) : null
30
+ }
31
+
32
+ /** The x-domain families the shared cursor partitions on, derived from a chart's own keys — never
33
+ * a prop, never `ChartCursorScope`. A chart's kind is stamped on every broadcast it makes, and a
34
+ * receiving chart ignores any broadcast whose kind differs from its own — so a category chart can
35
+ * never be mistaken for a time chart just because both domains happen to parse. */
36
+ export type DomainKind = 'time' | 'linear' | 'band'
37
+
38
+ /**
39
+ * Classify a domain by the SAME parse precedence `parseKey` applies to each key, so a domain can
40
+ * never be classified one way and resolved another:
41
+ *
42
+ * - empty domain → `'band'` (nothing to share).
43
+ * - every key numeric (same test `parseKey` tries first) → `'linear'`.
44
+ * - otherwise every key `Date.parse`-able (same test `parseKey` falls back to) → `'time'`.
45
+ * - otherwise → `'band'` (categories, or a mixed domain).
46
+ *
47
+ * Ordering matters and mirrors `parseKey` exactly: a domain of bare years like `"2026"` is numeric
48
+ * FIRST, so it classifies `'linear'`, not `'time'`, even though every value is also a valid date.
49
+ */
50
+ export function classifyDomain(keys: readonly string[]): DomainKind {
51
+ if (keys.length === 0) return 'band'
52
+ if (keys.every(isNumericString)) return 'linear'
53
+ if (keys.every(isDateParseableString)) return 'time'
54
+ return 'band'
23
55
  }
24
56
 
25
57
  /**