basalt-ui 1.24.0 → 1.26.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 (467) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +25 -0
  3. package/MIGRATING.md +547 -21
  4. package/README.md +46 -33
  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 +155 -0
  8. package/agent/rules/basalt-mantine.md +146 -439
  9. package/agent/rules/basalt-state.md +113 -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 +1275 -31
  17. package/configs/oxlint-plugin.test.ts +947 -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 +30 -0
  73. package/dist/controls/enum-filter.d.ts.map +1 -0
  74. package/dist/controls/enum-filter.js +91 -0
  75. package/dist/controls/filter-context.d.ts +55 -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 +19 -0
  93. package/dist/controls/index.d.ts.map +1 -0
  94. package/dist/controls/index.js +44 -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/peer-boundary.test.d.ts +2 -0
  99. package/dist/controls/peer-boundary.test.d.ts.map +1 -0
  100. package/dist/controls/props-contract.test.d.ts +2 -0
  101. package/dist/controls/props-contract.test.d.ts.map +1 -0
  102. package/dist/controls/range-filter.d.ts +29 -0
  103. package/dist/controls/range-filter.d.ts.map +1 -0
  104. package/dist/controls/range-filter.js +120 -0
  105. package/dist/controls/search-filter.d.ts +10 -0
  106. package/dist/controls/search-filter.d.ts.map +1 -0
  107. package/dist/controls/search-filter.js +81 -0
  108. package/dist/controls/select-filter.d.ts +74 -0
  109. package/dist/controls/select-filter.d.ts.map +1 -0
  110. package/dist/controls/select-filter.js +10 -0
  111. package/dist/controls/sync-button.d.ts +38 -0
  112. package/dist/controls/sync-button.d.ts.map +1 -0
  113. package/dist/controls/sync-button.js +119 -0
  114. package/dist/controls/sync-button.module.css +63 -0
  115. package/dist/controls/sync-button.module.css.d.ts +11 -0
  116. package/dist/controls/sync-button.test.d.ts +2 -0
  117. package/dist/controls/sync-button.test.d.ts.map +1 -0
  118. package/dist/controls/toggle-filter.d.ts +9 -0
  119. package/dist/controls/toggle-filter.d.ts.map +1 -0
  120. package/dist/controls/toggle-filter.js +42 -0
  121. package/dist/controls/view-tabs.d.ts +23 -0
  122. package/dist/controls/view-tabs.d.ts.map +1 -0
  123. package/dist/controls/view-tabs.js +59 -0
  124. package/dist/controls-dates/date-range-picker.d.ts +4 -0
  125. package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
  126. package/dist/controls-dates/date-range-picker.js +18 -0
  127. package/dist/controls-dates/index.d.ts +9 -0
  128. package/dist/controls-dates/index.d.ts.map +1 -0
  129. package/dist/controls-dates/index.js +4 -0
  130. package/dist/dashboard/delta-badge.d.ts +6 -12
  131. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  132. package/dist/dashboard/delta-badge.js +1 -50
  133. package/dist/dashboard/query-state.d.ts +1 -1
  134. package/dist/dashboard/settings-section.d.ts +6 -4
  135. package/dist/dashboard/settings-section.d.ts.map +1 -1
  136. package/dist/dashboard/settings-section.js +23 -16
  137. package/dist/dashboard/settings-section.module.css +10 -0
  138. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  139. package/dist/dashboard/settings-section.test.d.ts +2 -0
  140. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  141. package/dist/dashboard/stat-card.d.ts +44 -9
  142. package/dist/dashboard/stat-card.d.ts.map +1 -1
  143. package/dist/dashboard/stat-card.js +80 -48
  144. package/dist/dashboard/stat-card.module.css +82 -0
  145. package/dist/dashboard/stat-card.module.css.d.ts +11 -0
  146. package/dist/data/data-table.d.ts +102 -20
  147. package/dist/data/data-table.d.ts.map +1 -1
  148. package/dist/data/data-table.js +166 -65
  149. package/dist/data/table.d.ts +1 -1
  150. package/dist/data/table.d.ts.map +1 -1
  151. package/dist/guard/grace.test.d.ts +20 -0
  152. package/dist/guard/grace.test.d.ts.map +1 -0
  153. package/dist/guard/index.d.ts +66 -3
  154. package/dist/guard/index.d.ts.map +1 -1
  155. package/dist/guard/index.js +82 -7
  156. package/dist/guard/types.d.ts +3 -3
  157. package/dist/guard/types.d.ts.map +1 -1
  158. package/dist/index.d.ts +12 -2
  159. package/dist/index.d.ts.map +1 -1
  160. package/dist/index.js +13 -10
  161. package/dist/nav/types.d.ts +78 -2
  162. package/dist/nav/types.d.ts.map +1 -1
  163. package/dist/notifications/bell.d.ts +4 -1
  164. package/dist/notifications/bell.d.ts.map +1 -1
  165. package/dist/notifications/index.d.ts +1 -1
  166. package/dist/router-tanstack/field.d.ts +14 -0
  167. package/dist/router-tanstack/field.d.ts.map +1 -0
  168. package/dist/router-tanstack/field.js +4 -0
  169. package/dist/router-tanstack/index.d.ts +6 -2
  170. package/dist/router-tanstack/index.d.ts.map +1 -1
  171. package/dist/router-tanstack/index.js +4 -0
  172. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  173. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  174. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  175. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  176. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  177. package/dist/router-tanstack/search-param-store.js +16 -45
  178. package/dist/router-tanstack/search-store.d.ts +81 -0
  179. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  180. package/dist/router-tanstack/search-store.js +187 -0
  181. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  182. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  183. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  184. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  185. package/dist/router-tanstack/use-nav.d.ts +0 -2
  186. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  187. package/dist/router-tanstack/use-nav.js +34 -12
  188. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  189. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  190. package/dist/section/index.d.ts +2 -0
  191. package/dist/section/index.d.ts.map +1 -0
  192. package/dist/section/index.js +4 -0
  193. package/dist/section/section.d.ts +24 -0
  194. package/dist/section/section.d.ts.map +1 -0
  195. package/dist/section/section.js +126 -0
  196. package/dist/section/section.module.css +104 -0
  197. package/dist/section/section.module.css.d.ts +11 -0
  198. package/dist/section/section.test.d.ts +2 -0
  199. package/dist/section/section.test.d.ts.map +1 -0
  200. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  201. package/dist/shell/app-breadcrumbs.js +18 -15
  202. package/dist/shell/app-header.module.css +77 -37
  203. package/dist/shell/app-header.module.css.d.ts +2 -1
  204. package/dist/shell/app-mobile-nav.d.ts +9 -2
  205. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  206. package/dist/shell/app-mobile-nav.js +98 -11
  207. package/dist/shell/app-mobile-nav.module.css +8 -0
  208. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  209. package/dist/shell/app-sidebar.d.ts +55 -28
  210. package/dist/shell/app-sidebar.d.ts.map +1 -1
  211. package/dist/shell/app-sidebar.js +116 -67
  212. package/dist/shell/app-sidebar.module.css +303 -20
  213. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  214. package/dist/shell/index.d.ts +55 -21
  215. package/dist/shell/index.d.ts.map +1 -1
  216. package/dist/shell/index.js +40 -18
  217. package/dist/shell/mobile-nav-model.d.ts +13 -2
  218. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  219. package/dist/shell/mobile-nav-model.js +15 -1
  220. package/dist/shell/nav-count-badge.d.ts +1 -1
  221. package/dist/shell/page-bar.d.ts +51 -0
  222. package/dist/shell/page-bar.d.ts.map +1 -0
  223. package/dist/shell/page-bar.js +154 -0
  224. package/dist/shell/page-bar.module.css +178 -0
  225. package/dist/shell/page-bar.module.css.d.ts +17 -0
  226. package/dist/shell/page-bar.test.d.ts +2 -0
  227. package/dist/shell/page-bar.test.d.ts.map +1 -0
  228. package/dist/shell/sidebar-block-model.d.ts +48 -0
  229. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  230. package/dist/shell/sidebar-block-model.js +39 -0
  231. package/dist/shell/sidebar-blocks.d.ts +32 -0
  232. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  233. package/dist/shell/sidebar-blocks.js +169 -0
  234. package/dist/shell/sidebar-search.d.ts +13 -1
  235. package/dist/shell/sidebar-search.d.ts.map +1 -1
  236. package/dist/shell/sidebar-search.js +34 -1
  237. package/dist/shell/sidebar-search.module.css +26 -0
  238. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  239. package/dist/state/fields.d.ts +392 -0
  240. package/dist/state/fields.d.ts.map +1 -0
  241. package/dist/state/fields.js +369 -0
  242. package/dist/state/fields.test.d.ts +2 -0
  243. package/dist/state/fields.test.d.ts.map +1 -0
  244. package/dist/state/persisted.d.ts +48 -0
  245. package/dist/state/persisted.d.ts.map +1 -0
  246. package/dist/state/persisted.js +105 -0
  247. package/dist/state.d.ts +11 -56
  248. package/dist/state.d.ts.map +1 -1
  249. package/dist/state.js +14 -120
  250. package/dist/styles.css +13 -0
  251. package/dist/surfaces.d.ts +106 -24
  252. package/dist/surfaces.d.ts.map +1 -1
  253. package/dist/surfaces.js +155 -19
  254. package/dist/theme/controls.module.css +16 -1
  255. package/dist/theme/ctl-theme.d.ts +32 -0
  256. package/dist/theme/ctl-theme.d.ts.map +1 -0
  257. package/dist/theme/ctl-theme.js +54 -0
  258. package/dist/theme/ctl-theme.test.d.ts +2 -0
  259. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  260. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  261. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  262. package/dist/theme/divider-law.test.d.ts +2 -0
  263. package/dist/theme/divider-law.test.d.ts.map +1 -0
  264. package/dist/theme/icon-slot.d.ts +44 -0
  265. package/dist/theme/icon-slot.d.ts.map +1 -0
  266. package/dist/theme/icon-slot.js +21 -0
  267. package/dist/theme/icon-slot.module.css +47 -0
  268. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  269. package/dist/theme/icon-slot.test.d.ts +2 -0
  270. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  271. package/dist/theme/index.d.ts +1 -0
  272. package/dist/theme/index.d.ts.map +1 -1
  273. package/dist/theme/index.js +82 -5
  274. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  275. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  276. package/dist/theme/segmented-control.module.css +20 -0
  277. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  278. package/dist/tokens/index.d.ts +12 -0
  279. package/dist/tokens/index.d.ts.map +1 -1
  280. package/dist/tokens/index.js +38 -8
  281. package/dist/tokens/palette.d.ts +97 -61
  282. package/dist/tokens/palette.d.ts.map +1 -1
  283. package/dist/tokens/palette.js +76 -38
  284. package/dist/tokens.css +10 -2
  285. package/dist/widget-header/delta-badge.d.ts +13 -0
  286. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  287. package/dist/widget-header/delta-badge.js +29 -0
  288. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  289. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  290. package/dist/widget-header/index.d.ts +3 -0
  291. package/dist/widget-header/index.d.ts.map +1 -0
  292. package/dist/widget-header/index.js +6 -0
  293. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  294. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  295. package/dist/widget-header/widget-header.d.ts +37 -0
  296. package/dist/widget-header/widget-header.d.ts.map +1 -0
  297. package/dist/widget-header/widget-header.js +77 -0
  298. package/dist/widget-header/widget-header.module.css +335 -0
  299. package/dist/widget-header/widget-header.module.css.d.ts +24 -0
  300. package/dist/widget-header/widget-header.test.d.ts +2 -0
  301. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  302. package/llms.txt +30 -17
  303. package/package.json +14 -1
  304. package/src/charts/cursor/resolve.test.ts +29 -1
  305. package/src/charts/cursor/resolve.ts +38 -6
  306. package/src/charts/cursor/scope.tsx +6 -3
  307. package/src/charts/cursor/store.test.ts +19 -1
  308. package/src/charts/cursor/store.ts +10 -5
  309. package/src/charts/hooks/useChartCursor.ts +16 -8
  310. package/src/charts/index.ts +2 -2
  311. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  312. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  313. package/src/charts/primitives/ChartCard.tsx +86 -146
  314. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  315. package/src/cli/check-coverage.test.ts +297 -0
  316. package/src/cli/check-theme.test.ts +38 -14
  317. package/src/cli/index.ts +392 -16
  318. package/src/cli/init-sync.test.ts +97 -0
  319. package/src/connectivity/connectivity-indicator.tsx +16 -9
  320. package/src/content/index.ts +16 -3
  321. package/src/content/prose.module.css +7 -28
  322. package/src/content/prose.module.css.test.ts +8 -24
  323. package/src/controls/actions.module.css +62 -0
  324. package/src/controls/actions.module.css.d.ts +13 -0
  325. package/src/controls/actions.test.tsx +252 -0
  326. package/src/controls/actions.tsx +655 -0
  327. package/src/controls/actions.type-guard.test.ts +63 -0
  328. package/src/controls/compare-filter.tsx +64 -0
  329. package/src/controls/control-group.test.tsx +229 -0
  330. package/src/controls/control-group.tsx +63 -0
  331. package/src/controls/controls.module.css +542 -0
  332. package/src/controls/controls.module.css.d.ts +35 -0
  333. package/src/controls/controls.router.test.tsx +637 -0
  334. package/src/controls/enum-filter.tsx +159 -0
  335. package/src/controls/filter-context.tsx +153 -0
  336. package/src/controls/filter-pill.tsx +120 -0
  337. package/src/controls/filter-set.test.tsx +472 -0
  338. package/src/controls/filter-set.tsx +225 -0
  339. package/src/controls/filter-sheet.tsx +285 -0
  340. package/src/controls/glyphs.tsx +81 -0
  341. package/src/controls/index.ts +48 -0
  342. package/src/controls/multi-select-filter.tsx +140 -0
  343. package/src/controls/peer-boundary.test.ts +85 -0
  344. package/src/controls/props-contract.test.tsx +106 -0
  345. package/src/controls/range-filter.tsx +184 -0
  346. package/src/controls/search-filter.tsx +123 -0
  347. package/src/controls/select-filter.tsx +89 -0
  348. package/src/controls/sync-button.module.css +63 -0
  349. package/src/controls/sync-button.module.css.d.ts +11 -0
  350. package/src/controls/sync-button.test.tsx +183 -0
  351. package/src/controls/sync-button.tsx +197 -0
  352. package/src/controls/toggle-filter.tsx +60 -0
  353. package/src/controls/view-tabs.tsx +110 -0
  354. package/src/controls-dates/date-range-picker.tsx +42 -0
  355. package/src/controls-dates/index.ts +8 -0
  356. package/src/dashboard/delta-badge.tsx +4 -74
  357. package/src/dashboard/query-state.tsx +1 -1
  358. package/src/dashboard/settings-section.module.css +10 -0
  359. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  360. package/src/dashboard/settings-section.test.tsx +56 -0
  361. package/src/dashboard/settings-section.tsx +33 -30
  362. package/src/dashboard/stat-card.module.css +82 -0
  363. package/src/dashboard/stat-card.module.css.d.ts +11 -0
  364. package/src/dashboard/stat-card.test.tsx +67 -1
  365. package/src/dashboard/stat-card.tsx +209 -68
  366. package/src/data/data-table.test.tsx +282 -6
  367. package/src/data/data-table.tsx +429 -94
  368. package/src/data/table.ts +2 -0
  369. package/src/guard/check-source.test.ts +126 -13
  370. package/src/guard/grace.test.ts +149 -0
  371. package/src/guard/index.ts +172 -29
  372. package/src/guard/types.ts +4 -2
  373. package/src/index.ts +46 -5
  374. package/src/nav/types.ts +84 -2
  375. package/src/notifications/bell.tsx +6 -3
  376. package/src/notifications/index.ts +1 -1
  377. package/src/router-tanstack/field.ts +31 -0
  378. package/src/router-tanstack/index.ts +24 -2
  379. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  380. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  381. package/src/router-tanstack/search-param-store.ts +42 -159
  382. package/src/router-tanstack/search-store.router.test.tsx +661 -0
  383. package/src/router-tanstack/search-store.test.ts +418 -0
  384. package/src/router-tanstack/search-store.ts +380 -0
  385. package/src/router-tanstack/use-nav.test.tsx +187 -0
  386. package/src/router-tanstack/use-nav.ts +72 -14
  387. package/src/section/index.ts +1 -0
  388. package/src/section/section.module.css +104 -0
  389. package/src/section/section.module.css.d.ts +11 -0
  390. package/src/section/section.test.tsx +204 -0
  391. package/src/section/section.tsx +220 -0
  392. package/src/shell/app-breadcrumbs.tsx +44 -31
  393. package/src/shell/app-header.module.css +77 -37
  394. package/src/shell/app-header.module.css.d.ts +2 -1
  395. package/src/shell/app-mobile-nav.module.css +8 -0
  396. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  397. package/src/shell/app-mobile-nav.tsx +147 -8
  398. package/src/shell/app-sidebar-account.tsx +2 -2
  399. package/src/shell/app-sidebar.module.css +303 -20
  400. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  401. package/src/shell/app-sidebar.test.tsx +456 -41
  402. package/src/shell/app-sidebar.tsx +276 -140
  403. package/src/shell/index.test.tsx +291 -5
  404. package/src/shell/index.tsx +137 -38
  405. package/src/shell/mobile-nav-model.test.ts +228 -7
  406. package/src/shell/mobile-nav-model.ts +53 -3
  407. package/src/shell/nav-count-badge.tsx +1 -1
  408. package/src/shell/page-bar.module.css +178 -0
  409. package/src/shell/page-bar.module.css.d.ts +17 -0
  410. package/src/shell/page-bar.test.tsx +316 -0
  411. package/src/shell/page-bar.tsx +315 -0
  412. package/src/shell/sidebar-block-model.ts +83 -0
  413. package/src/shell/sidebar-blocks.tsx +301 -0
  414. package/src/shell/sidebar-search.module.css +26 -0
  415. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  416. package/src/shell/sidebar-search.tsx +71 -2
  417. package/src/state/fields.test.tsx +258 -0
  418. package/src/state/fields.ts +892 -0
  419. package/src/state/persisted.ts +211 -0
  420. package/src/state.ts +46 -240
  421. package/src/styles.css +13 -0
  422. package/src/surfaces.ts +203 -36
  423. package/src/theme/controls.module.css +16 -1
  424. package/src/theme/ctl-theme.test.tsx +275 -0
  425. package/src/theme/ctl-theme.tsx +138 -0
  426. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  427. package/src/theme/density-relations.test.ts +106 -51
  428. package/src/theme/divider-law.test.ts +259 -0
  429. package/src/theme/icon-slot.module.css +47 -0
  430. package/src/theme/icon-slot.module.css.d.ts +8 -0
  431. package/src/theme/icon-slot.test.tsx +275 -0
  432. package/src/theme/icon-slot.tsx +59 -0
  433. package/src/theme/index.ts +165 -3
  434. package/src/theme/layout-rhythm-css.test.ts +72 -0
  435. package/src/theme/radius.test.ts +11 -1
  436. package/src/theme/segmented-control.module.css +20 -0
  437. package/src/theme/shadow-surfaces.test.ts +20 -7
  438. package/src/theme/spacing.test.ts +75 -27
  439. package/src/theme/use-basalt-spacing.ts +2 -2
  440. package/src/tokens/density.test.ts +7 -25
  441. package/src/tokens/index.ts +48 -8
  442. package/src/tokens/palette.ts +107 -73
  443. package/src/widget-header/delta-badge.test.tsx +60 -0
  444. package/src/widget-header/delta-badge.tsx +57 -0
  445. package/src/widget-header/index.ts +2 -0
  446. package/src/widget-header/no-mantine.test.ts +33 -0
  447. package/src/widget-header/widget-header.module.css +335 -0
  448. package/src/widget-header/widget-header.module.css.d.ts +24 -0
  449. package/src/widget-header/widget-header.test.tsx +187 -0
  450. package/src/widget-header/widget-header.tsx +168 -0
  451. package/agent/rules/basalt-agent.md +0 -452
  452. package/agent/rules/basalt-app.md +0 -246
  453. package/agent/rules/basalt-commands.md +0 -320
  454. package/agent/rules/basalt-content.md +0 -409
  455. package/agent/rules/basalt-data.md +0 -269
  456. package/agent/rules/basalt-forms.md +0 -186
  457. package/agent/rules/basalt-notifications.md +0 -264
  458. package/agent/rules/basalt-query.md +0 -187
  459. package/agent/rules/basalt-router.md +0 -336
  460. package/dist/content/article-filter-bar.d.ts +0 -40
  461. package/dist/content/article-filter-bar.d.ts.map +0 -1
  462. package/dist/content/article-filter-bar.js +0 -56
  463. package/dist/shell/page-header.d.ts +0 -13
  464. package/dist/shell/page-header.d.ts.map +0 -1
  465. package/dist/shell/page-header.js +0 -23
  466. package/src/content/article-filter-bar.tsx +0 -88
  467. package/src/shell/page-header.tsx +0 -35
