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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The C16 version gate (`docs/CONTROLS-SPEC.md` §1): a `GRACE_PERIOD_KINDS` entry fails the build
|
|
3
|
+
* once `package.json`'s version reaches its `promote`, and every entry must declare `since` before
|
|
4
|
+
* `promote`. This is the mechanism that replaces the honor system — deleting an entry used to be
|
|
5
|
+
* the ONLY enforcement of "warn for one minor", and nothing checked that anyone had (D4:
|
|
6
|
+
* `docs/archive/CONTROLS-SYNTHESIS.md`). Five kinds landed together in the round-4 guard minor and sat
|
|
7
|
+
* at `warn` for five minors before this test existed.
|
|
8
|
+
*
|
|
9
|
+
* `assertGraceLedger` is tested directly against synthetic entries below rather than only through
|
|
10
|
+
* the real ledger: every pre-existing entry promoted or moved to `PLUGIN_RULE_ADVISORY`'s guard-side
|
|
11
|
+
* sibling, and an `it.each` over the empty ledger that left behind ran zero assertions, which proved
|
|
12
|
+
* nothing about the gate actually firing. The ledger now carries the two wave-6 control kinds
|
|
13
|
+
* (`in-body-page-title`, `raw-selection-control`, both `promote: '1.27.0'`), and the real-ledger
|
|
14
|
+
* check below runs against them.
|
|
15
|
+
*
|
|
16
|
+
* This gate measures the version already PUBLISHED, so it can only go red after the release that
|
|
17
|
+
* shipped a due entry. `scripts/check-grace.ts` is the other end — `scripts/release.sh` runs it
|
|
18
|
+
* against the version the dry run computed, before the release is cut.
|
|
19
|
+
*
|
|
20
|
+
* Excluded from tsc (tsconfig exclude: src/**\/*.test.ts), run via `bun test`.
|
|
21
|
+
*/
|
|
22
|
+
import { describe, expect, it } from 'bun:test'
|
|
23
|
+
import { readFileSync } from 'node:fs'
|
|
24
|
+
import { resolve } from 'node:path'
|
|
25
|
+
import { GRACE_PERIOD_KINDS } from './index'
|
|
26
|
+
|
|
27
|
+
const PACKAGE_JSON_PATH = resolve(import.meta.dirname, '..', '..', 'package.json')
|
|
28
|
+
const CURRENT_VERSION = (JSON.parse(readFileSync(PACKAGE_JSON_PATH, 'utf8')) as { version: string })
|
|
29
|
+
.version
|
|
30
|
+
|
|
31
|
+
type GraceEntry = { since: string; promote: string; why: string }
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Tiny local semver compare — no dependency. Assumes plain `x.y.z`, no pre-release identifiers
|
|
35
|
+
* (basalt-ui bans majors and ships no pre-releases). Returns negative when `a` sorts before `b`,
|
|
36
|
+
* positive when after, zero when equal. Compares each component numerically, not lexically, so
|
|
37
|
+
* `'1.9.0' < '1.10.0'` and `'1.3.0' < '1.26.0'` — a string compare would get both backwards.
|
|
38
|
+
*/
|
|
39
|
+
export function compareSemver(a: string, b: string): number {
|
|
40
|
+
const partsA = a.split('.').map(Number)
|
|
41
|
+
const partsB = b.split('.').map(Number)
|
|
42
|
+
for (let i = 0; i < 3; i++) {
|
|
43
|
+
const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0)
|
|
44
|
+
if (diff !== 0) return diff > 0 ? 1 : -1
|
|
45
|
+
}
|
|
46
|
+
return 0
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The C16 gate itself, extracted so it can be exercised against a synthetic ledger as well as the
|
|
51
|
+
* real one. Throws naming the first offending entry when either invariant breaks: `since` must
|
|
52
|
+
* precede `promote` for every entry, and no entry's `promote` may be `<=` the given `version`.
|
|
53
|
+
*/
|
|
54
|
+
export function assertGraceLedger(ledger: Record<string, GraceEntry>, version: string): void {
|
|
55
|
+
for (const [id, entry] of Object.entries(ledger)) {
|
|
56
|
+
if (compareSemver(entry.since, entry.promote) >= 0) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`${id}: \`since\` (${entry.since}) must be before \`promote\` (${entry.promote}).`,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
if (compareSemver(version, entry.promote) >= 0) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`${id}: package.json is at ${version}, which has reached its promote version ` +
|
|
64
|
+
`${entry.promote} — promote to error or extend \`promote\` with a reason.`,
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
describe('compareSemver', () => {
|
|
71
|
+
it('orders multi-digit components numerically, not lexically', () => {
|
|
72
|
+
expect(compareSemver('1.9.0', '1.10.0')).toBeLessThan(0)
|
|
73
|
+
expect(compareSemver('1.10.0', '1.9.0')).toBeGreaterThan(0)
|
|
74
|
+
expect(compareSemver('1.3.0', '1.26.0')).toBeLessThan(0)
|
|
75
|
+
expect(compareSemver('1.26.0', '1.3.0')).toBeGreaterThan(0)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('treats equal versions as equal', () => {
|
|
79
|
+
expect(compareSemver('1.25.0', '1.25.0')).toBe(0)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
describe('assertGraceLedger', () => {
|
|
84
|
+
it('does not throw on a well-formed entry not yet due', () => {
|
|
85
|
+
const ledger = { 'fake-kind': { since: '1.0.0', promote: '2.0.0', why: 'synthetic' } }
|
|
86
|
+
expect(() => assertGraceLedger(ledger, '1.25.0')).not.toThrow()
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('throws once the given version reaches promote', () => {
|
|
90
|
+
const ledger = { 'fake-kind': { since: '1.0.0', promote: '1.25.0', why: 'synthetic' } }
|
|
91
|
+
expect(() => assertGraceLedger(ledger, '1.25.0')).toThrow(/fake-kind/)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('throws once the given version is past promote', () => {
|
|
95
|
+
const ledger = { 'fake-kind': { since: '1.0.0', promote: '1.9.0', why: 'synthetic' } }
|
|
96
|
+
expect(() => assertGraceLedger(ledger, '1.10.0')).toThrow(/fake-kind/)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('throws when since does not precede promote', () => {
|
|
100
|
+
const ledger = { 'fake-kind': { since: '1.5.0', promote: '1.5.0', why: 'synthetic' } }
|
|
101
|
+
expect(() => assertGraceLedger(ledger, '1.0.0')).toThrow(/since/)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('GRACE_PERIOD_KINDS — version gate (C16)', () => {
|
|
106
|
+
it('the real ledger passes the gate against the real package version', () => {
|
|
107
|
+
expect(() =>
|
|
108
|
+
assertGraceLedger(GRACE_PERIOD_KINDS as Record<string, GraceEntry>, CURRENT_VERSION),
|
|
109
|
+
).not.toThrow()
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// ── the release-time half: scripts/check-grace.ts ────────────────────────────
|
|
114
|
+
// Exercised as a PROCESS, because that is how `scripts/release.sh` consumes it — the exit code is
|
|
115
|
+
// the whole interface, and a script that printed the right words with status 0 would gate nothing.
|
|
116
|
+
|
|
117
|
+
const CHECK_GRACE = resolve(import.meta.dirname, '..', '..', 'scripts', 'check-grace.ts')
|
|
118
|
+
|
|
119
|
+
function runCheckGrace(...args: string[]): { code: number; stderr: string; stdout: string } {
|
|
120
|
+
const result = Bun.spawnSync(['bun', CHECK_GRACE, ...args])
|
|
121
|
+
return {
|
|
122
|
+
code: result.exitCode,
|
|
123
|
+
stderr: result.stderr.toString(),
|
|
124
|
+
stdout: result.stdout.toString(),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
describe('scripts/check-grace.ts', () => {
|
|
129
|
+
it('passes for a version below every promote', () => {
|
|
130
|
+
const { code, stdout } = runCheckGrace('1.0.0')
|
|
131
|
+
expect([code, stdout.includes('clear of all')]).toEqual([0, true])
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('refuses a version that has reached a promote, naming the entries', () => {
|
|
135
|
+
const { code, stderr } = runCheckGrace('9.9.9')
|
|
136
|
+
expect(code).toBe(1)
|
|
137
|
+
expect(stderr).toContain('raw-selection-control')
|
|
138
|
+
expect(stderr).toContain('basalt/control-outside-home')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('refuses the version the wave-6 entries are due in (1.27.0)', () => {
|
|
142
|
+
expect(runCheckGrace('1.27.0').code).toBe(1)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('exits 2 on a missing or malformed version rather than passing vacuously', () => {
|
|
146
|
+
expect(runCheckGrace().code).toBe(2)
|
|
147
|
+
expect(runCheckGrace('not-a-version').code).toBe(2)
|
|
148
|
+
})
|
|
149
|
+
})
|
package/src/guard/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ./guard — headless policy core. Mantine-free, dependency-free.
|
|
3
3
|
*
|
|
4
|
-
* GUARD_RULES: the closed registry of all
|
|
4
|
+
* GUARD_RULES: the closed registry of all 27 violation kinds.
|
|
5
5
|
* checkSource: pure (text, relPath, cfg) → Finding[]. No FS, no walk, no console.
|
|
6
6
|
*/
|
|
7
7
|
import type { Finding, GuardConfig, GuardKind, GuardSeverity } from './types'
|
|
@@ -483,6 +483,52 @@ const RAW_FORM_CONTROL = /<(?:input|select|textarea)\b/g
|
|
|
483
483
|
// for a sub-floor fontSize.
|
|
484
484
|
const RAW_FORM_CONTROL_TAG = /<(?:input|select|textarea)\b(?:=>|[^>])*?>/g
|
|
485
485
|
|
|
486
|
+
// ── Control-home patterns (docs/CONTROLS-SPEC.md §6 — the text lane of laws C1/C8) ──────────────
|
|
487
|
+
//
|
|
488
|
+
// Bounded full-text tag scans, the CARD_SURFACE_TAG / CHART_ENTRY_POINT_TAG shape: a `<Title>` and a
|
|
489
|
+
// `<Select>` are both routinely formatted across lines, so a per-line regex would see neither.
|
|
490
|
+
|
|
491
|
+
/** A `<Title …>` opening tag, however it is wrapped. `order` is judged separately, on the tag text. */
|
|
492
|
+
const PAGE_TITLE_TAG = /<Title(?![\w.])(?:=>|[^>])*?>/g
|
|
493
|
+
/** `order={1}` / `order={2}` — the two orders that name a PAGE rather than a section. */
|
|
494
|
+
const PAGE_TITLE_ORDER_PROP = /\border\s*=\s*\{\s*([12])\s*\}/
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* A raw Mantine selection control's opening tag — the text-lane twin of the oxlint plugin's
|
|
498
|
+
* RAW_FILTER_TAGS. `Chip.Group` carries a dot, so the name class allows one.
|
|
499
|
+
*/
|
|
500
|
+
const RAW_SELECTION_CONTROL_TAG =
|
|
501
|
+
/<(SegmentedControl|Select|MultiSelect|NativeSelect|DatePickerInput|DateInput|TagsInput|Chip\.Group)(?![\w])(?:=>|[^>])*?>/g
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* A tag whose presence makes an `order={1|2}` Title a DOCUMENT heading rather than a page title.
|
|
505
|
+
* File-scoped on purpose — see the scan's own comment for why the coarser test is the honest one.
|
|
506
|
+
*/
|
|
507
|
+
const PROSE_CONTEXT_TAG = /<(?:Prose|ArticleLayout|Modal|Drawer)(?![\w])/
|
|
508
|
+
|
|
509
|
+
/** An opening tag for one of the declared non-homes — a settings row, an overlay, a composer. */
|
|
510
|
+
const CONTROL_HOST_TAG =
|
|
511
|
+
/<(?:SettingsRow|Modal|Drawer|Popover\.Dropdown|Menu\.Dropdown|Composer)(?![\w])/
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* How far ABOVE a control the host-tag window reaches, in lines.
|
|
515
|
+
*
|
|
516
|
+
* The text lane has no ancestry, so "inside a SettingsRow" is approximated by "a SettingsRow opens
|
|
517
|
+
* within the last N lines". 12 covers every row shape in the five consumer repos (label +
|
|
518
|
+
* description + the `control={` line); past that the window would start swallowing the next row's
|
|
519
|
+
* control, which is the direction that makes the kind silent rather than noisy. The plugin rule
|
|
520
|
+
* (`basalt/control-outside-home`) is the one that answers this exactly — this kind exists for the
|
|
521
|
+
* PreToolUse hook lane, which sees one file's TEXT and no AST at all.
|
|
522
|
+
*/
|
|
523
|
+
const CONTROL_HOST_WINDOW_LINES = 12
|
|
524
|
+
|
|
525
|
+
/** A name whose DECLARATION means this file DEFINES a basalt control rather than consuming one. */
|
|
526
|
+
const CONTROL_OWNER_DEF =
|
|
527
|
+
/\b(?:function|const|class)\s+(?:RangeFilter|CompareFilter|SelectFilter|MultiSelectFilter|SearchFilter|ToggleFilter|ViewTabs|FilterSet|FilterPill|SyncButton|ActionGroup|OverflowMenu|CtlSlot)(?![\w])/
|
|
528
|
+
|
|
529
|
+
/** `@mantine/form` — a form is C1's third home, and its inputs are not filters. */
|
|
530
|
+
const MANTINE_FORM_IMPORT = /from\s+['"]@mantine\/form['"]/
|
|
531
|
+
|
|
486
532
|
// A Mantine `styles={{ input: {...} }}` per-part style — the `input` key specifically targets the
|
|
487
533
|
// rendered <input>/<textarea> part of TextInput/Select/Textarea/etc. Requires `input` be the FIRST
|
|
488
534
|
// key in the styles object (see the sub-16-input-font doc comment for the scoping trade-off this
|
|
@@ -541,20 +587,32 @@ export const DEFAULT_GUARD_CONFIG: GuardConfig = {
|
|
|
541
587
|
severity: {},
|
|
542
588
|
}
|
|
543
589
|
|
|
590
|
+
/**
|
|
591
|
+
* A grace-ledger entry: `since` and `promote` are plain `x.y.z` semver strings (C16,
|
|
592
|
+
* `docs/CONTROLS-SPEC.md` §1). `grace.test.ts` fails the build once `package.json`'s version
|
|
593
|
+
* reaches `promote` while the entry is still here — the version-gate that replaces the honor
|
|
594
|
+
* system a bare promotion-note string used to run on. `since` must be strictly before `promote`,
|
|
595
|
+
* also asserted there.
|
|
596
|
+
*/
|
|
597
|
+
export type GraceEntry = { since: string; promote: string; why: string }
|
|
598
|
+
|
|
544
599
|
/**
|
|
545
600
|
* Kinds still inside their grace minor — reported, not fatal, until the next minor promotes them.
|
|
546
601
|
* See {@link GuardSeverity} for the doctrine and why basalt-ui in particular needs it.
|
|
547
602
|
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
603
|
+
* Adding a kind here is part of shipping it; removing the entry IS the promotion, and belongs in
|
|
604
|
+
* its own commit so the changelog says enforcement got stricter. `grace.test.ts` is the other half
|
|
605
|
+
* of "belongs in its own commit": it fails once `package.json`'s version reaches an entry's
|
|
606
|
+
* `promote` while the entry is still here, so a kind can no longer sit at `warn` with nothing
|
|
607
|
+
* tracking it — which is exactly what happened to the five entries this ledger shipped in the
|
|
608
|
+
* round-4 guard minor (D4, `docs/CONTROLS-SPEC.md` §6): all five stayed `warn` for five minors
|
|
609
|
+
* because deleting the entry was the only enforcement, and nothing checked that anyone had.
|
|
551
610
|
*
|
|
552
611
|
* The runway is measured in consumer UPGRADES, not in version numbers: `mantine-shade-index`
|
|
553
612
|
* (the table's first tenant, promoted and gone) sat here across two minors because 1.8.0 shipped
|
|
554
613
|
* the same day as 1.7.0 and 1.9.0 carried the chart batch the same consumer was upgrading for —
|
|
555
614
|
* bundling a build-breaking promotion into the minor they take for the fixes turns a routine bump
|
|
556
|
-
* into an unplanned refactor, which is exactly what the grace minor exists to prevent.
|
|
557
|
-
* entries below all landed together in the round-4 guard minor and all promote one minor later.
|
|
615
|
+
* into an unplanned refactor, which is exactly what the grace minor exists to prevent.
|
|
558
616
|
*
|
|
559
617
|
* **A `SCANNABLE_EXT` widening cannot be expressed here, and 1.23.2's was deliberately not.**
|
|
560
618
|
* Adding `.astro`/`.jsx`/`.vue` widens the FILE SET for all 25 kinds at once — there is no kind to
|
|
@@ -572,29 +630,30 @@ export const DEFAULT_GUARD_CONFIG: GuardConfig = {
|
|
|
572
630
|
* with `theme-allow`, `basalt.severity` and `exemptRules` all still available.
|
|
573
631
|
*
|
|
574
632
|
* @example
|
|
575
|
-
* const GRACE: Partial<Record<GuardKind,
|
|
576
|
-
* 'raw-font-family': '
|
|
633
|
+
* const GRACE: Partial<Record<GuardKind, GraceEntry>> = {
|
|
634
|
+
* 'raw-font-family': { since: '1.4.0', promote: '1.5.0', why: 'introduced 1.4.0 — …' },
|
|
577
635
|
* }
|
|
578
636
|
*/
|
|
579
|
-
const GRACE_PERIOD_KINDS: Partial<Record<GuardKind,
|
|
580
|
-
'
|
|
581
|
-
'
|
|
582
|
-
'
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
'
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
637
|
+
export const GRACE_PERIOD_KINDS: Partial<Record<GuardKind, GraceEntry>> = {
|
|
638
|
+
'in-body-page-title': {
|
|
639
|
+
since: '1.26.0',
|
|
640
|
+
promote: '1.27.0',
|
|
641
|
+
why:
|
|
642
|
+
'new in the wave-6 control guards (docs/CONTROLS-SPEC.md §6, law C8). Its AST twin is the ' +
|
|
643
|
+
'oxlint rule of the SAME id, whose PLUGIN_RULE_GRACE entry carries the same dates — one law, ' +
|
|
644
|
+
'two lanes, one promotion. Warn for a minor because the fix is a route/breadcrumb change ' +
|
|
645
|
+
'(the page is named by staticData.title), not a prop edit, so a consumer needs to schedule it.',
|
|
646
|
+
},
|
|
647
|
+
'raw-selection-control': {
|
|
648
|
+
since: '1.26.0',
|
|
649
|
+
promote: '1.27.0',
|
|
650
|
+
why:
|
|
651
|
+
'new in the wave-6 control guards (docs/CONTROLS-SPEC.md §6, law C1). The text lane cannot ' +
|
|
652
|
+
'see ancestry, so "no home" is approximated by a 12-line host-tag window — the loosest ' +
|
|
653
|
+
'reading in the guard, and the reason this one lands warn rather than error even though its ' +
|
|
654
|
+
'law is settled. Promotion is gated with the plugin half on the wave-7 consumer run (≤3 ' +
|
|
655
|
+
'waivers), and `scripts/check-grace.ts` refuses the 1.27.0 release while the entry is still here.',
|
|
656
|
+
},
|
|
598
657
|
}
|
|
599
658
|
|
|
600
659
|
/** A kind's effective severity: consumer override first, then the grace table, then `error`. */
|
|
@@ -762,6 +821,16 @@ export const PLUGIN_RULE_IDS: ReadonlySet<string> = new Set([
|
|
|
762
821
|
'hand-rolled-shell',
|
|
763
822
|
'shadow-basalt-export',
|
|
764
823
|
'raw-scroll-container',
|
|
824
|
+
'hand-rolled-filter',
|
|
825
|
+
'control-outside-home',
|
|
826
|
+
'control-size-literal',
|
|
827
|
+
'page-bar-budget',
|
|
828
|
+
'responsive-twin',
|
|
829
|
+
'search-literal-link',
|
|
830
|
+
'use-search-from-literal',
|
|
831
|
+
// `in-body-page-title` is deliberately ABSENT: it is a plugin rule AND a guard kind under one id
|
|
832
|
+
// (one law, two lanes, one annotation), and this set is the ids OUTSIDE `checkSource`'s reach.
|
|
833
|
+
// `check-source.test.ts` asserts the two registries stay disjoint.
|
|
765
834
|
'visx-boundary',
|
|
766
835
|
'visx-tooltip',
|
|
767
836
|
'token-layer-boundary',
|
|
@@ -1388,6 +1457,11 @@ export const TOKENS_ONLY_DISABLED_KINDS: ReadonlySet<GuardKind> = new Set([
|
|
|
1388
1457
|
'chart-missing-aria-label',
|
|
1389
1458
|
'raw-form-control',
|
|
1390
1459
|
'sub-16-input-font',
|
|
1460
|
+
// Both wave-6 control kinds: `<Title>` is a Mantine component and the remedy for either one is a
|
|
1461
|
+
// Mantine-rendered home (PageBar / WidgetHeader) or a basalt control over @mantine/core. Neither
|
|
1462
|
+
// sentence is followable in a consumer that installs the token layer and nothing else.
|
|
1463
|
+
'in-body-page-title',
|
|
1464
|
+
'raw-selection-control',
|
|
1391
1465
|
])
|
|
1392
1466
|
|
|
1393
1467
|
// A `/` opens a regex literal (not division) when the previous significant token is one of these
|
|
@@ -1608,7 +1682,7 @@ type GuardRule = {
|
|
|
1608
1682
|
}
|
|
1609
1683
|
|
|
1610
1684
|
/**
|
|
1611
|
-
* The closed registry of all
|
|
1685
|
+
* The closed registry of all 27 guard kinds. The triad test asserts
|
|
1612
1686
|
* `surface.guardKinds ⊆ keyof GUARD_RULES` at runtime.
|
|
1613
1687
|
*
|
|
1614
1688
|
* raw-surface, raw-html-layout, and sub-16-input-font are handled inline in checkSource
|
|
@@ -1827,7 +1901,23 @@ export const GUARD_RULES = {
|
|
|
1827
1901
|
enabled: (cfg: GuardConfig) => cfg.rawHtmlLayout,
|
|
1828
1902
|
appliesTo: (relPath) => !isChartFile(relPath) && !relPath.endsWith('.css'),
|
|
1829
1903
|
message:
|
|
1830
|
-
'Raw HTML element with inline layout/surface styling that the line scan cannot see — the tag is formatted across lines, or the style object is hoisted to a const. Same violation as raw-html-layout: use a Mantine layout primitive (Box/Flex/Grid/Stack/Group).
|
|
1904
|
+
'Raw HTML element with inline layout/surface styling that the line scan cannot see — the tag is formatted across lines, or the style object is hoisted to a const. Same violation as raw-html-layout: use a Mantine layout primitive (Box/Flex/Grid/Stack/Group). Promoted to error in the Unreleased minor (C16) with severity only — the kind stayed standalone rather than folding into raw-html-layout as originally planned; the merge is still open, tracked as a follow-up, not done here.',
|
|
1905
|
+
},
|
|
1906
|
+
'in-body-page-title': {
|
|
1907
|
+
kind: 'in-body-page-title',
|
|
1908
|
+
pattern: PAGE_TITLE_TAG, // handled inline (full-text tag-scoped scan); entry keeps the registry complete
|
|
1909
|
+
// JSX-tag-shaped (`<Title order={1}>`) — never appears in CSS text.
|
|
1910
|
+
appliesTo: (relPath) => !relPath.endsWith('.css'),
|
|
1911
|
+
message:
|
|
1912
|
+
'In-body page title — a page is named ONCE, by the breadcrumb (staticData.title) or by PageBar.title in a shell-less app, and every section/card/table title is a WidgetHeader (docs/CONTROLS-SPEC.md law C8). An <Title order={1|2}> in the body is a second name for the same page, and it drifts. Prose / ArticleLayout / an overlay and anything under a content/ path are document headings and never report. Same law the oxlint plugin enforces as basalt/in-body-page-title — one id, two lanes, so one theme-allow covers both.',
|
|
1913
|
+
},
|
|
1914
|
+
'raw-selection-control': {
|
|
1915
|
+
kind: 'raw-selection-control',
|
|
1916
|
+
pattern: RAW_SELECTION_CONTROL_TAG, // handled inline (full-text tag-scoped scan + host window)
|
|
1917
|
+
// JSX-tag-shaped (`<Select …>`) — never appears in CSS text.
|
|
1918
|
+
appliesTo: (relPath) => !relPath.endsWith('.css'),
|
|
1919
|
+
message:
|
|
1920
|
+
'Raw selection control with no home — a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1–C3). Use the bound control from basalt-ui/controls. A settings row, an overlay and an @mantine/form file are the declared non-homes and never report. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST — where the two disagree, the plugin is right.',
|
|
1831
1921
|
},
|
|
1832
1922
|
'inline-font-size': {
|
|
1833
1923
|
kind: 'inline-font-size',
|
|
@@ -2249,6 +2339,59 @@ export function checkSource(text: string, relPath: string, cfg: GuardConfig): Fi
|
|
|
2249
2339
|
}
|
|
2250
2340
|
}
|
|
2251
2341
|
|
|
2342
|
+
// in-body-page-title — full-text tag scan (the card-with-border shape), because a `<Title>` with
|
|
2343
|
+
// its props on separate lines is the formatted default. `order` is judged on the tag's own text.
|
|
2344
|
+
//
|
|
2345
|
+
// Two file-level exemptions, both deliberately coarser than the plugin's node-level ones and both
|
|
2346
|
+
// false-NEGATIVE-only: anything under a `content/` path segment, and any file that renders
|
|
2347
|
+
// `<Prose>` / `<ArticleLayout>` / an overlay anywhere in it. A text scan has no ancestry, so
|
|
2348
|
+
// "under Prose" is not answerable per node — and a document heading told to become a breadcrumb is
|
|
2349
|
+
// the wrong advice, not merely noise. `basalt/in-body-page-title` (the AST lane, same id) is the
|
|
2350
|
+
// half that scopes this exactly.
|
|
2351
|
+
if (
|
|
2352
|
+
ruleApplies('in-body-page-title', relPath) &&
|
|
2353
|
+
!relPath.split('/').includes('content') &&
|
|
2354
|
+
!PROSE_CONTEXT_TAG.test(codeText)
|
|
2355
|
+
) {
|
|
2356
|
+
for (const m of codeText.matchAll(PAGE_TITLE_TAG)) {
|
|
2357
|
+
const order = PAGE_TITLE_ORDER_PROP.exec(m[0])
|
|
2358
|
+
if (order === null) continue
|
|
2359
|
+
const startLine = codeText.slice(0, m.index ?? 0).split('\n').length
|
|
2360
|
+
const endLine = startLine + (m[0].split('\n').length - 1)
|
|
2361
|
+
if (isAllowedInRange(startLine, endLine, 'in-body-page-title')) continue
|
|
2362
|
+
findings.push({
|
|
2363
|
+
relPath,
|
|
2364
|
+
line: startLine,
|
|
2365
|
+
token: `<Title order={${order[1]}}`,
|
|
2366
|
+
kind: 'in-body-page-title',
|
|
2367
|
+
})
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
// raw-selection-control — full-text tag scan plus the host WINDOW (see CONTROL_HOST_WINDOW_LINES).
|
|
2372
|
+
// Two file-level exemptions mirror the plugin rule's, so the two lanes agree on the same file:
|
|
2373
|
+
// a file that DEFINES a basalt control cannot be told to use one, and a file importing
|
|
2374
|
+
// `@mantine/form` is a form — C1's third home, whose inputs are not filters.
|
|
2375
|
+
if (
|
|
2376
|
+
ruleApplies('raw-selection-control', relPath) &&
|
|
2377
|
+
!CONTROL_OWNER_DEF.test(codeText) &&
|
|
2378
|
+
!MANTINE_FORM_IMPORT.test(codeText)
|
|
2379
|
+
) {
|
|
2380
|
+
for (const m of codeText.matchAll(RAW_SELECTION_CONTROL_TAG)) {
|
|
2381
|
+
const startLine = codeText.slice(0, m.index ?? 0).split('\n').length
|
|
2382
|
+
const windowStart = Math.max(0, startLine - 1 - CONTROL_HOST_WINDOW_LINES)
|
|
2383
|
+
if (CONTROL_HOST_TAG.test(codeLines.slice(windowStart, startLine).join('\n'))) continue
|
|
2384
|
+
const endLine = startLine + (m[0].split('\n').length - 1)
|
|
2385
|
+
if (isAllowedInRange(startLine, endLine, 'raw-selection-control')) continue
|
|
2386
|
+
findings.push({
|
|
2387
|
+
relPath,
|
|
2388
|
+
line: startLine,
|
|
2389
|
+
token: `<${m[1] ?? 'Select'}`,
|
|
2390
|
+
kind: 'raw-selection-control',
|
|
2391
|
+
})
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2252
2395
|
// sub-16-input-font — two full-text scans (same shape as the tag-scoped scans above): (a) a raw
|
|
2253
2396
|
// form-control's own inline `style={{ fontSize: N }}`, (b) a Mantine `styles={{ input: { fontSize:
|
|
2254
2397
|
// N } }}` per-part style. Either shape is now DEAD CODE against the `!important` floor in
|
package/src/guard/types.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Guard types — Mantine-free, dependency-free (zero imports beyond TS types).
|
|
3
3
|
*
|
|
4
|
-
* GuardKind is the closed set of
|
|
4
|
+
* GuardKind is the closed set of 27 violation kinds the theme guard can emit.
|
|
5
5
|
* Finding is the structured result per violation, replacing the old `Violation` shape.
|
|
6
6
|
* GuardConfig is the per-run configuration that drives checkSource.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
/** The
|
|
9
|
+
/** The 27 theme-guard violation kinds. */
|
|
10
10
|
export type GuardKind =
|
|
11
11
|
| 'raw-hex'
|
|
12
12
|
| 'raw-color-fn'
|
|
@@ -33,6 +33,8 @@ export type GuardKind =
|
|
|
33
33
|
| 'css-raw-surface'
|
|
34
34
|
| 'inline-font-size'
|
|
35
35
|
| 'hidden-inline-style'
|
|
36
|
+
| 'in-body-page-title'
|
|
37
|
+
| 'raw-selection-control'
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
40
|
* How hard a finding lands. `error` fails the build; `warn` reports and passes.
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,14 @@ export {
|
|
|
14
14
|
} from './provider'
|
|
15
15
|
export { createBasaltTheme, baseTheme, cssVariablesResolver } from './theme'
|
|
16
16
|
export type { BasaltFontsConfig, CreateBasaltThemeOptions } from './theme'
|
|
17
|
+
export { CTL_THEME, CtlSlot } from './theme'
|
|
18
|
+
export type { CtlSlotProps } from './theme'
|
|
19
|
+
/**
|
|
20
|
+
* The action vocabulary every home's `actions` slot takes (`docs/CONTROLS-SPEC.md` §2.1). The
|
|
21
|
+
* COMPONENTS that project it (`ActionGroup`, `OverflowMenu`, `SyncButton`, and the filter family)
|
|
22
|
+
* live on `basalt-ui/controls` — only the types a `PageBar`/`BasaltShell` prop mentions are here.
|
|
23
|
+
*/
|
|
24
|
+
export type { BarAction, ActionGroupProps, GlobalAction } from './controls/actions'
|
|
17
25
|
export { ThemeToggle, type ThemeToggleProps } from './theme-toggle'
|
|
18
26
|
export { MOTION_DURATION, MOTION_SPRING, MOTION_EASE_STANDARD } from './motion'
|
|
19
27
|
export {
|
|
@@ -22,6 +30,7 @@ export {
|
|
|
22
30
|
AppSidebar,
|
|
23
31
|
SidebarSearch,
|
|
24
32
|
type SidebarSearchConfig,
|
|
33
|
+
type SidebarSearchActions,
|
|
25
34
|
SidebarAccount,
|
|
26
35
|
MobileNav,
|
|
27
36
|
type MobileNavProps,
|
|
@@ -31,11 +40,16 @@ export {
|
|
|
31
40
|
MOBILE_MENU_MAX_DEFAULT,
|
|
32
41
|
MOBILE_MORE_KEY,
|
|
33
42
|
AppBreadcrumbs,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
PageActionsOutlet,
|
|
43
|
+
PageBar,
|
|
44
|
+
type PageBarProps,
|
|
37
45
|
type SidebarSection,
|
|
38
46
|
type SidebarItem,
|
|
47
|
+
type SidebarBlock,
|
|
48
|
+
type SidebarBlockItem,
|
|
49
|
+
type SidebarBlockTone,
|
|
50
|
+
type SidebarListBlock,
|
|
51
|
+
type SidebarProgressBlock,
|
|
52
|
+
type SidebarCustomBlock,
|
|
39
53
|
type NavAnchor,
|
|
40
54
|
type NavAnchorProps,
|
|
41
55
|
type NavMobilePlacement,
|
|
@@ -72,14 +86,41 @@ export {
|
|
|
72
86
|
type StandardSchemaV1,
|
|
73
87
|
} from './register'
|
|
74
88
|
|
|
75
|
-
// ── Persisted state +
|
|
89
|
+
// ── Persisted state + the headless store kernel (state.ts) ───────────────────────────────────────
|
|
90
|
+
// The `field.*` VALUE is deliberately not re-exported here — `basalt-ui/forms` already owns the
|
|
91
|
+
// name `field` on its own barrel. Import it from `basalt-ui/router-tanstack` (beside
|
|
92
|
+
// `createSearchStore`) or from `basalt-ui/state` (beside `createLocalStore`).
|
|
76
93
|
export {
|
|
77
94
|
createPersistedState,
|
|
78
95
|
type PersistedStateOptions,
|
|
79
|
-
useOnlineStatus,
|
|
80
96
|
readPersistedValue,
|
|
97
|
+
createLocalStore,
|
|
98
|
+
type LocalStore,
|
|
99
|
+
type AnyField,
|
|
100
|
+
type BooleanField,
|
|
101
|
+
type EnumField,
|
|
102
|
+
type FieldHandle,
|
|
103
|
+
type FieldLane,
|
|
104
|
+
type FieldOption,
|
|
105
|
+
type FieldValue,
|
|
106
|
+
type MultiField,
|
|
107
|
+
type NumberField,
|
|
108
|
+
type RangeField,
|
|
109
|
+
type RangeParams,
|
|
110
|
+
type RangePresets,
|
|
111
|
+
type RangeValue,
|
|
112
|
+
type ResolvedLane,
|
|
113
|
+
type SearchValues,
|
|
114
|
+
type StoredValues,
|
|
115
|
+
type StringField,
|
|
81
116
|
} from './state'
|
|
82
117
|
|
|
118
|
+
// ── WidgetHeader (unified section/widget/card heading primitive, docs/CONTROLS-SPEC.md §2.2) ─────
|
|
119
|
+
export { WidgetHeader, type WidgetHeaderProps } from './widget-header'
|
|
120
|
+
|
|
121
|
+
// ── Section (tier-2 heading composer over WidgetHeader, docs/CONTROLS-SPEC.md §2.2) ───────────────
|
|
122
|
+
export { Section, type SectionProps } from './section'
|
|
123
|
+
|
|
83
124
|
// ── Dashboard composites (KPI atoms + settings building blocks) ──────────────────────────────────
|
|
84
125
|
export {
|
|
85
126
|
DeltaBadge,
|
package/src/nav/types.ts
CHANGED
|
@@ -40,6 +40,8 @@ export type SidebarItem = {
|
|
|
40
40
|
/** Plain href — the no-router fallback, and the breadcrumb parent target. */
|
|
41
41
|
href?: string
|
|
42
42
|
active?: boolean
|
|
43
|
+
/** A parent on the path to the active row. NOT active itself — never carries `aria-current`. */
|
|
44
|
+
ancestor?: boolean
|
|
43
45
|
disabled?: boolean
|
|
44
46
|
onClick?: (e: MouseEvent) => void
|
|
45
47
|
/** Rich desktop badge (e.g. `NavCountBadge`). Desktop only. */
|
|
@@ -70,6 +72,88 @@ export type SidebarSection = {
|
|
|
70
72
|
mobile?: false | NavSectionMobile
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Status tone on a sidebar-block item — the same three-member vocabulary as `StatCardTone`, whose
|
|
77
|
+
* members are `VX.status` keys by construction. Declared here rather than imported from
|
|
78
|
+
* `../dashboard/stat-card`: this module is the one `./router-tanstack` reads, and even a
|
|
79
|
+
* type-only edge into the Mantine layer would make `nav/types.d.ts` unresolvable without
|
|
80
|
+
* `@mantine/core` installed.
|
|
81
|
+
*/
|
|
82
|
+
export type SidebarBlockTone = 'good' | 'warn' | 'bad'
|
|
83
|
+
|
|
84
|
+
/** One row inside a `kind: 'list'` block. Same router seam as `SidebarItem` — `Anchor`, then href. */
|
|
85
|
+
export type SidebarBlockItem = {
|
|
86
|
+
key: string
|
|
87
|
+
label: string
|
|
88
|
+
/** Trailing muted text (a timestamp, a count, an owner). */
|
|
89
|
+
meta?: string
|
|
90
|
+
icon?: ReactNode
|
|
91
|
+
/** Renders a status dot in place of a missing `icon`; reads `--vx-status-*`, never a hex. */
|
|
92
|
+
tone?: SidebarBlockTone
|
|
93
|
+
Anchor?: NavAnchor
|
|
94
|
+
href?: string
|
|
95
|
+
onClick?: () => void
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A list of non-destination rows under a micro-label — "Awaiting action", "Recents", a project
|
|
100
|
+
* list. `count` is the header badge (and what earns a rail dot); `max` shows the first N with a
|
|
101
|
+
* "Show more" toggle past it.
|
|
102
|
+
*/
|
|
103
|
+
export type SidebarListBlock = {
|
|
104
|
+
kind: 'list'
|
|
105
|
+
key: string
|
|
106
|
+
label: string
|
|
107
|
+
icon?: ReactNode
|
|
108
|
+
count?: number
|
|
109
|
+
/** Rows shown before the "Show more" toggle. Omit to show every row. */
|
|
110
|
+
max?: number
|
|
111
|
+
items: SidebarBlockItem[]
|
|
112
|
+
/** @default 'nav' */
|
|
113
|
+
placement?: 'nav' | 'bottom'
|
|
114
|
+
collapsible?: boolean
|
|
115
|
+
/** Collapsed-rail projection. @default 'dot' when `count` is set, `'hidden'` otherwise */
|
|
116
|
+
rail?: 'dot' | 'hidden'
|
|
117
|
+
/** @default 'more' */
|
|
118
|
+
mobile?: 'more' | 'hidden'
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** A "Getting started 1 of 5" progress row. Pinned above the settings footer; never in the nav. */
|
|
122
|
+
export type SidebarProgressBlock = {
|
|
123
|
+
kind: 'progress'
|
|
124
|
+
key: string
|
|
125
|
+
label: string
|
|
126
|
+
value: number
|
|
127
|
+
total: number
|
|
128
|
+
onClick?: () => void
|
|
129
|
+
/** @default 'bottom' — the only placement a progress row has. */
|
|
130
|
+
placement?: 'bottom'
|
|
131
|
+
/** Collapsed-rail projection: a ring on the settings row. @default 'ring' */
|
|
132
|
+
rail?: 'ring' | 'hidden'
|
|
133
|
+
/** @default 'hidden' */
|
|
134
|
+
mobile?: 'more' | 'hidden'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* DESKTOP ONLY — arbitrary consumer content (a tree, a filter panel) that no set of rows can
|
|
139
|
+
* express. Replaced `BasaltShellProps.sidebarNavExtra`; hidden on the collapsed rail and absent
|
|
140
|
+
* from mobile for the same reason that prop was.
|
|
141
|
+
*/
|
|
142
|
+
export type SidebarCustomBlock = {
|
|
143
|
+
kind: 'custom'
|
|
144
|
+
key: string
|
|
145
|
+
node: ReactNode
|
|
146
|
+
/** @default 'nav' */
|
|
147
|
+
placement?: 'nav' | 'bottom'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* A sidebar block — DECLARED DATA, never a `ReactNode` slot (law C13, `docs/CONTROLS-SPEC.md`
|
|
152
|
+
* §2.3), which is what lets basalt own the collapsed-rail badge and the mobile More-sheet
|
|
153
|
+
* projection instead of each consumer hand-rolling both.
|
|
154
|
+
*/
|
|
155
|
+
export type SidebarBlock = SidebarListBlock | SidebarProgressBlock | SidebarCustomBlock
|
|
156
|
+
|
|
73
157
|
export type MobileNavSurface = 'link' | 'menu' | 'sheet'
|
|
74
158
|
|
|
75
159
|
/** One row inside a `menu`/`sheet` slot, grouped under its source section. */
|
|
@@ -108,8 +192,6 @@ export type MobileNavConfig = {
|
|
|
108
192
|
menuMax?: number
|
|
109
193
|
/** @default 'More' */
|
|
110
194
|
moreLabel?: string
|
|
111
|
-
/** Extra content pinned to the bottom of the More surface. */
|
|
112
|
-
moreExtra?: ReactNode
|
|
113
195
|
/** Scroll container for active-slot re-tap. @default document.scrollingElement */
|
|
114
196
|
getScrollElement?: () => HTMLElement | null
|
|
115
197
|
}
|