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
|
@@ -7,14 +7,49 @@ type BarSparklineProps = {
|
|
|
7
7
|
width: number
|
|
8
8
|
height: number
|
|
9
9
|
color?: string
|
|
10
|
+
/**
|
|
11
|
+
* Gap between two bars, in px. `1` is the shipped look; `2` at ~10 bars reads as a discrete series
|
|
12
|
+
* rather than a histogram, which is the KPI-card treatment.
|
|
13
|
+
*
|
|
14
|
+
* @default 1
|
|
15
|
+
*/
|
|
16
|
+
barGap?: number
|
|
17
|
+
/**
|
|
18
|
+
* Bar corner radius, in px. `0` is the shipped look.
|
|
19
|
+
*
|
|
20
|
+
* @default 0
|
|
21
|
+
*/
|
|
22
|
+
barRadius?: number
|
|
23
|
+
/**
|
|
24
|
+
* Paints the LAST bar in `emphasisColor` at full opacity — "this is where the series ends", the
|
|
25
|
+
* one reading a trend beside a KPI value is actually for. Off by default: a chart that means
|
|
26
|
+
* something by its final point must say so.
|
|
27
|
+
*
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
emphasizeLast?: boolean
|
|
31
|
+
/** The emphasised bar's fill. @default `VX.accent` */
|
|
32
|
+
emphasisColor?: string
|
|
10
33
|
/** Accessible text alternative, applied as `aria-label` (+ `role="img"`) on the `<svg>`. */
|
|
11
34
|
ariaLabel?: string
|
|
12
35
|
}
|
|
13
36
|
|
|
14
37
|
/** Quiet inline bar trend — defaults to `VX.faint`, matching the sparkline family's restrained
|
|
15
|
-
* identity (docs/DESIGN-SPEC.md §5).
|
|
16
|
-
|
|
38
|
+
* identity (docs/DESIGN-SPEC.md §5). Every geometry option below defaults to the shipped look, so
|
|
39
|
+
* an existing call site renders byte-identically. */
|
|
40
|
+
export function BarSparkline({
|
|
41
|
+
data,
|
|
42
|
+
width,
|
|
43
|
+
height,
|
|
44
|
+
color,
|
|
45
|
+
barGap = 1,
|
|
46
|
+
barRadius = 0,
|
|
47
|
+
emphasizeLast = false,
|
|
48
|
+
emphasisColor,
|
|
49
|
+
ariaLabel,
|
|
50
|
+
}: BarSparklineProps) {
|
|
17
51
|
const fillColor = color ?? VX.faint
|
|
52
|
+
const lastColor = emphasisColor ?? VX.accent
|
|
18
53
|
const a11yProps = ariaLabel !== undefined ? { role: 'img' as const, 'aria-label': ariaLabel } : {}
|
|
19
54
|
|
|
20
55
|
const yScale = useMemo(() => {
|
|
@@ -25,12 +60,14 @@ export function BarSparkline({ data, width, height, color, ariaLabel }: BarSpark
|
|
|
25
60
|
if (!data.length) return <svg width={width} height={height} {...a11yProps} />
|
|
26
61
|
|
|
27
62
|
const step = width / data.length
|
|
28
|
-
const barWidth = Math.max(step -
|
|
63
|
+
const barWidth = Math.max(step - barGap, 1)
|
|
64
|
+
const lastIndex = data.length - 1
|
|
29
65
|
|
|
30
66
|
return (
|
|
31
67
|
<svg width={width} height={height} {...a11yProps}>
|
|
32
68
|
{data.map((v, i) => {
|
|
33
69
|
const bh = Math.max(yScale(Math.max(v, 0)), 0)
|
|
70
|
+
const isLast = emphasizeLast && i === lastIndex
|
|
34
71
|
return (
|
|
35
72
|
<rect
|
|
36
73
|
key={i}
|
|
@@ -38,8 +75,9 @@ export function BarSparkline({ data, width, height, color, ariaLabel }: BarSpark
|
|
|
38
75
|
y={height - bh}
|
|
39
76
|
width={barWidth}
|
|
40
77
|
height={bh}
|
|
41
|
-
|
|
42
|
-
|
|
78
|
+
{...(barRadius > 0 && { rx: barRadius })}
|
|
79
|
+
fill={isLast ? lastColor : fillColor}
|
|
80
|
+
fillOpacity={isLast ? 1 : 0.75}
|
|
43
81
|
/>
|
|
44
82
|
)
|
|
45
83
|
})}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `basalt-ui check-coverage` — the 11 assertions (SURFACES consistency + the agent-layer line
|
|
3
|
+
* budgets), and the generated `<!-- basalt:coverage -->` header each rule file carries
|
|
4
|
+
* (docs/CONTROLS-SPEC.md §7).
|
|
5
|
+
*
|
|
6
|
+
* The block exists because a rule file's own claim about what enforces it was prose, and prose
|
|
7
|
+
* drifted: a doc could name a guard kind and stay silent about the oxlint rule doing the real work,
|
|
8
|
+
* or claim coverage for a law nothing checks (D8). Generating it from SURFACES makes the claim a
|
|
9
|
+
* projection of the registry, and `--check` makes a stale claim a build failure.
|
|
10
|
+
*
|
|
11
|
+
* The pure half (`coverageFor` / `coverageBlock` / `applyCoverageBlock`) is tested directly; the fs
|
|
12
|
+
* half runs against a TEMP package root, never the real `agent/rules/**` — a test that wrote into
|
|
13
|
+
* the shipped rules would make `--write` untestable afterwards.
|
|
14
|
+
*/
|
|
15
|
+
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
16
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
17
|
+
import { tmpdir } from 'node:os'
|
|
18
|
+
import { resolve } from 'node:path'
|
|
19
|
+
|
|
20
|
+
import { GUARD_RULES } from '../guard/index'
|
|
21
|
+
import { RULE_NAMES, SURFACES } from '../surfaces'
|
|
22
|
+
import {
|
|
23
|
+
COVERAGE_BLOCK_CLOSE,
|
|
24
|
+
COVERAGE_BLOCK_OPEN,
|
|
25
|
+
applyCoverageBlock,
|
|
26
|
+
checkCoverage,
|
|
27
|
+
coverageBlock,
|
|
28
|
+
coverageFor,
|
|
29
|
+
readCoverageBlock,
|
|
30
|
+
reconcileCoverageBlocks,
|
|
31
|
+
} from './index'
|
|
32
|
+
|
|
33
|
+
const FRONTMATTER = `---\nsource: basalt-ui\ndescription: d\npaths:\n - 'src/**'\n---\n`
|
|
34
|
+
|
|
35
|
+
let dir: string
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
dir = mkdtempSync(resolve(tmpdir(), 'basalt-check-coverage-'))
|
|
39
|
+
mkdirSync(resolve(dir, 'agent/rules'), { recursive: true })
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
rmSync(dir, { recursive: true, force: true })
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
/** Writes one rule file into the temp package root and returns its path. */
|
|
47
|
+
function writeRuleFile(rule: string, body: string): string {
|
|
48
|
+
const path = resolve(dir, `agent/rules/basalt-${rule}.md`)
|
|
49
|
+
writeFileSync(path, body)
|
|
50
|
+
return path
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── the 11 assertions ────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
describe('checkCoverage', () => {
|
|
56
|
+
it('passes against the live registry', () => {
|
|
57
|
+
expect(checkCoverage()).toBe(0)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('rejects --write and --check together rather than picking one', () => {
|
|
61
|
+
expect(checkCoverage(['--write', '--check'])).toBe(1)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('holds every shipped agent-layer file inside its budget, and the rules inside the total', () => {
|
|
65
|
+
// Assertion 10, read off the real layer rather than a fixture: the budgets ARE the doctrine, so
|
|
66
|
+
// the interesting failure is a shipped rule growing past one, not a synthetic file doing it.
|
|
67
|
+
const pkgRoot = resolve(import.meta.dir, '../..')
|
|
68
|
+
const lines = (rel: string) =>
|
|
69
|
+
readFileSync(resolve(pkgRoot, rel), 'utf8').split('\n').length - 1
|
|
70
|
+
const total = RULE_NAMES.reduce((sum, rule) => sum + lines(`agent/rules/basalt-${rule}.md`), 0)
|
|
71
|
+
expect(total).toBeLessThanOrEqual(1050)
|
|
72
|
+
expect(RULE_NAMES.length).toBe(6)
|
|
73
|
+
for (const skill of ['basalt-app', 'basalt-design', 'basalt-charts']) {
|
|
74
|
+
expect(lines(`agent/skills/${skill}/SKILL.md`)).toBeLessThanOrEqual(100)
|
|
75
|
+
}
|
|
76
|
+
expect(lines('agent/templates/CLAUDE-block.md.tpl')).toBeLessThanOrEqual(40)
|
|
77
|
+
expect(lines('agent/templates/DESIGN.md.tpl')).toBeLessThanOrEqual(45)
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// ── coverageFor — the projection ─────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
describe('coverageFor', () => {
|
|
84
|
+
// `rule` is many-to-one: `data` covers ./data, ./data/table and ./data/virtual, `mantine` covers
|
|
85
|
+
// `.` and ./connectivity. Reading one surface would understate the rule's coverage.
|
|
86
|
+
it('unions every surface carrying the rule', () => {
|
|
87
|
+
const mantine = coverageFor('mantine')
|
|
88
|
+
expect(mantine.pluginRules).toContain('hand-rolled-shell')
|
|
89
|
+
expect(mantine.guardKinds).toContain('raw-form-control')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('names the control rules under the controls rule', () => {
|
|
93
|
+
expect(coverageFor('controls').pluginRules).toEqual([
|
|
94
|
+
'control-outside-home',
|
|
95
|
+
'control-size-literal',
|
|
96
|
+
'hand-rolled-filter',
|
|
97
|
+
'responsive-twin',
|
|
98
|
+
])
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('carries the advisory laws verbatim, in spec order', () => {
|
|
102
|
+
const { advisoryLaws } = coverageFor('controls')
|
|
103
|
+
expect(advisoryLaws.length).toBe(4)
|
|
104
|
+
expect(advisoryLaws[0]).toContain('C1 as a cross-file law')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('dedupes a kind two surfaces share', () => {
|
|
108
|
+
const { guardKinds } = coverageFor('charts')
|
|
109
|
+
expect(guardKinds.filter((k) => k === 'raw-hex')).toHaveLength(1)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('unions the plugin rules of every surface sharing one rule name', () => {
|
|
113
|
+
// `batteries` is eight surfaces (query, forms, notifications, commands, data ×3, agent ×2,
|
|
114
|
+
// content, #app); only the agent pair carries plugin rules, and the header must name them.
|
|
115
|
+
const { guardKinds, pluginRules } = coverageFor('batteries')
|
|
116
|
+
expect(guardKinds).toEqual([])
|
|
117
|
+
expect(pluginRules).toEqual(['agent-no-raw-usechat', 'agent-resume-guard', 'ai-sdk-major'])
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('leaves no rule with nothing on either lane', () => {
|
|
121
|
+
// The 13→6 merge folded the eight `guardKinds: []` rules into one file, so every remaining rule
|
|
122
|
+
// is backed by something — a header saying otherwise would be a rule with no reason to ship.
|
|
123
|
+
for (const rule of RULE_NAMES) {
|
|
124
|
+
const { guardKinds, pluginRules } = coverageFor(rule)
|
|
125
|
+
expect([rule, guardKinds.length + pluginRules.length > 0]).toEqual([rule, true])
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// ── coverageBlock — what the file actually carries ───────────────────────────
|
|
131
|
+
|
|
132
|
+
describe('coverageBlock', () => {
|
|
133
|
+
it('is HTML comments only, so it renders as nothing', () => {
|
|
134
|
+
for (const rule of RULE_NAMES) {
|
|
135
|
+
for (const line of coverageBlock(rule).split('\n')) {
|
|
136
|
+
expect([rule, line.startsWith('<!--') && line.endsWith('-->')]).toEqual([rule, true])
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('opens and closes with the delimiters', () => {
|
|
142
|
+
const block = coverageBlock('controls')
|
|
143
|
+
expect(block.startsWith(COVERAGE_BLOCK_OPEN)).toBe(true)
|
|
144
|
+
expect(block.endsWith(COVERAGE_BLOCK_CLOSE)).toBe(true)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('names both lanes, prefixing the oxlint ids', () => {
|
|
148
|
+
const block = coverageBlock('controls')
|
|
149
|
+
expect(block).toContain('oxlint rules — basalt/control-outside-home')
|
|
150
|
+
expect(block).toContain('guard kinds — raw-selection-control')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// The empty lane still prints — `none` is a claim someone can check, an omitted section is not.
|
|
154
|
+
it('prints `none` for a lane with nothing on it', () => {
|
|
155
|
+
expect(coverageBlock('batteries')).toContain('guard kinds — none')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// A block that simply omitted the section would read as "nothing to declare" whether or not
|
|
159
|
+
// anyone had looked. `—` is a claim someone can check.
|
|
160
|
+
it('prints `not guarded: —` when a rule declares no advisory law', () => {
|
|
161
|
+
expect(coverageBlock('tokens')).toContain('<!-- not guarded: — -->')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('prints one line per advisory law, so a diff points at the claim', () => {
|
|
165
|
+
const lines = coverageBlock('controls')
|
|
166
|
+
.split('\n')
|
|
167
|
+
.filter((l) => l.includes('not guarded'))
|
|
168
|
+
expect(lines).toHaveLength(4)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('is stable — the same registry emits the same bytes', () => {
|
|
172
|
+
expect(coverageBlock('charts')).toBe(coverageBlock('charts'))
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// ── applyCoverageBlock / readCoverageBlock ──────────────────────────────────
|
|
177
|
+
|
|
178
|
+
describe('applyCoverageBlock', () => {
|
|
179
|
+
const block = coverageBlock('controls')
|
|
180
|
+
|
|
181
|
+
it('inserts below the YAML frontmatter — the paths: header stays first', () => {
|
|
182
|
+
const out = applyCoverageBlock(`${FRONTMATTER}\n# Title\n`, block)
|
|
183
|
+
expect(out.startsWith(FRONTMATTER)).toBe(true)
|
|
184
|
+
expect(out).toContain(block)
|
|
185
|
+
expect(out.indexOf(block)).toBeLessThan(out.indexOf('# Title'))
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('replaces an existing block instead of stacking a second one', () => {
|
|
189
|
+
const stale = `${COVERAGE_BLOCK_OPEN}\n<!-- backed by: nothing -->\n${COVERAGE_BLOCK_CLOSE}`
|
|
190
|
+
const out = applyCoverageBlock(`${FRONTMATTER}\n${stale}\n\n# Title\n`, block)
|
|
191
|
+
expect(out).toContain(block)
|
|
192
|
+
expect(out).not.toContain('backed by: nothing')
|
|
193
|
+
expect(out.split(COVERAGE_BLOCK_OPEN)).toHaveLength(2)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('is idempotent', () => {
|
|
197
|
+
const once = applyCoverageBlock(`${FRONTMATTER}\n# T\n`, block)
|
|
198
|
+
expect(applyCoverageBlock(once, block)).toBe(once)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('prepends when there is no frontmatter', () => {
|
|
202
|
+
expect(applyCoverageBlock('# T\n', block).startsWith(block)).toBe(true)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('readCoverageBlock returns null for a file with no block', () => {
|
|
206
|
+
expect(readCoverageBlock(`${FRONTMATTER}\n# T\n`)).toBeNull()
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// ── reconcileCoverageBlocks — --write / --check ─────────────────────────────
|
|
211
|
+
|
|
212
|
+
describe('reconcileCoverageBlocks', () => {
|
|
213
|
+
/** Every rule file the temp root needs, so only the block state under test varies. */
|
|
214
|
+
function seedRuleFiles(body: (rule: string) => string): void {
|
|
215
|
+
for (const rule of RULE_NAMES) writeRuleFile(rule, body(rule))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
it('--write inserts a block into every rule file', () => {
|
|
219
|
+
seedRuleFiles(() => `${FRONTMATTER}\n# T\n`)
|
|
220
|
+
const { failures, notes } = reconcileCoverageBlocks(dir, 'write')
|
|
221
|
+
expect(failures).toEqual([])
|
|
222
|
+
expect(notes).toHaveLength(RULE_NAMES.length)
|
|
223
|
+
const text = readFileSync(resolve(dir, 'agent/rules/basalt-controls.md'), 'utf8')
|
|
224
|
+
expect(text).toContain(coverageBlock('controls'))
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('--write is idempotent — a second run reports unchanged and rewrites nothing', () => {
|
|
228
|
+
seedRuleFiles(() => `${FRONTMATTER}\n# T\n`)
|
|
229
|
+
reconcileCoverageBlocks(dir, 'write')
|
|
230
|
+
const before = readFileSync(resolve(dir, 'agent/rules/basalt-controls.md'), 'utf8')
|
|
231
|
+
const { notes } = reconcileCoverageBlocks(dir, 'write')
|
|
232
|
+
expect(notes.every((n) => n.endsWith('unchanged'))).toBe(true)
|
|
233
|
+
expect(readFileSync(resolve(dir, 'agent/rules/basalt-controls.md'), 'utf8')).toBe(before)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('--check passes once the blocks are written', () => {
|
|
237
|
+
seedRuleFiles(() => `${FRONTMATTER}\n# T\n`)
|
|
238
|
+
reconcileCoverageBlocks(dir, 'write')
|
|
239
|
+
expect(reconcileCoverageBlocks(dir, 'check').failures).toEqual([])
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
// The wave-6 requirement: the blocks are inserted by the agent-layer wave, and a gate that failed
|
|
243
|
+
// before they exist would have to land disabled — which is how a gate stays disabled.
|
|
244
|
+
it('--check REPORTS a file with no block yet, and does not fail it', () => {
|
|
245
|
+
seedRuleFiles(() => `${FRONTMATTER}\n# T\n`)
|
|
246
|
+
const { failures, notes } = reconcileCoverageBlocks(dir, 'check')
|
|
247
|
+
expect(failures).toEqual([])
|
|
248
|
+
expect(notes).toHaveLength(RULE_NAMES.length)
|
|
249
|
+
expect(notes[0]).toContain('no <!-- basalt:coverage --> block yet')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('--check FAILS a block that disagrees with SURFACES', () => {
|
|
253
|
+
seedRuleFiles(() => `${FRONTMATTER}\n# T\n`)
|
|
254
|
+
reconcileCoverageBlocks(dir, 'write')
|
|
255
|
+
const path = resolve(dir, 'agent/rules/basalt-controls.md')
|
|
256
|
+
writeFileSync(path, readFileSync(path, 'utf8').replace('basalt/hand-rolled-filter', 'basalt/x'))
|
|
257
|
+
const { failures } = reconcileCoverageBlocks(dir, 'check')
|
|
258
|
+
expect(failures).toHaveLength(1)
|
|
259
|
+
expect(failures[0]).toContain('basalt-controls.md')
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('skips a missing rule file — assertion 2 already reports that', () => {
|
|
263
|
+
expect(reconcileCoverageBlocks(dir, 'check')).toEqual({ failures: [], notes: [] })
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
// ── the registry itself ─────────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
describe('SURFACES.pluginRules', () => {
|
|
270
|
+
it('is required on every doctrine surface — [] is legal, absent is not', () => {
|
|
271
|
+
for (const [key, spec] of Object.entries(SURFACES)) {
|
|
272
|
+
if (spec.kind !== 'doctrine') continue
|
|
273
|
+
expect([key, Array.isArray(spec.pluginRules)]).toEqual([key, true])
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
describe('SURFACES.guardKinds — assertion 11', () => {
|
|
279
|
+
// The twin of assertion 9 for the text lane. Two wave-6 kinds shipped on no surface at all, so
|
|
280
|
+
// `basalt-controls.md` printed "guard kinds — none" while the guard was scanning for
|
|
281
|
+
// `raw-selection-control` — a generated header claiming less coverage than exists is D8 in the
|
|
282
|
+
// other direction, and it cannot be hand-corrected.
|
|
283
|
+
it('places every GUARD_RULES kind on at least one doctrine surface', () => {
|
|
284
|
+
const surfaced = new Set<string>()
|
|
285
|
+
for (const spec of Object.values(SURFACES)) {
|
|
286
|
+
if (spec.kind !== 'doctrine') continue
|
|
287
|
+
for (const kind of spec.guardKinds) surfaced.add(kind)
|
|
288
|
+
}
|
|
289
|
+
const orphans = Object.keys(GUARD_RULES).filter((kind) => !surfaced.has(kind))
|
|
290
|
+
expect(orphans).toEqual([])
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('names the two wave-6 control kinds on the surfaces whose headers must claim them', () => {
|
|
294
|
+
expect(coverageFor('controls').guardKinds).toContain('raw-selection-control')
|
|
295
|
+
expect(coverageFor('mantine').guardKinds).toContain('in-body-page-title')
|
|
296
|
+
})
|
|
297
|
+
})
|
|
@@ -84,7 +84,7 @@ describe('check-theme raw-surface', () => {
|
|
|
84
84
|
|
|
85
85
|
it('does NOT flag a line carrying a theme-allow comment', () => {
|
|
86
86
|
fixture(
|
|
87
|
-
`export const C = () => <div style={{ borderRadius: 8 }} /> // theme-allow
|
|
87
|
+
`export const C = () => <div style={{ borderRadius: 8 }} /> // theme-allow raw-surface — legacy widget\n`,
|
|
88
88
|
)
|
|
89
89
|
const { code, err } = run()
|
|
90
90
|
expect(code).toBe(0)
|
|
@@ -110,10 +110,11 @@ describe('check-theme raw-surface', () => {
|
|
|
110
110
|
})
|
|
111
111
|
|
|
112
112
|
it('does NOT flag a var(--…) boxShadow token (regression)', () => {
|
|
113
|
-
fixture(`export const C = () => <div style={{ boxShadow: 'var(--
|
|
113
|
+
fixture(`export const C = () => <div style={{ boxShadow: 'var(--vx-shadow-card)' }} />\n`)
|
|
114
114
|
const { code, err } = run()
|
|
115
115
|
expect(code).toBe(0)
|
|
116
116
|
expect(err).not.toContain('raw-surface')
|
|
117
|
+
expect(err).not.toContain('surface-shadow-override')
|
|
117
118
|
})
|
|
118
119
|
|
|
119
120
|
it('does NOT flag a Mantine radius="md" prop (not inline surface styling)', () => {
|
|
@@ -216,7 +217,7 @@ describe('check-theme off-system-surface-var', () => {
|
|
|
216
217
|
|
|
217
218
|
it('does NOT flag a line carrying a theme-allow comment', () => {
|
|
218
219
|
fixture(
|
|
219
|
-
`export const C = () => <Box bg="var(--mantine-color-gray-3)" /> // theme-allow
|
|
220
|
+
`export const C = () => <Box bg="var(--mantine-color-gray-3)" /> // theme-allow off-system-surface-var — legacy\n`,
|
|
220
221
|
)
|
|
221
222
|
const { code, err } = run()
|
|
222
223
|
expect(code).toBe(0)
|
|
@@ -266,7 +267,9 @@ describe('check-theme raw-html-layout', () => {
|
|
|
266
267
|
})
|
|
267
268
|
|
|
268
269
|
it('does NOT flag a line carrying a theme-allow comment', () => {
|
|
269
|
-
fixture(
|
|
270
|
+
fixture(
|
|
271
|
+
`export const C = () => <div style={{ display: 'flex' }} /> // theme-allow raw-html-layout inline-display — legacy\n`,
|
|
272
|
+
)
|
|
270
273
|
const { code, err } = run()
|
|
271
274
|
expect(code).toBe(0)
|
|
272
275
|
expect(err).not.toContain('raw-html-layout')
|
|
@@ -377,7 +380,7 @@ describe('check-theme raw-visx-axis', () => {
|
|
|
377
380
|
it('does NOT flag a chart-path <AxisLeft> carrying a theme-allow comment', () => {
|
|
378
381
|
fixtureAt(
|
|
379
382
|
'charts/kinds/Foo.tsx',
|
|
380
|
-
`export const Foo = () => <AxisLeft scale={s} /> // theme-allow
|
|
383
|
+
`export const Foo = () => <AxisLeft scale={s} /> // theme-allow raw-visx-axis — bespoke dual-panel\n`,
|
|
381
384
|
)
|
|
382
385
|
const { code, err } = run()
|
|
383
386
|
expect(code).toBe(0)
|
|
@@ -490,7 +493,7 @@ describe('check-theme unframed-chart', () => {
|
|
|
490
493
|
it('does NOT flag a line carrying a theme-allow comment', () => {
|
|
491
494
|
fixtureAt(
|
|
492
495
|
'charts/kinds/Foo.tsx',
|
|
493
|
-
`export const Foo = () => <ChartLegend items={[{ key: 'a', label: 'A' }]} /> // theme-allow
|
|
496
|
+
`export const Foo = () => <ChartLegend items={[{ key: 'a', label: 'A' }]} /> // theme-allow unframed-chart — bespoke legend\n`,
|
|
494
497
|
)
|
|
495
498
|
const { code, err } = run()
|
|
496
499
|
expect(code).toBe(0)
|
|
@@ -538,7 +541,9 @@ describe('check-theme raw-form-control', () => {
|
|
|
538
541
|
})
|
|
539
542
|
|
|
540
543
|
it('does NOT flag a line carrying a theme-allow comment', () => {
|
|
541
|
-
fixture(
|
|
544
|
+
fixture(
|
|
545
|
+
`export const C = () => <input type="text" /> // theme-allow raw-form-control — legacy widget\n`,
|
|
546
|
+
)
|
|
542
547
|
const { code, err } = run()
|
|
543
548
|
expect(code).toBe(0)
|
|
544
549
|
expect(err).not.toContain('raw-form-control')
|
|
@@ -563,34 +568,46 @@ describe('check-theme sub-16-input-font', () => {
|
|
|
563
568
|
expect(err).toContain('sub-16-input-font')
|
|
564
569
|
})
|
|
565
570
|
|
|
566
|
-
it('does NOT flag
|
|
571
|
+
it('does NOT flag sub-16-input-font on a <Text> (not a form control)', () => {
|
|
572
|
+
// The literal itself is still a raw font-size, so `inline-font-size` (an independent kind,
|
|
573
|
+
// no `sub16InputFont` gate) reports it — only `sub-16-input-font`'s form-control scoping is
|
|
574
|
+
// under test here.
|
|
567
575
|
fixture(`export const C = () => <Text style={{ fontSize: 12 }}>caption</Text>\n`)
|
|
568
576
|
const { code, err } = run()
|
|
569
|
-
expect(code).toBe(
|
|
577
|
+
expect(code).toBe(1)
|
|
570
578
|
expect(err).not.toContain('sub-16-input-font')
|
|
579
|
+
expect(err).toContain('inline-font-size')
|
|
571
580
|
})
|
|
572
581
|
|
|
573
|
-
it('does NOT flag
|
|
582
|
+
it('does NOT flag sub-16-input-font on a <span> (not a form control)', () => {
|
|
583
|
+
// Same as the <Text> case above: `inline-font-size` fires independently of the form-control
|
|
584
|
+
// scoping this test verifies for `sub-16-input-font`.
|
|
574
585
|
fixture(`export const C = () => <span style={{ fontSize: 11 }}>micro</span>\n`)
|
|
575
586
|
const { code, err } = run()
|
|
576
|
-
expect(code).toBe(
|
|
587
|
+
expect(code).toBe(1)
|
|
577
588
|
expect(err).not.toContain('sub-16-input-font')
|
|
589
|
+
expect(err).toContain('inline-font-size')
|
|
578
590
|
})
|
|
579
591
|
|
|
580
592
|
it('respects the sub16InputFont:false config knob', () => {
|
|
593
|
+
// `inline-font-size` has no config knob and fires on any raw literal regardless — only
|
|
594
|
+
// `sub-16-input-font` itself is under test here.
|
|
581
595
|
fixture(`export const C = () => <input style={{ fontSize: 12 }} />\n`, {
|
|
582
596
|
roots: ['src'],
|
|
583
597
|
rawFormControl: false,
|
|
584
598
|
sub16InputFont: false,
|
|
585
599
|
})
|
|
586
600
|
const { code, err } = run()
|
|
587
|
-
expect(code).toBe(
|
|
601
|
+
expect(code).toBe(1)
|
|
588
602
|
expect(err).not.toContain('sub-16-input-font')
|
|
603
|
+
expect(err).toContain('inline-font-size')
|
|
589
604
|
})
|
|
590
605
|
|
|
591
606
|
it('does NOT flag a line carrying a theme-allow comment', () => {
|
|
607
|
+
// Names both kinds the literal can trip: sub-16-input-font (scoped by rawFormControl above)
|
|
608
|
+
// and inline-font-size (ungated, fires on the literal regardless of element).
|
|
592
609
|
fixture(
|
|
593
|
-
`export const C = () => <input style={{ fontSize: 12 }} /> // theme-allow
|
|
610
|
+
`export const C = () => <input style={{ fontSize: 12 }} /> // theme-allow sub-16-input-font inline-font-size — legacy widget\n`,
|
|
594
611
|
{ roots: ['src'], rawFormControl: false },
|
|
595
612
|
)
|
|
596
613
|
const { code, err } = run()
|
|
@@ -646,11 +663,18 @@ describe('check-theme scans .css', () => {
|
|
|
646
663
|
})
|
|
647
664
|
|
|
648
665
|
it('does NOT false-positive a JSX-shaped rule on genuine CSS', () => {
|
|
649
|
-
|
|
666
|
+
// `border-radius: 8px` is a genuine, unrelated `css-raw-surface` violation (a raw literal
|
|
667
|
+
// with no `--vx-radius-*` token) — the point of this test is that the JSX-shaped rules
|
|
668
|
+
// (`inline-display`, `raw-html-layout`) do not also fire on plain CSS syntax.
|
|
669
|
+
fixtureAt(
|
|
670
|
+
'theme/controls.module.css',
|
|
671
|
+
'.foo {\n display: flex;\n border-radius: var(--vx-radius-card);\n}\n',
|
|
672
|
+
)
|
|
650
673
|
const { code, err } = run()
|
|
651
674
|
expect(code).toBe(0)
|
|
652
675
|
expect(err).not.toContain('inline-display')
|
|
653
676
|
expect(err).not.toContain('raw-html-layout')
|
|
677
|
+
expect(err).not.toContain('css-raw-surface')
|
|
654
678
|
})
|
|
655
679
|
})
|
|
656
680
|
|