@@ -1,268 +1,76 @@
1
1
  ---
2
2
  name: basalt-design
3
- description: Design and restyle data visualizations and analytics UIs in a basalt-ui app (Mantine v9 + visx). The dataviz successor — restraint doctrine plus the live theme-lab tuning loop and the centralized, lint-enforced --vx-* token system. Use when building or restyling charts/dashboards, choosing a palette, tuning dark/light, or when charts look childish / inconsistent / over-colored.
4
- when_to_use: User is building or restyling charts, dashboards, or metric UIs in a basalt-ui consumer; picking or tuning a palette; deciding on dark/light shades; or complaining that charts look "AI default", inconsistent across tabs, or over-saturated. Also the entry point for any "make this look professional" dataviz request in a Mantine + visx app.
3
+ description: Design and restyle dashboards, charts and metric UIs in a basalt-ui app (Mantine v9 + visx) — the procedure: read the law, decide before coding, tune by eye in the theme lab, bake the values back into the palette. Use when building or restyling charts/dashboards, choosing or tuning a palette, or when a UI looks childish / inconsistent / over-colored.
4
+ when_to_use: User is building or restyling charts, dashboards or metric UIs in a basalt-ui consumer; picking or tuning a palette; deciding dark/light shades; or complaining that a UI looks "AI default", inconsistent across tabs or over-saturated. Also the entry point for any "make this look professional" request in a Mantine + visx app.
5
5
  ---
