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
package/README.md CHANGED
@@ -154,9 +154,9 @@ palette. Skip both — `BasaltOverlays` and `paletteGroups` — if you don't use
154
154
  > need it — there is no hydration to mismatch. See `docs/MANTINE-THEMING.md`.
155
155
 
156
156
  ```tsx
157
- // Shell — sidebar / mobile-nav / breadcrumbs / page-header; router-agnostic
158
- import { BasaltShell, NavCountBadge } from 'basalt-ui'
159
- import type { SidebarSection } from 'basalt-ui'
157
+ // Shell — sidebar / mobile nav / breadcrumbs / PageBar; router-agnostic
158
+ import { BasaltShell, NavCountBadge, PageBar } from 'basalt-ui'
159
+ import type { SidebarBlock, SidebarSection } from 'basalt-ui'
160
160
  ```
161
161
 
162
162
  ```ts
@@ -215,7 +215,7 @@ scanned files, and `doctor`'s `guard-scan` check agrees with it. Each root's **p
215
215
  contributes its `index.html` and its `public/` tree — the Vite layout `basaltViteConfig` assumes,
216
216
  and where a raw `theme-color` or webmanifest `background_color` actually lives. `.json` is never
217
217
  blanket-scanned; `include` names one explicitly and is the only route to it. `profile:
218
- "tokens-only"` turns off the 17 kinds whose remedy is a Mantine component or prop, and must be
218
+ "tokens-only"` turns off the 19 kinds whose remedy is a Mantine component or prop, and must be
219
219
  declared (or `--tokens-only`) — it is deliberately never inferred from a missing `@mantine/core`,
220
220
  which would silence half the guard on any repo keeping Mantine in a different workspace package.
221
221
  Other keys (`exempt`, `severity`, `spacingSteps`, `forbiddenAccents`, …) are documented on the
@@ -292,31 +292,31 @@ deleted rather than silently surviving into the next real skew.
292
292
 
293
293
  ## Subpath exports
294
294
 
