basalt-ui 1.24.0 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (467) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +25 -0
  3. package/MIGRATING.md +547 -21
  4. package/README.md +46 -33
  5. package/agent/rules/basalt-batteries.md +205 -0
  6. package/agent/rules/basalt-charts.md +125 -555
  7. package/agent/rules/basalt-controls.md +155 -0
  8. package/agent/rules/basalt-mantine.md +146 -439
  9. package/agent/rules/basalt-state.md +113 -96
  10. package/agent/rules/basalt-tokens.md +126 -288
  11. package/agent/skills/basalt-app/SKILL.md +70 -195
  12. package/agent/skills/basalt-charts/SKILL.md +54 -234
  13. package/agent/skills/basalt-design/SKILL.md +56 -248
  14. package/agent/templates/CLAUDE-block.md.tpl +31 -40
  15. package/agent/templates/DESIGN.md.tpl +22 -51
  16. package/configs/oxlint-plugin.js +1275 -31
  17. package/configs/oxlint-plugin.test.ts +947 -11
  18. package/configs/oxlint.json +11 -3
  19. package/dist/charts/cursor/resolve.d.ts +18 -0
  20. package/dist/charts/cursor/resolve.d.ts.map +1 -1
  21. package/dist/charts/cursor/resolve.js +15 -6
  22. package/dist/charts/cursor/scope.d.ts +6 -3
  23. package/dist/charts/cursor/scope.d.ts.map +1 -1
  24. package/dist/charts/cursor/store.d.ts +5 -1
  25. package/dist/charts/cursor/store.d.ts.map +1 -1
  26. package/dist/charts/cursor/store.js +4 -4
  27. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  28. package/dist/charts/hooks/useChartCursor.js +9 -8
  29. package/dist/charts/index.d.ts +2 -2
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/primitives/ChartCard.d.ts +34 -9
  32. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  33. package/dist/charts/primitives/ChartCard.js +29 -114
  34. package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
  35. package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
  36. package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
  37. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  38. package/dist/charts/sparklines/BarSparkline.js +18 -4
  39. package/dist/cli/check-coverage.test.d.ts +2 -0
  40. package/dist/cli/check-coverage.test.d.ts.map +1 -0
  41. package/dist/cli/index.d.ts +67 -4
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +225 -7
  44. package/dist/connectivity/connectivity-indicator.d.ts +4 -1
  45. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  46. package/dist/connectivity/connectivity-indicator.js +1 -1
  47. package/dist/content/index.d.ts +5 -3
  48. package/dist/content/index.d.ts.map +1 -1
  49. package/dist/content/index.js +4 -2
  50. package/dist/content/prose.module.css +7 -28
  51. package/dist/controls/actions.d.ts +169 -0
  52. package/dist/controls/actions.d.ts.map +1 -0
  53. package/dist/controls/actions.js +287 -0
  54. package/dist/controls/actions.module.css +62 -0
  55. package/dist/controls/actions.module.css.d.ts +13 -0
  56. package/dist/controls/actions.test.d.ts +2 -0
  57. package/dist/controls/actions.test.d.ts.map +1 -0
  58. package/dist/controls/actions.type-guard.test.d.ts +2 -0
  59. package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
  60. package/dist/controls/compare-filter.d.ts +41 -0
  61. package/dist/controls/compare-filter.d.ts.map +1 -0
  62. package/dist/controls/compare-filter.js +23 -0
  63. package/dist/controls/control-group.d.ts +19 -0
  64. package/dist/controls/control-group.d.ts.map +1 -0
  65. package/dist/controls/control-group.js +20 -0
  66. package/dist/controls/control-group.test.d.ts +2 -0
  67. package/dist/controls/control-group.test.d.ts.map +1 -0
  68. package/dist/controls/controls.module.css +542 -0
  69. package/dist/controls/controls.module.css.d.ts +35 -0
  70. package/dist/controls/controls.router.test.d.ts +2 -0
  71. package/dist/controls/controls.router.test.d.ts.map +1 -0
  72. package/dist/controls/enum-filter.d.ts +30 -0
  73. package/dist/controls/enum-filter.d.ts.map +1 -0
  74. package/dist/controls/enum-filter.js +91 -0
  75. package/dist/controls/filter-context.d.ts +55 -0
  76. package/dist/controls/filter-context.d.ts.map +1 -0
  77. package/dist/controls/filter-context.js +69 -0
  78. package/dist/controls/filter-pill.d.ts +28 -0
  79. package/dist/controls/filter-pill.d.ts.map +1 -0
  80. package/dist/controls/filter-pill.js +70 -0
  81. package/dist/controls/filter-set.d.ts +13 -0
  82. package/dist/controls/filter-set.d.ts.map +1 -0
  83. package/dist/controls/filter-set.js +125 -0
  84. package/dist/controls/filter-set.test.d.ts +2 -0
  85. package/dist/controls/filter-set.test.d.ts.map +1 -0
  86. package/dist/controls/filter-sheet.d.ts +117 -0
  87. package/dist/controls/filter-sheet.d.ts.map +1 -0
  88. package/dist/controls/filter-sheet.js +146 -0
  89. package/dist/controls/glyphs.d.ts +27 -0
  90. package/dist/controls/glyphs.d.ts.map +1 -0
  91. package/dist/controls/glyphs.js +49 -0
  92. package/dist/controls/index.d.ts +19 -0
  93. package/dist/controls/index.d.ts.map +1 -0
  94. package/dist/controls/index.js +44 -0
  95. package/dist/controls/multi-select-filter.d.ts +23 -0
  96. package/dist/controls/multi-select-filter.d.ts.map +1 -0
  97. package/dist/controls/multi-select-filter.js +89 -0
  98. package/dist/controls/peer-boundary.test.d.ts +2 -0
  99. package/dist/controls/peer-boundary.test.d.ts.map +1 -0
  100. package/dist/controls/props-contract.test.d.ts +2 -0
  101. package/dist/controls/props-contract.test.d.ts.map +1 -0
  102. package/dist/controls/range-filter.d.ts +29 -0
  103. package/dist/controls/range-filter.d.ts.map +1 -0
  104. package/dist/controls/range-filter.js +120 -0
  105. package/dist/controls/search-filter.d.ts +10 -0
  106. package/dist/controls/search-filter.d.ts.map +1 -0
  107. package/dist/controls/search-filter.js +81 -0
  108. package/dist/controls/select-filter.d.ts +74 -0
  109. package/dist/controls/select-filter.d.ts.map +1 -0
  110. package/dist/controls/select-filter.js +10 -0
  111. package/dist/controls/sync-button.d.ts +38 -0
  112. package/dist/controls/sync-button.d.ts.map +1 -0
  113. package/dist/controls/sync-button.js +119 -0
  114. package/dist/controls/sync-button.module.css +63 -0
  115. package/dist/controls/sync-button.module.css.d.ts +11 -0
  116. package/dist/controls/sync-button.test.d.ts +2 -0
  117. package/dist/controls/sync-button.test.d.ts.map +1 -0
  118. package/dist/controls/toggle-filter.d.ts +9 -0
  119. package/dist/controls/toggle-filter.d.ts.map +1 -0
  120. package/dist/controls/toggle-filter.js +42 -0
  121. package/dist/controls/view-tabs.d.ts +23 -0
  122. package/dist/controls/view-tabs.d.ts.map +1 -0
  123. package/dist/controls/view-tabs.js +59 -0
  124. package/dist/controls-dates/date-range-picker.d.ts +4 -0
  125. package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
  126. package/dist/controls-dates/date-range-picker.js +18 -0
  127. package/dist/controls-dates/index.d.ts +9 -0
  128. package/dist/controls-dates/index.d.ts.map +1 -0
  129. package/dist/controls-dates/index.js +4 -0
  130. package/dist/dashboard/delta-badge.d.ts +6 -12
  131. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  132. package/dist/dashboard/delta-badge.js +1 -50
  133. package/dist/dashboard/query-state.d.ts +1 -1
  134. package/dist/dashboard/settings-section.d.ts +6 -4
  135. package/dist/dashboard/settings-section.d.ts.map +1 -1
  136. package/dist/dashboard/settings-section.js +23 -16
  137. package/dist/dashboard/settings-section.module.css +10 -0
  138. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  139. package/dist/dashboard/settings-section.test.d.ts +2 -0
  140. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  141. package/dist/dashboard/stat-card.d.ts +44 -9
  142. package/dist/dashboard/stat-card.d.ts.map +1 -1
  143. package/dist/dashboard/stat-card.js +80 -48
  144. package/dist/dashboard/stat-card.module.css +82 -0
  145. package/dist/dashboard/stat-card.module.css.d.ts +11 -0
  146. package/dist/data/data-table.d.ts +102 -20
  147. package/dist/data/data-table.d.ts.map +1 -1
  148. package/dist/data/data-table.js +166 -65
  149. package/dist/data/table.d.ts +1 -1
  150. package/dist/data/table.d.ts.map +1 -1
  151. package/dist/guard/grace.test.d.ts +20 -0
  152. package/dist/guard/grace.test.d.ts.map +1 -0
  153. package/dist/guard/index.d.ts +66 -3
  154. package/dist/guard/index.d.ts.map +1 -1
  155. package/dist/guard/index.js +82 -7
  156. package/dist/guard/types.d.ts +3 -3
  157. package/dist/guard/types.d.ts.map +1 -1
  158. package/dist/index.d.ts +12 -2
  159. package/dist/index.d.ts.map +1 -1
  160. package/dist/index.js +13 -10
  161. package/dist/nav/types.d.ts +78 -2
  162. package/dist/nav/types.d.ts.map +1 -1
  163. package/dist/notifications/bell.d.ts +4 -1
  164. package/dist/notifications/bell.d.ts.map +1 -1
  165. package/dist/notifications/index.d.ts +1 -1
  166. package/dist/router-tanstack/field.d.ts +14 -0
  167. package/dist/router-tanstack/field.d.ts.map +1 -0
  168. package/dist/router-tanstack/field.js +4 -0
  169. package/dist/router-tanstack/index.d.ts +6 -2
  170. package/dist/router-tanstack/index.d.ts.map +1 -1
  171. package/dist/router-tanstack/index.js +4 -0
  172. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  173. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  174. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  175. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  176. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  177. package/dist/router-tanstack/search-param-store.js +16 -45
  178. package/dist/router-tanstack/search-store.d.ts +81 -0
  179. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  180. package/dist/router-tanstack/search-store.js +187 -0
  181. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  182. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  183. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  184. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  185. package/dist/router-tanstack/use-nav.d.ts +0 -2
  186. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  187. package/dist/router-tanstack/use-nav.js +34 -12
  188. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  189. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  190. package/dist/section/index.d.ts +2 -0
  191. package/dist/section/index.d.ts.map +1 -0
  192. package/dist/section/index.js +4 -0
  193. package/dist/section/section.d.ts +24 -0
  194. package/dist/section/section.d.ts.map +1 -0
  195. package/dist/section/section.js +126 -0
  196. package/dist/section/section.module.css +104 -0
  197. package/dist/section/section.module.css.d.ts +11 -0
  198. package/dist/section/section.test.d.ts +2 -0
  199. package/dist/section/section.test.d.ts.map +1 -0
  200. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  201. package/dist/shell/app-breadcrumbs.js +18 -15
  202. package/dist/shell/app-header.module.css +77 -37
  203. package/dist/shell/app-header.module.css.d.ts +2 -1
  204. package/dist/shell/app-mobile-nav.d.ts +9 -2
  205. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  206. package/dist/shell/app-mobile-nav.js +98 -11
  207. package/dist/shell/app-mobile-nav.module.css +8 -0
  208. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  209. package/dist/shell/app-sidebar.d.ts +55 -28
  210. package/dist/shell/app-sidebar.d.ts.map +1 -1
  211. package/dist/shell/app-sidebar.js +116 -67
  212. package/dist/shell/app-sidebar.module.css +303 -20
  213. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  214. package/dist/shell/index.d.ts +55 -21
  215. package/dist/shell/index.d.ts.map +1 -1
  216. package/dist/shell/index.js +40 -18
  217. package/dist/shell/mobile-nav-model.d.ts +13 -2
  218. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  219. package/dist/shell/mobile-nav-model.js +15 -1
  220. package/dist/shell/nav-count-badge.d.ts +1 -1
  221. package/dist/shell/page-bar.d.ts +51 -0
  222. package/dist/shell/page-bar.d.ts.map +1 -0
  223. package/dist/shell/page-bar.js +154 -0
  224. package/dist/shell/page-bar.module.css +178 -0
  225. package/dist/shell/page-bar.module.css.d.ts +17 -0
  226. package/dist/shell/page-bar.test.d.ts +2 -0
  227. package/dist/shell/page-bar.test.d.ts.map +1 -0
  228. package/dist/shell/sidebar-block-model.d.ts +48 -0
  229. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  230. package/dist/shell/sidebar-block-model.js +39 -0
  231. package/dist/shell/sidebar-blocks.d.ts +32 -0
  232. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  233. package/dist/shell/sidebar-blocks.js +169 -0
  234. package/dist/shell/sidebar-search.d.ts +13 -1
  235. package/dist/shell/sidebar-search.d.ts.map +1 -1
  236. package/dist/shell/sidebar-search.js +34 -1
  237. package/dist/shell/sidebar-search.module.css +26 -0
  238. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  239. package/dist/state/fields.d.ts +392 -0
  240. package/dist/state/fields.d.ts.map +1 -0
  241. package/dist/state/fields.js +369 -0
  242. package/dist/state/fields.test.d.ts +2 -0
  243. package/dist/state/fields.test.d.ts.map +1 -0
  244. package/dist/state/persisted.d.ts +48 -0
  245. package/dist/state/persisted.d.ts.map +1 -0
  246. package/dist/state/persisted.js +105 -0
  247. package/dist/state.d.ts +11 -56
  248. package/dist/state.d.ts.map +1 -1
  249. package/dist/state.js +14 -120
  250. package/dist/styles.css +13 -0
  251. package/dist/surfaces.d.ts +106 -24
  252. package/dist/surfaces.d.ts.map +1 -1
  253. package/dist/surfaces.js +155 -19
  254. package/dist/theme/controls.module.css +16 -1
  255. package/dist/theme/ctl-theme.d.ts +32 -0
  256. package/dist/theme/ctl-theme.d.ts.map +1 -0
  257. package/dist/theme/ctl-theme.js +54 -0
  258. package/dist/theme/ctl-theme.test.d.ts +2 -0
  259. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  260. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  261. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  262. package/dist/theme/divider-law.test.d.ts +2 -0
  263. package/dist/theme/divider-law.test.d.ts.map +1 -0
  264. package/dist/theme/icon-slot.d.ts +44 -0
  265. package/dist/theme/icon-slot.d.ts.map +1 -0
  266. package/dist/theme/icon-slot.js +21 -0
  267. package/dist/theme/icon-slot.module.css +47 -0
  268. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  269. package/dist/theme/icon-slot.test.d.ts +2 -0
  270. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  271. package/dist/theme/index.d.ts +1 -0
  272. package/dist/theme/index.d.ts.map +1 -1
  273. package/dist/theme/index.js +82 -5
  274. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  275. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  276. package/dist/theme/segmented-control.module.css +20 -0
  277. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  278. package/dist/tokens/index.d.ts +12 -0
  279. package/dist/tokens/index.d.ts.map +1 -1
  280. package/dist/tokens/index.js +38 -8
  281. package/dist/tokens/palette.d.ts +97 -61
  282. package/dist/tokens/palette.d.ts.map +1 -1
  283. package/dist/tokens/palette.js +76 -38
  284. package/dist/tokens.css +10 -2
  285. package/dist/widget-header/delta-badge.d.ts +13 -0
  286. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  287. package/dist/widget-header/delta-badge.js +29 -0
  288. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  289. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  290. package/dist/widget-header/index.d.ts +3 -0
  291. package/dist/widget-header/index.d.ts.map +1 -0
  292. package/dist/widget-header/index.js +6 -0
  293. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  294. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  295. package/dist/widget-header/widget-header.d.ts +37 -0
  296. package/dist/widget-header/widget-header.d.ts.map +1 -0
  297. package/dist/widget-header/widget-header.js +77 -0
  298. package/dist/widget-header/widget-header.module.css +335 -0
  299. package/dist/widget-header/widget-header.module.css.d.ts +24 -0
  300. package/dist/widget-header/widget-header.test.d.ts +2 -0
  301. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  302. package/llms.txt +30 -17
  303. package/package.json +14 -1
  304. package/src/charts/cursor/resolve.test.ts +29 -1
  305. package/src/charts/cursor/resolve.ts +38 -6
  306. package/src/charts/cursor/scope.tsx +6 -3
  307. package/src/charts/cursor/store.test.ts +19 -1
  308. package/src/charts/cursor/store.ts +10 -5
  309. package/src/charts/hooks/useChartCursor.ts +16 -8
  310. package/src/charts/index.ts +2 -2
  311. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  312. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  313. package/src/charts/primitives/ChartCard.tsx +86 -146
  314. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  315. package/src/cli/check-coverage.test.ts +297 -0
  316. package/src/cli/check-theme.test.ts +38 -14
  317. package/src/cli/index.ts +392 -16
  318. package/src/cli/init-sync.test.ts +97 -0
  319. package/src/connectivity/connectivity-indicator.tsx +16 -9
  320. package/src/content/index.ts +16 -3
  321. package/src/content/prose.module.css +7 -28
  322. package/src/content/prose.module.css.test.ts +8 -24
  323. package/src/controls/actions.module.css +62 -0
  324. package/src/controls/actions.module.css.d.ts +13 -0
  325. package/src/controls/actions.test.tsx +252 -0
  326. package/src/controls/actions.tsx +655 -0
  327. package/src/controls/actions.type-guard.test.ts +63 -0
  328. package/src/controls/compare-filter.tsx +64 -0
  329. package/src/controls/control-group.test.tsx +229 -0
  330. package/src/controls/control-group.tsx +63 -0
  331. package/src/controls/controls.module.css +542 -0
  332. package/src/controls/controls.module.css.d.ts +35 -0
  333. package/src/controls/controls.router.test.tsx +637 -0
  334. package/src/controls/enum-filter.tsx +159 -0
  335. package/src/controls/filter-context.tsx +153 -0
  336. package/src/controls/filter-pill.tsx +120 -0
  337. package/src/controls/filter-set.test.tsx +472 -0
  338. package/src/controls/filter-set.tsx +225 -0
  339. package/src/controls/filter-sheet.tsx +285 -0
  340. package/src/controls/glyphs.tsx +81 -0
  341. package/src/controls/index.ts +48 -0
  342. package/src/controls/multi-select-filter.tsx +140 -0
  343. package/src/controls/peer-boundary.test.ts +85 -0
  344. package/src/controls/props-contract.test.tsx +106 -0
  345. package/src/controls/range-filter.tsx +184 -0
  346. package/src/controls/search-filter.tsx +123 -0
  347. package/src/controls/select-filter.tsx +89 -0
  348. package/src/controls/sync-button.module.css +63 -0
  349. package/src/controls/sync-button.module.css.d.ts +11 -0
  350. package/src/controls/sync-button.test.tsx +183 -0
  351. package/src/controls/sync-button.tsx +197 -0
  352. package/src/controls/toggle-filter.tsx +60 -0
  353. package/src/controls/view-tabs.tsx +110 -0
  354. package/src/controls-dates/date-range-picker.tsx +42 -0
  355. package/src/controls-dates/index.ts +8 -0
  356. package/src/dashboard/delta-badge.tsx +4 -74
  357. package/src/dashboard/query-state.tsx +1 -1
  358. package/src/dashboard/settings-section.module.css +10 -0
  359. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  360. package/src/dashboard/settings-section.test.tsx +56 -0
  361. package/src/dashboard/settings-section.tsx +33 -30
  362. package/src/dashboard/stat-card.module.css +82 -0
  363. package/src/dashboard/stat-card.module.css.d.ts +11 -0
  364. package/src/dashboard/stat-card.test.tsx +67 -1
  365. package/src/dashboard/stat-card.tsx +209 -68
  366. package/src/data/data-table.test.tsx +282 -6
  367. package/src/data/data-table.tsx +429 -94
  368. package/src/data/table.ts +2 -0
  369. package/src/guard/check-source.test.ts +126 -13
  370. package/src/guard/grace.test.ts +149 -0
  371. package/src/guard/index.ts +172 -29
  372. package/src/guard/types.ts +4 -2
  373. package/src/index.ts +46 -5
  374. package/src/nav/types.ts +84 -2
  375. package/src/notifications/bell.tsx +6 -3
  376. package/src/notifications/index.ts +1 -1
  377. package/src/router-tanstack/field.ts +31 -0
  378. package/src/router-tanstack/index.ts +24 -2
  379. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  380. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  381. package/src/router-tanstack/search-param-store.ts +42 -159
  382. package/src/router-tanstack/search-store.router.test.tsx +661 -0
  383. package/src/router-tanstack/search-store.test.ts +418 -0
  384. package/src/router-tanstack/search-store.ts +380 -0
  385. package/src/router-tanstack/use-nav.test.tsx +187 -0
  386. package/src/router-tanstack/use-nav.ts +72 -14
  387. package/src/section/index.ts +1 -0
  388. package/src/section/section.module.css +104 -0
  389. package/src/section/section.module.css.d.ts +11 -0
  390. package/src/section/section.test.tsx +204 -0
  391. package/src/section/section.tsx +220 -0
  392. package/src/shell/app-breadcrumbs.tsx +44 -31
  393. package/src/shell/app-header.module.css +77 -37
  394. package/src/shell/app-header.module.css.d.ts +2 -1
  395. package/src/shell/app-mobile-nav.module.css +8 -0
  396. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  397. package/src/shell/app-mobile-nav.tsx +147 -8
  398. package/src/shell/app-sidebar-account.tsx +2 -2
  399. package/src/shell/app-sidebar.module.css +303 -20
  400. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  401. package/src/shell/app-sidebar.test.tsx +456 -41
  402. package/src/shell/app-sidebar.tsx +276 -140
  403. package/src/shell/index.test.tsx +291 -5
  404. package/src/shell/index.tsx +137 -38
  405. package/src/shell/mobile-nav-model.test.ts +228 -7
  406. package/src/shell/mobile-nav-model.ts +53 -3
  407. package/src/shell/nav-count-badge.tsx +1 -1
  408. package/src/shell/page-bar.module.css +178 -0
  409. package/src/shell/page-bar.module.css.d.ts +17 -0
  410. package/src/shell/page-bar.test.tsx +316 -0
  411. package/src/shell/page-bar.tsx +315 -0
  412. package/src/shell/sidebar-block-model.ts +83 -0
  413. package/src/shell/sidebar-blocks.tsx +301 -0
  414. package/src/shell/sidebar-search.module.css +26 -0
  415. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  416. package/src/shell/sidebar-search.tsx +71 -2
  417. package/src/state/fields.test.tsx +258 -0
  418. package/src/state/fields.ts +892 -0
  419. package/src/state/persisted.ts +211 -0
  420. package/src/state.ts +46 -240
  421. package/src/styles.css +13 -0
  422. package/src/surfaces.ts +203 -36
  423. package/src/theme/controls.module.css +16 -1
  424. package/src/theme/ctl-theme.test.tsx +275 -0
  425. package/src/theme/ctl-theme.tsx +138 -0
  426. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  427. package/src/theme/density-relations.test.ts +106 -51
  428. package/src/theme/divider-law.test.ts +259 -0
  429. package/src/theme/icon-slot.module.css +47 -0
  430. package/src/theme/icon-slot.module.css.d.ts +8 -0
  431. package/src/theme/icon-slot.test.tsx +275 -0
  432. package/src/theme/icon-slot.tsx +59 -0
  433. package/src/theme/index.ts +165 -3
  434. package/src/theme/layout-rhythm-css.test.ts +72 -0
  435. package/src/theme/radius.test.ts +11 -1
  436. package/src/theme/segmented-control.module.css +20 -0
  437. package/src/theme/shadow-surfaces.test.ts +20 -7
  438. package/src/theme/spacing.test.ts +75 -27
  439. package/src/theme/use-basalt-spacing.ts +2 -2
  440. package/src/tokens/density.test.ts +7 -25
  441. package/src/tokens/index.ts +48 -8
  442. package/src/tokens/palette.ts +107 -73
  443. package/src/widget-header/delta-badge.test.tsx +60 -0
  444. package/src/widget-header/delta-badge.tsx +57 -0
  445. package/src/widget-header/index.ts +2 -0
  446. package/src/widget-header/no-mantine.test.ts +33 -0
  447. package/src/widget-header/widget-header.module.css +335 -0
  448. package/src/widget-header/widget-header.module.css.d.ts +24 -0
  449. package/src/widget-header/widget-header.test.tsx +187 -0
  450. package/src/widget-header/widget-header.tsx +168 -0
  451. package/agent/rules/basalt-agent.md +0 -452
  452. package/agent/rules/basalt-app.md +0 -246
  453. package/agent/rules/basalt-commands.md +0 -320
  454. package/agent/rules/basalt-content.md +0 -409
  455. package/agent/rules/basalt-data.md +0 -269
  456. package/agent/rules/basalt-forms.md +0 -186
  457. package/agent/rules/basalt-notifications.md +0 -264
  458. package/agent/rules/basalt-query.md +0 -187
  459. package/agent/rules/basalt-router.md +0 -336
  460. package/dist/content/article-filter-bar.d.ts +0 -40
  461. package/dist/content/article-filter-bar.d.ts.map +0 -1
  462. package/dist/content/article-filter-bar.js +0 -56
  463. package/dist/shell/page-header.d.ts +0 -13
  464. package/dist/shell/page-header.d.ts.map +0 -1
  465. package/dist/shell/page-header.js +0 -23
  466. package/src/content/article-filter-bar.tsx +0 -88
  467. package/src/shell/page-header.tsx +0 -35