6
6
 
7
- `/basalt-design` is the design authority for a basalt-ui app. It is the **method**; the project's
8
- doctrine is the **law**. Professional dataviz is restraint, not decoration — this skill encodes
9
- that restraint and the concrete, lint-enforced way basalt-ui makes it impossible to bypass.
7
+ `/basalt-design` is the METHOD. The law is the `basalt-*` rules plus this app's `DESIGN.md`, and the
8
+ managed `CLAUDE.md` block states which wins — read them, do not re-derive them here. This skill is
9
+ the sequence to follow and the loop to run.
10
10
 
11
- ## Read the law first (strict precedence)
11
+ ## 1. Load the law before touching code
12
12
 
13
- Before applying any generic guidance, load the doctrine in this exact order and obey the highest
14
- one that exists. Lower layers fill gaps; they never override a higher layer.
13
+ - `.claude/rules/basalt-tokens.md` — the identity, the color/spacing/type law, `theme-allow`.
14
+ - `.claude/rules/basalt-mantine.md` — provider, shell, surfaces, depth, motion.
15
+ - `.claude/rules/basalt-controls.md` — where a control lives and what sizes it.
16
+ - `.claude/rules/basalt-charts.md` — the chart contract. `/basalt-charts` is the how-to.
17
+ - `./DESIGN.md` — this app's deltas: identity confirmation, series dictionary, deviations.
15
18
 
