basalt-ui 1.25.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.
- package/AGENTS.md +8 -6
- package/CHANGELOG.md +18 -0
- package/MIGRATING.md +546 -20
- package/README.md +45 -32
- package/agent/rules/basalt-batteries.md +205 -0
- package/agent/rules/basalt-charts.md +125 -555
- package/agent/rules/basalt-controls.md +155 -0
- package/agent/rules/basalt-mantine.md +146 -439
- package/agent/rules/basalt-state.md +113 -96
- package/agent/rules/basalt-tokens.md +126 -288
- package/agent/skills/basalt-app/SKILL.md +70 -195
- package/agent/skills/basalt-charts/SKILL.md +54 -234
- package/agent/skills/basalt-design/SKILL.md +56 -248
- package/agent/templates/CLAUDE-block.md.tpl +31 -40
- package/agent/templates/DESIGN.md.tpl +22 -51
- package/configs/oxlint-plugin.js +1275 -31
- package/configs/oxlint-plugin.test.ts +947 -11
- package/configs/oxlint.json +11 -3
- package/dist/charts/cursor/resolve.d.ts +18 -0
- package/dist/charts/cursor/resolve.d.ts.map +1 -1
- package/dist/charts/cursor/resolve.js +15 -6
- package/dist/charts/cursor/scope.d.ts +6 -3
- package/dist/charts/cursor/scope.d.ts.map +1 -1
- package/dist/charts/cursor/store.d.ts +5 -1
- package/dist/charts/cursor/store.d.ts.map +1 -1
- package/dist/charts/cursor/store.js +4 -4
- package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
- package/dist/charts/hooks/useChartCursor.js +9 -8
- package/dist/charts/index.d.ts +2 -2
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.d.ts +34 -9
- package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.js +29 -114
- package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
- package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
- package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
- package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
- package/dist/charts/sparklines/BarSparkline.js +18 -4
- package/dist/cli/check-coverage.test.d.ts +2 -0
- package/dist/cli/check-coverage.test.d.ts.map +1 -0
- package/dist/cli/index.d.ts +67 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +225 -7
- package/dist/connectivity/connectivity-indicator.d.ts +4 -1
- package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
- package/dist/connectivity/connectivity-indicator.js +1 -1
- package/dist/content/index.d.ts +5 -3
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +4 -2
- package/dist/content/prose.module.css +7 -28
- package/dist/controls/actions.d.ts +169 -0
- package/dist/controls/actions.d.ts.map +1 -0
- package/dist/controls/actions.js +287 -0
- package/dist/controls/actions.module.css +62 -0
- package/dist/controls/actions.module.css.d.ts +13 -0
- package/dist/controls/actions.test.d.ts +2 -0
- package/dist/controls/actions.test.d.ts.map +1 -0
- package/dist/controls/actions.type-guard.test.d.ts +2 -0
- package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
- package/dist/controls/compare-filter.d.ts +41 -0
- package/dist/controls/compare-filter.d.ts.map +1 -0
- package/dist/controls/compare-filter.js +23 -0
- package/dist/controls/control-group.d.ts +19 -0
- package/dist/controls/control-group.d.ts.map +1 -0
- package/dist/controls/control-group.js +20 -0
- package/dist/controls/control-group.test.d.ts +2 -0
- package/dist/controls/control-group.test.d.ts.map +1 -0
- package/dist/controls/controls.module.css +542 -0
- package/dist/controls/controls.module.css.d.ts +35 -0
- package/dist/controls/controls.router.test.d.ts +2 -0
- package/dist/controls/controls.router.test.d.ts.map +1 -0
- package/dist/controls/enum-filter.d.ts +30 -0
- package/dist/controls/enum-filter.d.ts.map +1 -0
- package/dist/controls/enum-filter.js +91 -0
- package/dist/controls/filter-context.d.ts +55 -0
- package/dist/controls/filter-context.d.ts.map +1 -0
- package/dist/controls/filter-context.js +69 -0
- package/dist/controls/filter-pill.d.ts +28 -0
- package/dist/controls/filter-pill.d.ts.map +1 -0
- package/dist/controls/filter-pill.js +70 -0
- package/dist/controls/filter-set.d.ts +13 -0
- package/dist/controls/filter-set.d.ts.map +1 -0
- package/dist/controls/filter-set.js +125 -0
- package/dist/controls/filter-set.test.d.ts +2 -0
- package/dist/controls/filter-set.test.d.ts.map +1 -0
- package/dist/controls/filter-sheet.d.ts +117 -0
- package/dist/controls/filter-sheet.d.ts.map +1 -0
- package/dist/controls/filter-sheet.js +146 -0
- package/dist/controls/glyphs.d.ts +27 -0
- package/dist/controls/glyphs.d.ts.map +1 -0
- package/dist/controls/glyphs.js +49 -0
- package/dist/controls/index.d.ts +19 -0
- package/dist/controls/index.d.ts.map +1 -0
- package/dist/controls/index.js +44 -0
- package/dist/controls/multi-select-filter.d.ts +23 -0
- package/dist/controls/multi-select-filter.d.ts.map +1 -0
- package/dist/controls/multi-select-filter.js +89 -0
- package/dist/controls/peer-boundary.test.d.ts +2 -0
- package/dist/controls/peer-boundary.test.d.ts.map +1 -0
- package/dist/controls/props-contract.test.d.ts +2 -0
- package/dist/controls/props-contract.test.d.ts.map +1 -0
- package/dist/controls/range-filter.d.ts +29 -0
- package/dist/controls/range-filter.d.ts.map +1 -0
- package/dist/controls/range-filter.js +120 -0
- package/dist/controls/search-filter.d.ts +10 -0
- package/dist/controls/search-filter.d.ts.map +1 -0
- package/dist/controls/search-filter.js +81 -0
- package/dist/controls/select-filter.d.ts +74 -0
- package/dist/controls/select-filter.d.ts.map +1 -0
- package/dist/controls/select-filter.js +10 -0
- package/dist/controls/sync-button.d.ts +38 -0
- package/dist/controls/sync-button.d.ts.map +1 -0
- package/dist/controls/sync-button.js +119 -0
- package/dist/controls/sync-button.module.css +63 -0
- package/dist/controls/sync-button.module.css.d.ts +11 -0
- package/dist/controls/sync-button.test.d.ts +2 -0
- package/dist/controls/sync-button.test.d.ts.map +1 -0
- package/dist/controls/toggle-filter.d.ts +9 -0
- package/dist/controls/toggle-filter.d.ts.map +1 -0
- package/dist/controls/toggle-filter.js +42 -0
- package/dist/controls/view-tabs.d.ts +23 -0
- package/dist/controls/view-tabs.d.ts.map +1 -0
- package/dist/controls/view-tabs.js +59 -0
- package/dist/controls-dates/date-range-picker.d.ts +4 -0
- package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
- package/dist/controls-dates/date-range-picker.js +18 -0
- package/dist/controls-dates/index.d.ts +9 -0
- package/dist/controls-dates/index.d.ts.map +1 -0
- package/dist/controls-dates/index.js +4 -0
- package/dist/dashboard/delta-badge.d.ts +6 -12
- package/dist/dashboard/delta-badge.d.ts.map +1 -1
- package/dist/dashboard/delta-badge.js +1 -50
- package/dist/dashboard/settings-section.d.ts +6 -4
- package/dist/dashboard/settings-section.d.ts.map +1 -1
- package/dist/dashboard/settings-section.js +23 -16
- package/dist/dashboard/settings-section.module.css +10 -0
- package/dist/dashboard/settings-section.module.css.d.ts +1 -0
- package/dist/dashboard/settings-section.test.d.ts +2 -0
- package/dist/dashboard/settings-section.test.d.ts.map +1 -0
- package/dist/dashboard/stat-card.d.ts +44 -9
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +80 -48
- package/dist/dashboard/stat-card.module.css +82 -0
- package/dist/dashboard/stat-card.module.css.d.ts +11 -0
- package/dist/data/data-table.d.ts +37 -15
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +88 -58
- package/dist/guard/grace.test.d.ts +20 -0
- package/dist/guard/grace.test.d.ts.map +1 -0
- package/dist/guard/index.d.ts +66 -3
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +82 -7
- package/dist/guard/types.d.ts +3 -3
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -10
- package/dist/nav/types.d.ts +78 -2
- package/dist/nav/types.d.ts.map +1 -1
- package/dist/notifications/bell.d.ts +4 -1
- package/dist/notifications/bell.d.ts.map +1 -1
- package/dist/notifications/index.d.ts +1 -1
- package/dist/router-tanstack/field.d.ts +14 -0
- package/dist/router-tanstack/field.d.ts.map +1 -0
- package/dist/router-tanstack/field.js +4 -0
- package/dist/router-tanstack/index.d.ts +6 -2
- package/dist/router-tanstack/index.d.ts.map +1 -1
- package/dist/router-tanstack/index.js +4 -0
- package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
- package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/multi-search-param-store.js +19 -27
- package/dist/router-tanstack/search-param-store.d.ts +3 -87
- package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/search-param-store.js +16 -45
- package/dist/router-tanstack/search-store.d.ts +81 -0
- package/dist/router-tanstack/search-store.d.ts.map +1 -0
- package/dist/router-tanstack/search-store.js +187 -0
- package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
- package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
- package/dist/router-tanstack/search-store.test.d.ts +2 -0
- package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
- package/dist/router-tanstack/use-nav.d.ts +0 -2
- package/dist/router-tanstack/use-nav.d.ts.map +1 -1
- package/dist/router-tanstack/use-nav.js +34 -12
- package/dist/router-tanstack/use-nav.test.d.ts +2 -0
- package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
- package/dist/section/index.d.ts +2 -0
- package/dist/section/index.d.ts.map +1 -0
- package/dist/section/index.js +4 -0
- package/dist/section/section.d.ts +24 -0
- package/dist/section/section.d.ts.map +1 -0
- package/dist/section/section.js +126 -0
- package/dist/section/section.module.css +104 -0
- package/dist/section/section.module.css.d.ts +11 -0
- package/dist/section/section.test.d.ts +2 -0
- package/dist/section/section.test.d.ts.map +1 -0
- package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
- package/dist/shell/app-breadcrumbs.js +18 -15
- package/dist/shell/app-header.module.css +77 -37
- package/dist/shell/app-header.module.css.d.ts +2 -1
- package/dist/shell/app-mobile-nav.d.ts +9 -2
- package/dist/shell/app-mobile-nav.d.ts.map +1 -1
- package/dist/shell/app-mobile-nav.js +98 -11
- package/dist/shell/app-mobile-nav.module.css +8 -0
- package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
- package/dist/shell/app-sidebar.d.ts +55 -28
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +116 -67
- package/dist/shell/app-sidebar.module.css +303 -20
- package/dist/shell/app-sidebar.module.css.d.ts +19 -1
- package/dist/shell/index.d.ts +55 -21
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +40 -18
- package/dist/shell/mobile-nav-model.d.ts +13 -2
- package/dist/shell/mobile-nav-model.d.ts.map +1 -1
- package/dist/shell/mobile-nav-model.js +15 -1
- package/dist/shell/nav-count-badge.d.ts +1 -1
- package/dist/shell/page-bar.d.ts +51 -0
- package/dist/shell/page-bar.d.ts.map +1 -0
- package/dist/shell/page-bar.js +154 -0
- package/dist/shell/page-bar.module.css +178 -0
- package/dist/shell/page-bar.module.css.d.ts +17 -0
- package/dist/shell/page-bar.test.d.ts +2 -0
- package/dist/shell/page-bar.test.d.ts.map +1 -0
- package/dist/shell/sidebar-block-model.d.ts +48 -0
- package/dist/shell/sidebar-block-model.d.ts.map +1 -0
- package/dist/shell/sidebar-block-model.js +39 -0
- package/dist/shell/sidebar-blocks.d.ts +32 -0
- package/dist/shell/sidebar-blocks.d.ts.map +1 -0
- package/dist/shell/sidebar-blocks.js +169 -0
- package/dist/shell/sidebar-search.d.ts +13 -1
- package/dist/shell/sidebar-search.d.ts.map +1 -1
- package/dist/shell/sidebar-search.js +34 -1
- package/dist/shell/sidebar-search.module.css +26 -0
- package/dist/shell/sidebar-search.module.css.d.ts +2 -0
- package/dist/state/fields.d.ts +392 -0
- package/dist/state/fields.d.ts.map +1 -0
- package/dist/state/fields.js +369 -0
- package/dist/state/fields.test.d.ts +2 -0
- package/dist/state/fields.test.d.ts.map +1 -0
- package/dist/state/persisted.d.ts +48 -0
- package/dist/state/persisted.d.ts.map +1 -0
- package/dist/state/persisted.js +105 -0
- package/dist/state.d.ts +11 -56
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +14 -120
- package/dist/styles.css +13 -0
- package/dist/surfaces.d.ts +106 -24
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +155 -19
- package/dist/theme/controls.module.css +16 -1
- package/dist/theme/ctl-theme.d.ts +32 -0
- package/dist/theme/ctl-theme.d.ts.map +1 -0
- package/dist/theme/ctl-theme.js +54 -0
- package/dist/theme/ctl-theme.test.d.ts +2 -0
- package/dist/theme/ctl-theme.test.d.ts.map +1 -0
- package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
- package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
- package/dist/theme/divider-law.test.d.ts +2 -0
- package/dist/theme/divider-law.test.d.ts.map +1 -0
- package/dist/theme/icon-slot.d.ts +44 -0
- package/dist/theme/icon-slot.d.ts.map +1 -0
- package/dist/theme/icon-slot.js +21 -0
- package/dist/theme/icon-slot.module.css +47 -0
- package/dist/theme/icon-slot.module.css.d.ts +8 -0
- package/dist/theme/icon-slot.test.d.ts +2 -0
- package/dist/theme/icon-slot.test.d.ts.map +1 -0
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +82 -5
- package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
- package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
- package/dist/theme/segmented-control.module.css +20 -0
- package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
- package/dist/tokens/index.d.ts +12 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +38 -8
- package/dist/tokens/palette.d.ts +97 -61
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +76 -38
- package/dist/tokens.css +10 -2
- package/dist/widget-header/delta-badge.d.ts +13 -0
- package/dist/widget-header/delta-badge.d.ts.map +1 -0
- package/dist/widget-header/delta-badge.js +29 -0
- package/dist/widget-header/delta-badge.test.d.ts +2 -0
- package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
- package/dist/widget-header/index.d.ts +3 -0
- package/dist/widget-header/index.d.ts.map +1 -0
- package/dist/widget-header/index.js +6 -0
- package/dist/widget-header/no-mantine.test.d.ts +2 -0
- package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
- package/dist/widget-header/widget-header.d.ts +37 -0
- package/dist/widget-header/widget-header.d.ts.map +1 -0
- package/dist/widget-header/widget-header.js +77 -0
- package/dist/widget-header/widget-header.module.css +335 -0
- package/dist/widget-header/widget-header.module.css.d.ts +24 -0
- package/dist/widget-header/widget-header.test.d.ts +2 -0
- package/dist/widget-header/widget-header.test.d.ts.map +1 -0
- package/llms.txt +30 -17
- package/package.json +14 -1
- package/src/charts/cursor/resolve.test.ts +29 -1
- package/src/charts/cursor/resolve.ts +38 -6
- package/src/charts/cursor/scope.tsx +6 -3
- package/src/charts/cursor/store.test.ts +19 -1
- package/src/charts/cursor/store.ts +10 -5
- package/src/charts/hooks/useChartCursor.ts +16 -8
- package/src/charts/index.ts +2 -2
- package/src/charts/primitives/CartesianChart.test.tsx +124 -0
- package/src/charts/primitives/ChartCard.test.tsx +66 -0
- package/src/charts/primitives/ChartCard.tsx +86 -146
- package/src/charts/sparklines/BarSparkline.tsx +43 -5
- package/src/cli/check-coverage.test.ts +297 -0
- package/src/cli/check-theme.test.ts +38 -14
- package/src/cli/index.ts +392 -16
- package/src/cli/init-sync.test.ts +97 -0
- package/src/connectivity/connectivity-indicator.tsx +16 -9
- package/src/content/index.ts +16 -3
- package/src/content/prose.module.css +7 -28
- package/src/content/prose.module.css.test.ts +8 -24
- package/src/controls/actions.module.css +62 -0
- package/src/controls/actions.module.css.d.ts +13 -0
- package/src/controls/actions.test.tsx +252 -0
- package/src/controls/actions.tsx +655 -0
- package/src/controls/actions.type-guard.test.ts +63 -0
- package/src/controls/compare-filter.tsx +64 -0
- package/src/controls/control-group.test.tsx +229 -0
- package/src/controls/control-group.tsx +63 -0
- package/src/controls/controls.module.css +542 -0
- package/src/controls/controls.module.css.d.ts +35 -0
- package/src/controls/controls.router.test.tsx +637 -0
- package/src/controls/enum-filter.tsx +159 -0
- package/src/controls/filter-context.tsx +153 -0
- package/src/controls/filter-pill.tsx +120 -0
- package/src/controls/filter-set.test.tsx +472 -0
- package/src/controls/filter-set.tsx +225 -0
- package/src/controls/filter-sheet.tsx +285 -0
- package/src/controls/glyphs.tsx +81 -0
- package/src/controls/index.ts +48 -0
- package/src/controls/multi-select-filter.tsx +140 -0
- package/src/controls/peer-boundary.test.ts +85 -0
- package/src/controls/props-contract.test.tsx +106 -0
- package/src/controls/range-filter.tsx +184 -0
- package/src/controls/search-filter.tsx +123 -0
- package/src/controls/select-filter.tsx +89 -0
- package/src/controls/sync-button.module.css +63 -0
- package/src/controls/sync-button.module.css.d.ts +11 -0
- package/src/controls/sync-button.test.tsx +183 -0
- package/src/controls/sync-button.tsx +197 -0
- package/src/controls/toggle-filter.tsx +60 -0
- package/src/controls/view-tabs.tsx +110 -0
- package/src/controls-dates/date-range-picker.tsx +42 -0
- package/src/controls-dates/index.ts +8 -0
- package/src/dashboard/delta-badge.tsx +4 -74
- package/src/dashboard/settings-section.module.css +10 -0
- package/src/dashboard/settings-section.module.css.d.ts +1 -0
- package/src/dashboard/settings-section.test.tsx +56 -0
- package/src/dashboard/settings-section.tsx +33 -30
- package/src/dashboard/stat-card.module.css +82 -0
- package/src/dashboard/stat-card.module.css.d.ts +11 -0
- package/src/dashboard/stat-card.test.tsx +67 -1
- package/src/dashboard/stat-card.tsx +209 -68
- package/src/data/data-table.test.tsx +96 -11
- package/src/data/data-table.tsx +220 -84
- package/src/guard/check-source.test.ts +126 -13
- package/src/guard/grace.test.ts +149 -0
- package/src/guard/index.ts +172 -29
- package/src/guard/types.ts +4 -2
- package/src/index.ts +46 -5
- package/src/nav/types.ts +84 -2
- package/src/notifications/bell.tsx +6 -3
- package/src/notifications/index.ts +1 -1
- package/src/router-tanstack/field.ts +31 -0
- package/src/router-tanstack/index.ts +24 -2
- package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
- package/src/router-tanstack/multi-search-param-store.ts +46 -77
- package/src/router-tanstack/search-param-store.ts +42 -159
- package/src/router-tanstack/search-store.router.test.tsx +661 -0
- package/src/router-tanstack/search-store.test.ts +418 -0
- package/src/router-tanstack/search-store.ts +380 -0
- package/src/router-tanstack/use-nav.test.tsx +187 -0
- package/src/router-tanstack/use-nav.ts +72 -14
- package/src/section/index.ts +1 -0
- package/src/section/section.module.css +104 -0
- package/src/section/section.module.css.d.ts +11 -0
- package/src/section/section.test.tsx +204 -0
- package/src/section/section.tsx +220 -0
- package/src/shell/app-breadcrumbs.tsx +44 -31
- package/src/shell/app-header.module.css +77 -37
- package/src/shell/app-header.module.css.d.ts +2 -1
- package/src/shell/app-mobile-nav.module.css +8 -0
- package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
- package/src/shell/app-mobile-nav.tsx +147 -8
- package/src/shell/app-sidebar-account.tsx +2 -2
- package/src/shell/app-sidebar.module.css +303 -20
- package/src/shell/app-sidebar.module.css.d.ts +19 -1
- package/src/shell/app-sidebar.test.tsx +456 -41
- package/src/shell/app-sidebar.tsx +276 -140
- package/src/shell/index.test.tsx +291 -5
- package/src/shell/index.tsx +137 -38
- package/src/shell/mobile-nav-model.test.ts +228 -7
- package/src/shell/mobile-nav-model.ts +53 -3
- package/src/shell/nav-count-badge.tsx +1 -1
- package/src/shell/page-bar.module.css +178 -0
- package/src/shell/page-bar.module.css.d.ts +17 -0
- package/src/shell/page-bar.test.tsx +316 -0
- package/src/shell/page-bar.tsx +315 -0
- package/src/shell/sidebar-block-model.ts +83 -0
- package/src/shell/sidebar-blocks.tsx +301 -0
- package/src/shell/sidebar-search.module.css +26 -0
- package/src/shell/sidebar-search.module.css.d.ts +2 -0
- package/src/shell/sidebar-search.tsx +71 -2
- package/src/state/fields.test.tsx +258 -0
- package/src/state/fields.ts +892 -0
- package/src/state/persisted.ts +211 -0
- package/src/state.ts +46 -240
- package/src/styles.css +13 -0
- package/src/surfaces.ts +203 -36
- package/src/theme/controls.module.css +16 -1
- package/src/theme/ctl-theme.test.tsx +275 -0
- package/src/theme/ctl-theme.tsx +138 -0
- package/src/theme/ctl-tier-coverage.test.ts +170 -0
- package/src/theme/density-relations.test.ts +106 -51
- package/src/theme/divider-law.test.ts +259 -0
- package/src/theme/icon-slot.module.css +47 -0
- package/src/theme/icon-slot.module.css.d.ts +8 -0
- package/src/theme/icon-slot.test.tsx +275 -0
- package/src/theme/icon-slot.tsx +59 -0
- package/src/theme/index.ts +165 -3
- package/src/theme/layout-rhythm-css.test.ts +72 -0
- package/src/theme/radius.test.ts +11 -1
- package/src/theme/segmented-control.module.css +20 -0
- package/src/theme/shadow-surfaces.test.ts +20 -7
- package/src/theme/spacing.test.ts +75 -27
- package/src/theme/use-basalt-spacing.ts +2 -2
- package/src/tokens/density.test.ts +7 -25
- package/src/tokens/index.ts +48 -8
- package/src/tokens/palette.ts +107 -73
- package/src/widget-header/delta-badge.test.tsx +60 -0
- package/src/widget-header/delta-badge.tsx +57 -0
- package/src/widget-header/index.ts +2 -0
- package/src/widget-header/no-mantine.test.ts +33 -0
- package/src/widget-header/widget-header.module.css +335 -0
- package/src/widget-header/widget-header.module.css.d.ts +24 -0
- package/src/widget-header/widget-header.test.tsx +187 -0
- package/src/widget-header/widget-header.tsx +168 -0
- package/agent/rules/basalt-agent.md +0 -452
- package/agent/rules/basalt-app.md +0 -246
- package/agent/rules/basalt-commands.md +0 -320
- package/agent/rules/basalt-content.md +0 -409
- package/agent/rules/basalt-data.md +0 -329
- package/agent/rules/basalt-forms.md +0 -186
- package/agent/rules/basalt-notifications.md +0 -264
- package/agent/rules/basalt-query.md +0 -187
- package/agent/rules/basalt-router.md +0 -336
- package/dist/content/article-filter-bar.d.ts +0 -40
- package/dist/content/article-filter-bar.d.ts.map +0 -1
- package/dist/content/article-filter-bar.js +0 -56
- package/dist/shell/page-header.d.ts +0 -13
- package/dist/shell/page-header.d.ts.map +0 -1
- package/dist/shell/page-header.js +0 -23
- package/src/content/article-filter-bar.tsx +0 -88
- 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).
|