basalt-ui 1.28.0 → 1.29.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 (455) hide show
  1. package/AGENTS.md +11 -18
  2. package/CHANGELOG.md +13 -0
  3. package/MIGRATING.md +151 -7
  4. package/README.md +23 -66
  5. package/agent/rules/basalt-batteries.md +59 -182
  6. package/agent/rules/basalt-charts.md +56 -75
  7. package/agent/rules/basalt-controls.md +38 -69
  8. package/agent/rules/basalt-mantine.md +58 -103
  9. package/agent/rules/basalt-state.md +42 -60
  10. package/agent/rules/basalt-tokens.md +46 -67
  11. package/agent/skills/basalt-app/SKILL.md +2 -0
  12. package/agent/templates/CLAUDE-block.md.tpl +14 -14
  13. package/configs/oxlint-plugin.js +70 -167
  14. package/configs/oxlint-plugin.test.ts +20 -186
  15. package/configs/oxlint.json +0 -1
  16. package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
  17. package/dist/agent/ai-sdk-transport.js +7 -4
  18. package/dist/agent/coalesce.js +1 -1
  19. package/dist/agent/merge.js +1 -1
  20. package/dist/agent/transport.d.ts +13 -2
  21. package/dist/agent/transport.d.ts.map +1 -1
  22. package/dist/agent/use-agent-thread-runs.d.ts +12 -1
  23. package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
  24. package/dist/agent/use-agent-thread-runs.js +20 -6
  25. package/dist/agent-chat/index.d.ts +1 -1
  26. package/dist/agent-chat/index.d.ts.map +1 -1
  27. package/dist/agent-chat/relative-time.js +1 -1
  28. package/dist/agent-chat/thread-detail-panel.js +1 -1
  29. package/dist/agent-chat/thread-feed-row.d.ts +32 -7
  30. package/dist/agent-chat/thread-feed-row.d.ts.map +1 -1
  31. package/dist/agent-chat/thread-feed-row.js +121 -86
  32. package/dist/agent-chat/thread-feed.d.ts +11 -1
  33. package/dist/agent-chat/thread-feed.d.ts.map +1 -1
  34. package/dist/agent-chat/thread-feed.js +10 -6
  35. package/dist/agent-chat/thread-message.js +1 -1
  36. package/dist/agent-chat/virtualize.d.ts +39 -0
  37. package/dist/agent-chat/virtualize.d.ts.map +1 -1
  38. package/dist/agent-chat/virtualize.js +15 -0
  39. package/dist/charts/index.d.ts +3 -3
  40. package/dist/charts/index.d.ts.map +1 -1
  41. package/dist/charts/index.js +8 -4
  42. package/dist/charts/kinds/BandStrip.js +1 -1
  43. package/dist/charts/kinds/Bars.d.ts +1 -3
  44. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  45. package/dist/charts/kinds/Bars.js +1 -1
  46. package/dist/charts/kinds/DualPanel.d.ts +3 -1
  47. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  48. package/dist/charts/kinds/DualPanel.js +1 -1
  49. package/dist/charts/kinds/StackedArea.js +1 -1
  50. package/dist/charts/kinds/ZonedLine.d.ts +1 -3
  51. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  52. package/dist/charts/primitives/Axes.d.ts +21 -2
  53. package/dist/charts/primitives/Axes.d.ts.map +1 -1
  54. package/dist/charts/primitives/Axes.js +34 -2
  55. package/dist/charts/primitives/ChartCard.d.ts +20 -4
  56. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  57. package/dist/charts/primitives/ChartCard.js +62 -33
  58. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  59. package/dist/charts/primitives/ChartFrame.js +8 -1
  60. package/dist/charts/primitives/ChartPending.d.ts +12 -6
  61. package/dist/charts/primitives/ChartPending.d.ts.map +1 -1
  62. package/dist/charts/primitives/ChartPending.js +1 -1
  63. package/dist/charts/utils/format.d.ts +5 -92
  64. package/dist/charts/utils/format.d.ts.map +1 -1
  65. package/dist/charts/utils/format.js +10 -80
  66. package/dist/cli/audit-allows.d.ts +24 -0
  67. package/dist/cli/audit-allows.d.ts.map +1 -0
  68. package/dist/cli/audit-allows.js +240 -0
  69. package/dist/cli/check-theme.d.ts +10 -0
  70. package/dist/cli/check-theme.d.ts.map +1 -0
  71. package/dist/cli/check-theme.js +139 -0
  72. package/dist/cli/doctor.d.ts +47 -0
  73. package/dist/cli/doctor.d.ts.map +1 -0
  74. package/dist/cli/doctor.js +197 -0
  75. package/dist/cli/guard-hook.d.ts +9 -0
  76. package/dist/cli/guard-hook.d.ts.map +1 -0
  77. package/dist/cli/guard-hook.js +93 -0
  78. package/dist/cli/help.d.ts +9 -0
  79. package/dist/cli/help.d.ts.map +1 -0
  80. package/dist/cli/help.js +53 -0
  81. package/dist/cli/index.d.ts +360 -270
  82. package/dist/cli/index.d.ts.map +1 -1
  83. package/dist/cli/index.js +85 -1901
  84. package/dist/cli/init.d.ts +4 -0
  85. package/dist/cli/init.d.ts.map +1 -0
  86. package/dist/cli/init.js +165 -0
  87. package/dist/cli/sync.d.ts +43 -0
  88. package/dist/cli/sync.d.ts.map +1 -0
  89. package/dist/cli/sync.js +206 -0
  90. package/dist/cli/tokens-css.d.ts +51 -0
  91. package/dist/cli/tokens-css.d.ts.map +1 -0
  92. package/dist/cli/tokens-css.js +165 -0
  93. package/dist/commands/define-commands.js +1 -1
  94. package/dist/commands/define-overlays.js +1 -1
  95. package/dist/commands/index.d.ts +2 -1
  96. package/dist/commands/index.d.ts.map +1 -1
  97. package/dist/commands/index.js +6 -0
  98. package/dist/commands/overlays-mount.d.ts +7 -9
  99. package/dist/commands/overlays-mount.d.ts.map +1 -1
  100. package/dist/commands/shell-bridge.d.ts +57 -0
  101. package/dist/commands/shell-bridge.d.ts.map +1 -0
  102. package/dist/commands/shell-bridge.js +32 -0
  103. package/dist/common/errors.d.ts +35 -0
  104. package/dist/common/errors.d.ts.map +1 -1
  105. package/dist/common/errors.js +29 -1
  106. package/dist/common/index.d.ts +2 -0
  107. package/dist/common/index.d.ts.map +1 -1
  108. package/dist/common/index.js +6 -0
  109. package/dist/common/is-dev.d.ts.map +1 -0
  110. package/dist/{motion/index.d.ts → common/motion.d.ts} +1 -1
  111. package/dist/common/motion.d.ts.map +1 -0
  112. package/dist/common/props.d.ts +1 -1
  113. package/dist/common/props.d.ts.map +1 -1
  114. package/dist/common/validate.js +1 -1
  115. package/dist/content/markdown.d.ts +3 -4
  116. package/dist/content/markdown.d.ts.map +1 -1
  117. package/dist/content/markdown.js +1 -4
  118. package/dist/controls/actions.d.ts +1 -1
  119. package/dist/controls/actions.d.ts.map +1 -1
  120. package/dist/controls/panel-row.js +1 -1
  121. package/dist/controls/range-filter.js +1 -1
  122. package/dist/dashboard/delta-badge.d.ts +18 -10
  123. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  124. package/dist/dashboard/delta-badge.js +33 -1
  125. package/dist/dashboard/empty-state.d.ts +1 -4
  126. package/dist/dashboard/empty-state.d.ts.map +1 -1
  127. package/dist/dashboard/empty-state.js +1 -2
  128. package/dist/dashboard/index.d.ts +4 -2
  129. package/dist/dashboard/index.d.ts.map +1 -1
  130. package/dist/dashboard/index.js +7 -1
  131. package/dist/dashboard/query-state.d.ts +4 -13
  132. package/dist/dashboard/query-state.d.ts.map +1 -1
  133. package/dist/dashboard/query-state.js +4 -7
  134. package/dist/{section → dashboard}/section.d.ts +2 -2
  135. package/dist/{section → dashboard}/section.d.ts.map +1 -1
  136. package/dist/{section → dashboard}/section.js +2 -2
  137. package/dist/dashboard/settings-section.d.ts +1 -1
  138. package/dist/dashboard/settings-section.d.ts.map +1 -1
  139. package/dist/dashboard/settings-section.js +1 -1
  140. package/dist/dashboard/stat-card.d.ts +1 -1
  141. package/dist/dashboard/stat-card.d.ts.map +1 -1
  142. package/dist/dashboard/stat-card.js +1 -1
  143. package/dist/dashboard/widget-header.d.ts.map +1 -0
  144. package/dist/data/data-table.js +2 -2
  145. package/dist/data/table.d.ts +1 -1
  146. package/dist/data/table.d.ts.map +1 -1
  147. package/dist/data/table.js +2 -16
  148. package/dist/data/virtual.d.ts +3 -3
  149. package/dist/dev-dock.d.ts +28 -0
  150. package/dist/dev-dock.d.ts.map +1 -0
  151. package/dist/dev-dock.js +85 -0
  152. package/dist/format/date.d.ts +62 -0
  153. package/dist/format/date.d.ts.map +1 -0
  154. package/dist/format/date.js +106 -0
  155. package/dist/format/index.d.ts +39 -0
  156. package/dist/format/index.d.ts.map +1 -0
  157. package/dist/format/index.js +54 -0
  158. package/dist/format/measure.d.ts +15 -0
  159. package/dist/format/measure.d.ts.map +1 -0
  160. package/dist/format/measure.js +16 -0
  161. package/dist/format/number.d.ts +69 -0
  162. package/dist/format/number.d.ts.map +1 -0
  163. package/dist/format/number.js +62 -0
  164. package/dist/format/shared.d.ts +27 -0
  165. package/dist/format/shared.d.ts.map +1 -0
  166. package/dist/format/shared.js +29 -0
  167. package/dist/forms/field.d.ts +4 -20
  168. package/dist/forms/field.d.ts.map +1 -1
  169. package/dist/forms/field.js +0 -4
  170. package/dist/forms/form-layout.d.ts +1 -1
  171. package/dist/forms/form-layout.d.ts.map +1 -1
  172. package/dist/forms/form-layout.js +1 -1
  173. package/dist/forms/index.d.ts +1 -1
  174. package/dist/forms/index.d.ts.map +1 -1
  175. package/dist/forms/index.js +1 -2
  176. package/dist/forms/use-form-submit.js +1 -1
  177. package/dist/guard/index.d.ts +2 -9
  178. package/dist/guard/index.d.ts.map +1 -1
  179. package/dist/guard/index.js +16 -26
  180. package/dist/guard/types.d.ts +8 -10
  181. package/dist/guard/types.d.ts.map +1 -1
  182. package/dist/index.d.ts +16 -11
  183. package/dist/index.d.ts.map +1 -1
  184. package/dist/index.js +23 -29
  185. package/dist/notifications/index.d.ts +5 -5
  186. package/dist/notifications/index.d.ts.map +1 -1
  187. package/dist/notifications/index.js +0 -2
  188. package/dist/notifications/mount-guard.d.ts.map +1 -1
  189. package/dist/notifications/mount-guard.js +2 -2
  190. package/dist/provider/connectivity-indicator.d.ts.map +1 -0
  191. package/dist/{connectivity → provider}/connectivity-provider.d.ts +1 -1
  192. package/dist/provider/connectivity-provider.d.ts.map +1 -0
  193. package/dist/{connectivity/types.d.ts → provider/connectivity-types.d.ts} +1 -1
  194. package/dist/provider/connectivity-types.d.ts.map +1 -0
  195. package/dist/provider/error-fallback.module.css +21 -0
  196. package/dist/provider/error-fallback.module.css.d.ts +9 -0
  197. package/dist/provider/index.d.ts +20 -36
  198. package/dist/provider/index.d.ts.map +1 -1
  199. package/dist/provider/index.js +42 -32
  200. package/dist/provider/lab-theme.js +1 -1
  201. package/dist/provider/use-connectivity.d.ts +3 -0
  202. package/dist/provider/use-connectivity.d.ts.map +1 -0
  203. package/dist/query-client.d.ts +63 -0
  204. package/dist/query-client.d.ts.map +1 -0
  205. package/dist/query-client.js +33 -0
  206. package/dist/{query/devtools.d.ts → query-devtools.d.ts} +2 -2
  207. package/dist/query-devtools.d.ts.map +1 -0
  208. package/dist/register.d.ts +17 -3
  209. package/dist/register.d.ts.map +1 -1
  210. package/dist/router-tanstack/index.d.ts +4 -7
  211. package/dist/router-tanstack/index.d.ts.map +1 -1
  212. package/dist/router-tanstack/index.js +1 -9
  213. package/dist/router-tanstack/nav.d.ts +1 -1
  214. package/dist/router-tanstack/nav.d.ts.map +1 -1
  215. package/dist/router-tanstack/search-store.d.ts +3 -6
  216. package/dist/router-tanstack/search-store.d.ts.map +1 -1
  217. package/dist/router-tanstack/use-nav.d.ts +1 -1
  218. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  219. package/dist/shell/app-breadcrumbs.d.ts +1 -1
  220. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  221. package/dist/shell/app-main.module.css +11 -0
  222. package/dist/shell/app-mobile-nav.d.ts +1 -1
  223. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  224. package/dist/shell/app-mobile-nav.js +24 -2
  225. package/dist/shell/app-sidebar.d.ts +1 -1
  226. package/dist/shell/app-sidebar.d.ts.map +1 -1
  227. package/dist/shell/app-sidebar.js +31 -2
  228. package/dist/shell/app-sidebar.module.css +10 -0
  229. package/dist/shell/index.d.ts +10 -4
  230. package/dist/shell/index.d.ts.map +1 -1
  231. package/dist/shell/index.js +13 -17
  232. package/dist/shell/mobile-nav-model.d.ts +1 -1
  233. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  234. package/dist/{nav/types.d.ts → shell/nav-types.d.ts} +1 -1
  235. package/dist/shell/nav-types.d.ts.map +1 -0
  236. package/dist/shell/page-aside.d.ts.map +1 -1
  237. package/dist/shell/page-aside.js +2 -29
  238. package/dist/shell/page-title.d.ts +14 -0
  239. package/dist/shell/page-title.d.ts.map +1 -0
  240. package/dist/shell/page-title.js +25 -0
  241. package/dist/shell/page-title.module.css +33 -0
  242. package/dist/shell/page-title.module.css.d.ts +11 -0
  243. package/dist/shell/sidebar-block-model.d.ts +1 -1
  244. package/dist/shell/sidebar-block-model.d.ts.map +1 -1
  245. package/dist/shell/sidebar-blocks.d.ts +1 -1
  246. package/dist/shell/sidebar-blocks.d.ts.map +1 -1
  247. package/dist/shell/use-breakpoint.d.ts +11 -0
  248. package/dist/shell/use-breakpoint.d.ts.map +1 -0
  249. package/dist/shell/use-breakpoint.js +33 -0
  250. package/dist/state/fields.d.ts +3 -19
  251. package/dist/state/fields.d.ts.map +1 -1
  252. package/dist/state/fields.js +6 -16
  253. package/dist/styles.css +36 -0
  254. package/dist/surfaces.d.ts +18 -41
  255. package/dist/surfaces.d.ts.map +1 -1
  256. package/dist/surfaces.js +46 -59
  257. package/dist/theme/index.d.ts.map +1 -1
  258. package/dist/{theme-toggle/index.d.ts → theme/theme-toggle.d.ts} +1 -1
  259. package/dist/theme/theme-toggle.d.ts.map +1 -0
  260. package/dist/{theme-toggle/index.js → theme/theme-toggle.js} +1 -1
  261. package/dist/tokens/index.d.ts +1 -0
  262. package/dist/tokens/index.d.ts.map +1 -1
  263. package/dist/tokens/index.js +10 -0
  264. package/dist/tokens/palette.d.ts +12 -0
  265. package/dist/tokens/palette.d.ts.map +1 -1
  266. package/dist/tokens/palette.js +13 -1
  267. package/dist/tokens.css +1 -0
  268. package/llms.txt +9 -24
  269. package/package.json +12 -16
  270. package/src/agent/ai-sdk-transport.ts +11 -4
  271. package/src/agent/coalesce.ts +1 -1
  272. package/src/agent/merge.ts +1 -1
  273. package/src/agent/transport.ts +15 -2
  274. package/src/agent/use-agent-thread-runs.ts +65 -15
  275. package/src/agent-chat/index.ts +6 -4
  276. package/src/agent-chat/relative-time.ts +1 -1
  277. package/src/agent-chat/thread-detail-panel.tsx +1 -1
  278. package/src/agent-chat/thread-feed-row.tsx +105 -30
  279. package/src/agent-chat/thread-feed.tsx +51 -25
  280. package/src/agent-chat/thread-message.tsx +1 -1
  281. package/src/agent-chat/virtualize.ts +48 -0
  282. package/src/charts/index.ts +7 -10
  283. package/src/charts/kinds/BandStrip.tsx +1 -1
  284. package/src/charts/kinds/Bars.tsx +2 -5
  285. package/src/charts/kinds/DualPanel.tsx +4 -3
  286. package/src/charts/kinds/StackedArea.tsx +1 -1
  287. package/src/charts/kinds/ZonedLine.tsx +1 -4
  288. package/src/charts/primitives/Axes.tsx +49 -0
  289. package/src/charts/primitives/ChartCard.tsx +49 -6
  290. package/src/charts/primitives/ChartFrame.tsx +5 -1
  291. package/src/charts/primitives/ChartPending.tsx +14 -8
  292. package/src/charts/utils/format.ts +17 -186
  293. package/src/cli/audit-allows.ts +371 -0
  294. package/src/cli/check-theme.ts +197 -0
  295. package/src/cli/doctor.ts +331 -0
  296. package/src/cli/guard-hook.ts +115 -0
  297. package/src/cli/help.ts +101 -0
  298. package/src/cli/index.ts +269 -3439
  299. package/src/cli/init.ts +247 -0
  300. package/src/cli/sync.ts +324 -0
  301. package/src/cli/tokens-css.ts +252 -0
  302. package/src/commands/define-commands.ts +1 -1
  303. package/src/commands/define-overlays.ts +1 -1
  304. package/src/commands/index.ts +10 -1
  305. package/src/commands/overlays-mount.tsx +8 -10
  306. package/src/commands/shell-bridge.ts +86 -0
  307. package/src/common/errors.ts +70 -0
  308. package/src/common/index.ts +2 -0
  309. package/src/common/props.ts +1 -1
  310. package/src/common/validate.ts +1 -1
  311. package/src/content/markdown.tsx +4 -8
  312. package/src/controls/actions.tsx +1 -1
  313. package/src/controls/panel-row.tsx +1 -1
  314. package/src/controls/range-filter.tsx +1 -1
  315. package/src/dashboard/delta-badge.tsx +74 -7
  316. package/src/dashboard/empty-state.tsx +2 -6
  317. package/src/dashboard/index.ts +11 -2
  318. package/src/dashboard/query-state.tsx +5 -18
  319. package/src/{section → dashboard}/section.tsx +4 -4
  320. package/src/dashboard/settings-section.tsx +2 -2
  321. package/src/dashboard/stat-card.tsx +2 -2
  322. package/src/data/data-table.tsx +3 -3
  323. package/src/data/table.ts +6 -25
  324. package/src/data/virtual-list.tsx +1 -1
  325. package/src/data/virtual.ts +3 -3
  326. package/src/dev-dock.tsx +159 -0
  327. package/src/format/date.ts +184 -0
  328. package/src/format/index.ts +69 -0
  329. package/src/format/measure.ts +31 -0
  330. package/src/format/number.ts +129 -0
  331. package/src/format/shared.ts +63 -0
  332. package/src/forms/field.ts +4 -24
  333. package/src/forms/form-layout.tsx +2 -2
  334. package/src/forms/index.ts +1 -2
  335. package/src/forms/use-form-submit.ts +1 -1
  336. package/src/guard/index.ts +30 -56
  337. package/src/guard/types.ts +7 -10
  338. package/src/index.ts +38 -29
  339. package/src/notifications/index.ts +5 -7
  340. package/src/notifications/mount-guard.ts +7 -9
  341. package/src/{connectivity → provider}/connectivity-indicator.tsx +1 -1
  342. package/src/{connectivity → provider}/connectivity-provider.tsx +1 -1
  343. package/src/provider/error-fallback.module.css +21 -0
  344. package/src/provider/error-fallback.module.css.d.ts +9 -0
  345. package/src/provider/index.tsx +71 -71
  346. package/src/provider/lab-theme.ts +1 -1
  347. package/src/{connectivity → provider}/use-connectivity.ts +3 -3
  348. package/src/query-client.ts +100 -0
  349. package/src/{query/devtools.tsx → query-devtools.tsx} +5 -4
  350. package/src/register.ts +20 -4
  351. package/src/router-tanstack/index.ts +4 -15
  352. package/src/router-tanstack/nav.ts +1 -1
  353. package/src/router-tanstack/search-store.ts +3 -4
  354. package/src/router-tanstack/use-nav.ts +1 -1
  355. package/src/shell/app-breadcrumbs.tsx +1 -1
  356. package/src/shell/app-main.module.css +11 -0
  357. package/src/shell/app-mobile-nav.tsx +28 -1
  358. package/src/shell/app-sidebar.module.css +10 -0
  359. package/src/shell/app-sidebar.tsx +31 -2
  360. package/src/shell/index.tsx +23 -28
  361. package/src/shell/mobile-nav-model.ts +1 -1
  362. package/src/shell/page-aside.tsx +2 -52
  363. package/src/shell/page-title.module.css +33 -0
  364. package/src/shell/page-title.module.css.d.ts +11 -0
  365. package/src/shell/page-title.tsx +57 -0
  366. package/src/shell/sidebar-block-model.ts +1 -1
  367. package/src/shell/sidebar-blocks.tsx +1 -1
  368. package/src/shell/use-breakpoint.ts +73 -0
  369. package/src/state/fields.ts +7 -43
  370. package/src/styles.css +36 -0
  371. package/src/surfaces.ts +45 -60
  372. package/src/theme/index.ts +7 -0
  373. package/src/{theme-toggle/index.tsx → theme/theme-toggle.tsx} +1 -1
  374. package/src/tokens/index.ts +10 -0
  375. package/src/tokens/palette.ts +12 -0
  376. package/dist/connectivity/connectivity-indicator.d.ts.map +0 -1
  377. package/dist/connectivity/connectivity-provider.d.ts.map +0 -1
  378. package/dist/connectivity/index.d.ts +0 -14
  379. package/dist/connectivity/index.d.ts.map +0 -1
  380. package/dist/connectivity/index.js +0 -8
  381. package/dist/connectivity/types.d.ts.map +0 -1
  382. package/dist/connectivity/use-connectivity.d.ts +0 -3
  383. package/dist/connectivity/use-connectivity.d.ts.map +0 -1
  384. package/dist/data/index.d.ts +0 -38
  385. package/dist/data/index.d.ts.map +0 -1
  386. package/dist/data/index.js +0 -24
  387. package/dist/motion/index.d.ts.map +0 -1
  388. package/dist/nav/types.d.ts.map +0 -1
  389. package/dist/notifications/overlay.d.ts +0 -20
  390. package/dist/notifications/overlay.d.ts.map +0 -1
  391. package/dist/notifications/overlay.js +0 -14
  392. package/dist/query/devtools.d.ts.map +0 -1
  393. package/dist/query/error-message.d.ts +0 -41
  394. package/dist/query/error-message.d.ts.map +0 -1
  395. package/dist/query/error-message.js +0 -30
  396. package/dist/query/index.d.ts +0 -47
  397. package/dist/query/index.d.ts.map +0 -1
  398. package/dist/query/index.js +0 -56
  399. package/dist/router-tanstack/field.d.ts +0 -14
  400. package/dist/router-tanstack/field.d.ts.map +0 -1
  401. package/dist/router-tanstack/field.js +0 -4
  402. package/dist/router-tanstack/multi-search-param-store.d.ts +0 -43
  403. package/dist/router-tanstack/multi-search-param-store.d.ts.map +0 -1
  404. package/dist/router-tanstack/multi-search-param-store.js +0 -30
  405. package/dist/router-tanstack/search-param-store.d.ts +0 -42
  406. package/dist/router-tanstack/search-param-store.d.ts.map +0 -1
  407. package/dist/router-tanstack/search-param-store.js +0 -25
  408. package/dist/section/index.d.ts +0 -2
  409. package/dist/section/index.d.ts.map +0 -1
  410. package/dist/section/index.js +0 -4
  411. package/dist/theme-toggle/index.d.ts.map +0 -1
  412. package/dist/utils/is-dev.d.ts.map +0 -1
  413. package/dist/widget-header/delta-badge.d.ts +0 -19
  414. package/dist/widget-header/delta-badge.d.ts.map +0 -1
  415. package/dist/widget-header/delta-badge.js +0 -36
  416. package/dist/widget-header/index.d.ts +0 -3
  417. package/dist/widget-header/index.d.ts.map +0 -1
  418. package/dist/widget-header/index.js +0 -8
  419. package/dist/widget-header/widget-header.d.ts.map +0 -1
  420. package/src/connectivity/index.ts +0 -19
  421. package/src/data/index.ts +0 -69
  422. package/src/notifications/overlay.tsx +0 -66
  423. package/src/query/error-message.ts +0 -70
  424. package/src/query/index.ts +0 -97
  425. package/src/router-tanstack/field.ts +0 -36
  426. package/src/router-tanstack/multi-search-param-store.ts +0 -106
  427. package/src/router-tanstack/search-param-store.ts +0 -100
  428. package/src/section/index.ts +0 -1
  429. package/src/widget-header/delta-badge.tsx +0 -77
  430. package/src/widget-header/index.ts +0 -10
  431. /package/dist/{utils → common}/is-dev.d.ts +0 -0
  432. /package/dist/{utils → common}/is-dev.js +0 -0
  433. /package/dist/{motion/index.js → common/motion.js} +0 -0
  434. /package/dist/{section → dashboard}/section.module.css +0 -0
  435. /package/dist/{section → dashboard}/section.module.css.d.ts +0 -0
  436. /package/dist/{widget-header → dashboard}/widget-header.d.ts +0 -0
  437. /package/dist/{widget-header → dashboard}/widget-header.js +0 -0
  438. /package/dist/{widget-header → dashboard}/widget-header.module.css +0 -0
  439. /package/dist/{widget-header → dashboard}/widget-header.module.css.d.ts +0 -0
  440. /package/dist/{connectivity → provider}/connectivity-indicator.d.ts +0 -0
  441. /package/dist/{connectivity → provider}/connectivity-indicator.js +0 -0
  442. /package/dist/{connectivity → provider}/connectivity-provider.js +0 -0
  443. /package/dist/{connectivity/types.js → provider/connectivity-types.js} +0 -0
  444. /package/dist/{connectivity → provider}/use-connectivity.js +0 -0
  445. /package/dist/{query/devtools.js → query-devtools.js} +0 -0
  446. /package/dist/{nav/types.js → shell/nav-types.js} +0 -0
  447. /package/src/{utils → common}/is-dev.ts +0 -0
  448. /package/src/{motion/index.ts → common/motion.ts} +0 -0
  449. /package/src/{section → dashboard}/section.module.css +0 -0
  450. /package/src/{section → dashboard}/section.module.css.d.ts +0 -0
  451. /package/src/{widget-header → dashboard}/widget-header.module.css +0 -0
  452. /package/src/{widget-header → dashboard}/widget-header.module.css.d.ts +0 -0
  453. /package/src/{widget-header → dashboard}/widget-header.tsx +0 -0
  454. /package/src/{connectivity/types.ts → provider/connectivity-types.ts} +0 -0
  455. /package/src/{nav/types.ts → shell/nav-types.ts} +0 -0
