basalt-ui 1.25.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (476) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +26 -0
  3. package/MIGRATING.md +815 -23
  4. package/README.md +45 -32
  5. package/agent/rules/basalt-batteries.md +205 -0
  6. package/agent/rules/basalt-charts.md +125 -555
  7. package/agent/rules/basalt-controls.md +160 -0
  8. package/agent/rules/basalt-mantine.md +146 -439
  9. package/agent/rules/basalt-state.md +118 -96
  10. package/agent/rules/basalt-tokens.md +126 -288
  11. package/agent/skills/basalt-app/SKILL.md +70 -195
  12. package/agent/skills/basalt-charts/SKILL.md +54 -234
  13. package/agent/skills/basalt-design/SKILL.md +56 -248
  14. package/agent/templates/CLAUDE-block.md.tpl +31 -40
  15. package/agent/templates/DESIGN.md.tpl +22 -51
  16. package/configs/oxlint-plugin.js +1465 -36
  17. package/configs/oxlint-plugin.test.ts +1190 -11
  18. package/configs/oxlint.json +11 -3
  19. package/dist/charts/cursor/resolve.d.ts +18 -0
  20. package/dist/charts/cursor/resolve.d.ts.map +1 -1
  21. package/dist/charts/cursor/resolve.js +15 -6
  22. package/dist/charts/cursor/scope.d.ts +6 -3
  23. package/dist/charts/cursor/scope.d.ts.map +1 -1
  24. package/dist/charts/cursor/store.d.ts +5 -1
  25. package/dist/charts/cursor/store.d.ts.map +1 -1
  26. package/dist/charts/cursor/store.js +4 -4
  27. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  28. package/dist/charts/hooks/useChartCursor.js +9 -8
  29. package/dist/charts/index.d.ts +2 -2
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/primitives/ChartCard.d.ts +34 -9
  32. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  33. package/dist/charts/primitives/ChartCard.js +29 -114
  34. package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
  35. package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
  36. package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
  37. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  38. package/dist/charts/sparklines/BarSparkline.js +18 -4
  39. package/dist/cli/check-coverage.test.d.ts +2 -0
  40. package/dist/cli/check-coverage.test.d.ts.map +1 -0
  41. package/dist/cli/index.d.ts +67 -4
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +225 -7
  44. package/dist/connectivity/connectivity-indicator.d.ts +4 -1
  45. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  46. package/dist/connectivity/connectivity-indicator.js +1 -1
  47. package/dist/content/index.d.ts +5 -3
  48. package/dist/content/index.d.ts.map +1 -1
  49. package/dist/content/index.js +4 -2
  50. package/dist/content/prose.module.css +7 -28
  51. package/dist/controls/actions.d.ts +169 -0
  52. package/dist/controls/actions.d.ts.map +1 -0
  53. package/dist/controls/actions.js +287 -0
  54. package/dist/controls/actions.module.css +62 -0
  55. package/dist/controls/actions.module.css.d.ts +13 -0
  56. package/dist/controls/actions.test.d.ts +2 -0
  57. package/dist/controls/actions.test.d.ts.map +1 -0
  58. package/dist/controls/actions.type-guard.test.d.ts +2 -0
  59. package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
  60. package/dist/controls/compare-filter.d.ts +41 -0
  61. package/dist/controls/compare-filter.d.ts.map +1 -0
  62. package/dist/controls/compare-filter.js +23 -0
  63. package/dist/controls/control-group.d.ts +19 -0
  64. package/dist/controls/control-group.d.ts.map +1 -0
  65. package/dist/controls/control-group.js +20 -0
  66. package/dist/controls/control-group.test.d.ts +2 -0
  67. package/dist/controls/control-group.test.d.ts.map +1 -0
  68. package/dist/controls/controls.module.css +542 -0
  69. package/dist/controls/controls.module.css.d.ts +35 -0
  70. package/dist/controls/controls.router.test.d.ts +2 -0
  71. package/dist/controls/controls.router.test.d.ts.map +1 -0
  72. package/dist/controls/enum-filter.d.ts +39 -0
  73. package/dist/controls/enum-filter.d.ts.map +1 -0
  74. package/dist/controls/enum-filter.js +93 -0
  75. package/dist/controls/filter-context.d.ts +60 -0
  76. package/dist/controls/filter-context.d.ts.map +1 -0
  77. package/dist/controls/filter-context.js +69 -0
  78. package/dist/controls/filter-pill.d.ts +28 -0
  79. package/dist/controls/filter-pill.d.ts.map +1 -0
  80. package/dist/controls/filter-pill.js +70 -0
  81. package/dist/controls/filter-set.d.ts +13 -0
  82. package/dist/controls/filter-set.d.ts.map +1 -0
  83. package/dist/controls/filter-set.js +125 -0
  84. package/dist/controls/filter-set.test.d.ts +2 -0
  85. package/dist/controls/filter-set.test.d.ts.map +1 -0
  86. package/dist/controls/filter-sheet.d.ts +117 -0
  87. package/dist/controls/filter-sheet.d.ts.map +1 -0
  88. package/dist/controls/filter-sheet.js +146 -0
  89. package/dist/controls/glyphs.d.ts +27 -0
  90. package/dist/controls/glyphs.d.ts.map +1 -0
  91. package/dist/controls/glyphs.js +49 -0
  92. package/dist/controls/index.d.ts +20 -0
  93. package/dist/controls/index.d.ts.map +1 -0
  94. package/dist/controls/index.js +46 -0
  95. package/dist/controls/multi-select-filter.d.ts +23 -0
  96. package/dist/controls/multi-select-filter.d.ts.map +1 -0
  97. package/dist/controls/multi-select-filter.js +89 -0
  98. package/dist/controls/number-filter.d.ts +26 -0
  99. package/dist/controls/number-filter.d.ts.map +1 -0
  100. package/dist/controls/number-filter.js +143 -0
  101. package/dist/controls/peer-boundary.test.d.ts +2 -0
  102. package/dist/controls/peer-boundary.test.d.ts.map +1 -0
  103. package/dist/controls/props-contract.test.d.ts +2 -0
  104. package/dist/controls/props-contract.test.d.ts.map +1 -0
  105. package/dist/controls/range-filter.d.ts +43 -0
  106. package/dist/controls/range-filter.d.ts.map +1 -0
  107. package/dist/controls/range-filter.js +135 -0
  108. package/dist/controls/range-filter.type-guard.test.d.ts +2 -0
  109. package/dist/controls/range-filter.type-guard.test.d.ts.map +1 -0
  110. package/dist/controls/search-filter.d.ts +10 -0
  111. package/dist/controls/search-filter.d.ts.map +1 -0
  112. package/dist/controls/search-filter.js +81 -0
  113. package/dist/controls/select-filter.d.ts +74 -0
  114. package/dist/controls/select-filter.d.ts.map +1 -0
  115. package/dist/controls/select-filter.js +10 -0
  116. package/dist/controls/sync-button.d.ts +38 -0
  117. package/dist/controls/sync-button.d.ts.map +1 -0
  118. package/dist/controls/sync-button.js +119 -0
  119. package/dist/controls/sync-button.module.css +63 -0
  120. package/dist/controls/sync-button.module.css.d.ts +11 -0
  121. package/dist/controls/sync-button.test.d.ts +2 -0
  122. package/dist/controls/sync-button.test.d.ts.map +1 -0
  123. package/dist/controls/toggle-filter.d.ts +9 -0
  124. package/dist/controls/toggle-filter.d.ts.map +1 -0
  125. package/dist/controls/toggle-filter.js +42 -0
  126. package/dist/controls/view-tabs.d.ts +23 -0
  127. package/dist/controls/view-tabs.d.ts.map +1 -0
  128. package/dist/controls/view-tabs.js +59 -0
  129. package/dist/controls-dates/date-range-picker.d.ts +4 -0
  130. package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
  131. package/dist/controls-dates/date-range-picker.js +18 -0
  132. package/dist/controls-dates/index.d.ts +9 -0
  133. package/dist/controls-dates/index.d.ts.map +1 -0
  134. package/dist/controls-dates/index.js +4 -0
  135. package/dist/dashboard/delta-badge.d.ts +6 -12
  136. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  137. package/dist/dashboard/delta-badge.js +1 -50
  138. package/dist/dashboard/index.d.ts +1 -1
  139. package/dist/dashboard/index.d.ts.map +1 -1
  140. package/dist/dashboard/index.js +3 -1
  141. package/dist/dashboard/settings-section.d.ts +6 -4
  142. package/dist/dashboard/settings-section.d.ts.map +1 -1
  143. package/dist/dashboard/settings-section.js +23 -16
  144. package/dist/dashboard/settings-section.module.css +10 -0
  145. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  146. package/dist/dashboard/settings-section.test.d.ts +2 -0
  147. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  148. package/dist/dashboard/stat-card.d.ts +95 -9
  149. package/dist/dashboard/stat-card.d.ts.map +1 -1
  150. package/dist/dashboard/stat-card.js +108 -49
  151. package/dist/dashboard/stat-card.module.css +153 -0
  152. package/dist/dashboard/stat-card.module.css.d.ts +15 -0
  153. package/dist/data/data-table.d.ts +37 -15
  154. package/dist/data/data-table.d.ts.map +1 -1
  155. package/dist/data/data-table.js +96 -58
  156. package/dist/guard/grace.test.d.ts +20 -0
  157. package/dist/guard/grace.test.d.ts.map +1 -0
  158. package/dist/guard/index.d.ts +66 -3
  159. package/dist/guard/index.d.ts.map +1 -1
  160. package/dist/guard/index.js +78 -7
  161. package/dist/guard/types.d.ts +3 -3
  162. package/dist/guard/types.d.ts.map +1 -1
  163. package/dist/index.d.ts +13 -3
  164. package/dist/index.d.ts.map +1 -1
  165. package/dist/index.js +13 -10
  166. package/dist/nav/types.d.ts +78 -2
  167. package/dist/nav/types.d.ts.map +1 -1
  168. package/dist/notifications/bell.d.ts +4 -1
  169. package/dist/notifications/bell.d.ts.map +1 -1
  170. package/dist/notifications/index.d.ts +1 -1
  171. package/dist/router-tanstack/field.d.ts +14 -0
  172. package/dist/router-tanstack/field.d.ts.map +1 -0
  173. package/dist/router-tanstack/field.js +4 -0
  174. package/dist/router-tanstack/index.d.ts +6 -2
  175. package/dist/router-tanstack/index.d.ts.map +1 -1
  176. package/dist/router-tanstack/index.js +4 -0
  177. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  178. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  179. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  180. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  181. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  182. package/dist/router-tanstack/search-param-store.js +16 -45
  183. package/dist/router-tanstack/search-store.d.ts +84 -0
  184. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  185. package/dist/router-tanstack/search-store.js +271 -0
  186. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  187. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  188. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  189. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  190. package/dist/router-tanstack/use-nav.d.ts +0 -2
  191. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  192. package/dist/router-tanstack/use-nav.js +34 -12
  193. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  194. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  195. package/dist/section/index.d.ts +2 -0
  196. package/dist/section/index.d.ts.map +1 -0
  197. package/dist/section/index.js +4 -0
  198. package/dist/section/section.d.ts +24 -0
  199. package/dist/section/section.d.ts.map +1 -0
  200. package/dist/section/section.js +126 -0
  201. package/dist/section/section.module.css +104 -0
  202. package/dist/section/section.module.css.d.ts +11 -0
  203. package/dist/section/section.test.d.ts +2 -0
  204. package/dist/section/section.test.d.ts.map +1 -0
  205. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  206. package/dist/shell/app-breadcrumbs.js +18 -15
  207. package/dist/shell/app-header.module.css +77 -37
  208. package/dist/shell/app-header.module.css.d.ts +2 -1
  209. package/dist/shell/app-mobile-nav.d.ts +9 -2
  210. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  211. package/dist/shell/app-mobile-nav.js +98 -11
  212. package/dist/shell/app-mobile-nav.module.css +8 -0
  213. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  214. package/dist/shell/app-sidebar.d.ts +55 -28
  215. package/dist/shell/app-sidebar.d.ts.map +1 -1
  216. package/dist/shell/app-sidebar.js +116 -67
  217. package/dist/shell/app-sidebar.module.css +303 -20
  218. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  219. package/dist/shell/index.d.ts +55 -21
  220. package/dist/shell/index.d.ts.map +1 -1
  221. package/dist/shell/index.js +40 -18
  222. package/dist/shell/mobile-nav-model.d.ts +13 -2
  223. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  224. package/dist/shell/mobile-nav-model.js +15 -1
  225. package/dist/shell/nav-count-badge.d.ts +1 -1
  226. package/dist/shell/page-bar.d.ts +51 -0
  227. package/dist/shell/page-bar.d.ts.map +1 -0
  228. package/dist/shell/page-bar.js +154 -0
  229. package/dist/shell/page-bar.module.css +178 -0
  230. package/dist/shell/page-bar.module.css.d.ts +17 -0
  231. package/dist/shell/page-bar.test.d.ts +2 -0
  232. package/dist/shell/page-bar.test.d.ts.map +1 -0
  233. package/dist/shell/sidebar-block-model.d.ts +48 -0
  234. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  235. package/dist/shell/sidebar-block-model.js +39 -0
  236. package/dist/shell/sidebar-blocks.d.ts +32 -0
  237. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  238. package/dist/shell/sidebar-blocks.js +169 -0
  239. package/dist/shell/sidebar-search.d.ts +13 -1
  240. package/dist/shell/sidebar-search.d.ts.map +1 -1
  241. package/dist/shell/sidebar-search.js +34 -1
  242. package/dist/shell/sidebar-search.module.css +26 -0
  243. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  244. package/dist/state/fields.d.ts +565 -0
  245. package/dist/state/fields.d.ts.map +1 -0
  246. package/dist/state/fields.js +405 -0
  247. package/dist/state/fields.test.d.ts +2 -0
  248. package/dist/state/fields.test.d.ts.map +1 -0
  249. package/dist/state/persisted.d.ts +60 -0
  250. package/dist/state/persisted.d.ts.map +1 -0
  251. package/dist/state/persisted.js +123 -0
  252. package/dist/state/persisted.test.d.ts +2 -0
  253. package/dist/state/persisted.test.d.ts.map +1 -0
  254. package/dist/state.d.ts +11 -56
  255. package/dist/state.d.ts.map +1 -1
  256. package/dist/state.js +14 -120
  257. package/dist/styles.css +13 -0
  258. package/dist/surfaces.d.ts +106 -24
  259. package/dist/surfaces.d.ts.map +1 -1
  260. package/dist/surfaces.js +156 -19
  261. package/dist/theme/controls.module.css +16 -1
  262. package/dist/theme/ctl-theme.d.ts +32 -0
  263. package/dist/theme/ctl-theme.d.ts.map +1 -0
  264. package/dist/theme/ctl-theme.js +54 -0
  265. package/dist/theme/ctl-theme.test.d.ts +2 -0
  266. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  267. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  268. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  269. package/dist/theme/divider-law.test.d.ts +2 -0
  270. package/dist/theme/divider-law.test.d.ts.map +1 -0
  271. package/dist/theme/icon-slot.d.ts +44 -0
  272. package/dist/theme/icon-slot.d.ts.map +1 -0
  273. package/dist/theme/icon-slot.js +21 -0
  274. package/dist/theme/icon-slot.module.css +47 -0
  275. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  276. package/dist/theme/icon-slot.test.d.ts +2 -0
  277. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  278. package/dist/theme/index.d.ts +1 -0
  279. package/dist/theme/index.d.ts.map +1 -1
  280. package/dist/theme/index.js +82 -5
  281. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  282. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  283. package/dist/theme/segmented-control.module.css +20 -0
  284. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  285. package/dist/tokens/index.d.ts +12 -0
  286. package/dist/tokens/index.d.ts.map +1 -1
  287. package/dist/tokens/index.js +38 -8
  288. package/dist/tokens/palette.d.ts +97 -61
  289. package/dist/tokens/palette.d.ts.map +1 -1
  290. package/dist/tokens/palette.js +76 -38
  291. package/dist/tokens.css +10 -2
  292. package/dist/widget-header/delta-badge.d.ts +13 -0
  293. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  294. package/dist/widget-header/delta-badge.js +29 -0
  295. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  296. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  297. package/dist/widget-header/index.d.ts +3 -0
  298. package/dist/widget-header/index.d.ts.map +1 -0
  299. package/dist/widget-header/index.js +6 -0
  300. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  301. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  302. package/dist/widget-header/widget-header.d.ts +67 -0
  303. package/dist/widget-header/widget-header.d.ts.map +1 -0
  304. package/dist/widget-header/widget-header.js +89 -0
  305. package/dist/widget-header/widget-header.module.css +350 -0
  306. package/dist/widget-header/widget-header.module.css.d.ts +25 -0
  307. package/dist/widget-header/widget-header.test.d.ts +2 -0
  308. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  309. package/llms.txt +30 -17
  310. package/package.json +14 -1
  311. package/src/charts/cursor/resolve.test.ts +29 -1
  312. package/src/charts/cursor/resolve.ts +38 -6
  313. package/src/charts/cursor/scope.tsx +6 -3
  314. package/src/charts/cursor/store.test.ts +19 -1
  315. package/src/charts/cursor/store.ts +10 -5
  316. package/src/charts/hooks/useChartCursor.ts +16 -8
  317. package/src/charts/index.ts +2 -2
  318. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  319. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  320. package/src/charts/primitives/ChartCard.tsx +86 -146
  321. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  322. package/src/cli/check-coverage.test.ts +297 -0
  323. package/src/cli/check-theme.test.ts +38 -14
  324. package/src/cli/index.ts +392 -16
  325. package/src/cli/init-sync.test.ts +97 -0
  326. package/src/connectivity/connectivity-indicator.tsx +16 -9
  327. package/src/content/index.ts +16 -3
  328. package/src/content/prose.module.css +7 -28
  329. package/src/content/prose.module.css.test.ts +8 -24
  330. package/src/controls/actions.module.css +62 -0
  331. package/src/controls/actions.module.css.d.ts +13 -0
  332. package/src/controls/actions.test.tsx +252 -0
  333. package/src/controls/actions.tsx +655 -0
  334. package/src/controls/actions.type-guard.test.ts +63 -0
  335. package/src/controls/compare-filter.tsx +64 -0
  336. package/src/controls/control-group.test.tsx +229 -0
  337. package/src/controls/control-group.tsx +63 -0
  338. package/src/controls/controls.module.css +542 -0
  339. package/src/controls/controls.module.css.d.ts +35 -0
  340. package/src/controls/controls.router.test.tsx +1053 -0
  341. package/src/controls/enum-filter.tsx +170 -0
  342. package/src/controls/filter-context.tsx +158 -0
  343. package/src/controls/filter-pill.tsx +120 -0
  344. package/src/controls/filter-set.test.tsx +472 -0
  345. package/src/controls/filter-set.tsx +225 -0
  346. package/src/controls/filter-sheet.tsx +285 -0
  347. package/src/controls/glyphs.tsx +81 -0
  348. package/src/controls/index.ts +49 -0
  349. package/src/controls/multi-select-filter.tsx +140 -0
  350. package/src/controls/number-filter.tsx +291 -0
  351. package/src/controls/peer-boundary.test.ts +85 -0
  352. package/src/controls/props-contract.test.tsx +137 -0
  353. package/src/controls/range-filter.tsx +237 -0
  354. package/src/controls/range-filter.type-guard.test.tsx +122 -0
  355. package/src/controls/search-filter.tsx +123 -0
  356. package/src/controls/select-filter.tsx +89 -0
  357. package/src/controls/sync-button.module.css +63 -0
  358. package/src/controls/sync-button.module.css.d.ts +11 -0
  359. package/src/controls/sync-button.test.tsx +183 -0
  360. package/src/controls/sync-button.tsx +197 -0
  361. package/src/controls/toggle-filter.tsx +60 -0
  362. package/src/controls/view-tabs.tsx +110 -0
  363. package/src/controls-dates/date-range-picker.tsx +42 -0
  364. package/src/controls-dates/index.ts +8 -0
  365. package/src/dashboard/delta-badge.tsx +4 -74
  366. package/src/dashboard/index.ts +6 -1
  367. package/src/dashboard/settings-section.module.css +10 -0
  368. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  369. package/src/dashboard/settings-section.test.tsx +56 -0
  370. package/src/dashboard/settings-section.tsx +33 -30
  371. package/src/dashboard/stat-card.module.css +153 -0
  372. package/src/dashboard/stat-card.module.css.d.ts +15 -0
  373. package/src/dashboard/stat-card.test.tsx +298 -1
  374. package/src/dashboard/stat-card.tsx +289 -67
  375. package/src/data/data-table.test.tsx +96 -11
  376. package/src/data/data-table.tsx +228 -84
  377. package/src/guard/check-source.test.ts +159 -13
  378. package/src/guard/grace.test.ts +158 -0
  379. package/src/guard/index.ts +185 -29
  380. package/src/guard/types.ts +4 -2
  381. package/src/index.ts +51 -5
  382. package/src/nav/types.ts +84 -2
  383. package/src/notifications/bell.tsx +6 -3
  384. package/src/notifications/index.ts +1 -1
  385. package/src/router-tanstack/field.ts +35 -0
  386. package/src/router-tanstack/index.ts +28 -2
  387. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  388. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  389. package/src/router-tanstack/search-param-store.ts +42 -159
  390. package/src/router-tanstack/search-store.router.test.tsx +882 -0
  391. package/src/router-tanstack/search-store.test.ts +578 -0
  392. package/src/router-tanstack/search-store.ts +546 -0
  393. package/src/router-tanstack/use-nav.test.tsx +187 -0
  394. package/src/router-tanstack/use-nav.ts +72 -14
  395. package/src/section/index.ts +1 -0
  396. package/src/section/section.module.css +104 -0
  397. package/src/section/section.module.css.d.ts +11 -0
  398. package/src/section/section.test.tsx +204 -0
  399. package/src/section/section.tsx +220 -0
  400. package/src/shell/app-breadcrumbs.tsx +44 -31
  401. package/src/shell/app-header.module.css +77 -37
  402. package/src/shell/app-header.module.css.d.ts +2 -1
  403. package/src/shell/app-mobile-nav.module.css +8 -0
  404. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  405. package/src/shell/app-mobile-nav.tsx +147 -8
  406. package/src/shell/app-sidebar-account.tsx +2 -2
  407. package/src/shell/app-sidebar.module.css +303 -20
  408. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  409. package/src/shell/app-sidebar.test.tsx +456 -41
  410. package/src/shell/app-sidebar.tsx +276 -140
  411. package/src/shell/index.test.tsx +291 -5
  412. package/src/shell/index.tsx +137 -38
  413. package/src/shell/mobile-nav-model.test.ts +228 -7
  414. package/src/shell/mobile-nav-model.ts +53 -3
  415. package/src/shell/nav-count-badge.tsx +1 -1
  416. package/src/shell/page-bar.module.css +178 -0
  417. package/src/shell/page-bar.module.css.d.ts +17 -0
  418. package/src/shell/page-bar.test.tsx +316 -0
  419. package/src/shell/page-bar.tsx +315 -0
  420. package/src/shell/sidebar-block-model.ts +83 -0
  421. package/src/shell/sidebar-blocks.tsx +301 -0
  422. package/src/shell/sidebar-search.module.css +26 -0
  423. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  424. package/src/shell/sidebar-search.tsx +71 -2
  425. package/src/state/fields.test.tsx +512 -0
  426. package/src/state/fields.ts +1150 -0
  427. package/src/state/persisted.test.ts +91 -0
  428. package/src/state/persisted.ts +261 -0
  429. package/src/state.ts +52 -240
  430. package/src/styles.css +13 -0
  431. package/src/surfaces.ts +204 -36
  432. package/src/theme/controls.module.css +16 -1
  433. package/src/theme/ctl-theme.test.tsx +275 -0
  434. package/src/theme/ctl-theme.tsx +138 -0
  435. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  436. package/src/theme/density-relations.test.ts +106 -51
  437. package/src/theme/divider-law.test.ts +259 -0
  438. package/src/theme/icon-slot.module.css +47 -0
  439. package/src/theme/icon-slot.module.css.d.ts +8 -0
  440. package/src/theme/icon-slot.test.tsx +275 -0
  441. package/src/theme/icon-slot.tsx +59 -0
  442. package/src/theme/index.ts +165 -3
  443. package/src/theme/layout-rhythm-css.test.ts +72 -0
  444. package/src/theme/radius.test.ts +11 -1
  445. package/src/theme/segmented-control.module.css +20 -0
  446. package/src/theme/shadow-surfaces.test.ts +20 -7
  447. package/src/theme/spacing.test.ts +75 -27
  448. package/src/theme/use-basalt-spacing.ts +2 -2
  449. package/src/tokens/density.test.ts +7 -25
  450. package/src/tokens/index.ts +48 -8
  451. package/src/tokens/palette.ts +107 -73
  452. package/src/widget-header/delta-badge.test.tsx +60 -0
  453. package/src/widget-header/delta-badge.tsx +57 -0
  454. package/src/widget-header/index.ts +2 -0
  455. package/src/widget-header/no-mantine.test.ts +33 -0
  456. package/src/widget-header/widget-header.module.css +350 -0
  457. package/src/widget-header/widget-header.module.css.d.ts +25 -0
  458. package/src/widget-header/widget-header.test.tsx +187 -0
  459. package/src/widget-header/widget-header.tsx +213 -0
  460. package/agent/rules/basalt-agent.md +0 -452
  461. package/agent/rules/basalt-app.md +0 -246
  462. package/agent/rules/basalt-commands.md +0 -320
  463. package/agent/rules/basalt-content.md +0 -409
  464. package/agent/rules/basalt-data.md +0 -329
  465. package/agent/rules/basalt-forms.md +0 -186
  466. package/agent/rules/basalt-notifications.md +0 -264
  467. package/agent/rules/basalt-query.md +0 -187
  468. package/agent/rules/basalt-router.md +0 -336
  469. package/dist/content/article-filter-bar.d.ts +0 -40
  470. package/dist/content/article-filter-bar.d.ts.map +0 -1
  471. package/dist/content/article-filter-bar.js +0 -56
  472. package/dist/shell/page-header.d.ts +0 -13
  473. package/dist/shell/page-header.d.ts.map +0 -1
  474. package/dist/shell/page-header.js +0 -23
  475. package/src/content/article-filter-bar.tsx +0 -88
  476. package/src/shell/page-header.tsx +0 -35