16
- 1. **Consumer `DESIGN.md`** (repo root) — the thin, app-specific instantiation. Identity
17
- confirmation, the series dictionary, app deviations. **This wins on every conflict.** It is the
18
- Google-spec convention (YAML token front matter + Markdown prose, sibling to `CLAUDE.md`).
19
- 2. **Shipped `basalt-*` rules** (`.claude/rules/basalt-{tokens,charts,mantine,state}.md`) — the
20
- universal law AND its enforcement: the Basalt modern-zinc palette + saturated sky accent,
21
- one-hue-per-metric, neutral structure, gradient defaults, the three-tier token contract, the
22
- elevation/density doctrine.
23
- 3. **This skill** — only the loop and the judgment, where the above are silent.
19
+ **No `DESIGN.md` means the app was never scaffolded** — run `/basalt-app` first. Never invent a
20
+ palette ad hoc.
24
21
 
25
- If a consumer `DESIGN.md` does not exist yet, the app has not been scaffolded — run `/basalt-app`
26
- (it wraps `basalt-ui init`, which writes the thin `DESIGN.md` and the `basalt-*` rules). Do not invent
27
- a palette ad hoc.
22
+ ## 2. Decide before coding
28
23
 
29
- ## Restraint doctrine (decide before touching code)
24
+ Answer these four in one pass; each one is a decision, not a preference:
30
25
 
31
- This is the same restraint `frontend-design` calls for, applied to data: intentionality over
32
- intensity. The `basalt-ui init` CLAUDE block sets a restraint override for exactly this reason — in a
33
- dashboard the bold move is calm.
26
+ 1. **What earns a hue here?** Trend, signal/status, or genuine categorical separation. Everything
27
+ else is neutral. One hue per metric, stable across every view.
28
+ 2. **Where does each control live?** One home per control, and the home sets its size (law C1/C5).
29
+ 3. **What is the one primary action on this view?** Exactly one filled accent; everything else
30
+ neutral.
31
+ 4. **What does this surface reuse?** Check the shipped composite FIRST — `StatCard`, `ChartCard`,
32
+ `Section`, `BasaltDataTable`, `QueryState`, `EmptyState`. A fork by a token-fluent author passes
33
+ every gate, so no guard will tell you; asking is the control. If a composite genuinely cannot
34
+ express the case, that is a gap to REPORT, not to route around.
34
35
 