@@ -7,7 +7,7 @@ paths:
7
7
  ---
8
8
 
9
9
  <!-- basalt:coverage -->
10
- <!-- GENERATED from src/surfaces.ts — `basalt-ui check-coverage --write`. Do not hand-edit. -->
10
+ <!-- GENERATED from src/surfaces.ts — `bun scripts/check-coverage.ts --write`. Do not hand-edit. -->
11
11
  <!-- backed by: guard kinds — raw-selection-control · oxlint rules — basalt/bound-control-outside-home, basalt/control-outside-home, basalt/control-size-literal, basalt/hand-rolled-filter, basalt/responsive-twin -->
12
12
  <!-- not guarded: C1 as a cross-file law (a control placed in one file, its home declared in another) -->
13
13
  <!-- not guarded: hand-rolled section headings (argo writes `<Text fw={600} size="sm">` + children, which no AST heuristic matches without false positives) -->
@@ -55,21 +55,16 @@ C1's cross-file case, a hand-rolled section heading, C11 outside `BasaltDataTabl
55
55
  | `SettingsRow` | `control` | Mantine `md` | ONE form field, bound to a setting |
56
56
  | `FormRow` / `FormGroup` (`basalt-ui/forms`) | children | Mantine `md` | ONE form field or a labelled cluster |
