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,322 +1,160 @@
|
|
|
1
1
|
---
|
|
2
2
|
source: basalt-ui
|
|
3
|
-
description: Color, spacing, radius
|
|
3
|
+
description: Color, spacing, radius and type discipline — route every value through the basalt-ui token system. Enforced by `basalt-ui check-theme` plus the oxlint type-scale rules.
|
|
4
4
|
paths:
|
|
5
5
|
- 'src/**'
|
|
6
6
|
- 'apps/**/src/**'
|
|
7
7
|
- 'packages/**/src/**'
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
<!-- basalt:coverage -->
|
|
11
|
+
<!-- GENERATED from src/surfaces.ts — `basalt-ui check-coverage --write`. Do not hand-edit. -->
|
|
12
|
+
<!-- backed by: guard kinds — css-raw-surface, inline-font-size, off-identity-accent, off-system-surface-var, raw-color-fn, raw-font-family, raw-hex, raw-radius, raw-surface, surface-shadow-override, theme-allow-unscoped · oxlint rules — basalt/no-raw-font-size, basalt/raw-size-literal, basalt/token-layer-boundary -->
|
|
13
|
+
<!-- not guarded: — -->
|
|
14
|
+
<!-- /basalt:coverage -->
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
charts. The **identity is modern zinc** (long form: <https://github.com/jkrumm/basalt-ui/blob/master/docs/DESIGN-SPEC.md>, which does not ship in the
|
|
14
|
-
package) —
|
|
15
|
-
**cool-neutral zinc surfaces**
|
|
16
|
-
(Tailwind zinc family) on both light and dark, low-contrast panel lift on a slightly darker page,
|
|
17
|
-
depth via a whisper shadow + 1px ring (`shadow-card` on panels, `shadow-raised` on interactive
|
|
18
|
-
controls, `shadow-overlay` on floating surfaces — see basalt-mantine.md) rather than a hairline
|
|
19
|
-
border, carrying
|
|
20
|
-
**one saturated sky-blue accent**, split by ROLE: as INK (links, active-nav icon, chart
|
|
21
|
-
lines, focus ring) it is `#0077bd` light / `#8ec5ff` dark; as a FILLED SURFACE it is
|
|
22
|
-
`#0077bd` in BOTH schemes with a WHITE label (`--vx-accent-fill`, never `--vx-accent`). Neutrals do ~90% of the
|
|
23
|
-
surface (60/30/10, pushed toward 90/10); the accent only points — primary CTA, focus, links, small
|
|
24
|
-
status pops — never floods. (Blueprint/Basalt zinc-charcoal are the historical hue-tuning
|
|
25
|
-
ancestors; DESIGN-SPEC supersedes both — see its "Doctrine inversions" section.) This
|
|
26
|
-
rule is the operational checklist; it is enforced mechanically by **`basalt-ui check-theme`**
|
|
27
|
-
(wire it into `lint`: `oxlint . && basalt-ui check-theme`; `init` seeds that as `lint:basalt`). A
|
|
28
|
-
violation fails the build. Escape hatch: a scoped `theme-allow <rule-id> — <reason>` comment
|
|
29
|
-
(diff-visible, deliberate) — see the contract below.
|
|
16
|
+
# Basalt Tokens — color, spacing, radius, type
|
|
30
17
|
|
|
31
|
-
|
|
32
|
-
(`{ roots?, exempt?, include?, profile?, severity?, spacingSteps?, forbiddenAccents? }`); set `roots`
|
|
33
|
-
to your source dirs and `exempt` to the files that legitimately define palette values.
|
|
18
|
+
## The identity (stated once, here)
|
|
34
19
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
could look fully wired while the gate scanned nothing.
|
|
20
|
+
basalt-ui ships ONE identity shared by the Mantine chrome and the visx charts: **cool-neutral zinc
|
|
21
|
+
surfaces on both schemes**, a page slightly darker than its panels, depth from a whisper shadow with
|
|
22
|
+
a 1px ring baked in rather than a hairline border, and **one saturated sky-blue accent** that only
|
|
23
|
+
points. Neutrals carry ~90% of the surface.
|
|
40
24
|
|
|
41
|
-
The
|
|
42
|
-
`public/` tree (the Vite layout basalt's own preset assumes), because that is where a `theme-color`
|
|
43
|
-
meta and a webmanifest's `background_color` actually live. `.json` is never blanket-scanned — name a
|
|
44
|
-
design-surface JSON in `basalt.include`, the only route to one.
|
|
25
|
+
The accent is **split by ROLE**, and this is the one thing an author gets wrong:
|
|
45
26
|
|
|
46
|
-
|
|
27
|
+
- **As ink** — links, active-nav icon, chart lines, focus ring — it is read against the page, so it
|
|
28
|
+
inverts across schemes. `VX.accent`.
|
|
29
|
+
- **As a filled surface** — a control carrying a white label — it cannot invert: it is squeezed
|
|
30
|
+
between the label's contrast and the page's, which leaves one narrow luminance band. `VX.accentFill`,
|
|
31
|
+
labelled with `VX.onAccent`. Every Mantine family's fill sits in that same band, so a filled
|
|
32
|
+
control always reads white on either page. **Never fill with `VX.accent`.**
|
|
47
33
|
|
|
48
|
-
The
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
theme
|
|
53
|
-
theme-allow-file <id>[, <id>…] — <why> → the WHOLE FILE, those rules; a bare one waives NOTHING
|
|
54
|
-
"basalt:theme-allow[-file]": "<id>… — <why>" → the same two, for JSON / .webmanifest
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
An id is a guard kind (`raw-surface`, `inline-spacing`, …) or an oxlint plugin rule
|
|
58
|
-
(`hand-rolled-plot`, `raw-scroll-container`, …), with or without the `basalt/` prefix.
|
|
59
|
-
|
|
60
|
-
```tsx
|
|
61
|
-
// theme-allow raw-surface — third-party widget needs a literal corner
|
|
62
|
-
<Widget style={{ borderRadius: 3 }} />
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
- **An annotation must START its comment** — after `//`, `/*`, `<!--`, a block-comment gutter `*`,
|
|
66
|
-
or nothing but whitespace. Before 1.21.0 any comment that merely _mentioned_ the token parsed as
|
|
67
|
-
the bare blanket form and switched every rule off on the line below: linewatch documented its own
|
|
68
|
-
waivers in a docblock and thereby disarmed the file. Prose about the escape hatch no longer waives
|
|
69
|
-
anything; everything a consumer actually writes still qualifies.
|
|
70
|
-
- **File scope is spelled, not inferred.** `theme-allow-file <id>… — <why>`, anywhere in the file.
|
|
71
|
-
Plain `theme-allow` is the node/line waiver in both engines. At 1.20.0 the two halves of the
|
|
72
|
-
contract intersected at exactly one legal shape and that shape was whole-file — naming a rule and
|
|
73
|
-
giving a reason was read as a file declaration, which is why per-node scoping did not actually
|
|
74
|
-
ship. **This is the consumer break in 1.21.0**: move each existing file declaration one word.
|
|
75
|
-
- **A bare `theme-allow` still waives everything on its placement** but reports
|
|
76
|
-
`theme-allow-unscoped` (`warn` for one minor, then `error`). A bare `theme-allow-file` waives
|
|
77
|
-
nothing at all — whole-file blanket immunity off one unnamed comment is the thing this contract
|
|
78
|
-
exists to price.
|
|
79
|
-
- **Spell the id right — a typo waives nothing.** A word in the id slot that names no rule is
|
|
80
|
-
recorded as unknown and suppresses nothing; the annotation covers exactly the ids it got right.
|
|
81
|
-
Because the id slot is read strictly, a prose reason has to be introduced by a separator:
|
|
82
|
-
`theme-allow: <why>` or `theme-allow <id> — <why>`, never `theme-allow <why>`.
|
|
83
|
-
- **JSON and `.webmanifest` get a member key**, since they have been scanned since 1.20.0 and cannot
|
|
84
|
-
hold a comment — their findings were unwaivable and the printed remedy prescribed something
|
|
85
|
-
impossible. `"basalt:theme-allow-file": "raw-hex — a PWA manifest theme_color must be a literal"`
|
|
86
|
-
carries the same grammar. For a manifest the first remedy is `basaltAppPlugin`, not a waiver: a
|
|
87
|
-
hand-copied hex drifts from the palette.
|
|
88
|
-
- **Three placements work and both engines agree on all three**: the reported line, a comment-ONLY
|
|
89
|
-
line directly above it (the only form JSX can express — the reported line is usually a multi-line
|
|
90
|
-
opening tag or a `{expr}` child), and in CSS a trailing annotation reaching back over the
|
|
91
|
-
declaration it terminates. The third is what survives the shipped `oxfmt` reflowing a long
|
|
92
|
-
`background-color` so the hex lands ABOVE the comment.
|
|
93
|
-
- **A comment-only annotation reaches the first CODE line below it**, walking through the rest of
|
|
94
|
-
its own comment block. A wrapped reason, or a docblock's `*/`, used to absorb the waiver so the
|
|
95
|
-
natural shape silently waived nothing — argo hit that three times in one upgrade. A blank line
|
|
96
|
-
still ends the block; that is how you say "this comment is not about the next statement".
|
|
97
|
-
- **`basalt/hand-rolled-plot` grants whole-file immunity only through `theme-allow-file`.** Every
|
|
98
|
-
assembly node is otherwise waived on its own.
|
|
99
|
-
- **Eight shapes are asserted NOT to waive, in both halves.** They are pinned as unsupported rather
|
|
100
|
-
than merely left out, so "unsupported" and "silently broken" stop reading the same. Don't write
|
|
101
|
-
them:
|
|
102
|
-
|
|
103
|
-
| Shape | Why it does not waive |
|
|
104
|
-
| ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
105
|
-
| a blank line between the annotation and the code — after `//`, after `{/* */}`, or after a `{/*` whose closer sits on its own line | a blank line is how you say "this comment is not about the next statement" |
|
|
106
|
-
| the token mid-sentence in a line comment, a docblock gutter, or a JSX expression comment | prose that MENTIONS the token is not an annotation — the reason it must START its comment |
|
|
107
|
-
| the token inside a string literal | same |
|
|
108
|
-
| above a multi-line OPENING tag, when the finding sits on a later attribute line | a waiver reaches the first line below its comment, not an arbitrary line further down — put it beside the attribute |
|
|
109
|
-
|
|
110
|
-
The whole grid — four axes, comment style × token position × where the closer falls × what
|
|
111
|
-
follows — is pinned row for row in `src/guard/check-source.test.ts` (37 supported + these 8) and
|
|
112
|
-
`configs/oxlint-plugin.test.ts` (32 + 8; five of the guard's rows are CSS/HTML/JSON dialects
|
|
113
|
-
oxlint never sees). Zero disagreements between the two, and no waiver tally moved in any of the
|
|
114
|
-
seven consumer repos.
|
|
115
|
-
|
|
116
|
-
### Audit them: `basalt-ui check-theme --audit-allows`
|
|
117
|
-
|
|
118
|
-
Reports instead of scanning. For every annotation and every `basalt.exemptRules` entry it re-runs
|
|
119
|
-
the guard with that one occurrence neutralized, so the verdict comes from the scan rather than from
|
|
120
|
-
parsing the waiver's text:
|
|
121
|
-
|
|
122
|
-
```text
|
|
123
|
-
theme-allow annotations (3):
|
|
124
|
-
src/shell/x.tsx:42 suppresses raw-surface@43
|
|
125
|
-
src/theme/y.ts:18 SUPPRESSES NOTHING — dead, delete it
|
|
126
|
-
src/charts/z.tsx:7 suppresses hand-rolled-plot@8 (oxlint)
|
|
127
|
-
|
|
128
|
-
basalt.exemptRules entries (1):
|
|
129
|
-
raw-hex: "site.webmanifest" suppresses findings in public/site.webmanifest — <the recorded reason>
|
|
130
|
-
|
|
131
|
-
3 live, 1 dead, 0 unjudgeable, 0 unaccountable (reported as theme-allow-unscoped by a normal run).
|
|
132
|
-
Scope: the N file(s) check-theme scans — everything under basalt.roots (…), plus each root's
|
|
133
|
-
sibling index.html and public/ tree, plus anything named in basalt.include (none). …
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
**Two sections, not one list** — annotations first, then `basalt.exemptRules`, each with its own
|
|
137
|
-
count. A plugin-rule annotation is judged too (since 1.22.0, by re-running oxlint over one
|
|
138
|
-
neutralized sibling file), so `unjudgeable` now means only "names no rule at all" or "oxlint could
|
|
139
|
-
not be run here" — never "not a check-theme kind". The scope line is part of the report: `0 dead`
|
|
140
|
-
is not `0 dead anywhere`.
|
|
141
|
-
|
|
142
|
-
It closes with a `live / dead / unjudgeable / unaccountable` tally and **exits 1 on a dead
|
|
143
|
-
waiver**, so it wires into CI. A waiver that stopped covering anything is not harmless: it silently
|
|
144
|
-
covers the next real finding on that line. Basalt's own source had one; it is deleted.
|
|
145
|
-
|
|
146
|
-
### New in 1.20.0 — five guard kinds, all `warn` for one minor
|
|
147
|
-
|
|
148
|
-
| Kind | Fires on |
|
|
149
|
-
| ------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
150
|
-
| `theme-allow-unscoped` | a `theme-allow` with no rule id, no reason, or an id that names no rule |
|
|
151
|
-
| `surface-shadow-override` | a `boxShadow` built FROM tokens that REPLACES `--vx-shadow-card` instead of composing with it |
|
|
152
|
-
| `css-raw-surface` | the kebab dialect of the surface kinds in CSS (`border-radius: 6px`); sub-scale corners and circle/pill values pass |
|
|
153
|
-
| `inline-font-size` | `style={{ fontSize: 11 }}` — the `check-theme` half of `basalt/no-raw-font-size`, for a CI that runs only the guard |
|
|
154
|
-
| `hidden-inline-style` | a multi-line opening tag, or a style object hoisted to a const — `raw-html-layout`'s formatting-independent half |
|
|
155
|
-
|
|
156
|
-
Two new oxlint rules ship at `warn` alongside them: **`basalt/shadow-basalt-export`** (below) and
|
|
157
|
-
**`basalt/hand-rolled-shell`** (`AppShell` parts or `Burger` in a file that does not render
|
|
158
|
-
`BasaltShell`). **`basalt/raw-size-literal` is now `error`** — CSS-length strings on
|
|
159
|
-
`size`/`fz`/`fontSize`. Every kind keeps its own boolean, `basalt.severity` and `exemptRules`.
|
|
160
|
-
|
|
161
|
-
## The guard sees palette, not vocabulary
|
|
162
|
-
|
|
163
|
-
Round 4 swept seven consumer repos: every gate green, and ~15 independently re-rolled copies of
|
|
164
|
-
components basalt already ships (`StatCard` in 4 of 4 apps, `EmptyState`, a 266-line hand-rolled
|
|
165
|
-
`AppShell`). A fork written by a token-fluent author uses exactly the right tokens, so no palette
|
|
166
|
-
guard can ever see it. **`basalt/shadow-basalt-export` is a cheap detector, not a safety net** — the
|
|
167
|
-
rule says so in its own message: it is a **tripwire, not coverage**, and silence is not evidence that
|
|
168
|
-
nothing is forked.
|
|
169
|
-
|
|
170
|
-
- **Exact name match only** — the one limit that is structural. Round 5 confirmed the miss rate:
|
|
171
|
-
linewatch's forks are named `Cell` and `Box`, rb's is `Stat`. Rename the fork and the rule goes
|
|
172
|
-
quiet, which is what a fork's author naturally does.
|
|
173
|
-
- **Scope: basalt consumers only, and component-shaped declarations only.** Since 1.22.0 the rule
|
|
174
|
-
gates on `isBasaltScopedFile` like every other rule in the file, and needs a function, an arrow, a
|
|
175
|
-
`memo`/`forwardRef` wrapper or a class extending one. A plain data class sharing a name with a
|
|
176
|
-
shipped export is a collision, not a fork — it fired on a `SlugTracker` class in a React-free
|
|
177
|
-
package that cannot import basalt-ui.
|
|
178
|
-
- **Scope: everything importable.** Since 1.21.0 it reads all nine published barrels, not just
|
|
179
|
-
`dist/index.d.ts` — so the `basalt-ui/charts` layer, which is where forks actually live, is finally
|
|
180
|
-
covered. Names a barrel merely re-exports from a third party are skipped: `./charts` passes
|
|
181
|
-
`Bar` / `Line` / `Pie` straight through from `@visx/shape`, and a local `Bar` is not a fork of
|
|
182
|
-
anything basalt wrote.
|
|
183
|
-
|
|
184
|
-
So the renamed majority goes unreported. **Detection does not substitute for expressiveness**: a
|
|
185
|
-
composite that cannot express a common case gets routed around by compliant-looking code, and a
|
|
186
|
-
name-collision rule only catches the authors who did not bother to rename. The habit it stands in
|
|
187
|
-
for is the actual control: check whether basalt already ships the thing before building it.
|
|
188
|
-
|
|
189
|
-
## Filled surfaces — the fill band
|
|
190
|
-
|
|
191
|
-
A filled control is a SURFACE, not ink, and it does not invert across schemes. It is squeezed from
|
|
192
|
-
both sides at once: a **white label** needs ≥4.5:1 against the fill, and the control needs ≥3:1
|
|
193
|
-
against the page behind it — on BOTH pages. That leaves one narrow luminance band, so:
|
|
194
|
-
|
|
195
|
-
- **Every family's fill sits in that band** (`FILL` / `ACCENT` in the palette): the hue varies, the
|
|
196
|
-
luminance does not. Every filled surface therefore reads WHITE, on either page, at ~4.9:1.
|
|
197
|
-
- **Never fill with the ink accent.** `--vx-accent` is light on dark by design (it is read against
|
|
198
|
-
the page) — filling with it puts a white label on a light blue button. Fill with
|
|
199
|
-
`--vx-accent-fill` / `--vx-fill-{family}`; label with `--vx-on-accent` / `--vx-on-{family}`.
|
|
200
|
-
- **Never decide a foreground yourself**, and do not trust Mantine's `autoContrast`: it resolves the
|
|
201
|
-
color once in JS, scheme-blindly, using a brightness heuristic that does not track WCAG contrast.
|
|
202
|
-
The theme emits `--vx-on-*` per scheme instead, and every filled control is bound to it.
|
|
203
|
-
- Hover is **derived** from the fill in CSS, so retuning a fill carries its hover along.
|
|
204
|
-
|
|
205
|
-
Retuning a fill OUTSIDE the band breaks its label. `theme/contrast.test.ts` fails the build on it.
|
|
34
|
+
**The palette is DERIVED, not authored** (`tokens/derive.ts` from one seed + five bounded knobs), and
|
|
35
|
+
a consumer retunes it through `createBasaltTheme(overrides?, { derive, fonts, radius, density })`.
|
|
36
|
+
That is why no hex, no shade index and no pixel constant belongs in a rule, a doc or a component:
|
|
37
|
+
every one of them is an output of a config the consumer may move. Read the values off `VX.*` and the
|
|
38
|
+
theme; read the numbers, when you genuinely need them, off `tokens/palette.ts`.
|
|
206
39
|
|
|
207
40
|
## Color — never a raw literal
|
|
208
41
|
|
|
209
|
-
- **No raw `#hex` / `rgb()` / `rgba()` / `hsl()
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
- **"Ink earns its color"**
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
42
|
+
- **No raw `#hex` / `rgb()` / `rgba()` / `hsl()` in scanned source.** Route color through `VX.*`
|
|
43
|
+
(from `basalt-ui/tokens` or `basalt-ui/charts` — they are CSS vars, so they work in components AND
|
|
44
|
+
in non-component files) or through the Mantine theme (`color` / `c` / `bg`, `theme.colors`).
|
|
45
|
+
- **Opacity is `alpha(token, a)`** (`color-mix`), never `rgba()` — so the hue keeps resolving per
|
|
46
|
+
scheme.
|
|
47
|
+
- **No off-identity Mantine accents.** `teal`/`violet`/`grape`/`indigo`/`pink` still render
|
|
48
|
+
on-palette (the theme reskins every family), and are rejected anyway because they signal
|
|
49
|
+
off-identity intent. Allowed: **`blue`** (the one earned hue), **`gray`**, and the status names
|
|
50
|
+
`red`/`green`/`orange`/`yellow`. A positive delta is `color="green"`, never `teal`.
|
|
51
|
+
- **Never pin a shade index** — `c="yellow.7"`, `bg="blue.4"`, `var(--mantine-color-red-6)` each name
|
|
52
|
+
one fixed swatch that is identical in both schemes, so the shade that is legible on dark is the one
|
|
53
|
+
that fails on light. Use `VX.status.*` for a verdict, or the bare hue name and let the theme pick.
|
|
54
|
+
- **"Ink earns its color."** Default to neutral. A hue is justified by trend, signal/status, or
|
|
55
|
+
genuine multi-series separation. The accent lands on the single primary CTA per view, focus rings,
|
|
56
|
+
links and small status pops — never on borders, large fills, routine icons or secondary buttons.
|
|
57
|
+
- **Categorical/series color is consumer data**, declared once through `defineSeries` →
|
|
58
|
+
`seriesTokens`/`groupTokens` and wired into `buildPaletteCss`, never a Mantine accent prop and
|
|
59
|
+
never an inline pair. `groupTokens(GROUP, MAP)` assumes the same map reaches
|
|
60
|
+
`BasaltProvider`'s `paletteOptions.groups` under the key `` `${GROUP}-` `` — **with the trailing
|
|
61
|
+
dash**, which is a CSS-var prefix and not a group name. Derive both from one `GROUP` constant;
|
|
62
|
+
a mismatch emits refs to variables the stylesheet never declares, with no tsc error and no
|
|
63
|
+
runtime throw — just unstyled charts.
|
|
231
64
|
|
|
232
65
|
## Surfaces — one collapsed token set
|
|
233
66
|
|
|
234
|
-
|
|
67
|
+
`VX.surface` is exactly: `bg` · `panel` · `panelHover` · `elevated` · `subtle` · `overlay` ·
|
|
68
|
+
`field` · `border` · `hairline`. Plus `VX.divider` for layout separators, which is NOT a surface.
|
|
235
69
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
**below white on light** (`#f2f2f1`), **above panel on dark** (`#323239`). Used for Table
|
|
241
|
-
hover/striped rows, `Code`, the `SegmentedControl` track, and Tabs/Accordion/Menu hover.
|
|
242
|
-
- `--vx-surface-border` (`VX.surface.border`) — the single hairline border token.
|
|
243
|
-
|
|
244
|
-
The theme **collapses Mantine's raw ramp steps onto these tokens** (`cssVariablesResolver` plus
|
|
245
|
-
theme `styles`): light/dark border ramp steps → `--vx-surface-border`, light hover steps →
|
|
246
|
-
`--vx-surface-subtle`, card bg → `--vx-surface-panel`, `--app-shell-border-color` →
|
|
247
|
-
`--vx-surface-border`. So **every** Mantine component (AppShell, Table, Input, Divider, Tabs,
|
|
248
|
-
Popover, Accordion, cards) renders one border shade, one card background, and one radius
|
|
249
|
-
(`--vx-radius-card`). Never inline a surface color or reach for a raw ramp-step var
|
|
250
|
-
(`var(--mantine-color-gray-N)`) — the `off-system-surface-var` guard rejects it.
|
|
70
|
+
The theme **collapses Mantine's raw ramp steps onto those tokens** (`cssVariablesResolver` + theme
|
|
71
|
+
`styles`), so every Mantine component — AppShell, Table, Input, Divider, Tabs, Popover, Accordion,
|
|
72
|
+
Card — renders one border shade, one card background and one radius. Never inline a surface color
|
|
73
|
+
and never reach for a raw ramp-step var (`var(--mantine-color-gray-N)`).
|
|
251
74
|
|
|
252
75
|
## Spacing & radius — prefer the scale token
|
|
253
76
|
|
|
254
|
-
- `baseTheme` owns
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
- **Dense by default
|
|
265
|
-
|
|
266
|
-
basalt-mantine.md for the shipped dense defaults).
|
|
267
|
-
- **Sub-scale micro-spacing is legitimate and allowed raw** — `gap={2}`, `pl={4}`, `mt={6}` have no
|
|
268
|
-
token equivalent (the scale starts at 11). Use them freely for tight clusters; don't invent
|
|
269
|
-
micro-tokens or pepper `theme-allow`. One-off layout dims (`h={36}`, `w={64}`) are also fine raw.
|
|
270
|
-
**The same permission holds in CSS modules**, where it has to be enforced differently: there is no
|
|
271
|
-
prop form to prefer, so `inline-spacing` simply does not fire on a declaration whose every literal
|
|
272
|
-
is ≤ 10px (`gap: 2px`, `padding: 4px 8px`). It reads longhands and logical properties too
|
|
273
|
-
(`padding-top`, `margin-inline`, `row-gap`, `padding-inline-start`), not just the shorthands.
|
|
274
|
-
`rem` resolves against the 16px root, so `0.25rem` and `4px` get the same answer; units that need
|
|
275
|
-
layout context (`em`, `%`, `ch`, `vw`) are not micro-spacing claims and flag. A `var()` component
|
|
276
|
-
is dropped before that judgement; one non-micro value makes the whole declaration a finding
|
|
277
|
-
(`padding: 4px 16px` flags). An inline style OBJECT in TSX (`style={{ padding: 4 }}`) also still
|
|
278
|
-
flags: there the Mantine prop exists and should have been used.
|
|
279
|
-
- **Icons** size via the icon's own `size` prop (`size={16}`), not spacing tokens — that's not spacing.
|
|
77
|
+
- `baseTheme` owns both scales and both are DERIVED (`deriveSpacing(level)` / `deriveRadius(level)`
|
|
78
|
+
move them with the `density` / `radius` knobs). **Use the token, not the number**: `p="md"`, not
|
|
79
|
+
the integer that happens to equal `md` today; `radius="sm"`, never a numeric radius.
|
|
80
|
+
- **Card radius has one source**, `VX.radiusCard` / `--vx-radius-card`: the Mantine chrome and the
|
|
81
|
+
Mantine-free `ChartCard` resolve to the same token. Cards must never diverge.
|
|
82
|
+
- **Sub-scale micro-spacing is legitimate and allowed raw** — a tight cluster below the scale's
|
|
83
|
+
first step has no token, so use the raw number rather than inventing a micro-token or peppering
|
|
84
|
+
`theme-allow`. The same permission holds in CSS modules: a declaration whose every literal is
|
|
85
|
+
micro does not fire. One non-micro value makes the whole declaration a finding, and an inline
|
|
86
|
+
style OBJECT in TSX still fires, because there the Mantine prop existed.
|
|
87
|
+
- **Dense by default** — the framework targets compact surfaces; reach for the tighter step.
|
|
88
|
+
- **Icons size through the icon's own `size` prop**, which is not spacing.
|
|
280
89
|
|
|
281
90
|
## Type
|
|
282
91
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
numbers. Don't hard-code `fontSize` in px on chrome; use `size`/`fz` tokens.
|
|
290
|
-
|
|
291
|
-
## `groupTokens` ↔ `paletteOptions.groups` — the prefix lockstep
|
|
92
|
+
Three fonts: sans (body), a condensed head font (headings, brand, card titles), mono (every numeral,
|
|
93
|
+
micro-label, kbd, axis tick). They ship as exact-pinned `@fontsource-variable/*` peers imported by
|
|
94
|
+
`basalt-ui/styles.css`; `createBasaltTheme(_, { fonts })` is the ONE override seam, writing the
|
|
95
|
+
`--basalt-font-*` vars. Size and weight come from the theme's `fontSizes` / `headings` / named
|
|
96
|
+
`fontWeights` ladder (`fw="semibold"`) — never a hardcoded `fontSize`, and never a CSS-length string
|
|
97
|
+
on `size`/`fz`.
|
|
292
98
|
|
|
293
|
-
|
|
294
|
-
refs assuming the same map is also handed to `BasaltProvider`'s `paletteOptions.groups` under the key
|
|
295
|
-
`` `${GROUP}-` `` (**with the trailing dash** — `paletteOptions.groups` is keyed by CSS-var _prefix_,
|
|
296
|
-
not by group name). Drift between the two — e.g. calling `groupTokens('activity', MAP)` but wiring
|
|
297
|
-
`paletteOptions={{ groups: { activity: MAP } }}` (missing the dash) — emits token refs that point at
|
|
298
|
-
CSS variables the palette stylesheet never declares: no tsc error, silent unstyled/transparent charts
|
|
299
|
-
at runtime. Derive both from one `GROUP` constant so they can't drift:
|
|
99
|
+
## `theme-allow` — the escape hatch and its grammar (stated once, here)
|
|
300
100
|
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
101
|
+
```text
|
|
102
|
+
theme-allow → this node/line, EVERY rule (reports theme-allow-unscoped)
|
|
103
|
+
theme-allow <id>[, <id>…] [— <why>] → this node/line, those rules (unscoped without a why)
|
|
104
|
+
theme-allow-file <id>[, <id>…] — <why> → the WHOLE FILE, those rules; a bare one waives NOTHING
|
|
105
|
+
"basalt:theme-allow[-file]": "<id>… — <why>" → the same two, for JSON / .webmanifest
|
|
306
106
|
```
|
|
307
107
|
|
|
108
|
+
An id is a guard kind (`raw-surface`, `inline-spacing`, …) or an oxlint plugin rule
|
|
109
|
+
(`hand-rolled-plot`, `hand-rolled-filter`, …), with or without the `basalt/` prefix.
|
|
110
|
+
|
|
308
111
|
```tsx
|
|
309
|
-
//
|
|
310
|
-
<
|
|
112
|
+
// theme-allow raw-surface — third-party widget needs a literal corner
|
|
113
|
+
<Widget style={{ borderRadius: 3 }} />
|
|
311
114
|
```
|
|
312
115
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
116
|
+
- **An annotation must START its comment** — after `//`, `/*`, `<!--`, a block gutter `*`, or
|
|
117
|
+
nothing but whitespace. Prose that merely MENTIONS the token waives nothing; a file documenting
|
|
118
|
+
its own waivers used to disarm itself.
|
|
119
|
+
- **File scope is spelled, never inferred**: `theme-allow-file <id>… — <why>`, anywhere in the file.
|
|
120
|
+
A bare `theme-allow-file` waives nothing at all.
|
|
121
|
+
- **A typo waives nothing.** A word in the id slot naming no rule is recorded as unknown; the
|
|
122
|
+
annotation still covers the ids it got right. Because the id slot is read strictly, a bare reason
|
|
123
|
+
needs a separator — `theme-allow: <why>` or `theme-allow <id> — <why>`, never `theme-allow <why>`.
|
|
124
|
+
- **Three placements work, and both engines agree on all three**: the reported line, a comment-ONLY
|
|
125
|
+
line directly above it (reaching the first CODE line below, through the rest of its comment
|
|
126
|
+
block), and in CSS a trailing annotation reaching back over the declaration it terminates. A blank
|
|
127
|
+
line ends the reach — that is how you say "this comment is not about the next statement".
|
|
128
|
+
- **JSON and `.webmanifest` take a member key**, since they cannot hold a comment. For a manifest
|
|
129
|
+
the first remedy is `basaltAppPlugin`, not a waiver: a hand-copied hex drifts from the palette.
|
|
130
|
+
- **The three import-boundary rules honour no escape at all** (`visx-boundary`, `visx-tooltip`,
|
|
131
|
+
`token-layer-boundary`) — an architecture boundary a stray comment can switch off is the bypass
|
|
132
|
+
they exist to prevent. The agent-chat guards honour `basalt-agent-allow`, a separate token that
|
|
133
|
+
never waives a `theme-allow` finding, or the reverse.
|
|
134
|
+
|
|
135
|
+
Unsupported shapes are asserted as unsupported in both engines' test grids (`src/guard/
|
|
136
|
+
check-source.test.ts`, `configs/oxlint-plugin.test.ts`), so "unsupported" and "silently broken"
|
|
137
|
+
cannot read the same. **Audit them**: `basalt-ui check-theme --audit-allows` re-runs the scan with each waiver
|
|
138
|
+
neutralized, one at a time, and **exits 1 on a waiver that suppresses nothing** — a dead waiver
|
|
139
|
+
silently covers the next real finding on that line. It judges plugin-rule waivers too, by re-running
|
|
140
|
+
oxlint; where oxlint is unreachable the verdict is "cannot judge", never "dead". Its scope line is
|
|
141
|
+
part of the report: `0 dead` is not `0 dead anywhere`.
|
|
142
|
+
|
|
143
|
+
## Wiring the guard
|
|
144
|
+
|
|
145
|
+
`check-theme` reads `package.json`'s `"basalt"` key (`roots`, `exempt`, `include`, `profile`,
|
|
146
|
+
`severity`, `exemptRules`, …). **`roots` is not optional in practice** — omitted, it falls back to
|
|
147
|
+
`src` relative to cwd, which is zero files for anything under `apps/*/src`; `init` writes a real one
|
|
148
|
+
and `doctor`'s `guard-scan` fails when it resolves to nothing. The scan also reaches each root's
|
|
149
|
+
PARENT for its `index.html` and `public/` tree, because that is where a `theme-color` meta and a
|
|
150
|
+
webmanifest's `background_color` live. Wire it as `oxlint . && basalt-ui check-theme` — what `init`
|
|
151
|
+
seeds as `lint:basalt`.
|
|
152
|
+
|
|
153
|
+
**When it fires, fix the source** — the right token first. `theme-allow` is for a documented
|
|
154
|
+
exception; a palette-definition file belongs in `exempt`, not behind a comment.
|
|
155
|
+
|
|
156
|
+
**The guard sees palette, not vocabulary.** A fork written by a token-fluent author uses exactly the
|
|
157
|
+
right tokens, so no palette guard can see it — `basalt/shadow-basalt-export` catches an exact name
|
|
158
|
+
collision and nothing else, and renaming the fork is what a fork's author does anyway. It is a
|
|
159
|
+
tripwire, not coverage. The habit is the control: **check whether basalt already ships the thing
|
|
160
|
+
before building it.**
|