@@ -1,5 +1,6 @@
1
1
  import { createElement, useMemo } from "react";
2
2
  import { Link, useMatchRoute, useRouterState } from "@tanstack/react-router";
3
+ import { flattenNav } from "./nav.js";
3
4
  function useNav(config, opts) {
4
5
  const matchRoute = useMatchRoute();
5
6
  const href = useRouterState({ select: (s) => s.location.href });
@@ -7,29 +8,52 @@ function useNav(config, opts) {
7
8
  const isActiveOverride = opts?.isActive;
8
9
  const sections = useMemo(() => {
9
10
  const badgeOf = (id) => badges?.[id];
10
- const isActive = (item) => isActiveOverride?.(item) ?? Boolean(
11
+ const matchOf = (item) => isActiveOverride?.(item) ?? Boolean(
11
12
  matchRoute({
12
13
  to: item.link.to,
13
14
  ...item.link.params !== void 0 && { params: item.link.params },
14
- // Prefix match by default so a parent stays lit on its children; `'/'` would then match
15
- // everything, so it is always exact.
16
15
  fuzzy: item.exact !== true && item.link.to !== "/"
17
16
  })
18
17
  );
18
+ const allItems = flattenNav(config);
19
+ const specificity = (to) => to.split("/").filter(Boolean).length;
20
+ const matchedById = /* @__PURE__ */ new Map();
21
+ let winnerId;
22
+ let winnerTo;
23
+ for (const item of allItems) {
24
+ const matched = matchOf(item);
25
+ matchedById.set(item.id, matched);
26
+ if (matched && (winnerTo === void 0 || specificity(item.link.to) > specificity(winnerTo))) {
27
+ winnerId = item.id;
28
+ winnerTo = item.link.to;
29
+ }
30
+ }
31
+ const resolveState = (item) => {
32
+ if (item.id === winnerId) return { active: true, ancestor: false };
33
+ const matched = matchedById.get(item.id) ?? false;
34
+ const ancestor = winnerTo !== void 0 && matched && winnerTo.startsWith(`${item.link.to}/`);
35
+ return { active: false, ancestor };
36
+ };
19
37
  const toSidebarItem = (item) => {
20
- const Anchor = (props) => createElement(Link, { ...item.link, ...props });
38
+ const Anchor = (props) => createElement(Link, {
39
+ ...item.link,
40
+ activeOptions: { exact: true },
41
+ ...props
42
+ });
21
43
  const badge = badgeOf(item.id);
22
44
  const children = item.children?.map(toSidebarItem);
45
+ const { active, ancestor } = resolveState(item);
23
46
  return {
24
47
  key: item.id,
25
48
  label: item.label,
26
49
  icon: item.icon,
27
50
  Anchor,
28
- // Per-destination active only. Rolling a child's active state up into its parent is the
29
- // MOBILE SLOT's rule (§2.3.12) and belongs to `projectMobileNav`, which reads `children`
30
- // anyway — doing it here too would also light a desktop parent row whose own route does
31
- // not match.
32
- active: isActive(item),
51
+ // Exactly one destination in the WHOLE definition reads `active` (`resolveState` above).
52
+ // Rolling that up into a mobile SLOT (any descendant active → the slot reads active) is
53
+ // still `projectMobileNav`'s job, which reads `children` for that. `ancestor` is the
54
+ // desktop-only middle state: a matched item that sits on the winner's path but isn't it.
55
+ active,
56
+ ...ancestor && { ancestor },
33
57
  ...item.short !== void 0 && { short: item.short },
34
58
  ...item.mobile !== void 0 && { mobile: item.mobile },
35
59
  ...item.disabled !== void 0 && { disabled: item.disabled },
@@ -47,7 +71,6 @@ function useNav(config, opts) {
47
71
  }));
48
72
  }, [config, matchRoute, href, badges, isActiveOverride]);
49
73
  const mobile = config.mobile;
50
- const moreExtra = opts?.moreExtra;
51
74
  const getScrollElement = opts?.getScrollElement;
52
75
  const mobileNav = useMemo(() => {
53
76
  const groupLabelById = new Map(config.groups.map((g) => [g.id, g.label]));
@@ -60,10 +83,9 @@ function useNav(config, opts) {
60
83
  ...mobile?.maxTabs !== void 0 && { maxTabs: mobile.maxTabs },
61
84
  ...mobile?.menuMax !== void 0 && { menuMax: mobile.menuMax },
62
85
  ...mobile?.moreLabel !== void 0 && { moreLabel: mobile.moreLabel },
63
- ...moreExtra !== void 0 && { moreExtra },
64
86
  ...getScrollElement !== void 0 && { getScrollElement }
65
87
  };
66
- }, [config, mobile, moreExtra, getScrollElement]);
88
+ }, [config, mobile, getScrollElement]);
67
89
  return { sections, mobileNav };
68
90
  }