57
57
 
58
- - **Inside `BasaltShell`** both `PageBar` rows are portals — row 1 into the header, row 2 into the
59
- shell's band above the scrollport — so where you write `<PageBar>` never moves it and nothing in
60
- the scrollport has chrome to clear: a sticky table head takes no offset. Without a shell both rows
61
- render in flow, sticky, with `title` + `icon` leading. The header height is a token, never state.
62
- - **The form row is a real home, and it keeps Mantine's own `md` tier.** A raw `Select` bound to a
63
- setting is the right answer in `SettingsRow.control`, and the `size` prop there is load-bearing
64
- rather than redundant. `SettingsRow` is not a tiered slot, and no filter or size rule applies to it.
65
- `FormRow`/`FormGroup` are the same home written for a `<form>` rather than a settings page — a
66
- control nested in either never reports either.
67
- - **A control's home is a decision about reach**: it belongs next to what it acts on. If it affects
68
- more than one widget it is promoted to the page bar; if it formats one widget it stays in that
69
- widget's header.
70
- - **An overlay rendered by the PARENT is exempt by FILENAME**: `control-outside-home` and
71
- `raw-selection-control` skip a file basenamed `*-{modal,drawer,popover,panel,form}.tsx`, since no
72
- scan of it sees the `<Modal>`. Outside that: `theme-allow-file control-outside-home — overlay`.
58
+ - **Inside `BasaltShell`** both `PageBar` rows are portals (header / the band above the scrollport)
59
+ — where you write `<PageBar>` never moves it. Without a shell both rows render in flow, sticky.
60
+ The header height is a token, never state.
61
+ - **The form row keeps Mantine's own `md` tier** — a raw `Select` in `SettingsRow.control` is the
62
+ right answer, `size` there is load-bearing. Neither `SettingsRow` nor `FormRow`/`FormGroup` is a
63
+ tiered slot; no filter or size rule applies to them.
64
+ - **A control's home is a decision about reach**: affects more than one widget → promote to the
65
+ page bar; formats one widget → stays in that widget's header.
66
+ - **An overlay rendered by the PARENT is exempt by FILENAME** — `*-{modal,drawer,popover,panel,form}.tsx`.
67
+ Outside that: `theme-allow-file control-outside-home — overlay`.
73
68
 