@@ -1,409 +0,0 @@
1
- ---
2
- source: basalt-ui
3
- description: Content/prose surface from basalt-ui/content — Prose, CodeBlock, Callout, TableOfContents, ReadingProgress, Markdown (streaming-aware), MermaidDiagram, MDX component map, ArticleLayout, ArticleCard/Grid, GuideLink/GuideDrawer, and the content-collections + TanStack Start recipe. Mantine-coupled.
4
- paths:
5
- - 'src/content/**'
6
- - 'apps/**/src/**/*content*'
7
- ---
8
-
9
- # Basalt Content — Prose, Markdown, Mermaid, MDX, Docs Framing
10
-
11
- basalt-ui ships `./content` — a Mantine-coupled surface for long-form reading (docs, guides,
12
- hand-authored articles, AI-streamed prose) plus the docs-framing chrome around it: a page layout,
13
- overview cards, and a contextual-help drawer.
14
-
15
- ## Prose vs Markdown vs MDX
16
-
17
- | Entry | Input | Use case |
18
- | ----------------------------------------- | ------------------ | ---------------------------------------------------------------------- |
19
- | `<Prose>` | React children | hand-authored JSX articles |
20
- | `<Markdown>` | a markdown string | CMS content, files, **AI-streamed output** (`streaming` prop) |
21
- | `mdxComponents` / `createMdxComponents()` | MDX runtime output | `content-collections`/any MDX pipeline's `MDXContent components={...}` |
22
-
23
- All three route through the SAME typography CSS (`Prose`), the same `CodeBlock`/`MermaidDiagram`
24
- fenced-code rendering, and the same GFM-alert-aware `Callout` blockquote mapping — a hand-authored
25
- article, a rendered markdown file, and an MDX guide are visually indistinguishable. `Markdown`
26
- wraps its own `<Prose>` internally; `mdxComponents` does NOT — wrap MDX output in `<Prose>`
27
- yourself, or reach for `ArticleLayout` (below), which wraps it for you.
28
-
29
- ## Density
30
-
31
- `density='article'` (default) — 16px body, 1.7 leading, a 72ch measure (`VX.text.h1`/`h2` + the
32
- `--vx-prose-measure` token), generous block spacing. `density='chat'` — the compact chat typography
33
- (15px/1.55). Use `chat` density for agent/chat text, e.g. `<Markdown streaming density="chat">`.
34
-
35
- **The type-ladder rule applies here too**: never a literal font-size in JSX or a bespoke CSS value
36
- — every size in `Prose`/`CodeBlock`/`Callout`/`ArticleLayout`/`ArticleCard`/`GuideDrawer` resolves
37
- to a `VX.text.*` / `--vx-text-*` step. `h1` (26px) and `h2` (21px) are the two ladder steps this
38
- surface added.
39
-
40
- ## CodeBlock — optional-peer behavior
41
-
42
- `shiki` is an OPTIONAL peer, lazy-loaded via a module-level singleton (`createHighlighterCore`,
43
- dual `github-light`/`github-dark` themes, `defaultColor: false` — a Mantine scheme flip is then
44
- pure CSS, zero re-highlighting). Without `shiki` installed, or for an unrecognized `language`,
45
- `CodeBlock` degrades to a plain mono `<pre>` — never a crash, never an unhandled rejection.
46
-
47
- ```bash
48
- bun add shiki
49
- ```
50
-
51
- ```tsx
52
- import { CodeBlock } from 'basalt-ui/content'
53
-
54
- <CodeBlock title="vite.config.ts" language="ts" code={source} />
55
- <CodeBlock language="bash" code="bun add basalt-ui" />
56
- ```
57
-
58
- ## TableOfContents / ReadingProgress
59
-
60
- `TableOfContents` auto-collects `h2`/`h3` ids from a `containerRef` (IntersectionObserver
61
- scroll-spy) or takes an explicit `items` list — pair it with `headingSlug`/`SlugTracker` when
62
- authoring heading ids by hand. `ReadingProgress` is an opt-in scroll-driven top bar, `target`
63
- defaults to the whole document. Both are headless-styled but Mantine-coupled; `ArticleLayout`
64
- already wires both for you (below) — reach for them directly only when composing a custom page
65
- shell.
66
-
67
- ## Markdown — streaming contract
68
-
69
- `<Markdown streaming>` is the flagship AI-stream path: pass the accumulating string on every
70
- token, no debouncing needed.
71
-
72
- - **`blockSplit`** (own fence-aware, blank-line lexer — no dep) splits `children` into top-level
73
- blocks. Every block except the last renders through a `React.memo`'d renderer — referentially
74
- stable `components`/`urlTransform` (built once per `Markdown` render via `useMemo`) is what makes
75
- the memo actually skip re-rendering an unchanged settled block.
76
- - The LAST (in-flight) block is repaired via `remend` before rendering — unclosed `**`/`` ` ``/
77
- `~~`/`$$`, an incomplete `[text](url` link (remend rewrites it to
78
- `streamdown:incomplete-link` — any `streamdown:` href is dropped by the link hardening below).
79
- `remend` is a lazy optional peer (dynamic `import()`); the root `.` entry no longer hard-requires
80
- it.
81
- - **`streaming` is a RENDERING mode only** — it says the text is still arriving, not who wrote it.
82
- Trust is a separate prop, `contentTrust: 'trusted' | 'untrusted'`, and it is the sole input to the
83
- image allowlist below. Any surface rendering agent/model output must pin
84
- `contentTrust="untrusted"` explicitly — a finished agent message is still model-generated, so
85
- don't lean on `streaming` as a stand-in for trust.
86
- - **Hardening is ALWAYS ON**, streaming or not: `allowedLinkPrefixes` (default `['https://',
87
- 'mailto:', '#', '/']`) / `defaultOrigin` (resolves a relative URL before the allowlist check's
88
- result is used) / a `rehype-sanitize` pass (optional peer; `sanitizeSchema` only ADDS to the
89
- default schema, never removes from it). `allowedImagePrefixes` defaults off `contentTrust`, NOT
90
- `streaming`: `['https://', '/']` when trusted, same-origin-only `['/']` when untrusted — an
91
- auto-fetched image is the classic prompt-injection exfiltration channel. `contentTrust` itself
92
- defaults to `'untrusted'` when `streaming` is set, `'trusted'` otherwise — a backward-compat
93
- floor, not the policy (see the bullet above). An explicit `allowedImagePrefixes` always overrides
94
- the `contentTrust` default. A disallowed `href`/`src` renders as an unlinked `<span>` / a dropped
95
- image — never a raw unsafe URL.
96
- - Fenced code highlights/upgrades per BLOCK on settle, not per token — the in-flight fence renders
97
- plain mono (`CodeBlock showCopy={false}`) until its block settles.
98
- - Heading ids: a document-wide `SlugTracker` in non-streaming mode gives correct cross-heading
99
- dedup (`overview`, `overview-1`, …). In `streaming` mode each block gets its OWN tracker instead
100
- (a shared tracker would have to live across memoized blocks that never re-render) — a heading
101
- text repeated across two DIFFERENT blocks will NOT get a deduped suffix. Streamed chat content
102
- doesn't drive a `TableOfContents`, so this is an accepted tradeoff, not a bug.
103
- - `StreamingMarkdown` is DELETED — `Markdown` is the package's only markdown renderer. Render
104
- agent/chat text via `<Markdown streaming density="chat">`. `agent/** -> content` is lint-blocked
105
- by design, so `./agent` deliberately ships NO renderer — `PartList` takes a consumer-supplied
106
- `components.text` instead.
107
-
108
- ## MermaidDiagram — support matrix
109
-
110
- `beautiful-mermaid` (optional peer, lazy dynamic import, module-level singleton) covers exactly
111
- six diagram types: **flowchart, state, sequence, class, ER, XY chart**. Anything else (full
112
- mermaid grammar — gantt, git graphs, mindmaps, …) is a consumer escape hatch: install the full
113
- `mermaid` package yourself and render it outside `Markdown`/`MermaidDiagram`; this module does not
114
- attempt to cover it.
115
-
116
- Theming passes `var(--vx-*)` strings straight through as `bg`/`fg`/`line`/`accent`/`muted`/
117
- `surface`/`border` — the emitted SVG references those custom properties directly, so a Mantine
118
- scheme flip never re-renders the diagram. A ` ```mermaid ` fence inside `Markdown`'s `streaming`
119
- mode renders as plain `CodeBlock` while it's the in-flight block and upgrades to `MermaidDiagram`
120
- once the block settles (parse errors keep the last successfully-rendered SVG rather than blanking
121
- a working diagram).
122
-
123
- ## ArticleLayout — the docs-page frame
124
-
125
- The page shell around a `Prose`/`Markdown`/`MDXContent` body: a centered content column, a sticky
126
- scroll-spy TOC rail (`toc`, default `true`, auto-hides below 1200px), an opt-in reading-progress
127
- bar (`readingProgress`, default `false`), a title/description/date/reading-time meta header
128
- (`meta`), and a prev/next pagination footer (`prev`/`next`). The header/content/footer share ONE
129
- grid column, so the article body reads as a single consistent width whether or not the TOC rail is
130
- visible.
131
-
132
- ```tsx
133
- import { ArticleLayout } from 'basalt-ui/content'
134
- import { Link } from '@tanstack/react-router'
135
- ;<ArticleLayout
136
- meta={{
137
- title: 'Reading dashboards',
138
- description: 'Why most internal dashboards fail the five-second glance test.',
139
- date: 'Jul 16, 2026',
140
- readingTime: 6,
141
- }}
142
- readingProgress
143
- next={{ label: 'Chart guide', href: '/guides/charts' }}
144
- renderLink={(target, node) => <Link to={target.href}>{node}</Link>}
145
- >
146
- <h2 id="setup">Setup</h2>
147
- <p>…</p>
148
- </ArticleLayout>
149
- ```
150
-
151
- **The `renderLink` router-bridge convention** — every content component that links (`ArticleLayout`
152
- prev/next, `ArticleCard`, `GuideDrawer`'s "open full page" link) takes an optional `renderLink:
153
- (target, node) => ReactNode` prop. It defaults to a plain `<a href={target.href}>{node}</a>`; pass
154
- a router bridge to get client-side navigation:
155
-
156
- ```tsx
157
- // TanStack Router
158
- renderLink={(target, node) => <Link to={target.href}>{node}</Link>}
159
- ```
160
-
161
- ## ArticleCard / ArticleGrid — overview pages
162
-
163
- The docs-landing card grid (StatCard-family aesthetics: panel bg, shadow-card, card radius, no
164
- explicit `p`/`radius` on the underlying `Card` — the shared inset idiom). `ArticleCard` is
165
- clickable three ways: `href` alone renders the card itself as an anchor; `href` + `renderLink`
166
- hands it to a router bridge; `onClick` alone renders a keyboard-operable button-role card. Omit all
167
- three for a static card.
168
-
169
- ```tsx
170
- import { ArticleCard, ArticleGrid } from 'basalt-ui/content'
171
- import { Link } from '@tanstack/react-router'
172
- ;<ArticleGrid>
173
- <ArticleCard
174
- title="Reading dashboards"
175
- description="Why most internal dashboards fail the five-second glance test."
176
- meta="6 min read · guide"
177
- href="/content"
178
- renderLink={(target, node) => <Link to={target.href}>{node}</Link>}
179
- />
180
- </ArticleGrid>
181
- ```
182
-
183
- ## Article model — classification, sorting, filtering, search
184
-
185
- `Article<C, T>` (`basalt-ui/content`) is the structured data model behind `ArticleCard`/
186
- `ArticleLayout`: pure data + pure operators (`sortArticles`, `filterArticles`,
187
- `formatArticleDate`), no React, no `@mantine/*`. It is GENERIC over the taxonomy — the framework
188
- ships the shape and the operators; _which_ categories/tags exist is domain data the consumer
189
- declares. This is the `VX.series` doctrine (see `packages/basalt-ui/CLAUDE.md` "Consumer-series
190
- extensibility") applied to content instead of chart series. Reference implementation:
191
- `apps/playground/src/demo/articles.ts` (`ARTICLE_CATEGORIES`, `ARTICLE_TAGS`, `ARTICLES satisfies
192
- readonly Article<ArticleCategory, ArticleTag>[]`).
193
-
194
- There is deliberately NO `defineArticles` factory. `satisfies readonly Article<Cat, Tag>[]` gives
195
- identical inference with no import — contrast `defineSeries`, which earns its place because
196
- `buildPaletteCss`/`seriesTokens` actually consume the map it returns; a plain article list has no
197
- such consumer, so a factory would be a config bag with no payoff.
198
-
199
- `date` is ISO 8601 (`'2026-07-16'`). Rendering always goes through `formatArticleDate`, which pins
200
- BOTH `locale` (`'en-US'`) and `timeZone` (`'UTC'`) — both load-bearing against SSR/client hydration
201
- mismatch: an unpinned zone shifts a date-only ISO string by a day depending on the runtime's UTC
202
- offset, the same failure mode an unpinned locale causes on the string shape. "No date library"
203
- still holds — `Intl` is a native API.
204
-
205
- **Filter semantics** (`filterArticles`): `category` is exact-match; `tags` is ANY-OF (OR); the two
206
- axes AND together — matching the existing multi-facet `filterFn` semantics in
207
- `data/data-table.tsx`. An empty query returns the list unchanged.
208
-
209
- **Filter state is URL state.** Single-select axes (category) use `createSearchParamStore`;
210
- multi-select axes (tags) use `createMultiSearchParamStore` — both from `basalt-ui/router-tanstack`.
211
- That holds for the enum-shaped axes an article filter has. **It is not a general URL-state mandate**:
212
- a route whose `validateSearch` is a real Zod object is hand-written, and `createSearchSchemaStore`
213
- is planned, not shipped — see `basalt-router.md` → "validateSearch".
214
-
215
- Two stores compose on one route by spreading both `validateSearch` results:
216
-
217
- ```ts
218
- // apps/playground/src/demo/article-filter-stores.ts (reference implementation in the basalt-ui repo)
219
- export const articleCategory = createSearchParamStore({
220
- key: 'article-category',
221
- param: 'category',
222
- values: ['all', ...ARTICLE_CATEGORIES] as const,
223
- fallback: 'all',
224
- })
225
-
226
- export const articleTags = createMultiSearchParamStore({
227
- key: 'article-tags',
228
- param: 'tags',
229
- values: ARTICLE_TAGS,
230
- })
231
-
232
- // routes/content-overview.tsx
233
- export const Route = createFileRoute('/content-overview')({
234
- validateSearch: (search) => ({
235
- ...articleCategory.validateSearch(search),
236
- ...articleTags.validateSearch(search),
237
- }),
238
- })
239
- ```
240
-
241
- The `'all'` sentinel is a UI concern added by the consumer, NOT an `Article`/`filterArticles`
242
- concept — `filterArticles` treats `undefined`/`''` as "no constraint" and deliberately does NOT
243
- special-case the string `'all'` (a consumer could legitimately have a category literally named
244
- `'all'`), so the page maps `category === 'all' ? undefined : category` before calling it.
245
-
246
- **The store/UI split is a LAYER boundary, not a style choice.** `basalt-ui/router-tanstack` is
247
- headless (`@mantine/*` banned by the Mantine-free boundary), so the store itself cannot render.
248
- `ArticleFilterBar` (`basalt-ui/content`) is the Mantine half: a fully controlled component wired to
249
- the store pair per `createSearchParamStore`'s 5-step JSDoc recipe (the filter bar is that recipe's
250
- step 3).
251
-
252
- **`toArticleActions`** is a pure in-memory PROJECTOR into the already-shipped Spotlight command
253
- surface (`basalt-ui/commands`) — the same tier as `toRouteActions`. It joins title, description,
254
- category, tags, and slug into a `keywords` string for substring search; it does not tokenize, rank,
255
- index, or persist. An app that outgrows substring matching over a client-side array brings its own
256
- search index — see <https://github.com/jkrumm/basalt-ui/blob/master/docs/CONTENT-SPEC.md> §9 (Non-goals), which is not in the package.
257
-
258
- `ArticleGrid` stays a dumb layout wrapper — deliberate, so callers keep composition (sorting,
259
- filtering, and pagination all happen above it, not inside it).
260
-
261
- **Mobile.** `ArticleFilterBar`'s category control renders a `SegmentedControl` above the `sm`
262
- breakpoint and a `Select` below it, switched via Mantine's CSS-based `visibleFrom`/`hiddenFrom`
263
- props — NOT `useMediaQuery`. A JS media-query hook renders differently on the server than on the
264
- first client paint (the server has no viewport to query), which trips a React hydration mismatch;
265
- the CSS-based switch has no such server/client branch.
266
-
267
- ## GuideLink / GuideDrawer — contextual help
268
-
269
- The "this chart has a guide" pattern (GitLab Pajamas / Stripe-dashboard style): `GuideDrawer` is a
270
- controlled right-side `Drawer` rendering an article at article density (`markdown` string via
271
- `Markdown`, or `children` via `Prose`) with an optional "open full page" footer link; `GuideLink`
272
- is the uncontrolled quiet trigger + drawer pair — mount it next to whatever it explains. The
273
- trigger MUST read as quiet (faint ink, accent on hover) — it is never a loud primary button.
274
-
275
- ```tsx
276
- import { GuideLink } from 'basalt-ui/content'
277
-
278
- <GuideLink
279
- title="How p95 latency is measured"
280
- markdown={p95GuideMarkdown}
281
- fullPageHref="/guides/p95-latency"
282
- />
283
-
284
- // Icon-only variant, for a tight chart-card header
285
- <GuideLink title="How this metric works" markdown={guideMarkdown} iconOnly />
286
- ```
287
-
288
- ## content-collections + TanStack Start recipe
289
-
290
- Build-time content tooling is a **recipe, not a dependency** — `@content-collections/*` are
291
- consumer devDeps, never a `basalt-ui` dependency. Resolved versions: `@content-collections/core`
292
- `0.15`, `@content-collections/vite` `0.3`, `@content-collections/mdx` `0.2`. Wire the MDX runtime
293
- part (`@content-collections/mdx/react`) as a regular dependency instead of a devDep if the consumer
294
- does strict prod-only installs — it's imported at runtime, not just build time.
295
-
296
- **Install:**
297
-
298
- ```bash
299
- bun add -D @content-collections/core @content-collections/vite @content-collections/mdx
300
- ```
301
-
302
- **`vite.config.ts`** — the content-collections vite plugin MUST precede every other plugin (it
303
- generates the virtual `content-collections` module other plugins/transforms may depend on).
304
- TanStack Start (`>= 1.121`) wires it the same way. This ordering holds regardless of what else is in
305
- the `plugins` array — see `agent/rules/basalt-app.md` for where `basaltAppPlugin` sits relative to
306
- it:
307
-
308
- ```ts
309
- import { defineConfig } from 'vite'
310
- import viteReact from '@vitejs/plugin-react'
311
- import { contentCollections } from '@content-collections/vite'
312
- import { tanstackStart } from '@tanstack/react-start/plugin/vite'
313
-
314
- export default defineConfig({
315
- resolve: { tsconfigPaths: true }, // makes the `content-collections` tsconfig alias resolve
316
- plugins: [
317
- contentCollections(), // FIRST — before tanstackStart()/react()
318
- tanstackStart(),
319
- viteReact(), // react's vite plugin must come AFTER start's
320
- ],
321
- })
322
- ```
323
-
324
- **`tsconfig.json`** — path alias so `import { allDocs } from 'content-collections'` resolves to the
325
- generated output (picked up by `resolve.tsconfigPaths` above):
326
-
327
- ```json
328
- {
329
- "compilerOptions": {
330
- "paths": {
331
- "content-collections": ["./.content-collections/generated"]
332
- }
333
- }
334
- }
335
- ```
336
-
337
- **`.gitignore`** — the generated output is build tooling, not source:
338
-
339
- ```
340
- .content-collections
341
- ```
342
-
343
- **`content-collections.ts`** — a Standard Schema definition (a plain zod object works directly;
344
- `content: z.string()` is required by the frontmatter parser) plus an MDX `transform` that compiles
345
- the body once at build time:
346
-
347
- ```ts
348
- import { defineCollection, defineConfig } from '@content-collections/core'
349
- import { compileMDX } from '@content-collections/mdx'
350
- import { z } from 'zod'
351
-
352
- const docs = defineCollection({
353
- name: 'docs',
354
- directory: 'content/docs',
355
- include: '**/*.mdx',
356
- schema: (z) => ({
357
- title: z.string(),
358
- description: z.string().optional(),
359
- date: z.string().optional(),
360
- content: z.string(),
361
- }),
362
- transform: async (doc, ctx) => ({
363
- ...doc,
364
- mdx: await compileMDX(ctx, doc),
365
- }),
366
- })
367
-
368
- export default defineConfig({ collections: [docs] })
369
- ```
370
-
371
- **The page/route** — `MDXContent` from `@content-collections/mdx/react` renders the compiled body
372
- onto `mdxComponents`, wrapped in `ArticleLayout` for the full docs-page frame:
373
-
374
- ```tsx
375
- import { allDocs } from 'content-collections'
376
- import { MDXContent } from '@content-collections/mdx/react'
377
- import { ArticleLayout } from 'basalt-ui/content'
378
- import { mdxComponents } from 'basalt-ui/content'
379
- import { Link } from '@tanstack/react-router'
380
-
381
- const doc = allDocs.find((d) => d.slug === slug)
382
-
383
- <ArticleLayout
384
- meta={{ title: doc.title, description: doc.description, date: doc.date }}
385
- renderLink={(target, node) => <Link to={target.href}>{node}</Link>}
386
- >
387
- <MDXContent code={doc.mdx} components={mdxComponents} />
388
- </ArticleLayout>
389
- ```
390
-
391
- **Wiring articles into Spotlight** — `allDocs` is a typed article list; map it into page actions the
392
- same way the shell's own nav model does, via `toRouteActions` (`basalt-ui/commands`) so every
393
- article is instantly searchable without a separate search index:
394
-
395
- ```ts
396
- import { allDocs } from 'content-collections'
397
- import { toRouteActions } from 'basalt-ui/commands'
398
-
399
- const articleActions = toRouteActions(
400
- allDocs.map((doc) => ({ key: doc.slug, label: doc.title, href: `/docs/${doc.slug}` })),
401
- { navigate },
402
- )
403
- ```
404
-
405
- ## Escape hatches / non-goals
406
-
407
- KaTeX/math, full mermaid grammar bundling (d2, pintora), Vega-Lite streamed specs, versioned docs/
408
- i18n routing, and a `create-basalt-docs` scaffold are all out of scope for 1.0 — see
409
- <https://github.com/jkrumm/basalt-ui/blob/master/docs/CONTENT-SPEC.md> §9 for the full non-goals list and rationale (not in the package).