69
91
  export {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-nav.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-nav.test.d.ts","sourceRoot":"","sources":["../../src/router-tanstack/use-nav.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export { Section, type SectionProps } from './section.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/section/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { Section } from "./section.js";
2
+ export {
3
+ Section
4
+ };
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { WidgetHeaderProps } from '../widget-header/index.js';
3
+ export type SectionProps = Omit<WidgetHeaderProps, 'tier'> & {
4
+ /** Rendered in the header row, in its own `CtlSlot` — hidden while the section is collapsed. */
5
+ tabs?: ReactNode;
6
+ /** Renders a chevron toggle in the header; the header stays drawn when closed, only `children`
7
+ * (and `tabs`) unmount. @default false */
8
+ collapsible?: boolean;
9
+ /** Persists the fold state at `basalt:section:<persistKey>`. Omit for a local, unpersisted fold
10
+ * (`useState`). */
11
+ persistKey?: string;
12
+ /** The fold state a section opens on, respected only while no persisted value exists — a section
13
+ * a reader has closed stays closed. `false` is what a long secondary block wants. @default true */
14
+ defaultOpen?: boolean;
15
+ /** Rendered directly under the header and ALWAYS visible, collapsed or not: a collapsed section
16
+ * still states its headline figures, which is what makes collapsing it a real option. Keep it to
17
+ * one row — the body is what `children` is for. */
18
+ summary?: ReactNode;
19
+ /** Turns the root into a scroll anchor (`id` + `scroll-margin-top` cleared for both sticky bars). */
20
+ id?: string;
21
+ children: ReactNode;
22
+ };
23
+ export declare function Section({ title, icon, subtitle, info, value, delta, deltaPeriod, sparkline, count, actions, tabs, collapsible, persistKey, defaultOpen, summary, id, children, }: SectionProps): import("react").JSX.Element;
24
+ //# sourceMappingURL=section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section.d.ts","sourceRoot":"","sources":["../../src/section/section.tsx"],"names":[],"mappings":"AA8CA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAGzD,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,GAAG;IAC3D,gGAAgG;IAChG,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;8CAC0C;IAC1C,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;uBACmB;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;uGACmG;IACnG,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;uDAEmD;IACnD,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,qGAAqG;IACrG,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAgFD,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,EACL,WAAW,EACX,SAAS,EACT,KAAK,EACL,OAAO,EACP,IAAI,EACJ,WAAmB,EACnB,UAAU,EACV,WAAkB,EAClB,OAAO,EACP,EAAE,EACF,QAAQ,GACT,EAAE,YAAY,+BAiDd"}
@@ -0,0 +1,126 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Children, useId, useMemo, useState } from "react";
3
+ import { CtlSlot } from "../theme/index.js";
4
+ import { createPersistedState } from "../state.js";
5
+ import { WidgetHeader } from "../widget-header/index.js";
6
+ import classes from "./section.module.css";
7
+ function isDev() {
8
+ return process.env["NODE_ENV"] !== "production";
9
+ }
10
+ function warnPastActionBudget(title, actions) {
11
+ if (!isDev()) return;
12
+ const count = Children.count(actions);
13
+ if (count <= 3) return;
14
+ console.warn(
15
+ `Section "${title}": ${count} actions exceeds the \u22643 budget (docs/CONTROLS-SPEC.md C6) \u2014 move the rest behind a menu.`
16
+ );
17
+ }
18
+ const UNPERSISTED_KEY = "__local__";
19
+ function useSectionOpen(persistKey, defaultOpen) {
20
+ const [localOpen, setLocalOpen] = useState(defaultOpen);
21
+ const usePersistedOpen = useMemo(
22
+ () => createPersistedState({
23
+ key: `section:${persistKey ?? UNPERSISTED_KEY}`,
24
+ version: 1,
25
+ initial: defaultOpen
26
+ }),
27
+ [persistKey, defaultOpen]
28
+ );
29
+ const [persistedOpen, setPersistedOpen] = usePersistedOpen();
30
+ if (persistKey !== void 0) return [persistedOpen, setPersistedOpen];
31
+ return [localOpen, setLocalOpen];
32
+ }
33
+ function ChevronToggle({
34
+ open,
35
+ onToggle,
36
+ controls
37
+ }) {
38
+ return /* @__PURE__ */ jsx(
39
+ "button",
40
+ {
41
+ type: "button",
42
+ className: classes.chevron,
43
+ "data-open": open,
44
+ "aria-expanded": open,
45
+ "aria-controls": controls,
46
+ "aria-label": open ? "Collapse section" : "Expand section",
47
+ onClick: onToggle,
48
+ children: /* @__PURE__ */ jsx(
49
+ "svg",
50
+ {
51
+ width: 12,
52
+ height: 12,
53
+ viewBox: "0 0 24 24",
54
+ fill: "none",
55
+ stroke: "currentColor",
56
+ strokeWidth: 2.5,
57
+ "aria-hidden": "true",
58
+ children: /* @__PURE__ */ jsx("path", { d: "M6 9l6 6 6-6", strokeLinecap: "round", strokeLinejoin: "round" })
59
+ }
60
+ )
61
+ }
62
+ );
63
+ }
64
+ function Section({
65
+ title,
66
+ icon,
67
+ subtitle,
68
+ info,
69
+ value,
70
+ delta,
71
+ deltaPeriod,
72
+ sparkline,
73
+ count,
74
+ actions,
75
+ tabs,
76
+ collapsible = false,
77
+ persistKey,
78
+ defaultOpen = true,
79
+ summary,
80
+ id,
81
+ children
82
+ }) {
83
+ const [open, setOpen] = useSectionOpen(persistKey, defaultOpen);
84
+ const bodyId = useId();
85
+ if (actions !== void 0) warnPastActionBudget(title, actions);
86
+ const headerActions = tabs !== void 0 || actions !== void 0 || collapsible ? /* @__PURE__ */ jsxs(Fragment, { children: [
87
+ tabs !== void 0 && open && /* @__PURE__ */ jsx(CtlSlot, { children: tabs }),
88
+ actions !== void 0 && /* @__PURE__ */ jsx(CtlSlot, { children: actions }),
89
+ collapsible && /* @__PURE__ */ jsx(ChevronToggle, { open, onToggle: () => setOpen(!open), controls: bodyId })
90
+ ] }) : void 0;
91
+ return /* @__PURE__ */ jsxs(
92
+ "div",
93
+ {
94
+ className: classes.root,
95
+ ...id !== void 0 && { id },
96
+ ...id !== void 0 && {
97
+ style: {
98
+ scrollMarginTop: "calc(var(--app-shell-header-height, 0px) + var(--basalt-page-bar-h, 0px))"
99
+ }
100
+ },
101
+ children: [
102
+ /* @__PURE__ */ jsx(
103
+ WidgetHeader,
104
+ {
105
+ tier: "section",
106
+ title,
107
+ ...icon !== void 0 && { icon },
108
+ ...subtitle !== void 0 && { subtitle },
109
+ ...info !== void 0 && { info },
110
+ ...value !== void 0 && { value },
111
+ ...delta !== void 0 && { delta },
112
+ ...deltaPeriod !== void 0 && { deltaPeriod },
113
+ ...sparkline !== void 0 && { sparkline },
114
+ ...count !== void 0 && { count },
115
+ ...headerActions !== void 0 && { actions: headerActions }
116
+ }
117
+ ),
118
+ summary !== void 0 && /* @__PURE__ */ jsx("div", { className: classes.summary, children: summary }),
119
+ open && /* @__PURE__ */ jsx("div", { id: bodyId, className: classes.body, children })
120
+ ]
121
+ }
122
+ );
123
+ }
124
+ export {
125
+ Section
126
+ };
@@ -0,0 +1,104 @@
1
+ /*
2
+ * Section — the tier-2 heading composer (docs/CONTROLS-SPEC.md §2.2). No border/background of its
3
+ * own — one shaded container level per page belongs to the cards inside a Section
4
+ * (`ChartCard`/`StatCard`), not to the Section itself.
5
+ */
6
+
7
+ /*
8
+ * 12px from the header to the body, and the same between two body children — one rhythm, read from
9
+ * the stack scale (`--vx-space-stack-md`) instead of the card-inset scale
10
+ * (`--mantine-spacing-sm`, 13px). A Section is a page-level GROUPING, so its gaps belong to the
11
+ * vertical rhythm, not to the inset ladder its cards use.
12
+ */
13
+ .root {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: var(--vx-space-stack-md, 0.75rem);
17
+ }
18
+
19
+ .body {
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: var(--vx-space-stack-md, 0.75rem);
23
+ }
24
+
25
+ /*
26
+ * `summary` — the always-visible row under the header. No border, no background (the Section owns
27
+ * neither); it is a plain flow row, so a consumer's `Group` of stats or one line of `Text` lands
28
+ * where the body would have started.
29
+ */
30
+ .summary {
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: var(--mantine-spacing-xs, 0.625rem);
34
+ }
35
+
36
+ /*
37
+ * The collapse chevron — a plain control, not a Mantine ActionIcon, so it sits directly in the header
38
+ * row (its own CtlSlot fragment would nest a theme provider around one glyph). It is sized to the
39
+ * `ctl` tier BY HAND for exactly that reason: a 12px glyph in a 20px box was a different height from
40
+ * every other control in the same header, which is what made the section header read as ragged. 30px
41
+ * square, glyph centred, no padding — the box IS the tier.
42
+ */
43
+ .chevron {
44
+ /* `relative` is the anchor for the C15 hit-area overlay in the `< sm` block at the bottom. */
45
+ position: relative;
46
+ display: inline-flex;
47
+ flex: none;
48
+ align-items: center;
49
+ justify-content: center;
50
+ /* `--vx-space-control-height-ctl` is a bare rem — the theme's `ctlSizeVars` (`theme/index.ts`)
51
+ * reconstructs every OTHER ctl-tier box as `calc(<rem> * var(--mantine-scale))` so it tracks
52
+ * Mantine's `theme.scale`; read raw here, this box used to stay fixed while a scaled Button/Input
53
+ * beside it grew or shrank, reintroducing the exact raggedness this control was sized to fix. */
54
+ width: calc(var(--vx-space-control-height-ctl) * var(--mantine-scale, 1));
55
+ height: calc(var(--vx-space-control-height-ctl) * var(--mantine-scale, 1));
56
+ appearance: none;
57
+ margin: 0;
58
+ padding: 0;
59
+ border: none;
60
+ background: none;
61
+ border-radius: var(--vx-radius-ctrl);
62
+ color: var(--vx-muted);
63
+ cursor: pointer;
64
+ }
65
+
66
+ .chevron:hover,
67
+ .chevron:focus-visible {
68
+ color: var(--vx-ink);
69
+ background: var(--vx-surface-subtle);
70
+ }
71
+
72
+ .chevron svg {
73
+ transition: transform 150ms ease;
74
+ }
75
+
76
+ .chevron[data-open='false'] svg {
77
+ transform: rotate(-90deg);
78
+ }
79
+
80
+ /*
81
+ * Law C15 — the chevron is a 12px glyph in a 20px box, which is a fine mouse target and an
82
+ * unusable thumb one. Below `sm` an `::after` overlay lifts the HIT area to
83
+ * `--vx-space-touch-control-height` (36px at level 0) while the visible glyph stays put — the same
84
+ * overlay `shell/page-bar.module.css` draws for the bar's own controls. `overflow: visible` is part
85
+ * of the fix, not tidying: an overlay that extends past the box is clipped by any `overflow:
86
+ * hidden` on the host (which is what made the identical overlay a no-op on Mantine's Button roots).
87
+ */
88
+ @media (max-width: 47.99375em) {
89
+ .chevron {
90
+ overflow: visible;
91
+ }
92
+
93
+ .chevron::after {
94
+ content: '';
95
+ position: absolute;
96
+ top: 50%;
97
+ left: 50%;
98
+ translate: -50% -50%;
99
+ width: 100%;
100
+ height: 100%;
101
+ min-width: var(--vx-space-touch-control-height, 2.25rem);
102
+ min-height: var(--vx-space-touch-control-height, 2.25rem);
103
+ }
104
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Typed CSS-module declaration (the S4 CSS-modules path). Named string props — not an index
3
+ * signature — so dot access stays verbatim under the package's strict flags.
4
+ */
5
+ declare const classes: {
6
+ readonly root: string
7
+ readonly body: string
8
+ readonly summary: string
9
+ readonly chevron: string
10
+ }
11
+ export default classes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=section.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section.test.d.ts","sourceRoot":"","sources":["../../src/section/section.test.tsx"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"app-breadcrumbs.d.ts","sourceRoot":"","sources":["../../src/shell/app-breadcrumbs.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAkB7C,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,MAAM,EACN,YAAY,EACZ,UAAU,EACV,IAAI,GACL,EAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IACpC,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,sCAyCA"}
1
+ {"version":3,"file":"app-breadcrumbs.d.ts","sourceRoot":"","sources":["../../src/shell/app-breadcrumbs.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAmB7C,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,MAAM,EACN,YAAY,EACZ,UAAU,EACV,IAAI,GACL,EAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IACpC,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,sCAqDA"}
@@ -1,6 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Anchor, Group, Text } from "@mantine/core";
3
3
  import { VX } from "../tokens/index.js";
4
+ import classes from "./app-header.module.css";
4
5
  const crumbStyle = { fontSize: VX.text.md, color: "var(--vx-faint)" };
5
6
  const separatorStyle = { fontSize: VX.text.md, color: "var(--vx-surface-border)" };
6
7
  const currentStyle = {
@@ -19,21 +20,23 @@ function AppBreadcrumbs({
19
20
  }) {
20
21
  if (!page) return null;
21
22
  return /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", style: { minWidth: 0 }, children: [
22
- section && /* @__PURE__ */ jsxs(Fragment, { children: [
23
- /* @__PURE__ */ jsx(Text, { style: crumbStyle, truncate: true, children: section }),
24
- /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
25
- ] }),
26
- parent && parentAnchor && /* @__PURE__ */ jsxs(Fragment, { children: [
27
- /* @__PURE__ */ jsx(Anchor, { style: crumbStyle, underline: "never", component: parentAnchor, truncate: true, children: parent }),
28
- /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
29
- ] }),
30
- parent && !parentAnchor && parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
31
- /* @__PURE__ */ jsx(Anchor, { style: crumbStyle, underline: "never", href: parentHref, truncate: true, children: parent }),
32
- /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
33
- ] }),
34
- parent && !parentAnchor && !parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
35
- /* @__PURE__ */ jsx(Text, { style: crumbStyle, truncate: true, children: parent }),
36
- /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
23
+ (section !== void 0 || parent !== void 0) && /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", className: classes.crumbAncestors, style: { minWidth: 0 }, children: [
24
+ section && /* @__PURE__ */ jsxs(Fragment, { children: [
25
+ /* @__PURE__ */ jsx(Text, { style: crumbStyle, truncate: true, children: section }),
26
+ /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
27
+ ] }),
28
+ parent && parentAnchor && /* @__PURE__ */ jsxs(Fragment, { children: [
29
+ /* @__PURE__ */ jsx(Anchor, { style: crumbStyle, underline: "never", component: parentAnchor, truncate: true, children: parent }),
30
+ /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
31
+ ] }),
32
+ parent && !parentAnchor && parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
33
+ /* @__PURE__ */ jsx(Anchor, { style: crumbStyle, underline: "never", href: parentHref, truncate: true, children: parent }),
34
+ /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
35
+ ] }),
36
+ parent && !parentAnchor && !parentHref && /* @__PURE__ */ jsxs(Fragment, { children: [
37
+ /* @__PURE__ */ jsx(Text, { style: crumbStyle, truncate: true, children: parent }),
38
+ /* @__PURE__ */ jsx(Text, { style: separatorStyle, children: "/" })
39
+ ] })
37
40
  ] }),