74
69
  ## The `ctl` tier — the home sizes the control
75
70
 
@@ -81,13 +76,11 @@ C1's cross-file case, a hand-rolled section heading, C11 outside `BasaltDataTabl
81
76
  | `touchControlHeight` | hit area below `sm` | every home |
82
77
  | `controlHeight` | `size="md"` | forms — unchanged |
83
78
 
84
- Each home wraps its **slot** — never its body — in a hoisted theme provider that defaults every
85
- Mantine control inside it to `size="ctl"`, plus a `data-basalt-tier` attribute. So a raw `Button`
86
- dropped into `PageBar.actions` is already the right height with no prop, and a `size="xs"` typed
87
- there is law C5 (`basalt/control-size-literal`). Mantine's own `sm`/`xs` are NOT re-pointed: a
88
- `size="sm"` in a modal or a form keeps Mantine's height. One exception, by boundary:
89
- `ChartCard.actions` lives inside the Mantine-free chart layer, so it cannot mount the slot theme —
90
- it carries only the tier attribute, and the basalt controls placed there size themselves.
79
+ Each home wraps its **slot** — never its body — in a hoisted theme provider defaulting every
80
+ Mantine control inside it to `size="ctl"`. A raw `Button` in `PageBar.actions` is already the right
81
+ height with no prop; a `size="xs"` typed there is law C5 (`basalt/control-size-literal`). Mantine's
82
+ own `sm`/`xs` are NOT re-pointed elsewhere. Exception: `ChartCard.actions` is Mantine-free, so it
83
+ carries only the tier attribute and its controls size themselves.
91
84
 
92
85
  ## Binding a control to a store
93
86
 