295
- | Subpath | Mantine? | Purpose |
296
- | ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
297
- | `.` | coupled | `BasaltProvider`, `createBasaltTheme` / `baseTheme` / `cssVariablesResolver`, `BasaltShell` + sidebar / mobile-nav / breadcrumbs / page-header, `NavCountBadge`, `SidebarAccount` + the provider-agnostic account contract (`BasaltAccountProps`/`State`/`Actions`), `ThreadWorkspace` + thread-chat components, dashboard composites (`DeltaBadge`, `StatCard`, `EmptyState`, `SettingsSection`/`SettingsRow`/`DangerZone`) and the query-branch trio `QueryState` / `LoadingState` / `ErrorState`, shell types — the root entry still re-exports the thread-chat components; `./agent-chat` takes the same components standalone, without `BasaltProvider`, the shell, the dashboard composites, or `./connectivity` |
298
- | `./charts` | **free** | `CartesianChart` + visx primitives, kinds, sparklines, hooks, token re-exports |
299
- | `./tokens` | **free** | `VX` token refs, `buildPaletteCss`, `defineSeries`, `seriesTokens`, `groupTokens`, `alpha`, `ColorPair` / `SeriesMap` types |
300
- | `./theme-lab` | coupled | `ThemeLabControls`, `applyOverrides`, `loadOverrides`, `COLOR_GROUPS` for live theme inspection |
301
- | `./vite` | — | `basaltViteConfig(opts)` — Vite preset for basalt-ui consumer apps; `basaltAppPlugin(opts)` — PWA head, manifest, and icon metadata derived from the token palette |
302
- | `./guard` | **free** | `checkSource`, `GUARD_RULES`, `Finding` types — the headless theme-guard core; plus the annotation reader `--audit-allows` is built on: `findAllowAnnotations`, `neutralizeAllowAnnotation`, `NEUTRALIZED_ALLOW_TOKEN`, `PLUGIN_RULE_IDS`, `AllowAnnotationSite` |
303
- | `./query` | **free** | `createBasaltQueryClient`, transport-agnostic `unwrap`, lazy `BasaltQueryDevtools`, `toErrorMessage` / `errorStatus` — the only route to those two; the root barrel does not re-export them |
304
- | `./router-tanstack` | **free** | TanStack Router bridge: `useBasaltNav` (active route) + `useRouterBreadcrumbs` |
305
- | `./forms` | coupled | Mantine form adapter: `useBasaltForm`, `field`, `FormErrorSummary`, `useFormDraft` (Standard Schema) |
306
- | `./notifications` | coupled | Mantine notifications: `notify` helpers, typed registry, persisted history, `NotificationBell` |
307
- | `./commands` | coupled | Typed command bus + overlay controller, `toSpotlightActions`, `ShortcutsHelp`, `BasaltOverlays` |
308
- | `./data` | coupled | Convenience barrel pulling both peer groups: `BasaltDataTable`, `BasaltVirtualList` (Mantine-rendered) — prefer `./data/table` / `./data/virtual` for per-feature opt-in |
309
- | `./data/table` | coupled | `BasaltDataTable` — sortable data table over TanStack Table, rendered with Mantine |
310
- | `./data/virtual` | coupled | `BasaltVirtualList` — windowed virtual list over TanStack Virtual, rendered with Mantine |
311
- | `./agent` | **free** | Headless streaming layer (`useAgentStream`, `PartList`) + multi-thread `createThreadsStore` / `useAgentThreadRuns` / outcome seam |
312
- | `./agent-chat` | coupled | Mantine-styled thread-chat components over `./agent`: `ThreadWorkspace`, `ThreadFeed` (`variant`/`renderRow`), `ThreadFeedRow` (inline-expanding Slack row, lazily mounted + kept mounted), `ThreadOutcomeCard`, `ThreadDetailPanel`, `Composer`, `ThreadTranscript` (`groupConsecutive`/`affordances`/`virtualize` — windowed transcripts open at the newest message unless `initialScroll: 'start'`; `virtualize` is enabled by the optional `@tanstack/react-virtual` peer, absent it degrades to an unwindowed, height-bound pane), `threadPartRenderers` — **requires `motion`**, not merely an optional peer; see [Requirements](#requirements) |
313
- | `./state` | **free** | `createPersistedState` (versioned localStorage) + `useOnlineStatus` — Mantine-free state primitives |
314
- | `./connectivity` | coupled | `ConnectivityProvider` (aggregates browser/React-Query/SSE/health-check status), `useConnectivity`, `ConnectivityIndicator` — auto-mounted by `BasaltProvider` |
315
- | `./content` | coupled | `Prose`, `CodeBlock`, `Callout`, `TableOfContents`, `ReadingProgress`, `Markdown`, `MermaidDiagram`, `ArticleLayout`, `ArticleCard` / `ArticleGrid`, `GuideLink` / `GuideDrawer`, `mdxComponents` |
316
- | `./styles.css` | — | `@layer basalt` base styles, iOS input safety net, font stack |
317
- | `./tokens.css` | **free** | Prebuilt `--vx-*` stylesheet — the default `buildPaletteCss()` output as a plain file, for a consumer with a bundler but no React or Mantine. **With no bundler, use the `basalt-ui tokens:css` CLI instead** and commit the emitted file: importing this subpath needs bare-specifier resolution. See [`docs/FRAMEWORK-FREE.md`](https://github.com/jkrumm/basalt-ui/blob/master/docs/FRAMEWORK-FREE.md) |
318
- | `./configs/*` | — | Raw toolchain presets — oxlint, oxfmt, tsconfig (base/react-app/node), lefthook |
319
- | `./llms.txt` | — | Machine-readable surface map — one entry per published subpath with import specifier, description, layer, and optional peers |
295
+ | Subpath | Mantine? | Purpose |
296
+ | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
297
+ | `.` | coupled | `BasaltProvider`, `createBasaltTheme` / `baseTheme` / `cssVariablesResolver`, `BasaltShell` + sidebar / mobile nav / breadcrumbs / `PageBar`, `Section` / `WidgetHeader`, `NavCountBadge`, `SidebarAccount` + the provider-agnostic account contract (`BasaltAccountProps`/`State`/`Actions`), `ThreadWorkspace` + thread-chat components, dashboard composites (`DeltaBadge`, `StatCard`, `EmptyState`, `SettingsSection`/`SettingsRow`/`DangerZone`) and the query-branch trio `QueryState` / `LoadingState` / `ErrorState`, shell types — the root entry still re-exports the thread-chat components; `./agent-chat` takes the same components standalone, without `BasaltProvider`, the shell, the dashboard composites, or `./connectivity` |
298
+ | `./charts` | **free** | `CartesianChart` + visx primitives, kinds, sparklines, hooks, token re-exports |
299
+ | `./tokens` | **free** | `VX` token refs, `buildPaletteCss`, `defineSeries`, `seriesTokens`, `groupTokens`, `alpha`, `ColorPair` / `SeriesMap` types |
300
+ | `./theme-lab` | coupled | `ThemeLabControls`, `applyOverrides`, `loadOverrides`, `COLOR_GROUPS` for live theme inspection |
301
+ | `./vite` | — | `basaltViteConfig(opts)` — Vite preset for basalt-ui consumer apps; `basaltAppPlugin(opts)` — PWA head, manifest, and icon metadata derived from the token palette |
302
+ | `./guard` | **free** | `checkSource`, `GUARD_RULES`, `Finding` types — the headless theme-guard core; plus the annotation reader `--audit-allows` is built on: `findAllowAnnotations`, `neutralizeAllowAnnotation`, `NEUTRALIZED_ALLOW_TOKEN`, `PLUGIN_RULE_IDS`, `AllowAnnotationSite` |
303
+ | `./query` | **free** | `createBasaltQueryClient`, transport-agnostic `unwrap`, lazy `BasaltQueryDevtools`, `toErrorMessage` / `errorStatus` — the only route to those two; the root barrel does not re-export them |
304
+ | `./router-tanstack` | **free** | TanStack Router bridge: `defineNav`/`navGroup`/`navTarget`/`flattenNav` + `useNav` (→ `{ sections, mobileNav }`), `useBasaltNav` (active route), `useRouterBreadcrumbs`, and `createSearchStore` + the `field.*` vocabulary |
305
+ | `./forms` | coupled | Mantine form adapter: `useBasaltForm`, `field`, `FormErrorSummary`, `useFormDraft` (Standard Schema) |
306
+ | `./notifications` | coupled | Mantine notifications: `notify` helpers, typed registry, persisted history, `NotificationBell` |
307
+ | `./commands` | coupled | Typed command bus + overlay controller, `toSpotlightActions`, `ShortcutsHelp`, `BasaltOverlays` |
308
+ | `./data` | coupled | Convenience barrel pulling both peer groups: `BasaltDataTable`, `BasaltVirtualList` (Mantine-rendered) — prefer `./data/table` / `./data/virtual` for per-feature opt-in |
309
+ | `./data/table` | coupled | `BasaltDataTable` — sortable data table over TanStack Table, rendered with Mantine |
310
+ | `./data/virtual` | coupled | `BasaltVirtualList` — windowed virtual list over TanStack Virtual, rendered with Mantine |
311
+ | `./agent` | **free** | Headless streaming layer (`useAgentStream`, `PartList`) + multi-thread `createThreadsStore` / `useAgentThreadRuns` / outcome seam |
312
+ | `./agent-chat` | coupled | Mantine-styled thread-chat components over `./agent`: `ThreadWorkspace`, `ThreadFeed` (`variant`/`renderRow`), `ThreadFeedRow` (inline-expanding Slack row, lazily mounted + kept mounted), `ThreadOutcomeCard`, `ThreadDetailPanel`, `Composer`, `ThreadTranscript` (`groupConsecutive`/`affordances`/`virtualize` — windowed transcripts open at the newest message unless `initialScroll: 'start'`; `virtualize` is enabled by the optional `@tanstack/react-virtual` peer, absent it degrades to an unwindowed, height-bound pane), `threadPartRenderers` — **requires `motion`**, not merely an optional peer; see [Requirements](#requirements) |
313
+ | `./state` | **free** | 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 |
314
+ | `./connectivity` | coupled | `ConnectivityProvider` (aggregates browser/React-Query/SSE/health-check status), `useConnectivity`, `ConnectivityIndicator` — auto-mounted by `BasaltProvider` |
315
+ | `./content` | coupled | `Prose`, `CodeBlock`, `Callout`, `TableOfContents`, `ReadingProgress`, `Markdown`, `MermaidDiagram`, `ArticleLayout`, `ArticleCard` / `ArticleGrid`, `GuideLink` / `GuideDrawer`, `mdxComponents` |
316
+ | `./styles.css` | — | `@layer basalt` base styles, iOS input safety net, font stack |
317
+ | `./tokens.css` | **free** | Prebuilt `--vx-*` stylesheet — the default `buildPaletteCss()` output as a plain file, for a consumer with a bundler but no React or Mantine. **With no bundler, use the `basalt-ui tokens:css` CLI instead** and commit the emitted file: importing this subpath needs bare-specifier resolution. See [`docs/FRAMEWORK-FREE.md`](https://github.com/jkrumm/basalt-ui/blob/master/docs/FRAMEWORK-FREE.md) |
318
+ | `./configs/*` | — | Raw toolchain presets — oxlint, oxfmt, tsconfig (base/react-app/node), lefthook |
319
+ | `./llms.txt` | — | Machine-readable surface map — one entry per published subpath with import specifier, description, layer, and optional peers |
320
320
 
321
321
  Named exports only — no default exports.
322
322
 
@@ -542,12 +542,25 @@ bun add @tanstack/react-router
542
542
  ```
543
543
 
544
544
  ```tsx
545
- import { useBasaltNav, useRouterBreadcrumbs } from 'basalt-ui/router-tanstack'
545
+ import { createSearchStore, defineNav, field, useNav } from 'basalt-ui/router-tanstack'
546
546
 
547
- const { isActive } = useBasaltNav()
548
- const crumbs = useRouterBreadcrumbs()
547
+ export const NAV = defineNav({
548
+ groups: [
549
+ /* navGroup(...) */
550
+ ],
551
+ })
552
+ export const analytics = createSearchStore({
553
+ key: 'analytics',
554
+ fields: { range: field.range({ presets: ['7d', '30d'], fallback: '30d' }) },
555
+ })
556
+
557
+ const nav = useNav(NAV) // spread onto <BasaltShell {...nav} />
558
+ // route: validateSearch: analytics.validateSearch — nav link: search: analytics.linkSearch
549
559
  ```
550
560
 
561
+ `useBasaltNav` (active route) and `useRouterBreadcrumbs` ship here too. A control binds to
562
+ `analytics.field.range`, never to `value`/`onChange` — see `basalt-ui/controls`.
563
+
551
564
  ### `./forms` — Mantine form adapter
552
565
 
553
566
  ```bash
@@ -625,7 +638,7 @@ const columns = [
625
638
  - `emptyState` renders inside a `<td colSpan>`, so the header row survives an empty table. There is
626
639
  no mode that replaces the whole table.
627
640
 
628
- **Honest result:** porting argo's three tables onto these props made them 341 → **370** lines, 29
641
+ **Honest result:** porting argo's three tables onto these props made them 341 → **370–379** lines, 29–38
629
642
  longer. Column defs cost more than JSX rows when every cell is bespoke. Adopt the props for what
630
643
  they actually buy — the `type="native"` footgun, alignment stated once instead of on both `th` and
631
644
  `td`, and sorting/filtering/pagination owned by basalt — not for a line count.
@@ -0,0 +1,205 @@
1
+ ---
2
+ source: basalt-ui
3
+ description: The adapter batteries — query, forms, notifications, commands, data, content, agent — plus app bootstrapping (vite preset, CSS layer order, init/sync). One doctrine each: use the shipped seam, don't re-roll it.
4
+ paths:
5
+ - 'src/**'
6
+ - 'apps/**/src/**'
7
+ - '**/vite.config.ts'
8
+ - '**/index.html'
9
+ ---
10
+
11
+ <!-- basalt:coverage -->
12
+ <!-- GENERATED from src/surfaces.ts — `basalt-ui check-coverage --write`. Do not hand-edit. -->
13
+ <!-- backed by: guard kinds — none · oxlint rules — basalt/agent-no-raw-usechat, basalt/agent-resume-guard, basalt/ai-sdk-major -->
14
+ <!-- not guarded: — -->
15
+ <!-- /basalt:coverage -->
16
+
17
+ # Basalt Batteries — the adapter surfaces
18
+
19
+ Each battery is a subpath with **one** doctrine: the shipped seam is the seam, and re-rolling it is
20
+ the failure this rule exists to prevent. Every peer named below is OPTIONAL — install it before
21
+ importing the battery. The API is in the shipped types, the JSDoc and `llms.txt` at the install
22
+ directory; what follows is only what a reader gets wrong without being told.
23
+
24
+ ## `./query` — TanStack Query
25
+
26
+ `@tanstack/react-query` is a REQUIRED peer (`BasaltProvider` needs it at build time). Import the
27
+ hooks from `basalt-ui/query`, never dual-import from `@tanstack/react-query`.
28
+
29
+ - **One factory per resource**, keyed `[resource, action, ...params]`, built with `queryOptions`;
30
+ components import the factory, never a raw string key.
31
+ - **`queryFn` wraps the call in `unwrap()`** — it throws on the error branch AND on a `null` data
32
+ with no error (a 204, a silent transport failure). Both are caller bugs, so both must surface.
33
+ - **Render a query with `QueryState`, don't write the switch.** Rendering only the empty branch is
34
+ the bug it retires: a consumer's library showed `No images` on a 500 because nobody wrote the other
35
+ three branches. Precedence is error-without-data → empty → loading → children, plus a cached-data
36
+ banner when a refresh fails over existing data. `LoadingState`/`ErrorState` ship beside it for a
37
+ page that must place the branches in different DOM positions.
38
+ - **`QueryState` takes `useQuery`, not `useSuspenseQuery`** — a suspense read never hands you an
39
+ undefined `data` or an error branch, so there is nothing to decide. Suspense + an error boundary
40
+ and `QueryState` are the two ways to render a query; pick ONE per read. Inside a
41
+ loader-prefetched route, `useSuspenseQuery` + `ensureQueryData` is the pair.
42
+ - **`query` is a structural subset checked at runtime** — a composed or hand-rolled result passes
43
+ with no cast, and a missing `isError` throws naming the field rather than rendering a false claim.
44
+ - Decode an unknown error with `toErrorMessage(err, fallback?)` / `errorStatus(err)`; invalidate by
45
+ resource PREFIX after a mutation; `BasaltQueryDevtools` is lazy and production-excluded, so import
46
+ it unconditionally and never the raw devtools package.
47
+
48
+ ## Eden Treaty — three silent `any`s (stated once)
49
+
50
+ Stated here because they reach both `./query`'s `unwrap` and `./agent`'s transport. Three
51
+ patterns silently degrade Treaty inference to `any`, with no TypeScript error at the point of
52
+ breakage: a route that is not method-chained back onto the app instance; client and server packages
53
+ whose tsconfig path aliases do not resolve identically; and a `t.Object`/`t.Union` **response schema
54
+ on a streaming route** (Eden #231, still open). For a stream: no response schema, an explicit
55
+ `: AsyncGenerator<AgentPart>` return annotation, and validation at yield time via `parseAgentPart`.
56
+ Check these three first whenever Eden types regress.
57
+
58
+ ## `./forms` — `@mantine/form`
59
+
60
+ - **`useBasaltForm` is the entry**: `mode: 'uncontrolled'` by default (no per-keystroke re-render)
61
+ and `schemaResolver(schema, { sync: true })` when a `schema` is passed. Type a schema param as
62
+ `StandardSchemaV1` from `basalt-ui`, never `ZodSchema` — Valibot, Zod 4, ArkType and Effect all
63
+ satisfy it structurally.
64
+ - **`validate` is deliberately omitted** from its options — a cross-field or async rule that the
65
+ schema cannot express drops to a raw `useForm`.
66
+ - **`field(form, path)` is the one spread** every uncontrolled Mantine field needs (it bundles
67
+ `getInputProps` + `key`); writing the two by hand is how one gets forgotten.
68
+ - **`FormErrorSummary` goes at the TOP of the form** so assistive tech lands on it after a failed
69
+ submit. It renders `null` on a clean form, so include it unconditionally.
70
+ - **`useFormDraft` persists through `createPersistedState`** — wire `saveDraft` into
71
+ `onValuesChange` for autosave, call `clearDraft()` in the success path, keep `key` stable across
72
+ renders (changing it recreates the store and loses the draft) and bump `version` on a shape change.
73
+ - Errors are inline field errors; a toast is for submit-level failure only.
74
+
75
+ ## `./notifications` — `@mantine/notifications`
76
+
77
+ - **`notify({ intent, message })` is the surface**, plus the four `notifySuccess/Error/Warning/Info`
78
+ wrappers and `notifyPromise` for an async operation. Intent — not a color — is what maps to the
79
+ Mantine color, the correct aria `role` (polite vs assertive) and the per-intent `autoClose`, so
80
+ **never pass a raw color instead of an intent**.
81
+ - **`defineNotifications` + `emit` is the typed registry**: augment `BasaltRegister.notifications`
82
+ once and unknown kinds become tsc errors. `defineNotification` only TYPES a single spec — only
83
+ `defineNotifications(map)` registers, and `emit` before that registration is a runtime no-op.
84
+ - **A spec carries no `title`/`icon`** by design; those stay per-call so a varying title is not
85
+ forced into the registry.
86
+ - **Colors beyond the four intents live OUTSIDE the registry** — call Mantine's raw
87
+ `notifications.show({ color })` directly, and accept the stated cost: a raw toast is invisible to
88
+ the persisted history, the bell and the center, because only `notify`/`notifyPromise` record.
89
+ - History is a module-level persisted ring buffer; `useNotificationHistory` reads it, and
90
+ `NotificationBell` (built for the shell's `globalActions`) opens a `NotificationCenter`, which also
91
+ mounts standalone in any Drawer or panel.
92
+ - The overlay MOUNT is basalt-mantine.md's — one place, one statement.
93
+
94
+ ## `./commands` — the command bus
95
+
96
+ - **`defineCommands` + `runCommand`, `defineOverlays` + `overlays.open`** — augment
97
+ `BasaltRegister` once per registry and unknown ids are tsc errors. The augmentation is type-only
98
+ and erased, so both factories stash their map at module scope: call the factory once, at app boot,
99
+ before anything runs a command.
100
+ - **A TS circular-inference footgun**: `overlays.open(...)` inside a `run` in the same module that
101
+ augments `BasaltRegister.commands` can make the map infer as `any`. Annotate that `run`'s return
102
+ type explicitly, or split overlay-opening commands into their own file.
103
+ - **Projections, not parallel lists**: `toSpotlightActions()` (respecting each command's `when()`),
104
+ `toShortcutList()` / `ShortcutsHelp` for display, `toHotkeyBindings()` / `useCommandHotkeys()` for
105
+ the live keys. Never hand-author a second list of the same commands.
106
+ - Reach for `openSpotlight`/`closeSpotlight` from `basalt-ui/commands`, not Mantine's global
107
+ `spotlight` — `BasaltOverlays` mounts against basalt's own store.
108
+ - **Imperative overlay vs route**: `overlays.open` for an ephemeral confirm or quick edit; a route
109
+ for anything that must be shareable, refreshable or back-button-addressable.
110
+
111
+ ## `./data` — TanStack Table + Virtual
112
+
113
+ - **Read the props before hand-rolling anything**: title/icon/subtitle + count, an `actions`
114
+ toolbar slot, `facets`, global search, client- or server-side pagination, column pinning, sticky
115
+ header, `maxHeight`/`minWidth`, per-column `meta.align` and `meta.numeral` are all there.
116
+ `meta.align` is a `ColumnMeta` augmentation, so a typo is a tsc error and a bad VALUE throws naming
117
+ the column — declare alignment once instead of on both the `th` and the `td`.
118
+ - **Adopt it for ownership, not for a line count.** One consumer's port ran 29–38 lines LONGER:
119
+ accessor blocks cost more than bespoke `<Table.Td>` rows. What it buys is the `type="native"`
120
+ scroll footgun, alignment stated once, and sorting/filtering/pagination no longer being yours.
121
+ A table with no sorting, no pagination, no filter and no alignment to declare is a layout grid —
122
+ adopting the component there buys nothing.
123
+ - **`manualPagination` imposes a contract on every other prop**: `data` becomes one server page
124
+ while the bar still claims `of N`, so sorting (`manualSorting` or `enableSorting={false}`),
125
+ filtering (`manualFiltering`) and the count (`rowCount`) must each be resolved explicitly.
126
+ Unresolved, it throws in dev naming every breach and degrades to the honest table in production.
127
+ Read the throw; don't work around it.
128
+ - **The escape hatch is the same subpath**: `useReactTable`/`flexRender`/the row models are
129
+ re-exported, so a bespoke table needs no direct `@tanstack/react-table` import — and it scrolls
130
+ through the identical `Table.ScrollContainer type="native"` node.
131
+ - **`BasaltVirtualList` above a few hundred rows**; below that a plain `Stack`/`Table` is cheaper.
132
+ It sets `useFlushSync: false` for React 19 itself — do not override it.
133
+
134
+ ## `./content` — prose, markdown, MDX
135
+
136
+ - **Three entries, one typography layer**: `Prose` (JSX children), `Markdown` (a string — CMS,
137
+ files, streamed model output), `mdxComponents` (an MDX pipeline's component map). `Markdown` wraps
138
+ its own `Prose`; MDX output does not, so wrap it or use `ArticleLayout`.
139
+ - **`streaming` is a RENDERING mode, not a trust claim.** `contentTrust` is the independent security
140
+ input and the sole input to the image-origin allowlist: **any surface rendering agent or model
141
+ output must pin `contentTrust="untrusted"`** — an auto-fetched image is the classic
142
+ prompt-injection exfiltration channel. Link/script hardening is always on, streaming or not, and
143
+ `sanitizeSchema` only ADDS to the default schema.
144
+ - **Every optional peer degrades, except one.** No `shiki` (or an unknown language) renders a plain
145
+ mono block; no `beautiful-mermaid` leaves the fence as code; `remend`'s streaming repair and the
146
+ markdown renderer resolve lazily. Six mermaid diagram types are covered — anything else is a
147
+ consumer escape hatch outside `Markdown`.
148
+ - **`ArticleLayout` is the docs frame** (meta header, sticky scroll-spy TOC, prev/next); every
149
+ linking content component takes `renderLink` as the router bridge and defaults to a plain anchor.
150
+ - **The Article model is data + pure operators**, generic over the taxonomy — the app declares which
151
+ categories and tags exist (`satisfies readonly Article<Cat, Tag>[]`, deliberately no factory).
152
+ Filter state is store fields, and the filter UI is `FilterSet`/`ViewTabs`/`MultiSelectFilter`,
153
+ re-exported here so a content-only consumer has one import path (basalt-controls.md).
154
+ - Build-time content tooling (`content-collections`) is a RECIPE, not a dependency; its vite plugin
155
+ must run FIRST because other transforms resolve its virtual module.
156
+
157
+ ## `./agent` + `./agent-chat` — streaming chat
158
+
159
+ - **`./agent` is headless and Mantine-free; the chrome is `./agent-chat`.** `./agent` ships no
160
+ markdown renderer at all (`agent → content` is lint-blocked), so `PartList` takes a
161
+ consumer-supplied `components.text`; basalt's own is `./content`'s `Markdown`.
162
+ - **Every switch over `AgentPart` ends with `default: return assertNever(part)`** — that is what
163
+ makes a new variant a compile error instead of a silently dropped part.
164
+ - **`aiSdkTransport` is the recommended default** (optional peer `ai`, resolved lazily on first
165
+ stream); `edenTransport` is the zero-extra-dependency alternative. Neither is deprecated. The
166
+ transport is an injected seam, so a mock is just an object with a `stream` generator.
167
+ - **`useAgentStream` runs ONE turn; `useAgentThreadRuns` runs N keyed by thread id.** Reach for the
168
+ latter for the many-short-chats shape, with `createThreadsStore` as its persisted registry.
169
+ - **basalt ships the outcome-resolver SEAM, never an LLM call.** `heuristicOutcome` is a demo
170
+ fallback; production derives `{ title, summary, status }` from the finished run. The outcome shape
171
+ is deliberately DIFFERENT from the transcript so raw prompts and thinking cannot leak into a feed.
172
+ - **Stream resumption is a client contract, not a backend**: a `StartPart` carries the token, the
173
+ transport implements `resume`, and a mount-time reconnect is attempted before an orphaned thread
174
+ falls back to interrupted. A transport that does neither behaves exactly as before.
175
+ - **Three lint guards protect this discipline** — an unguarded `resume`, a raw `useChat`, and an `ai`
176
+ major mismatch — and they honour `basalt-agent-allow`, never `theme-allow`. `doctor`'s
177
+ `ai-major-parity` is the cross-package counterpart, because a lint run only ever sees one
178
+ package's manifest.
179
+
180
+ ## App bootstrapping — `./vite`, CSS order, `init`
181
+
182
+ - **`basaltViteConfig` is config-only and always will be; `basaltAppPlugin` is the plugin half** —
183
+ spread it into your own `plugins` array. Keeping plugins out of the preset is why adopting it never
184
+ injects one you did not ask for. Position is free (`vite-plugin-pwa` self-orders by `enforce`); the
185
+ one real caveat is a plugin emitting files from its own `closeBundle` after the PWA plugin's.
186
+ - **CSS layer order is load-bearing**: every `@mantine/*/styles.layer.css` first, then
187
+ `basalt-ui/styles.css`. An unlayered Mantine import outranks `@layer basalt` regardless of
188
+ specificity.
189
+ - **Head, manifest and theme colors come from the token palette** — never hand-compute a
190
+ `theme-color` hex or an anti-FOUC background, and set `colorScheme` to whatever the app passes as
191
+ `defaultColorScheme` or you ship a flash of the wrong surface. Icons are bring-your-own: either
192
+ the six default filenames under `public/`, or an `icons` array naming what you actually have (an
193
+ entry reaches the head only when it names a `rel`; every entry becomes a manifest icon). The
194
+ service worker is opt-in and degrades to a warning without its peer.
195
+ - **`init` on an existing app is a lint-debt event, not a no-op** — the shipped preset turns on
196
+ whole oxlint plugins the repo was never linted against, so previously-clean code lands with real
197
+ findings. Triage them; do not blanket-disable a plugin. It also patches `basalt.roots` (everything
198
+ derives from it) and a `lint:basalt` script, and it KEEPS an existing `.oxlintrc.json`, which
199
+ leaves the whole basalt lint half off until you extend the preset.
200
+ - **The lefthook preset overrides YOU on a colliding key** — a `run:`/`glob:` under one of its
201
+ command names is discarded silently, while `env:`, `exclude:`, `skip:` and your own command names
202
+ merge; `BASALT_BIN` is the sanctioned seam. A broken `extends` target is a hard fail: lefthook
203
+ merges a missing target into ZERO commands and still exits 0.
204
+ - **Then run `doctor` and read it to the last line.** `SKIPPED` is a third outcome that exits
205
+ non-zero on its own — a check that could not run is not a check that passed.