35
- - **One muted hue family, reused everywhere.** The Basalt modern-zinc identity (below), never raw
36
- Material / AntD / Tailwind primaries (they read childish and clash). A small harmonious subset
37
- across every page so tabs feel like one app.
38
- - **Single hue per metric.** Each metric owns ONE color, stable across all views (HRV always
39
- violet, bench always amber). Series colors are app-side and distinct from the UI accent — don't
40
- reuse the sky accent as a metric hue. Multiple colors only for genuinely categorical data
41
- (sleep stages, source breakdown). A line chart almost never needs more than one hue plus neutrals.
42
- - **Neutral structure, colored signal.** Lines/axes/grids in muted neutrals; color is the data and
43
- status (good/warn/bad). A single neutral line with a soft tinted area reads more premium than a
44
- rainbow.
45
- - **Gradients on areas, not everything.** A soft single-hue vertical gradient under a line is the
46
- canonical "modern" move (`AreaGradient`). Keep stacked-area bands opaque — transparency leaks
47
- lower bands.
48
- - **Quiet chrome.** Hairline grids (~6–8% neutral), thin crosshairs, restrained tooltips with a
49
- card surface and one accent swatch per row. Generous whitespace.
50
- - **Motion restraint (same doctrine, applied to animation).** Subtle and purposeful, never
51
- decorative — a state change earns a transition, idle chrome does not. Reach for the shared
52
- `MOTION_DURATION` / `MOTION_SPRING` tokens (`basalt-ui`'s `src/motion`) instead of inventing
53
- per-component durations/easings, so animated chrome feels like one identity the same way the
54
- `--vx-*` palette does for color. Never a looping/pulsing idle animation. Always respect
55
- `useReducedMotion` (`@mantine/hooks`) — an animated component needs a real unanimated code path,
56
- not just `duration: 0`.
36
+ ## 3. Build it
57
37
 
58
- ## Color identity & accent restraint (the #1 lever)
38
+ Compose Mantine primitives and shipped composites; pull every color from `VX.*` / a series token.
39
+ Let the theme carry size, radius, depth and font — a prop you did not have to pass is the point.
59
40
 
60
- The Basalt identity is **modern zinc + a saturated sky accent** — cool-neutral zinc surfaces
61
- (Tailwind zinc family) on both schemes, a slightly darker page with panels lifting off it via depth
62
- (a whisper shadow + 1px ring, `shadow-card` — never a bare border), and one saturated sky-blue
63
- accent. Restraint is now about _placement_, not _saturation_ — the accent is louder than the old
64
- muted-slate identity, so the discipline below (where it appears / where it never appears) carries
65
- more of the weight.
41
+ ## 4. Tune by eye, then bake
66
42
 
67
- **Neutrals carry the surface — cool zinc on both schemes**, never warm-neutral/off-white, never
68
- steel-blue, never pure black/white:
69
-
70
- - **Dark:** bg `color-mix(in srgb, #27272a 70%, #18181b)` (#232326), panel `#27272a`, panel-hover
71
- `color-mix(in srgb, #3f3f46 50%, #27272a)` (#333338), hairline (card ring)
72
- `color-mix(in srgb, #52525c 50%, #3f3f46)` (#494951), line (strong border) `#3f3f46`. Ink
73
- `#e5e5e5`, muted `#d4d4d4`, faint `#a1a1a1`.
74
- - **Light:** bg `color-mix(in srgb, #f4f4f5 50%, #e4e4e7)` (#ececee), panel `#f4f4f5`, panel-hover
75
- `#fafafa`, hairline `#e5e5e5`, line (strong border) `color-mix(in srgb, #e4e4e7 50%, #d4d4d8)`
76
- (#dcdce0). Ink `#262626`, muted `#525252`, faint `#737373`.
77
-
78
- The surface token set is `bg` (page) · `panel` (cards, controls) · `panel-hover` · `line` (strong
79
- border) · `hairline` (card ring, lives inside `shadow-card`, never rendered as a bare `border`) ·
80
- `divider` (layout separators).
81
-
82
- **One accent, saturated sky-blue — split by ROLE.** As **ink** (links, active-nav icon, chart lines,
83
- focus ring) it is read against the page, so it inverts: `#0077bd` light / `#8ec5ff` dark. As a
84
- **filled surface** it carries a label, so it is squeezed between that label (≥4.5:1) and the page
85
- behind it (≥3:1) — which leaves one luminance band, the same on both pages: `#0077bd` with a WHITE
86
- label, in BOTH schemes. Fill with `--vx-accent-fill` / `--vx-fill-{family}`, never with `--vx-accent`
87
- (a light fill cannot carry white text). Every Mantine family's fill sits in that same band, so a
88
- filled surface always reads white. With `accent-hover` (derived from the fill) and `on-accent`
89
- companions (NOT the old ~50%-saturation muted slate).
90
-
91
- The accent appears on: the primary data series, active-nav **icons** and active **child** labels,
92
- links, primary buttons, focus rings, and the leader bar in meters. It stays off: general chrome,
93
- borders, large fills, every icon, secondary buttons. "Ink earns its color" — neutral does the
94
- structure, the accent only points.
95
-
96
- - **Active nav = an ink-9% tint + accent-colored icon + weight-600 ink text** — no panel fill, no
97
- shadow, and never a full accent fill. It's a selected row, not a raised control. The theme bakes
98
- this into `NavLink` for every render path — don't re-color it.
99
- - **Buttons:** exactly one filled-accent primary CTA per view; every other action is
100
- `variant="default"` (neutral). Avoid colored `variant="light"` for routine actions.
101
- - **Status:** positive deltas `color="green"` (forest), never `teal`/turquoise/saturated emerald.
102
- - **Dark done right:** cool zinc, neutral/faint-cool, never steel-blue, never pure black; the
103
- dark-scheme accent (`#8ec5ff`) is lighter/less saturated than its light counterpart so it doesn't
104
- glow/bleed.
105
- - **Light done right:** cool zinc page + panel lift (never warm/off-white); depth comes from
106
- `shadow-card`, not a bare hairline border.
107
-
108
- ### Density & surface consistency
109
-
110
- - **Dense by default** (Linear/Notion): compact nav rows, `sm` (12px) gaps and card padding, a
111
- 48px shell header. Separate by surface + `shadow-card`, not by large air — default to the
112
- tighter step.
113
- - **All cards render identically:** Mantine `Card`/`Paper` **and** the Mantine-free `ChartCard`
114
- resolve to **one radius token** (`--vx-radius-card` = 7px) and **`shadow-card`** — a whisper
115
- shadow with the 1px ring baked into the shadow itself, **no `border` property**. Never
116
- inline-override a surface's `border`/`borderRadius`/`boxShadow`/`backgroundColor` — use the
117
- radius token + `VX.surface.*` / the shadow-card token. Mechanically enforced by
118
- `basalt-ui check-theme`'s `raw-surface` guard.
119
- - **Depth splits control-vs-panel-vs-floating** — three tokens, not one. Panels holding content
120
- (Card/Paper/Notification/ChartCard) take `shadow-card`; anything you click or type into (Button,
121
- ActionIcon, inputs, chips, the search trigger) takes `shadow-raised`, whose ring is INSET so one
122
- value rides a fill, a tint and a panel alike; detached floating surfaces (Tooltip/Popover/Menu/
123
- Modal/Drawer) take `shadow-overlay`. Depth is static — nothing lifts or gains a shadow on hover,
124
- and emphasis is carried by fill weight, never z-height. Full doctrine in the `basalt-mantine`
125
- rule.
126
- - **Strict surfaces & primitives.** The theme runs a strict surface system: Mantine components read
127
- raw ramp steps directly, so `cssVariablesResolver` **collapses the ramp steps onto the
128
- `--vx-surface-*` tokens** — one depth token per tier, one card bg, one radius across every
129
- component (AppShell, Table, Input, Divider, Tabs, Popover, Accordion, cards). Consumer code must
130
- **use Mantine layout primitives** (`Box`/`Flex`/`Grid`/`SimpleGrid`/`Stack`/`Group`/`Paper`/`Card`),
131
- not raw `<div>`/`<span>` with inline `style`. `check-theme` adds five guard kinds to enforce this
132
- (default ON, `theme-allow` escape): `off-system-surface-var` (raw ramp-step vars),
133
- `mantine-shade-index` (a shade-pinned color — `c="yellow.7"` is one fixed swatch in both schemes;
134
- use `VX.status.*` or the bare hue name), `raw-html-layout`, `inline-spacing`, `inline-display`.
135
- Only the Mantine-free `src/charts/**` may use raw `<div>` (still with `VX.*` tokens).
136
- - **Reach for the shipped composite before wrapping one.** A `StatCard` carrying a threshold verdict
137
- takes `tone="good" | "warn" | "bad"` (an accent rail down its leading edge). Omitting `tone` is not
138
- the same as `"good"` — omitted means "fine, or nothing measured" and stays untinted, so `"good"` is
139
- a positive assertion you opt into for a value that earned it (`0 min` downtime), never a default.
140
- A chart goes in `ChartCard`. Wrapping
141
- a shipped card in a hand-positioned `Box` to add a mark it already has is how an app grows a second
142
- card idiom — one the guard cannot recognize as a card, and therefore cannot police. If a composite
143
- genuinely can't express what you need, that is a gap to report, not to route around.
144
-
145
- **Anti-slop checklist:** no pure `#000`/`#fff`; one accent locked page-wide; neutral does the
146
- structure, accent only points; don't flood blue; hierarchy via scale/weight/contrast/space, not
147
- color; depth = `shadow-card` (whisper shadow + ring), never a bare hairline border on cards.
148
-
149
- ## The three-tier token system (centralize + enforce)
150
-
151
- Aesthetics only survive if they cannot be bypassed. basalt-ui ships this as the `./tokens` and
152
- `./charts` surfaces:
153
-
154
- 1. **Palette data** — the Basalt modern-zinc ramps + the saturated sky accent family, plus
155
- every semantic/status/neutral/surface entry as a per-theme `{ light, dark }` pair. Pure data: no
156
- React, no `@mantine/*`, no browser API.
157
- 2. **CSS variables** — `buildPaletteCss(opts)` emits the pairs as `--vx-*` custom properties under
158
- the light/dark color-scheme selector. Theme resolution is **pure CSS** — no JS branching, works
159
- in non-component files.
160
- 3. **Token refs** — `VX.*` are just `var(--vx-*)` strings (colors) plus non-color sizing constants.
161
- Opacity via `alpha(token, a)` (= `color-mix`), **never** `rgba()` — the hue must keep resolving
162
- per scheme.
163
-
164
- Hard rules (the guard enforces these, not just prose):
165
-
166
- - **Never a raw hex / `rgb()` / `hsl()` in chart or app source.** Route every color through
167
- `VX.*` / the palette. `basalt-ui check-theme` fails the build on a literal; a `theme-allow` comment
168
- is the only deliberate exception.
169
- - **Never `localStorage.getItem('theme')`.** The scheme resolves via Mantine's color scheme + the
170
- `--vx-*` vars. The guard bans this read.
171
- - New colors go into the palette as `{ light, dark }` pairs — never inline, never two
172
- `fooLight`/`fooDark` keys.
173
-
174
- ```ts
175
- import { VX, alpha } from 'basalt-ui/tokens'
176
- import { SERIES } from '../lib/series' // consumer-owned — see /basalt-charts
177
-
178
- const stroke = SERIES.hrv // app-side series ref (consumer-defined — see /basalt-charts)
179
- const fill = alpha(SERIES.hrv, 0.12) // theme-aware soft area, NOT rgba()
180
- const grid = VX.grid // framework neutral
181
- ```
182
-
183
- ## Drive the theme-lab loop (tune by eye, then bake)
184
-
185
- Static HTML POCs do not translate 1:1 to visx/Mantine. Tune in the **real app** with the shipped
186
- theme lab (`basalt-ui/theme-lab`), then bake the values into the palette. The lab writes overrides
187
- as inline `--vx-*` styles on `<html>`, which beat the stylesheet's per-scheme rules — so everything
188
- restyles **instantly with no React re-render**.
189
-
190
- Wire it (DEV-only) inside a dev dock popover:
43
+ A static POC never translates 1:1 to visx + Mantine. Tune in the REAL app with the shipped lab
44
+ (`basalt-ui/theme-lab`), which writes inline `--vx-*` overrides on `<html>` — they beat the
45
+ stylesheet, so everything restyles instantly with no React re-render.
191
46
 
192
47
  ```tsx
193
48
  import { ThemeLabControls, applyOverrides, loadOverrides } from 'basalt-ui/theme-lab'
194
49
 
195
- // At boot — re-apply a persisted tuning session (the control body does NOT do the initial apply):
196
- applyOverrides(loadOverrides())
197
-
198
- // In the dev dock — pass the consumer's own series groups so they're tunable too:
199
- <ThemeLabControls
200
- groups={[
201
- ...COLOR_GROUPS, // framework: Semantic / Status / Neutral / Surface
202
- { title: 'Health', items: [{ var: '--vx-hrv', label: 'HRV' }, /* ... */] },
203
- ]}
204
- onCopy={(json) => notifications.show({ message: 'Overrides copied' })}
205
- copyIcon={<IconCopy size={16} />} // framework ships no icon/notification dep
206
- resetIcon={<IconRefresh size={16} />}
207
- />
50
+ applyOverrides(loadOverrides()) // at boot — the control body does NOT do the initial apply
51
+ ;<ThemeLabControls groups={[...COLOR_GROUPS, appSeriesGroup]} onCopy={notifyCopied} />
208
52
  ```
209
53
 
210
- The loop:
211
-
212
- 1. Open the lab, adjust `--vx-*` colors and the area-gradient strength (`--vx-area-top` /
213
- `--vx-area-bottom`) by eye, in both schemes.
214
- 2. Toggle the Mantine color scheme and re-check: saturated mid-tones **glow/bleed on dark** — step
215
- one shade lighter and slightly desaturated; on light go one shade deeper for contrast. Same hue,
216
- different shade — **never the same hex in both**.
217
- 3. **Copy JSON** → hand the values back into the palette data as `{ light, dark }` pairs.
218
- 4. Remove the override (or "Reset to palette defaults") and confirm the baked palette matches.
219
-
220
- The lab is a tuning sandbox, not a prod theme editor: inline overrides apply to whatever scheme is
221
- on screen and win over both light and dark rules. It is the closing step of design, not a shipped
222
- feature.
223
-
224
- ## Primitives + kind discipline (don't loosen, extend)
225
-
226
- Every chart composes the shipped primitives — never hand-rolled equivalents:
227
-
228
- - **`ChartCard`** wrapper (title + info-tooltip + extra slot, consistent margin).
229
- - **`ChartLegend`** (`line | bar | split | splitLine` shapes) — never hand-rolled legend markup.
230
- - **`ChartTooltipFloat`** + `TooltipHeader` / `TooltipRow` / `TooltipBody` — never import `@visx/tooltip`
231
- directly (oxlint bans it in chart files).
232
- - **`AxisBottomDate`** / **`AxisLeftNumeric`** / **`AxisRightNumeric`** — never raw
233
- `<AxisLeft>`/`<AxisBottom>` (they miss theme tokens + smart ticks). Now lint-enforced:
234
- `basalt-ui check-theme`'s `raw-visx-axis` guard fails the build on a raw axis inside a `/charts/`
235
- file (escape via `theme-allow`).
236
- - **The cursor is shared by default** — `CartesianChart` wires the hover overlay, crosshair, dots
237
- and tooltip itself, and every chart on the page tracks one cursor through a module-level store
238
- with no provider. Reach for `useChartCursor` only in a hand-composed non-single-plot shape, and
239
- for **`ChartCursorScope`** only to opt a subtree OUT of sharing.
240
- - **`AreaGradient`** / `areaFillUrl` for the soft single-hue fill.
241
-
242
- Shipped kinds beyond `ZonedLine` / `Bars` / `StackedArea` / `Donut`: **`MultiLine`** (N series on a
243
- shared y-axis, legend-hover dimming, dashed MA companions, per-point markers, zones/refLines, fixed
244
- or auto domain — also z-score/σ via a symmetric domain + zero refLine), **`DualPanel`** (line pane
245
-
246
- - signed-histogram pane on one x-scale and cursor, optional fill-between), **`Heatmap`**
247
- (category×category intensity grid with per-cell tooltip + optional gradient legend strip).
248
-
249
- > If the new chart does not fit the primitives, **add a kind — don't loosen the primitives.**
250
- > Recurring shape → extract a kind via the Rule of Three. Genuinely unique → stay bespoke,
251
- > composing primitives directly. New color → a `{ light, dark }` pair in the palette, wired through
252
- > `--vx-*`, exposed as a `VX.*` ref — never an inline hex. The mechanics of all three live in
253
- > **`/basalt-charts`** — defer to it for the "how".
254
-
255
- The Mantine-free boundary holds here too: `./charts` and `./tokens` import zero `@mantine/*` (an
256
- internal invariant keeping the token layer upstream of Mantine, and letting those two subpaths
257
- resolve/render with no `@mantine/*` installed — CI-tested, repo-local only, not shipped), and
258
- `@visx/*` is only allowed under chart files (oxlint-enforced in the shipped consumer preset too).
259
-
260
- ## Checklist before declaring a chart "done"
261
-
262
- - Every color comes from a token (`VX.*` / palette) — `basalt-ui check-theme` green, zero raw hex.
263
- - One hue per metric; categorical multi-color only where the data is genuinely categorical.
264
- - Neutral line/axis/grid; soft `AreaGradient` on plain metric lines, opaque stacked-area bands.
265
- - Tooltip = `ChartCard` / `ChartTooltipFloat` primitives, one swatch per row — no hand-rolled markup.
266
- - Looks right in BOTH schemes (toggled and checked: glow on dark, contrast on light).
267
- - New colors landed as `{ light, dark }` pairs in the palette, not inline.
268
- - Nothing reads `localStorage.getItem('theme')`.
54
+ 1. Adjust colors and the area-gradient strength by eye — in BOTH schemes.
55
+ 2. Toggle the scheme and re-check: a saturated mid-tone glows on dark (step lighter, slightly
56
+ desaturated) and washes out on light (step deeper). Same hue, different shade, **never the same
57
+ value in both**.
58
+ 3. Copy the JSON and hand the values back into the palette as `{ light, dark }` pairs — or, for the
59
+ identity itself, into the derive config's knobs rather than a hand-picked hex.
60
+ 4. Remove the override and confirm the baked palette matches.
61
+
62
+ `DeriveControls` is the identity/colour knobs (seed + the bounded levels + radius + density) and is
63
+ faithful to `createBasaltTheme`; `ThemeLabControls` is the low-level inspector for the non-derived
64
+ structural tokens. Both are DEV tools and inert in a production build — run the dev server.
65
+
66
+ ## 5. Verify before calling it done
67
+
68
+ - `basalt-ui check-theme` green: zero raw hex, zero `rgba()`, nothing reading `localStorage['theme']`.
69
+ - `oxlint .` green on the `basalt/*` rules — no hand-rolled filter (in a slot) and no in-body
70
+ `<Title order={1|2}>`. Hand-rolled section headings are NOT lint-visible (see the `not guarded`
71
+ line in `basalt-controls.md`'s coverage header): grep the diff for `fw={600}` / `<Title order={3`
72
+ headings by hand and replace them with `WidgetHeader` / `Section`.
73
+ - One hue per metric; neutral structure; the accent only points.
74
+ - Checked in BOTH schemes, and below `sm` as well as on a wide viewport.
75
+ - Any new color landed as a `{ light, dark }` pair in the consumer series file, never inline.
76
+ - Nothing you built duplicates a shipped composite.
@@ -2,48 +2,39 @@
2
2
 
3
3
  ## basalt-ui (managed — do not hand-edit)
4
4
 
5
- Scaffolded by `bunx basalt-ui init` (the one command that legitimately predates the install) and
6
- refreshed by the locally installed `basalt-ui sync` after every upgrade; `basalt-ui sync --check`
7
- gates drift in CI. This block is framework-owned — edit `DESIGN.md`
8
- or the `basalt-*` rules instead; manual changes here are overwritten on the next sync.
9
-
10
- **Stack:** React 19 + Mantine v9, themed by `basalt-ui` (`BasaltProvider` + `createBasaltTheme`).
11
- Colors come from the three-tier `--vx-*` token system — read `VX.*` / a `defineSeries` token,
12
- never a raw hex/`rgb()`/`hsl()`. Charts are visx via `basalt-ui/charts`: every single-plot chart
13
- composes `CartesianChart` (owns margins, scales, axes, grid, cursor, tooltip — draw only marks);
14
- legends/tooltip rows are DERIVED from `series`, never hand-authored (`basalt/hand-rolled-plot` +
15
- `basalt/chart-legend-literal` enforce both); `DualPanel`/`BandStrip`/`MirroredBars` declare
16
- themselves exceptions, and `Donut`/`Heatmap` render no plot-assembly element so the rule never
17
- fires on them. Add a kind on the third repeat, don't loosen the primitives. `basalt-ui/charts` and
18
- `basalt-ui/tokens` are Mantine-free internally (a framework invariant, not something your own app
19
- code must follow) — never import `@visx/*` outside a `charts/` directory (oxlint-enforced).
20
- Toolchain is oxlint + oxfmt (no ESLint/Biome/Prettier) and `basalt-ui check-theme` guards the
21
- palette. Runtime is Bun.
22
-
23
- **Before guessing an import, read the installed package's machine docs — `llms.txt` (per-subpath
24
- import map) and `AGENTS.md`, at the install directory.** That is `./node_modules/basalt-ui` only on
25
- a single-package app. In a workspace, basalt resolves under the package that depends on it
26
- (`packages/<name>/node_modules/basalt-ui`), and the repo root may have no copy at all. Run
27
- `basalt-ui doctor` — its `basalt-resolves` line prints the resolved install dir and version; read
28
- the two files there. Invoke the CLI through the **locally installed** bin (the `lint:basalt` script
29
- seeded by `init` shows the path); `bunx basalt-ui` fetches a second copy from npm and can answer
30
- for a different version than the one you are building against.
31
-
32
- **DESIGN.md is law.** `./DESIGN.md` (imported below) records this app's palette identity and series
33
- dictionary. Precedence: **DESIGN.md > `basalt-*` rules > skills.** When building or restyling any
34
- UI, that law wins over habit, over library defaults, and over a skill's instinct. The design/charts
35
- workflows are the managed skills in `.claude/skills/` (`/basalt-design`, `/basalt-charts`) — they
36
- defer to DESIGN.md.
5
+ Placed by `bunx basalt-ui init` and refreshed by `basalt-ui sync` after every upgrade
6
+ (`sync --check` gates drift in CI). Framework-owned: edit `DESIGN.md` or your own files instead —
7
+ changes here are overwritten on the next sync.
8
+
9
+ **Stack:** React 19 + Mantine v9, themed by `basalt-ui` (`BasaltProvider` + `createBasaltTheme`), Bun
10
+ runtime, oxlint + oxfmt (no ESLint/Biome/Prettier), no Tailwind. Color comes from the three-tier
11
+ `--vx-*` token system — read `VX.*` or a series token, never a raw hex/`rgb()`/`hsl()`. Charts are
12
+ visx via `basalt-ui/charts`. `basalt-ui check-theme` plus the `basalt/*` oxlint rules are the teeth.
13
+
14
+ **Precedence — the only statement of it.** Highest wins; a lower layer fills gaps and never
15
+ overrides a higher one:
16
+
17
+ > consumer `DESIGN.md` (app deltas) > the six shipped `basalt-*` rules > the `basalt-*` skills
18
+
19
+ **All six rules are law** — `basalt-tokens`, `basalt-mantine`, `basalt-charts`, `basalt-state`,
20
+ `basalt-controls`, `basalt-batteries` in `.claude/rules/`, each carrying a generated
21
+ `<!-- basalt:coverage -->` header naming what enforces it and what is only advisory. The skills
22
+ (`/basalt-app`, `/basalt-design`, `/basalt-charts`) are the METHOD that obeys them, never law.
23
+
24
+ **Run the LOCAL bin, not `bunx`** — `./node_modules/.bin/basalt-ui`, or a `package.json` script.
25
+ `bunx` does not re-resolve a cached package, so it can answer for a version you upgraded away from;
26
+ `init` is the one legitimate `bunx` invocation, because nothing is installed yet.
27
+
28
+ **Before guessing an import, read the installed package's machine docs** — `llms.txt` (per-subpath
29
+ import map) and `AGENTS.md`, at the install directory. In a workspace that is under the package that
30
+ depends on basalt, not the repo root; `basalt-ui doctor`'s `basalt-resolves` line prints where.
37
31
 
38
32
  @./DESIGN.md
39
33
 
40
- **Restraint override (supersedes `/frontend-design`).** This app is a calm, data-dense,
41
- dark-first professional surface — not a showcase. Ignore `/frontend-design`'s push toward a "BOLD
42
- aesthetic direction", gradient meshes, noise/grain, and dramatic motion. Here: the shipped
43
- three-font system (Nunito Sans body, Hubot Sans condensed headings, JetBrains Mono for every
44
- numeral/micro-label), depth via a whisper shadow + 1px ring (`shadow-card` on panels,
45
- `shadow-raised` on controls — never a decorative drop shadow, never a hover lift), neutral
46
- zinc-by-default with the single saturated accent spent only when earned (trend /
47
- signal / categorical separation). Restraint **is** the identity.
34
+ **Restraint override (supersedes `/frontend-design`).** This app is a calm, data-dense professional
35
+ surface, not a showcase. Ignore the push toward a "BOLD aesthetic direction", gradient meshes,
36
+ noise/grain and dramatic motion. Here: the shipped three-font system, depth from a whisper shadow
37
+ with a 1px ring, neutral-by-default with the single accent spent only when earned — trend, signal,
38
+ or genuine categorical separation. Restraint **is** the identity.
48
39
 
49
40
  <!-- basalt:end -->
@@ -1,74 +1,45 @@
1
1
  # {{APP_NAME}} — Design
2
2
 
3
- > Managed by basalt-ui — run `basalt-ui doctor` for the version, never trust a number written into
4
- > a file (this one is a seed: written once at `init`, then owned by you, so any version stamped
5
- > here would be frozen at scaffold time). This is a **thin** instantiation — it records this
6
- > app's **deltas only** on top of the shipped `basalt-*` rules. The universal law (earned color,
7
- > neutral-by-default, three-tier `--vx-*` tokens, theme-is-data, the chart primitive contract, the
8
- > elevation/density/shape doctrine) lives in those rules and the `/basalt:design` skill, and is
9
- > **not** repeated here. Touch this file only to confirm identity, register the app's series, or
10
- > record a genuine deviation.
11
-
12
- ## Precedence (when guidance conflicts)
13
-
14
- This file's deltas win, then the shipped rules, then any skill:
15
-
16
- 1. **This file** (`{{APP_NAME}}` DESIGN.md) — app-specific deltas. Highest authority.
17
- 2. **`basalt-*` rules** (`.claude/rules/basalt-*.md`) — the shipped law and its enforcement.
18
- 3. **Skills** (`/basalt:design`, `/basalt:charts`, `/frontend-design`, …) — generic method, lowest.
19
-
20
- A skill never overrides this file or the `basalt-*` rules. When a skill's instinct collides with the
21
- law, the law wins.
3
+ > Seeded by basalt-ui `init`, then owned by you — `sync` never touches it again, and no version is
4
+ > stamped here (run `basalt-ui doctor` for the version; a number written into a file is frozen at
5
+ > scaffold time). It records this app's **deltas only**: the law lives in `.claude/rules/basalt-*.md`,
6
+ > and the precedence between this file, those rules and the skills is stated once in `CLAUDE.md`.
22
7
 
23
8
  ## Identity
24
9
 
25
- {{APP_NAME}} inherits the basalt-ui identity verbatim: modern zinc surfaces, one earned saturated
26
- sky-blue accent, the `shadow-card` / `shadow-raised` / `shadow-overlay` depth split,
27
- dense-by-default spacing, and the three-font system. The
28
- law itself — every hex, role split, and enforcement rule — lives in the `basalt-tokens` and
29
- `basalt-mantine` rules (`.claude/rules/basalt-{tokens,mantine}.md`) and `docs/DESIGN-SPEC.md` in
30
- the basalt-ui repo; it is **not** restated here. Confirm or restate any intentional identity shift
31
- below; **silence means "inherits the basalt-ui defaults unchanged."**
10
+ {{APP_NAME}} inherits the basalt-ui identity verbatim, and every value behind it is DERIVED from the
11
+ theme's config knobs (see the `basalt-tokens` rule) — so nothing here restates one. **Silence means
12
+ "inherits the basalt-ui defaults unchanged."**
32
13
 
33
- - **Accent hue:** {{ACCENT_HUE}} (default: the saturated sky accent — `var(--vx-line)` neutral is
34
- still the default for single-series marks)
35
- - **Tone deltas:** _(none — inherits)_
14
+ - **Accent hue:** {{ACCENT_HUE}} (default: the shipped seed; a single-series mark stays neutral)
15
+ - **Theme knobs:** _(none — `createBasaltTheme()` bare)_ · record any non-zero `derive`/`fonts`/
16
+ `radius`/`density` level here, with its reason.
36
17
 
37
18
  ## Series dictionary
38
19
 
39
- The framework owns the **roles** and the **available hues** (see the `basalt-tokens` rule). This
40
- table is the app's **data dictionary** — which metric maps to which hue, as `{light,dark}` pairs,
41
- wired through `defineSeries()`. This is the one design artifact that legitimately lives in the
42
- consumer; keep it the single source of truth and never inline a hex elsewhere.
20
+ The framework owns the roles and the available hues; this table is the app's **data dictionary** —
21
+ which metric maps to which hue, as `{ light, dark }` pairs wired through `defineSeries()`. It is the
22
+ one design artifact that legitimately lives in the consumer; never inline a color elsewhere.
43
23
 
44
- | Series name | Light hex | Dark hex | `defineSeries` key | Role / earned reason |
45
- |-|-|-|-|-|
46
- | _e.g. requests_ | `#4f78a4` | `#7099c4` | `requests` | Primary metric (earned accent hue) |
47
- | | | | | |
24
+ | Series name | `defineSeries` key | Role / earned reason |
25
+ | ---------------- | ------------------ | ---------------------------------- |
26
+ | _e.g. requests_ | `requests` | Primary metric (earned trend hue) |
48
27
 
49
28
  ```ts
50
29
  // {{SERIES_MODULE_PATH}} — the app's guard-exempt series file
51
30
  import { defineSeries, groupTokens } from 'basalt-ui/tokens'
52
-
53
31
  const SERIES_MAP = defineSeries({
54
- // requests: { light: '#4f78a4', dark: '#7099c4' },
32
+ // requests: { light: '…', dark: '…' }, // deeper on light, lighter on dark — never one value
55
33
  })
56
-
57
- export const series = groupTokens('app', SERIES_MAP) // { requests: 'var(--vx-app-requests)', ... }
58
- export const paletteGroups = { 'app-': SERIES_MAP }
59
- // wire into the provider: <BasaltProvider paletteOptions={{ groups: paletteGroups }} .../>
60
- // (paletteOptions takes the group map directly — not a CSS string; read `series.requests` in charts)
34
+ export const series = groupTokens('app', SERIES_MAP) // { requests: 'var(--vx-app-requests)', … }
35
+ export const paletteGroups = { 'app-': SERIES_MAP } // the trailing dash is a CSS-var prefix
36
+ // <BasaltProvider paletteOptions={{ groups: paletteGroups }} …/> — then charts read `series.requests`
61
37
  ```
62
38
 
63
- Rules for this table (from the `basalt-tokens` / `basalt-charts` rules — do not relax):
64
- - One hue per series, drawn from the identity families only. Never raw Material/AntD/Tailwind.
65
- - A series earns a color only for **trend**, **signal/status**, or **categorical separation**.
66
- A lone single-series metric stays neutral (`var(--vx-line)`).
67
- - Light is one shade **deeper**, dark one shade **lighter** — same hue, never the same hex.
39
+ A series earns a color only for trend, signal/status or categorical separation. `/basalt-design` is the tuning loop; `/basalt-charts` is the wiring.
68
40
 
69
41
  ## App deviations
70
42
 
71
- Genuine, intentional departures from the basalt-ui defaults — each with a one-line justification. An
72
- empty section is the correct default; do not invent deviations to fill it.
43
+ Intentional departures from the defaults, each with a one-line justification. Empty is correct.
73
44
 
74
45
  - _(none yet)_