@@ -106,24 +99,21 @@ import { DateRangePicker } from 'basalt-ui/controls-dates'
106
99
  ```
107
100
 
108
101
  The store, its fields and their lanes are basalt-state.md. What this file adds: **the control takes
109
- `field` and nothing else about state.** No `value`, no `onChange`, no `size` — those props do not
110
- exist, so a wrong call site is a tsc error rather than a review comment. `ActionGroup`,
111
- `OverflowMenu` and `SyncButton` sit on the same subpath and take typed action DATA, never children.
102
+ `field` and nothing else about state** — no `value`, no `onChange`, no `size`, so a wrong call site
103
+ is a tsc error. `ActionGroup`/`OverflowMenu`/`SyncButton` take typed action DATA, never children.
112
104
 
113
- **The date picker is injected, never imported.** `basalt-ui/controls` resolves and renders with no
114
- `@mantine/dates` installed, which is what lets a consumer without a date picker use every other
115
- control; `DateRangePicker` comes from `basalt-ui/controls-dates` through `RangeFilter.customPicker`.
116
- Never import `@mantine/dates` from a shared module, and never reach for `DateInput`/`DatePickerInput`
117
- inside a home slot.
105
+ **The date picker is injected, never imported.** `basalt-ui/controls` resolves with no
106
+ `@mantine/dates` installed; `DateRangePicker` comes from `basalt-ui/controls-dates` through
107
+ `RangeFilter.customPicker`. Never import `@mantine/dates` from a shared module or reach for
108
+ `DateInput`/`DatePickerInput` inside a home slot.
118
109
 
119
110
  ## `FilterSet` owns the responsive story
120
111
 
121
112
  Do not build one at the call site. Above `sm`, `FilterSet` measures its own children and folds the
122
- tail into a `+N` pill rather than wrapping or scrolling. Below `sm`, the first `inline` children stay
123
- pills and one `Filters (n)` pill opens a bottom sheet where every child renders full-width, applies
124
- immediately, and answers one `Reset all`. **`n` and `Reset all` are DERIVED** from whether each field
125
- differs from its fallback — so adding a filter is one JSX line, with no count to maintain and no
126
- reset handler to extend.
113
+ tail into a `+N` pill. Below `sm`, the first `inline` children stay pills and one `Filters (n)`
114
+ pill opens a sheet where every child renders full-width and answers one `Reset all`. **`n` and
115
+ `Reset all` are DERIVED** from whether each field differs from its fallback — adding a filter is
116
+ one JSX line, no count or reset handler to maintain.
127
117
 
128
118
  | Below `sm` | What basalt does |
129
119
  | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
@@ -132,16 +122,14 @@ reset handler to extend.
132
122
  | a section header | title · count · one inline action, the rest in a kebab; tabs past three become a `Select` |
133
123
  | a widget header | value + delta wrap under the title; the sparkline drops to bleed; one `⋯` action |
134
124
 
135
- Every swap is CSS inside the control (one mount), never a JS media query — a media-query hook renders
136
- differently on the server than on the first client paint. Two mounts under
125
+ Every swap is CSS inside the control (one mount), never a JS media query. Two mounts under
137
126
  `visibleFrom`/`hiddenFrom` is `basalt/responsive-twin`.
138
127
 
139
128
  ## The aside
140
129
 
141
130
  `PageAside` is a shell REGION, not a fourth home — C1 still names three. Its body IS a home: it
142
- scopes its children to the `panel` surface, so the same `SelectFilter` that is a pill in the page
143
- bar is a ROW in the aside. Never two components and never a prop to pick between them — the surface
144
- is read from where the control is mounted.
131
+ scopes children to the `panel` surface, so the same `SelectFilter` that is a pill in the page bar
132
+ is a ROW in the aside — never two components, the surface is read from where it's mounted.
145
133
 
146
134
  | In the aside | Write |
147
135
  | ---------------- | --------------------------------------------------------------------------------- |
@@ -151,35 +139,16 @@ is read from where the control is mounted.
151
139
  | a group of rows | `Section` — flush inside the aside, which draws the rhythm itself |
152
140
  | a choice | `PanelChoice` — a track only while ≤3 options AND every label fits, else `Select` |
153
141
 
154
- - ONE `PageAside` per page, written AFTER the main content: it portals into the region from `sm` up,
155
- so its place in the tree is reading order, not layout.
156
- - Below `sm` it projects into `PageBar` row 2 as one `Panel` pill opening a sheet; with no row 2 —
157
- and in a shell-less app — it renders in flow where the page wrote it. One node either way (C9).
158
- - A bound control written outside all of this (a `Section` body, a bare page stack) is
159
- `basalt/bound-control-outside-home` — it renders as a stray pill. A slot prop, a `FilterSet`, a
160
- `PageAside` or a `PanelRow` is a home; nothing else is.
142
+ - ONE `PageAside` per page, written AFTER the main content — it portals into the region from `sm`
143
+ up, its tree position is reading order, not layout.
144
+ - Below `sm` it projects into `PageBar` row 2 as one `Panel` pill; with no row 2 — or shell-less —
145
+ it renders in flow. One node either way (C9).
146
+ - A bound control outside all of this is `basalt/bound-control-outside-home` — a slot prop, a
147
+ `FilterSet`, a `PageAside` or a `PanelRow` is a home; nothing else is.
161
148
 
162
149
  ## Sidebar blocks
163
150
 
164
151
  A non-destination list, a progress meter or a bespoke node in the sidebar is a `SidebarBlock` —
165
- declared data, three kinds (`list` / `progress` / `custom`), placed `'nav'` or `'bottom'`. Because it
166
- is data, basalt owns what a `ReactNode` slot could not express: a rail dot or ring when the desktop
167
- sidebar is collapsed, a persisted fold, and one More-sheet row per block on mobile opening a sheet of
168
- its items. A `custom` block is desktop-only, by design.
169
-
170
- ## Anti-patterns
171
-
172
- | Instead of | Write |
173
- | ------------------------------------------------------------ | ----------------------------------------------------- |
174
- | `useState` + a `SegmentedControl` in the page body | a store field + `ViewTabs` in the home's `tabs` slot |
175
- | a raw `Select`/`MultiSelect`/`Chip.Group` as a filter | `SelectFilter` / `MultiSelectFilter` |
176
- | a numeric threshold widened into a string enum | `field.number` + `NumberFilter` |
177
- | `<X visibleFrom="sm"/>` beside `<X hiddenFrom="sm"/>` | one control — the swap is already inside it |
178
- | `size="xs"` on a button in a home slot | nothing; the home sets the tier |
179
- | a hand-written `presetToParams` | `field.toWindow(value)` |
180
- | a hand-counted `Filters (3)` badge | nothing; `FilterSet` derives it |
181
- | `overflowX: 'auto'` on a filter row | nothing; the fold is basalt's |
182
- | a local `Section` / `PageHeader` / `RefreshButton` | `Section` / `PageBar` / `SyncButton` |
183
- | an in-body `<Title order={1}>` | the route's breadcrumb title |
184
- | the same on a shell-less surface (auth gate, error boundary) | `theme-allow in-body-page-title — shell-less surface` |
185
- | a hand-built joined button row | `ControlGroup` (or `group: true` on the actions) |
152
+ declared data, three kinds (`list`/`progress`/`custom`), placed `'nav'`/`'bottom'`. Because it's
153
+ data, basalt owns what a `ReactNode` slot couldn't: a rail dot/ring when collapsed, a persisted
154
+ fold, one More-sheet row per block. A `custom` block is desktop-only, by design.
@@ -7,8 +7,8 @@ paths:
7
7
  ---
8
8
 
9
9
  <!-- basalt:coverage -->
10
- <!-- GENERATED from src/surfaces.ts — `basalt-ui check-coverage --write`. Do not hand-edit. -->
11
- <!-- backed by: guard kinds — card-with-border, hidden-inline-style, in-body-page-title, inline-display, inline-spacing, mantine-shade-index, raw-form-control, raw-html-layout, raw-motion-value, raw-spacing, sub-16-input-font · oxlint rules — basalt/card-inset, basalt/deprecated-export, basalt/duplicate-notifications-mount, basalt/hand-rolled-shell, basalt/in-body-page-title, basalt/page-bar-budget, basalt/provider-above-router, basalt/raw-scroll-container, basalt/shadow-basalt-export -->
10
+ <!-- GENERATED from src/surfaces.ts — `bun scripts/check-coverage.ts --write`. Do not hand-edit. -->
11
+ <!-- backed by: guard kinds — card-with-border, hidden-inline-style, in-body-page-title, inline-display, inline-spacing, mantine-shade-index, raw-form-control, raw-html-layout, raw-motion-value, raw-spacing, sub-16-input-font · oxlint rules — basalt/card-inset, basalt/deprecated-export, basalt/hand-rolled-shell, basalt/in-body-page-title, basalt/no-import-meta-env, basalt/page-bar-budget, basalt/provider-above-router, basalt/raw-scroll-container, basalt/shadow-basalt-export -->
12
12
  <!-- not guarded: no second cssVariablesResolver — don't hand-build createTheme or re-add the resolver basalt already installs -->
13
13
  <!-- /basalt:coverage -->
14
14
 
@@ -30,15 +30,13 @@ the composition in its own client file.
30
30
 
31
31
  **Overlays mount exactly once, through `BasaltOverlays`** (`basalt-ui/commands`), inside
32
32
  `BasaltProvider`: it composes `ModalsProvider`, Spotlight (against basalt's own store), the command
33
- hotkeys and `<Notifications>` in one place, each disableable with `false`. `ModalsProvider` mounts as
34
- a **sibling** of `children`, not a wrapper — the imperative `modals.*`/`overlays.*` API works
35
- untouched (it runs over a window `CustomEvent` bus `ModalsProvider` subscribes to), but `useModals()`
36
- and `openContextModal` need real React context, which a sibling cannot provide: pass `modals={false}`
37
- and mount your own `ModalsProvider` instead when you need either. A standalone
38
- `<BasaltNotifications />` (`basalt-ui/notifications`) is the alternative for an app with no commands
39
- layer — **never both in one tree**, which double-mounts `<Notifications>`. Import the **layered**
40
- style bundles for every `@mantine/*` battery, then `basalt-ui/styles.css` last; an unlayered Mantine
41
- import outranks `@layer basalt` regardless of specificity.
33
+ hotkeys and `<Notifications>` in one place, each disableable with `false`. `ModalsProvider` mounts
34
+ as a **sibling** of `children`, not a wrapper — the imperative `modals.*`/`overlays.*` API works
35
+ untouched, but `useModals()`/`openContextModal` need real React context, which a sibling cannot
36
+ provide: pass `modals={false}` and mount your own `ModalsProvider` when you need either.
37
+ `<BasaltOverlays notifications />` is the ONE notifications mount; there is no standalone
38
+ component any more. Import the **layered** style bundle for every `@mantine/*` battery, then
39
+ `basalt-ui/styles.css` last.
42
40
 
43
41
  Color scheme is read and written through `useMantineColorScheme()` only — never a client store,
44
42
  never `localStorage.getItem('theme')` (guard-enforced; see basalt-state.md).
@@ -64,104 +62,61 @@ projection:
64
62
  | `account` | `BasaltAccountProps` | the footer row, below the settings menu |
65
63
 
66
64
  - **The router seam is ONE component, not a render callback.** `SidebarItem.Anchor` (a `NavAnchor`)
67
- is the consumer's `<Link>`; basalt renders every pixel around it — desktop row, mobile slot, sheet
68
- row. Absent, the row falls back to `<a href>` + `onClick`. The breadcrumb's equivalent is
69
- `parentAnchor`. There is no `renderNavLink` / `renderBreadcrumbLink` surface.
70
- - **`AppBreadcrumbs` is prop-driven** (`section` / `parent` / `parentAnchor` / `parentHref` /
71
- `page`) and renders `null` without `page`. `BasaltShell` derives those props from the active item;
72
- `useRouterBreadcrumbs` (basalt-state.md) is the router-side source for a custom trail.
73
- - **Collapse persists through basalt's own `createPersistedState`**, keyed `basalt:<storageKey>` —
74
- read it with `readPersistedValue`, never a bare `localStorage.getItem`. Pass `collapsed` +
75
- `onCollapsedChange` to own it instead (e.g. from your own hotkey — basalt binds none).
76
- - **The `.` entry is router-agnostic; a router adapter DOES ship**, at `./router-tanstack`. Badge /
77
- active / navigate wiring stays consumer-side either way; `NavCountBadge` is the count pattern.
78
- - **The account row is presentational over a provider-agnostic contract** — basalt has no auth
79
- dependency and ships no `./auth`. The authenticated row leads with initials derived from
80
- `identity.name` (never an avatar image); the unauthenticated row falls back to a person glyph.
81
- No separating hairline: the row's own top padding is the separation. The email is hidden unless
82
- `showEmail` is passed.
83
-
84
- **Mobile is a tab bar, not a menu.** A slot is a destination, so a tap navigates with nothing to
85
- dismiss; there is no full-height mobile drawer, and everything it used to hold reaches the trailing
86
- More slot. `projectMobileNav` is a PURE projection and infers the surface from row count — 0 drops
87
- the slot, 1 collapses to a plain link (a group of one IS a destination), up to `menuMax` is a menu,
88
- more is a bottom sheet. `SidebarItem.mobile` (`'tab'` / `'more'` / `'hidden'`) is the per-destination
89
- placement; `MobileNavConfig` is the escape hatch, not the interface. Two bar rules that look like
90
- bugs and are not: the active indicator is a **neutral** ink tint behind the ICON only (never the
91
- identity blue, never a full-tab fill — the desktop sidebar's accent-icon rule does not apply here),
92
- and the bar carries **no** `env(safe-area-inset-bottom)` padding, because Mantine's own
93
- `AppShell.Footer` rule already grows the box by the inset. Search lives in the SIDEBAR, not the
94
- header. Region edges come from `BasaltShell` itself — Mantine's `[data-with-border]`, coloured by
95
- `--app-shell-border-color` → `--vx-divider` through the theme — never draw a border on a shell
96
- region, a page-bar row or an aside panel, never pass `withBorder` to an `AppShell` section, and
97
- never set `--app-shell-border-color` yourself. `--vx-divider` is the only layout-line token; the
98
- sidebar's own internals still carry zero horizontal rules.
65
+ is the consumer's `<Link>`; absent, the row falls back to `<a href>` + `onClick`. The
66
+ breadcrumb's equivalent is `parentAnchor`. No `renderNavLink`/`renderBreadcrumbLink` surface.
67
+ - **`AppBreadcrumbs` is prop-driven** (`section`/`parent`/`parentAnchor`/`parentHref`/`page`),
68
+ renders `null` without `page`; `useRouterBreadcrumbs` (basalt-state.md) sources a custom trail.
69
+ - **Collapse persists through `createPersistedState`**, keyed `basalt:<storageKey>` — read with
70
+ `readPersistedValue`, never a bare `localStorage.getItem`.
71
+ - **The account row is presentational, provider-agnostic** — no `./auth`. Authenticated leads with
72
+ initials from `identity.name` (never an avatar image); unauthenticated falls back to a glyph.
73
+
74
+ **Mobile is a tab bar, not a menu.** A slot is a destination — a tap navigates with nothing to
75
+ dismiss, no full-height drawer. `projectMobileNav` is a PURE projection inferring the surface from
76
+ row count (0 drops the slot, 1 is a plain link, up to `menuMax` is a menu, more is a sheet).
77
+ `SidebarItem.mobile` (`'tab'`/`'more'`/`'hidden'`) is the per-destination placement. The active
78
+ indicator is a **neutral** ink tint behind the icon only (never the identity blue); the bar carries
79
+ **no** `env(safe-area-inset-bottom)` (Mantine's `AppShell.Footer` already grows the box). Region
80
+ edges come from `BasaltShell` itself (`--app-shell-border-color` → `--vx-divider`) — never draw a
81
+ border or pass `withBorder` to a shell region yourself.
99
82
 
100
83
  ## Surfaces, depth and shape
101
84
 
102
- - **Never pass `withBorder` to a `Card`/`Paper`.** Card depth is `VX.shadowCard`, which bakes its 1px
103
- ring into the shadow value; the theme pins bg/shadow/radius but does not clear `border`, so
104
- `withBorder` draws a second real edge and the card reads heavy. It is on-token, so only the
105
- `card-with-border` guard kind catches it. `<Card.Section withBorder>` is a section divider and fine.
106
- - **Three depth tiers, split control-vs-panel-vs-floating**, and the dividing line is that split —
107
- never component-by-component. `shadowCard` for surfaces that HOLD content (Card, Paper,
108
- Notification, ChartCard, SettingsSection): an outset ring on light, a deeper drop + inset rim on
109
- dark. `shadowRaised` for anything you click or type into: **never an outset ring**, because an
110
- outset ring paints in the color of whatever it is drawn over, so one value can ride a saturated
111
- fill, a tint and a panel alike. `shadowOverlay` for detached floating surfaces (Tooltip, Popover,
112
- Menu, Combobox, Modal, Drawer). Focus is a fourth tier and LAYERS over the resting depth.
113
- - **Emphasis is fill weight, not depth.** Every box-owning Button/ActionIcon variant takes the same
114
- resting depth; `outline` takes the ring-free `shadowCtrl` (its border is its edge) and `subtle` is
115
- **flat in both states** — a text affordance, with a neutral ink tint as its hover and nothing more.
116
- **Depth is static**: no variant lifts or gains a shadow on hover or press. A hover lift and a
117
- materializing `subtle` shadow were both tried and reverted.
118
- - **The ring must land on the box that carries the surface's `border-radius`** — it is drawn by that
119
- box's own corners. `ChartCard` legitimately puts the shadow on a box with no background (an inner
120
- box paints the fill, because `overflow: hidden` on the shadowed box would clip the shadow).
121
- - **Use Mantine primitives, not raw HTML** — `Box`/`Flex`/`Grid`/`SimpleGrid`/`Stack`/`Group`/
122
- `Paper`/`Card` instead of a `<div>` with an inline `style`. **The second sanctioned fix is a CSS
123
- module**, and in row code it is the right one: what the guard objects to is the inline literal, not
124
- the `<div>`, and a class reading `var(--vx-*)` costs no component instance per row. `check-theme`
125
- scans `.css`, so the tokens stay policed there. Only the Mantine-free `src/charts/**` may use raw
126
- `<div>` freely — still with `VX.*`.
127
- - **Inputs come from `@mantine/core`**, never a native `<select>`; the theme defaults them to the
128
- form tier, whose font size clears the iOS zoom threshold, and the floor in `styles.css` is
129
- `!important` (`sub-16-input-font` flags a smaller one as dead code against it).
130
- - **Charts measure themselves** — pass `height` / `aspectRatio` / `fill` and nothing else. Never
131
- `useElementSize` inside a chart file (Mantine is banned there), never raw `@visx/responsive`
132
- outside `charts/**`.
85
+ Depth-tier law (which token for which surface, why static, why the ring is inset) is
86
+ `docs/DESIGN-CORE.md` § Layout, elevation, shapes — not restated here. Mantine-specific facts only:
87
+
88
+ - **Never pass `withBorder` to a `Card`/`Paper`** — the theme pins bg/shadow/radius but does not
89
+ clear `border`, so `withBorder` draws a second real edge (`card-with-border` guard kind).
90
+ `<Card.Section withBorder>` is a section divider and fine.
91
+ - **The ring lands on the box carrying the surface's `border-radius`** — `ChartCard` puts the
92
+ shadow on a box with no background (an inner box paints the fill; `overflow: hidden` on the
93
+ shadowed box would clip the shadow otherwise).
94
+ - **Use Mantine primitives, not raw HTML** — `Box`/`Flex`/`Grid`/`Stack`/`Group`/`Paper`/`Card`
95
+ over a `<div>` with an inline style; a CSS module reading `var(--vx-*)` is the sanctioned second
96
+ fix in row code. Only Mantine-free `src/charts/**` uses raw `<div>` freely.
97
+ - **Inputs come from `@mantine/core`**, never a native `<select>` — the form tier's font size
98
+ clears the iOS zoom threshold via an `!important` floor (`sub-16-input-font` flags a smaller one).
99
+ - **Charts measure themselves** — pass `height`/`aspectRatio`/`fill` only; never `useElementSize`
100
+ in a chart file (Mantine is banned there), never raw `@visx/responsive` outside `charts/**`.
133
101
 
134
102
  ## Scroll regions
135
103
 
136
- **A scroll region inside app chrome is a Mantine `ScrollArea`, not a raw `overflow: auto` box** — it
137
- draws its own bar inside a `position: relative; overflow: hidden` root, so the bar floats instead of
138
- reserving gutter width and reflowing the column. `AppSidebar`'s nav is the reference (`type="hover"`,
139
- `scrollbars="y"`, the flex fill on the ScrollArea root). `styles.css` re-hides the native bar on the
140
- viewport, so **one overlay bar** is the framework-wide outcome — do not re-theme it.
141
-
142
- Raw `overflow: auto` stays correct where a library owns the scroll node (`BasaltStickToBottom`,
143
- `BasaltVirtualList`) and for a table body, which is `Table.ScrollContainer type="native"` — a
144
- `ScrollArea` viewport is the positioning context a sticky `<thead>` resolves against, so the default
145
- type pins the header to the page viewport instead of the table's box.
104
+ **A scroll region inside app chrome is a Mantine `ScrollArea`, not a raw `overflow: auto` box** —
105
+ it draws its own bar inside its root, so the bar floats instead of reflowing the column
106
+ (`AppSidebar`'s nav is the reference: `type="hover"`, `scrollbars="y"`). Raw `overflow: auto` stays
107
+ correct only where a library owns the node (`BasaltStickToBottom`, `BasaltVirtualList`) and for a
108
+ table body (`Table.ScrollContainer type="native"` — a `ScrollArea` viewport would break a sticky
109
+ `<thead>`'s positioning context).
146
110
 
147
111
  ## Interaction feedback and motion
148
112
 
149
- Every action button must confirm itself — silent success reads as broken. Three layers, together:
150
- the `loading` prop while in flight; a brief flip on the trigger itself (label `Save → Saved`,
151
- `color="green"`, a check revealed through Mantine's `<Transition>`), held about a second; and a toast
152
- ONLY for an outcome the user might miss (an off-screen write, an error, background work). The user's
153
- eye is on the thing they clicked. Destructive actions confirm through `modals.openConfirmModal`
154
- first; form errors are inline, and a toast covers only submit-level failure.
155
-
156
- - **Timing is a token, never a literal.** `MOTION_DURATION` / `MOTION_SPRING` /
157
- `MOTION_EASE_STANDARD` from `basalt-ui`; the duration cap is the same interaction-feedback ceiling
158
- above (`raw-motion-value` enforces it). Never animate a layout-shifting property — move
159
- `transform`, `opacity`, `color`, `background`.
160
- - **Import from `motion/react`, never `framer-motion`** (`no-restricted-imports`, repo and shipped).
161
- `motion` is an exact-pinned optional PEER, not a bundled dependency. Reach for Mantine's own
162
- `<Transition>` for a simple mount fade; reach for `motion` when the interaction needs a crossfade
163
- between two elements, spring physics or a gesture.
164
- - **Always branch on `useReducedMotion`** (`@mantine/hooks`) with a real unanimated code path — no
165
- `motion.*` wrapper at all, not just `duration: 0`. `ThemeToggle` is the pattern.
166
- - **Restraint applies to motion too.** A state change earns a transition; idle chrome does not.
167
- Never a looping or pulsing idle animation.
113
+ Every action button confirms itself: the `loading` prop in flight, a brief label flip
114
+ (`Save → Saved`) via Mantine's `<Transition>`, and a toast ONLY for an outcome the user might miss.
115
+ Destructive actions confirm through `modals.openConfirmModal` first.
116
+
117
+ - **Timing is a token, never a literal** — `MOTION_DURATION`/`MOTION_SPRING`/`MOTION_EASE_STANDARD`
118
+ (`raw-motion-value` enforces it). Animate `transform`/`opacity`/`color`, never a layout property.
119
+ - **Import from `motion/react`, never `framer-motion`** — exact-pinned optional PEER. Mantine's
120
+ `<Transition>` for a mount fade; `motion` for a crossfade, spring or gesture.
121
+ - **Always branch on `useReducedMotion`** with a real unanimated path, not just `duration: 0`
122
+ (`ThemeToggle` is the pattern). Restraint applies to motion too — no looping idle animation.
@@ -7,7 +7,7 @@ paths:
7
7
  ---
8
8
 
9
9
  <!-- basalt:coverage -->
10
- <!-- GENERATED from src/surfaces.ts — `basalt-ui check-coverage --write`. Do not hand-edit. -->
10
+ <!-- GENERATED from src/surfaces.ts — `bun scripts/check-coverage.ts --write`. Do not hand-edit. -->
11
11
  <!-- backed by: guard kinds — localstorage-theme · oxlint rules — basalt/search-literal-link, basalt/use-search-from-literal -->
12
12
  <!-- not guarded: — -->
13
13
  <!-- /basalt:coverage -->
@@ -49,45 +49,35 @@ export const analytics = createSearchStore({
49
49
  ```
50
50
 
51
51
  - **The URL is the truth; the localStorage mirror is a fallback UNDER it.** Every field resolves
52
- URL ⊳ localStorage ⊳ fallback, identically for every field kind (law C4). A deep link therefore
53
- wins over the mirror — that is the whole difference from the enum-only pair it replaced, whose
54
- reader WAS the localStorage state, so a shared link opened on the wrong window.
55
- - **Lanes are declared once, at definition**: `{ url, persist, history }`. `url: false` is the
56
- local-only lane (a per-card select, a compact toggle); `persist: false` is the URL-only lane
57
- (pagination, a tab that should not follow you to another feature). `createLocalStore`
58
- (`basalt-ui/state`) is the same field vocabulary with no router at all.
59
- - **Option LABELS come from `.labels()`, once, at definition** — never a per-control prop and never a
60
- lookup table in the page.
61
- - **`validateSearch: store.validateSearch`** on the route, in the function form. It returns every
62
- URL-lane param unconditionally, which is what makes the store's "does this route own the field"
63
- check exact. A route with a genuinely wider search shape hand-writes `validateSearch` and COMPOSES
64
- the store into it (`{ ...store.validateSearch(raw), ...rest }`) — there is no schema-backed store
65
- variant, and none is coming.
66
- - **A write from outside the owning route persists only**, and `validateSearch` picks it up on the
67
- next visit. Two different stores using one param name on overlapping routes read as one owner;
68
- presence in the search is all a store can see without the router naming the declarer.
69
- - **`field.range` keeps its three URL params** (preset + from/to, renamable) so existing loaders and
70
- deep links keep their shape; `handle.toWindow(value)` is the projection into query params — never a
71
- hand-written `presetToParams`.
52
+ URL ⊳ localStorage ⊳ fallback, identically for every field kind (law C4) — a deep link wins over
53
+ the mirror.
54
+ - **Lanes are declared once, at definition**: `{ url, persist, history }`. `url: false` is
55
+ local-only (a per-card select); `persist: false` is URL-only (pagination, a tab that shouldn't
56
+ follow you elsewhere). `createLocalStore` is the same field vocabulary with no router.
57
+ - **Option LABELS come from `.labels()`, once, at definition** — never a per-control prop.
58
+ - **`validateSearch: store.validateSearch`** on the route, function form — it returns every
59
+ URL-lane param unconditionally. A route with a wider search shape COMPOSES the store instead
60
+ (`{ ...store.validateSearch(raw), ...rest }`) — there is no schema-backed store variant.
61
+ - **A write from outside the owning route persists only**; `validateSearch` picks it up next visit.
62
+ - **`field.range` keeps its three URL params** (preset + from/to); `handle.toWindow(value)` is the
63
+ projection into query params — never a hand-written `presetToParams`.
72
64
 
73
65
  ## Reading and writing
74
66
 
75
67
  - **A page reads `store.useValues()`, or takes the value as a PROP.** Never
76
- `useSearch({ from: '<literal>' })` — a sibling or child route fails that `from`, and it is
77
- `basalt/use-search-from-literal`. Basalt's own controls read through the `FieldHandle`, which uses
78
- the non-strict search internally, so a control renders on any matched route with no `from` at all.
79
- - **A `FieldHandle` is the whole read/write surface** a control needs: `use()` returns
80
- `[value, setValue]` and the setter owns the navigate AND the persist. So a control never receives
81
- `value`/`onChange` (law C2) and a page never writes a navigate for a store field. Where you DO hand-write
82
- one — a route with its own search shape — use the reducer form, `navigate({ search: (prev) => ({ ...prev, … }) })`,
83
- so unrelated params survive.
84
- - **`useActiveCount()` / `useReset()`** are what a `Filters (n)` pill and its `Reset all` derive
85
- from. Never hand-count either.
86
- - **A reset UNSETS.** `useReset()` and `field.clear()` DELETE the persisted key; they never write
87
- `fallback`, which would pin a thunk fallback (`() => todayIso()`) as a value nobody chose. A
88
- hand-rolled reset calls `field.clear()`, never `setValue(field.fallback)`.
89
- - **`set(next, { patch })`** merges sibling params the store does NOT own into the same navigate. A
90
- key another field of the same store owns throws in dev — write it through that field's setter.
68
+ `useSearch({ from: '<literal>' })` (`basalt/use-search-from-literal`) — a sibling/child route
69
+ fails that `from`. Basalt controls read through `FieldHandle`, which uses the non-strict search
70
+ internally.
71
+ - **A `FieldHandle` is the whole read/write surface**: `use()` returns `[value, setValue]` and the
72
+ setter owns the navigate AND the persist — a control never receives `value`/`onChange` (law C2).
73
+ Hand-writing a navigate (a route with its own search shape) uses the reducer form,
74
+ `navigate({ search: (prev) => ({ ...prev, … }) })`, so unrelated params survive.
75
+ - **`useActiveCount()`/`useReset()`** back a `Filters (n)` pill and its `Reset all` — never
76
+ hand-count either.
77
+ - **A reset UNSETS.** `useReset()`/`field.clear()` DELETE the persisted key, never write
78
+ `fallback` (which would pin a thunk fallback as a value nobody chose).
79
+ - **`set(next, { patch })`** merges sibling params the store does NOT own into the same navigate; a
80
+ key another field of the same store owns throws in dev.
91
81
 
92
82
  ## Nav links carry the store BY REFERENCE
93
83
 
@@ -96,11 +86,9 @@ link: linkOptions({ to: '/dashboard', search: analytics.linkSearch })
96
86
  ```
97
87
 
98
88
  **Pass the function, never call it, never inline an object.** It is a click-time thunk, so arriving
99
- from anywhere restores the last selection; a module-scope literal (`search: { range: '30d' }`) pins
100
- the fallback on every click and is why one consumer's reader had zero call sites while every
101
- individual piece looked correct. `search: true` is a type error against a store-backed route.
102
- `basalt/search-literal-link` catches the literal, and in dev `validateSearch` warns once when the URL
103
- pins the fallback, something else is persisted, and no reader has ever been called.
89
+ from anywhere restores the last selection; a module-scope literal (`search: { range: '30d' }`)
90
+ pins the fallback on every click — `basalt/search-literal-link` catches it. `search: true` is a
91
+ type error against a store-backed route.
104
92
 
105
93
  ## The typed nav definition
106
94
 
@@ -113,27 +101,21 @@ spread it. `navTarget(NAV, id)` is one destination's link options, typed per id,
113
101
  the nav into Spotlight in one `.map()`).
114
102
 
115
103
  - **Route options live under `link:`, wrapped in TanStack's own `linkOptions`** — never spread flat
116
- onto the item. A flat shape validates `to` by assignability, which does no excess-property
117
- checking, so a typo'd metadata key compiles silently. `id`/`label`/`short`/`icon`/`mobile`/
118
- `disabled`/`exact` ride outside `link`.
104
+ onto the item (a flat shape validates `to` by assignability, no excess-property checking, so a
105
+ typo'd metadata key compiles silently). `id`/`label`/`short`/`icon`/`mobile`/`disabled`/`exact`
106
+ ride outside `link`.
119
107
  - **Nesting is `children`**; `mobile` places the destination on the bar (basalt-mantine.md).
120
108
  - **Without the `Register` module augmentation, `defineNav` validates NOTHING and reports zero
121
- errors** — `RegisteredRouter` falls back to `AnyRouter`, every `to` widens to `string`, and the API
122
- looks like it is working while catching nothing. Verify it once by changing a `to` to garbage and
123
- confirming the compile error lists the real route paths. The router's errors here are large; read
124
- the union for the intended path and fix the string. **Never reach for `as never`** to quiet one —
125
- the checking is the entire point.
126
- - Whether a MISSING `search` is caught depends on the schema's OUTPUT type: with every key required
127
- (what a store or a defaulted Zod object produces) TanStack raises and the compiler catches it. With
128
- every key optional, a forgotten `search` compiles — diff the target table by hand there.
129
- - `staticData` (`title` / `icon` / `navSection`) drives `useRouterBreadcrumbs` and needs the
130
- generated route tree; everything else in the bridge works against a hand-written router.
109
+ errors** — `RegisteredRouter` falls back to `AnyRouter`, every `to` widens to `string`. Verify
110
+ once by changing a `to` to garbage and confirming the compile error. **Never reach for
111
+ `as never`** to quiet one — the checking is the entire point.
112
+ - `staticData` (`title`/`icon`/`navSection`) drives `useRouterBreadcrumbs` and needs the generated
113
+ route tree.
131
114
 
132
115
  ## `createPersistedState` — the localStorage primitive
133
116
 
134
- Call it once at module scope; use the returned hook anywhere, with no provider. SSR-safe, cross-tab
135
- through the `storage` event, versioned — bump `version` when the shape changes and stale values fall
136
- back to `initial`. Keys are namespaced `basalt:<key>` automatically, and `readPersistedValue(key, v)`
137
- is the non-React read (how you mirror the shell's own collapse state). Pass a Standard Schema to
138
- validate what comes back. Every basalt module that persists — the shell's collapse, a `Section`
139
- fold, a sidebar block, a form draft, the notification history — persists through this one primitive.
117
+ Call it once at module scope; use the returned hook anywhere, no provider. SSR-safe, cross-tab via
118
+ the `storage` event, versioned (bump `version` on a shape change, stale values fall back to
119
+ `initial`). Keys are namespaced `basalt:<key>` automatically; `readPersistedValue(key, v)` is the
120
+ non-React read. Every basalt module that persists — shell collapse, `Section` fold, a sidebar
121
+ block, a form draft, notification history — goes through this one primitive.