38
41
  /* @__PURE__ */ jsx(Text, { style: currentStyle, truncate: true, children: page })
39
42
  ] });
@@ -1,7 +1,10 @@
1
- /* App-shell top bar layout. Desktop: a single 56px row —
2
- * [burger? · breadcrumb] ········ [page actions] | [global actions].
3
- * Mobile (< sm): wraps to two rows — lead + global on row 1, the page action row
4
- * (horizontally scrollable) on row 2. Header height is responsive to match. */
1
+ /* App-shell top bar layout. ONE 48px row on every viewport —
2
+ * [breadcrumb] ········ [page bar row 1] | [global actions].
3
+ *
4
+ * The pre-1.26.0 mobile layout wrapped to a second, always-reserved row for the page actions and
5
+ * scrolled it sideways. Both are gone (law C7 and law C14): the header height is now the
6
+ * `appShellHeaderHeight` token at every width, an empty page bar reserves nothing, and overflow
7
+ * folds into `ActionGroup`'s kebab instead of scrolling out of view. */
5
8
 
6
9
  /*
7
10
  * Transparent header (docs/DESIGN-SPEC.md §5): NO bottom rule at all. The header and the page body
@@ -16,58 +19,95 @@
16
19
  height: 100%;
17
20
  }
18
21
 
22
+ /*
23
+ * **Exactly ONE side of the header row is elastic, and it is the side whose content can ellipsize.**
24
+ * That is the whole rule, and both collisions this header has produced were the same violation of
25
+ * it from opposite directions.
26
+ *
27
+ * First the breadcrumb was `flex-shrink: 0`, so it won the whole width and the primary action
28
+ * rendered UNDERNEATH it. Then BOTH sides were made shrinkable (`.lead` at `0 1 auto`, `.pageBar` at
29
+ * `1 1 auto` with `min-width: 0` pushed onto its children) — and at 390px the controls, which cannot
30
+ * clip, shrank below their content and spilled: a flex container with `justify-content: flex-end`
31
+ * overflows towards its START, so the sync button painted 30px INTO the page title. Measured in
32
+ * Chrome: the title spanned 119.7–197.1 and the sync button 158.3–188.3.
33
+ *
34
+ * So the lead is now the elastic side (`flex: 1 1 auto`, `min-width: 0`, each crumb truncating via
35
+ * its own `truncate` prop) and the control side is rigid. A row of buttons that genuinely does not
36
+ * fit is not a shrinking problem — it is the OVERFLOW FOLD's problem, and basalt already owns that
37
+ * (law C7: below `sm` row 1 is the primary plus ONE kebab).
38
+ */
19
39
  .lead {
20
40
  display: flex;
21
41
  align-items: center;
22
42
  gap: var(--mantine-spacing-sm);
23
- flex-shrink: 0;
43
+ flex: 1 1 auto;
24
44
  min-width: 0;
25
45
  }
26
46
 
27
- .pageActions {
28
- flex: 1 1 auto;
47
+ /* No `overflow: hidden` here or on `.lead` above: Mantine's focus ring is `outline: 2px solid;
48
+ * outline-offset: 2px` — OUTSIDE the anchor box — and a wrapper clipping this tightly to the
49
+ * breadcrumb anchors clipped the ring on every edge, leaving the primary in-shell nav link with no
50
+ * visible focus indicator. Truncation is already owned by each crumb's own `truncate` prop
51
+ * (`app-breadcrumbs.tsx`, Mantine's `[data-truncate]` rule sets its own `overflow: hidden;
52
+ * text-overflow: ellipsis; white-space: nowrap` on the element that actually holds the text) — this
53
+ * wrapper only needs to let its children shrink. */
54
+ .lead * {
29
55
  min-width: 0;
30
- display: flex;
31
- align-items: center;
32
- justify-content: flex-end;
33
- gap: var(--mantine-spacing-xs);
34
- overflow-x: auto;
35
- scrollbar-width: thin;
36
56
  }
37
57
 
38
- /* Keep the whole control row on one horizontally-scrollable line. Mantine Group
39
- * defaults to flex-wrap:wrap, which otherwise stacks the nested control groups and
40
- * clips them against the slim bar's fixed height. */
41
- .pageActions :global(.mantine-Group-root) {
58
+ /*
59
+ * The `PageBar` row-1 portal target — the RIGID side of the row (see `.lead` above for the rule and
60
+ * for the two collisions that established it). Never a scroll container — see the header note above.
61
+ *
62
+ * `flex: 0 0 auto` and no `min-width: 0` anywhere below it, deliberately: a button cannot ellipsize,
63
+ * so letting this side shrink does not make it narrower, it makes it OVERFLOW — leftwards, into the
64
+ * breadcrumb, because of the `justify-content: flex-end` two lines down. The previous version pushed
65
+ * `min-width: 0` onto every child for exactly the reason that comment gave (a `wrap="nowrap"` group's
66
+ * automatic minimum size is its min-content width and flex will not go below it) — the reasoning was
67
+ * right and the conclusion was backwards: that floor is the row's protection, not its bug.
68
+ */
69
+ .pageBar {
70
+ flex: 0 0 auto;
71
+ display: flex;
42
72
  flex-wrap: nowrap;
73
+ align-items: center;
74
+ justify-content: flex-end;
75
+ gap: var(--vx-space-control-gap);
43
76
  }
44
77
 
78
+ /*
79
+ * The shell globals sit at the SAME 30px tier as everything else in the row, and they get there the
80
+ * same way a home's slot does — `HeaderGlobalActions` wraps this region in a `CtlSlot`, so a
81
+ * consumer's bare `<ActionIcon>` in `globalActions` defaults to `size="ctl"` with no prop at the call
82
+ * site (law C5). They rendered at Mantine's `md` 28px before, which is 2px under the page group
83
+ * beside them: close enough to look like a mistake rather than a tier.
84
+ *
85
+ * The gap to the page group is `.bar`'s own `sm`, so the globals read as a separate cluster; between
86
+ * themselves they use the control gap, like every other adjacent-control pair.
87
+ */
45
88
  .global {
46
89
  display: flex;
90
+ flex-wrap: nowrap;
47
91
  align-items: center;
48
- gap: var(--mantine-spacing-xs);
92
+ gap: var(--vx-space-control-gap);
49
93
  flex-shrink: 0;
50
94
  }
51
95
 
52
- @media (max-width: 47.99375em) {
53
- .bar {
54
- flex-wrap: wrap;
55
- align-content: center;
56
- }
57
-
58
- .lead {
59
- order: 1;
60
- flex: 1 1 auto;
61
- }
62
-
63
- .global {
64
- order: 2;
65
- }
96
+ .global :global(.mantine-ActionIcon-root) > svg {
97
+ width: 1rem;
98
+ height: 1rem;
99
+ }
66
100
 
67
- .pageActions {
68
- order: 3;
69
- flex-basis: 100%;
70
- height: var(--vx-space-app-header-mobile-actions-height);
71
- justify-content: flex-start;
101
+ /*
102
+ * The breadcrumb's ANCESTOR crumbs — hidden below `sm`. See `app-breadcrumbs.tsx` for why: at 390px
103
+ * the row's elastic side truncated `Overview / Dashboard` to `O… / D…`, which is two ellipses and a
104
+ * separator in place of the one word that says where the reader is.
105
+ *
106
+ * `47.99375em` is the `sm` breakpoint in the exact literal form every other basalt CSS module uses —
107
+ * a media query cannot resolve a custom property.
108
+ */
109
+ @media (max-width: 47.99375em) {
110
+ .crumbAncestors {
111
+ display: none;
72
112
  }
73
113
  }
@@ -5,7 +5,8 @@
5
5
  declare const classes: {
6
6
  readonly bar: string
7
7
  readonly lead: string
8
- readonly pageActions: string
8
+ readonly pageBar: string
9
9
  readonly global: string
10
+ readonly crumbAncestors: string
10
11
  }
11
12
  export default classes
@@ -1,5 +1,5 @@
1
1
  import type { ReactElement } from 'react';
2
- import type { MobileNavConfig, MobileNavModel } from '../nav/types.js';
2
+ import type { MobileNavConfig, MobileNavModel, SidebarBlock } from '../nav/types.js';
3
3
  import type { BasaltAccountProps } from './account-types.js';
4
4
  import type { SettingsMenuItem } from './index.js';
5
5
  export type MobileNavProps = {
@@ -11,6 +11,13 @@ export type MobileNavProps = {
11
11
  * deletable: everything it used to hold is reachable from More. */
12
12
  account?: BasaltAccountProps | undefined;
13
13
  settingsMenuItems?: SettingsMenuItem[] | undefined;
14
+ /**
15
+ * The sidebar's blocks, projected. A block with `mobile: 'more'` becomes ONE row in the More
16
+ * surface (`Awaiting action · 3`) that opens a nested 44px-row sheet of its items — the sidebar
17
+ * itself does not exist below `sm`, so this is the only way its blocks are reachable at all.
18
+ * Counted for the menu-vs-sheet threshold by `blockRowCount`, never re-counted here.
19
+ */
20
+ blocks?: SidebarBlock[] | undefined;
14
21
  };
15
22
  /**
16
23
  * How many rows `account` contributes to the More surface — DELEGATED to `accountRows` rather than
@@ -21,5 +28,5 @@ export type MobileNavProps = {
21
28
  * rows — still conjured a More slot that opened empty.
22
29
  */
23
30
  export declare function accountRowCount(account: BasaltAccountProps | undefined): number;
24
- export declare function MobileNav({ model, config, account, settingsMenuItems, }: MobileNavProps): ReactElement;
31
+ export declare function MobileNav({ model, config, account, settingsMenuItems, blocks, }: MobileNavProps): ReactElement;
25
32
  //# sourceMappingURL=app-mobile-nav.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-mobile-nav.d.ts","sourceRoot":"","sources":["../../src/shell/app-mobile-nav.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,YAAY,EAAa,MAAM,OAAO,CAAA;AAChE,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAGf,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAiB/C,MAAM,MAAM,cAAc,GAAG;IAC3B,kFAAkF;IAClF,KAAK,EAAE,cAAc,CAAA;IACrB,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC;;wEAEoE;IACpE,OAAO,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACxC,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAA;CACnD,CAAA;AAkFD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAE/E;AAsFD,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,MAAM,EACN,OAAO,EACP,iBAAiB,GAClB,EAAE,cAAc,GAAG,YAAY,CAiP/B"}
1
+ {"version":3,"file":"app-mobile-nav.d.ts","sourceRoot":"","sources":["../../src/shell/app-mobile-nav.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,YAAY,EAAa,MAAM,OAAO,CAAA;AAChE,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAEd,YAAY,EAKb,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAmB/C,MAAM,MAAM,cAAc,GAAG;IAC3B,kFAAkF;IAClF,KAAK,EAAE,cAAc,CAAA;IACrB,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC;;wEAEoE;IACpE,OAAO,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACxC,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAA;IAClD;;;;;OAKG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAA;CACpC,CAAA;AAkFD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAE/E;AAsFD,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,MAAM,EACN,OAAO,EACP,iBAAiB,EACjB,MAAM,GACP,EAAE,cAAc,GAAG,YAAY,CAuW/B"}