basalt-ui 1.25.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -6
- package/CHANGELOG.md +26 -0
- package/MIGRATING.md +815 -23
- 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 +160 -0
- package/agent/rules/basalt-mantine.md +146 -439
- package/agent/rules/basalt-state.md +118 -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 +1465 -36
- package/configs/oxlint-plugin.test.ts +1190 -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 +39 -0
- package/dist/controls/enum-filter.d.ts.map +1 -0
- package/dist/controls/enum-filter.js +93 -0
- package/dist/controls/filter-context.d.ts +60 -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 +20 -0
- package/dist/controls/index.d.ts.map +1 -0
- package/dist/controls/index.js +46 -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/number-filter.d.ts +26 -0
- package/dist/controls/number-filter.d.ts.map +1 -0
- package/dist/controls/number-filter.js +143 -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 +43 -0
- package/dist/controls/range-filter.d.ts.map +1 -0
- package/dist/controls/range-filter.js +135 -0
- package/dist/controls/range-filter.type-guard.test.d.ts +2 -0
- package/dist/controls/range-filter.type-guard.test.d.ts.map +1 -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/index.d.ts +1 -1
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +3 -1
- 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 +95 -9
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +108 -49
- package/dist/dashboard/stat-card.module.css +153 -0
- package/dist/dashboard/stat-card.module.css.d.ts +15 -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 +96 -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 +78 -7
- package/dist/guard/types.d.ts +3 -3
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +13 -3
- 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 +84 -0
- package/dist/router-tanstack/search-store.d.ts.map +1 -0
- package/dist/router-tanstack/search-store.js +271 -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 +565 -0
- package/dist/state/fields.d.ts.map +1 -0
- package/dist/state/fields.js +405 -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 +60 -0
- package/dist/state/persisted.d.ts.map +1 -0
- package/dist/state/persisted.js +123 -0
- package/dist/state/persisted.test.d.ts +2 -0
- package/dist/state/persisted.test.d.ts.map +1 -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 +156 -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 +67 -0
- package/dist/widget-header/widget-header.d.ts.map +1 -0
- package/dist/widget-header/widget-header.js +89 -0
- package/dist/widget-header/widget-header.module.css +350 -0
- package/dist/widget-header/widget-header.module.css.d.ts +25 -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 +1053 -0
- package/src/controls/enum-filter.tsx +170 -0
- package/src/controls/filter-context.tsx +158 -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 +49 -0
- package/src/controls/multi-select-filter.tsx +140 -0
- package/src/controls/number-filter.tsx +291 -0
- package/src/controls/peer-boundary.test.ts +85 -0
- package/src/controls/props-contract.test.tsx +137 -0
- package/src/controls/range-filter.tsx +237 -0
- package/src/controls/range-filter.type-guard.test.tsx +122 -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/index.ts +6 -1
- 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 +153 -0
- package/src/dashboard/stat-card.module.css.d.ts +15 -0
- package/src/dashboard/stat-card.test.tsx +298 -1
- package/src/dashboard/stat-card.tsx +289 -67
- package/src/data/data-table.test.tsx +96 -11
- package/src/data/data-table.tsx +228 -84
- package/src/guard/check-source.test.ts +159 -13
- package/src/guard/grace.test.ts +158 -0
- package/src/guard/index.ts +185 -29
- package/src/guard/types.ts +4 -2
- package/src/index.ts +51 -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 +35 -0
- package/src/router-tanstack/index.ts +28 -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 +882 -0
- package/src/router-tanstack/search-store.test.ts +578 -0
- package/src/router-tanstack/search-store.ts +546 -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 +512 -0
- package/src/state/fields.ts +1150 -0
- package/src/state/persisted.test.ts +91 -0
- package/src/state/persisted.ts +261 -0
- package/src/state.ts +52 -240
- package/src/styles.css +13 -0
- package/src/surfaces.ts +204 -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 +350 -0
- package/src/widget-header/widget-header.module.css.d.ts +25 -0
- package/src/widget-header/widget-header.test.tsx +187 -0
- package/src/widget-header/widget-header.tsx +213 -0
- package/agent/rules/basalt-agent.md +0 -452
- package/agent/rules/basalt-app.md +0 -246
- package/agent/rules/basalt-commands.md +0 -320
- package/agent/rules/basalt-content.md +0 -409
- package/agent/rules/basalt-data.md +0 -329
- package/agent/rules/basalt-forms.md +0 -186
- package/agent/rules/basalt-notifications.md +0 -264
- package/agent/rules/basalt-query.md +0 -187
- package/agent/rules/basalt-router.md +0 -336
- package/dist/content/article-filter-bar.d.ts +0 -40
- package/dist/content/article-filter-bar.d.ts.map +0 -1
- package/dist/content/article-filter-bar.js +0 -56
- package/dist/shell/page-header.d.ts +0 -13
- package/dist/shell/page-header.d.ts.map +0 -1
- package/dist/shell/page-header.js +0 -23
- package/src/content/article-filter-bar.tsx +0 -88
- package/src/shell/page-header.tsx +0 -35
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unit tests for checkSource — the pure (text, relPath, cfg) → Finding[] core.
|
|
3
3
|
*
|
|
4
|
-
* Covers all
|
|
4
|
+
* Covers all 27 guard kinds. Co-located with the guard, excluded from tsc
|
|
5
5
|
* (tsconfig exclude: src/**\/*.test.ts), run via `bun test`.
|
|
6
6
|
*
|
|
7
7
|
* The walker/reporter half is covered by the integration test in
|
|
@@ -1916,15 +1916,16 @@ describe('severity', () => {
|
|
|
1916
1916
|
expect(f.find((v) => v.kind === 'raw-spacing')?.severity).toBe('error')
|
|
1917
1917
|
})
|
|
1918
1918
|
|
|
1919
|
-
// The grace ledger.
|
|
1920
|
-
//
|
|
1919
|
+
// The grace ledger. These five kinds landed warn-only in the round-4 guard minor and sat there
|
|
1920
|
+
// for five minors with nothing tracking the promise (D4) — GRACE_PERIOD_KINDS is now empty and
|
|
1921
|
+
// the C16 version gate in grace.test.ts is what stops a future entry sitting unpromoted again.
|
|
1921
1922
|
it.each([
|
|
1922
1923
|
'theme-allow-unscoped',
|
|
1923
1924
|
'surface-shadow-override',
|
|
1924
1925
|
'css-raw-surface',
|
|
1925
1926
|
'inline-font-size',
|
|
1926
1927
|
'hidden-inline-style',
|
|
1927
|
-
])('%s is
|
|
1928
|
+
])('%s is promoted past its grace minor (error)', (kind) => {
|
|
1928
1929
|
const cases: Record<string, [string, string]> = {
|
|
1929
1930
|
'theme-allow-unscoped': ['const x = 1 // theme-allow', PATH],
|
|
1930
1931
|
'surface-shadow-override': ['const s = { boxShadow: `0 0 0 2px ${VX.accent}` }', PATH],
|
|
@@ -1937,7 +1938,7 @@ describe('severity', () => {
|
|
|
1937
1938
|
}
|
|
1938
1939
|
const [src, path] = cases[kind] as [string, string]
|
|
1939
1940
|
const hit = find(src, path).find((v) => v.kind === kind)
|
|
1940
|
-
expect(hit?.severity).toBe('
|
|
1941
|
+
expect(hit?.severity).toBe('error')
|
|
1941
1942
|
})
|
|
1942
1943
|
|
|
1943
1944
|
it('ships every OTHER kind past its grace period', () => {
|
|
@@ -2139,9 +2140,9 @@ describe('theme-allow scoping', () => {
|
|
|
2139
2140
|
expect(f).toHaveLength(0)
|
|
2140
2141
|
})
|
|
2141
2142
|
|
|
2142
|
-
it('ships theme-allow-unscoped as
|
|
2143
|
+
it('ships theme-allow-unscoped as an error (promoted, C16)', () => {
|
|
2143
2144
|
const f = find(`const x = 1 // theme-allow`)
|
|
2144
|
-
expect(f.every((v) => v.severity === '
|
|
2145
|
+
expect(f.every((v) => v.severity === 'error')).toBe(true)
|
|
2145
2146
|
})
|
|
2146
2147
|
|
|
2147
2148
|
// The annotation itself lives in a comment; a `theme-allow` in real code must not waive anything.
|
|
@@ -2370,9 +2371,9 @@ describe('surface-shadow-override', () => {
|
|
|
2370
2371
|
expect(kinds(f)).not.toContain('surface-shadow-override')
|
|
2371
2372
|
})
|
|
2372
2373
|
|
|
2373
|
-
it('
|
|
2374
|
+
it('is an error now that its grace minor has ended (promoted, C16)', () => {
|
|
2374
2375
|
const f = find('const s = { boxShadow: `0 0 0 2px ${VX.accent}` }')
|
|
2375
|
-
expect(f.find((v) => v.kind === 'surface-shadow-override')?.severity).toBe('
|
|
2376
|
+
expect(f.find((v) => v.kind === 'surface-shadow-override')?.severity).toBe('error')
|
|
2376
2377
|
})
|
|
2377
2378
|
})
|
|
2378
2379
|
|
|
@@ -2435,6 +2436,147 @@ describe('inline-font-size', () => {
|
|
|
2435
2436
|
})
|
|
2436
2437
|
})
|
|
2437
2438
|
|
|
2439
|
+
// ── 26. in-body-page-title (the text lane of law C8) ─────────────────────────
|
|
2440
|
+
|
|
2441
|
+
describe('in-body-page-title', () => {
|
|
2442
|
+
it('flags <Title order={1}> in the body', () => {
|
|
2443
|
+
expect(kinds(find(`<Title order={1}>Analytics</Title>`))).toContain('in-body-page-title')
|
|
2444
|
+
})
|
|
2445
|
+
|
|
2446
|
+
it('flags order={2} too', () => {
|
|
2447
|
+
expect(kinds(find(`<Title order={2}>Analytics</Title>`))).toContain('in-body-page-title')
|
|
2448
|
+
})
|
|
2449
|
+
|
|
2450
|
+
// The formatted default — the reason this kind is a bounded TAG scan and not a line regex.
|
|
2451
|
+
it('flags a Title formatted across lines', () => {
|
|
2452
|
+
const f = find(`<Title\n order={1}\n mb="sm"\n>\n Analytics\n</Title>`)
|
|
2453
|
+
expect(kinds(f)).toContain('in-body-page-title')
|
|
2454
|
+
})
|
|
2455
|
+
|
|
2456
|
+
it('does NOT flag order={3} — a section heading', () => {
|
|
2457
|
+
expect(kinds(find(`<Title order={3}>Sessions</Title>`))).not.toContain('in-body-page-title')
|
|
2458
|
+
})
|
|
2459
|
+
|
|
2460
|
+
it('does NOT flag a Title with no order', () => {
|
|
2461
|
+
expect(kinds(find(`<Title>Sessions</Title>`))).not.toContain('in-body-page-title')
|
|
2462
|
+
})
|
|
2463
|
+
|
|
2464
|
+
it('does NOT flag anything under a content/ path segment', () => {
|
|
2465
|
+
const f = find(`<Title order={1}>Doc</Title>`, 'src/content/Guide.tsx')
|
|
2466
|
+
expect(kinds(f)).not.toContain('in-body-page-title')
|
|
2467
|
+
})
|
|
2468
|
+
|
|
2469
|
+
// File-scoped and coarser than the plugin's node-level ancestry, on purpose: a document heading
|
|
2470
|
+
// told to become a breadcrumb is the WRONG advice, so the text lane errs silent.
|
|
2471
|
+
it('does NOT flag a file that renders Prose', () => {
|
|
2472
|
+
const f = find(`<Prose>\n <Title order={1}>Doc</Title>\n</Prose>`)
|
|
2473
|
+
expect(kinds(f)).not.toContain('in-body-page-title')
|
|
2474
|
+
})
|
|
2475
|
+
|
|
2476
|
+
it('honours a theme-allow', () => {
|
|
2477
|
+
const f = find(
|
|
2478
|
+
`// theme-allow in-body-page-title — a shell-less print view\n<Title order={1}>P</Title>`,
|
|
2479
|
+
)
|
|
2480
|
+
expect(kinds(f)).not.toContain('in-body-page-title')
|
|
2481
|
+
})
|
|
2482
|
+
|
|
2483
|
+
// Promoted at 1.27.0 — its GRACE_PERIOD_KINDS entry is gone, and so is the plugin rule of the
|
|
2484
|
+
// same id from PLUGIN_RULE_GRACE. One law, two lanes, one promotion.
|
|
2485
|
+
it('lands error now that the grace entry is gone (C16)', () => {
|
|
2486
|
+
const f = find(`<Title order={1}>Analytics</Title>`)
|
|
2487
|
+
expect(f.find((x) => x.kind === 'in-body-page-title')?.severity).toBe('error')
|
|
2488
|
+
})
|
|
2489
|
+
})
|
|
2490
|
+
|
|
2491
|
+
// ── 27. raw-selection-control (the text lane of law C1) ──────────────────────
|
|
2492
|
+
|
|
2493
|
+
describe('raw-selection-control', () => {
|
|
2494
|
+
it.each(['SegmentedControl', 'Select', 'MultiSelect', 'NativeSelect', 'TagsInput', 'Chip.Group'])(
|
|
2495
|
+
'flags a raw <%s>',
|
|
2496
|
+
(tag) => {
|
|
2497
|
+
expect(kinds(find(`<${tag} data={[]} />`))).toContain('raw-selection-control')
|
|
2498
|
+
},
|
|
2499
|
+
)
|
|
2500
|
+
|
|
2501
|
+
it('reports the tag it found, not the whole line', () => {
|
|
2502
|
+
const f = find(`<Select data={[]} />`)
|
|
2503
|
+
expect(f.find((x) => x.kind === 'raw-selection-control')?.token).toBe('<Select')
|
|
2504
|
+
})
|
|
2505
|
+
|
|
2506
|
+
it.each(['SettingsRow', 'Modal', 'Drawer', 'Menu.Dropdown'])(
|
|
2507
|
+
'does NOT flag one inside a %s window',
|
|
2508
|
+
(host) => {
|
|
2509
|
+
const f = find(`<${host}>\n <Select data={[]} />\n</${host}>`)
|
|
2510
|
+
expect(kinds(f)).not.toContain('raw-selection-control')
|
|
2511
|
+
},
|
|
2512
|
+
)
|
|
2513
|
+
|
|
2514
|
+
// The window is 12 lines; past it the approximation would start swallowing the NEXT row's control.
|
|
2515
|
+
it('DOES flag one 20 lines below the host tag — the window is bounded', () => {
|
|
2516
|
+
const f = find(`<Modal>\n${' <Text>x</Text>\n'.repeat(20)} <Select data={[]} />\n</Modal>`)
|
|
2517
|
+
expect(kinds(f)).toContain('raw-selection-control')
|
|
2518
|
+
})
|
|
2519
|
+
|
|
2520
|
+
it('does NOT flag anything in a file importing @mantine/form', () => {
|
|
2521
|
+
const f = find(`import { useForm } from '@mantine/form'\n<Select data={[]} />`)
|
|
2522
|
+
expect(kinds(f)).not.toContain('raw-selection-control')
|
|
2523
|
+
})
|
|
2524
|
+
|
|
2525
|
+
it('does NOT flag the file that DEFINES a basalt control', () => {
|
|
2526
|
+
const f = find(`export function ViewTabs() {\n return <SegmentedControl data={[]} />\n}`)
|
|
2527
|
+
expect(kinds(f)).not.toContain('raw-selection-control')
|
|
2528
|
+
})
|
|
2529
|
+
|
|
2530
|
+
it('does NOT flag a bound basalt control', () => {
|
|
2531
|
+
expect(kinds(find(`<SelectFilter field={f} label="Channel" />`))).not.toContain(
|
|
2532
|
+
'raw-selection-control',
|
|
2533
|
+
)
|
|
2534
|
+
})
|
|
2535
|
+
|
|
2536
|
+
it('honours a theme-allow', () => {
|
|
2537
|
+
const f = find(
|
|
2538
|
+
`{/* theme-allow raw-selection-control — a one-off admin picker */}\n<Select data={[]} />`,
|
|
2539
|
+
)
|
|
2540
|
+
expect(kinds(f)).not.toContain('raw-selection-control')
|
|
2541
|
+
})
|
|
2542
|
+
|
|
2543
|
+
// The CROSS-FILE exemption — the same basename convention the plugin's `control-outside-home`
|
|
2544
|
+
// applies, so both lanes agree on the same file (one law, two lanes, one exemption). argo carried
|
|
2545
|
+
// 9 of these: a control in a modal/form module whose `<Modal>` is rendered by the parent, which is
|
|
2546
|
+
// outside the 12-line host window because the host tag is not in the file at all.
|
|
2547
|
+
it.each([
|
|
2548
|
+
'src/edit-session-modal.tsx',
|
|
2549
|
+
'src/filters-drawer.tsx',
|
|
2550
|
+
'src/column-popover.tsx',
|
|
2551
|
+
'src/detail-panel.tsx',
|
|
2552
|
+
'src/booking-form.tsx',
|
|
2553
|
+
])('does NOT flag %s — the overlay filename convention', (relPath) => {
|
|
2554
|
+
expect(kinds(find(`<Select data={[]} />`, relPath))).not.toContain('raw-selection-control')
|
|
2555
|
+
})
|
|
2556
|
+
|
|
2557
|
+
it('still flags a page module beside them', () => {
|
|
2558
|
+
expect(kinds(find(`<Select data={[]} />`, 'src/bookings-page.tsx'))).toContain(
|
|
2559
|
+
'raw-selection-control',
|
|
2560
|
+
)
|
|
2561
|
+
})
|
|
2562
|
+
|
|
2563
|
+
// Basename only, and it needs the leading subject: a `modal/` DIRECTORY holds the page pieces
|
|
2564
|
+
// around the modals too, and a bare `modal.tsx` is a page module in every consumer that has one.
|
|
2565
|
+
it.each(['src/modal/session.tsx', 'src/modal.tsx'])(
|
|
2566
|
+
'is a subject-prefixed BASENAME convention, not %s',
|
|
2567
|
+
(relPath) => {
|
|
2568
|
+
expect(kinds(find(`<Select data={[]} />`, relPath))).toContain('raw-selection-control')
|
|
2569
|
+
},
|
|
2570
|
+
)
|
|
2571
|
+
|
|
2572
|
+
// Still warn at 1.27.0 — this kind and its AST twin `basalt/control-outside-home` are the C1 pair
|
|
2573
|
+
// that moved to `promote: '1.28.0'` while the other wave-6 entries promoted.
|
|
2574
|
+
it('lands warn while the grace entry stands (C16)', () => {
|
|
2575
|
+
const f = find(`<Select data={[]} />`)
|
|
2576
|
+
expect(f.find((x) => x.kind === 'raw-selection-control')?.severity).toBe('warn')
|
|
2577
|
+
})
|
|
2578
|
+
})
|
|
2579
|
+
|
|
2438
2580
|
// ── 26. hidden-inline-style ──────────────────────────────────────────────────
|
|
2439
2581
|
|
|
2440
2582
|
describe('hidden-inline-style', () => {
|
|
@@ -2475,12 +2617,12 @@ export const C = () => <div style={labelStyle} />`,
|
|
|
2475
2617
|
expect(kinds(f)).not.toContain('hidden-inline-style')
|
|
2476
2618
|
})
|
|
2477
2619
|
|
|
2478
|
-
it('
|
|
2620
|
+
it('is an error now that its grace minor has ended (promoted, C16)', () => {
|
|
2479
2621
|
const f = find(
|
|
2480
2622
|
`const wrapperStyle = { position: 'relative', width: '100%' }
|
|
2481
2623
|
export const C = () => <div style={wrapperStyle} />`,
|
|
2482
2624
|
)
|
|
2483
|
-
expect(f.find((v) => v.kind === 'hidden-inline-style')?.severity).toBe('
|
|
2625
|
+
expect(f.find((v) => v.kind === 'hidden-inline-style')?.severity).toBe('error')
|
|
2484
2626
|
})
|
|
2485
2627
|
})
|
|
2486
2628
|
|
|
@@ -2638,7 +2780,7 @@ describe('markup files (index.html / *.webmanifest)', () => {
|
|
|
2638
2780
|
* Round 9: `.astro` and `.vue` became scannable and fell through to the `ts` dialect, so `<!-- … -->`
|
|
2639
2781
|
* was never stripped — a `theme-allow` written in an HTML comment waived nothing there, and a color
|
|
2640
2782
|
* inside a commented-out block still reported. They resolve as `sfc` now: BOTH comment dialects,
|
|
2641
|
-
* and the full
|
|
2783
|
+
* and the full 27-kind set (not `MARKUP_KINDS` — an `.astro` template is JSX-shaped and a `.vue`
|
|
2642
2784
|
* `<script setup>` is real TS).
|
|
2643
2785
|
*/
|
|
2644
2786
|
describe('single-file components (.astro / .vue)', () => {
|
|
@@ -2770,11 +2912,15 @@ describe("profile: 'tokens-only'", () => {
|
|
|
2770
2912
|
'css-raw-surface': false,
|
|
2771
2913
|
'inline-font-size': false,
|
|
2772
2914
|
'hidden-inline-style': true,
|
|
2915
|
+
// The two wave-6 control kinds: `<Title>` is a Mantine component, and the remedy for either is
|
|
2916
|
+
// a Mantine-rendered home (PageBar / WidgetHeader) or a basalt control over @mantine/core.
|
|
2917
|
+
'in-body-page-title': true,
|
|
2918
|
+
'raw-selection-control': true,
|
|
2773
2919
|
}
|
|
2774
2920
|
|
|
2775
2921
|
it('classifies every kind in the registry — the table is exhaustive', () => {
|
|
2776
2922
|
expect(Object.keys(MANTINE_COUPLED).toSorted()).toEqual(Object.keys(GUARD_RULES).toSorted())
|
|
2777
|
-
expect(Object.keys(MANTINE_COUPLED)).toHaveLength(
|
|
2923
|
+
expect(Object.keys(MANTINE_COUPLED)).toHaveLength(27)
|
|
2778
2924
|
})
|
|
2779
2925
|
|
|
2780
2926
|
it('the disabled set is exactly the Mantine-coupled half — a complete partition', () => {
|
|
@@ -0,0 +1,158 @@
|
|
|
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 carries ONE wave-6 control kind at 1.27.0 —
|
|
13
|
+
* `raw-selection-control`, re-dated to `promote: '1.28.0'` with its AST twin
|
|
14
|
+
* `basalt/control-outside-home` (`in-body-page-title` promoted on schedule and its entry is gone) —
|
|
15
|
+
* and the real-ledger check below runs against it.
|
|
16
|
+
*
|
|
17
|
+
* This gate measures the version already PUBLISHED, so it can only go red after the release that
|
|
18
|
+
* shipped a due entry. `scripts/check-grace.ts` is the other end — `scripts/release.sh` runs it
|
|
19
|
+
* against the version the dry run computed, before the release is cut.
|
|
20
|
+
*
|
|
21
|
+
* Excluded from tsc (tsconfig exclude: src/**\/*.test.ts), run via `bun test`.
|
|
22
|
+
*/
|
|
23
|
+
import { describe, expect, it } from 'bun:test'
|
|
24
|
+
import { readFileSync } from 'node:fs'
|
|
25
|
+
import { resolve } from 'node:path'
|
|
26
|
+
import { GRACE_PERIOD_KINDS } from './index'
|
|
27
|
+
|
|
28
|
+
const PACKAGE_JSON_PATH = resolve(import.meta.dirname, '..', '..', 'package.json')
|
|
29
|
+
const CURRENT_VERSION = (JSON.parse(readFileSync(PACKAGE_JSON_PATH, 'utf8')) as { version: string })
|
|
30
|
+
.version
|
|
31
|
+
|
|
32
|
+
type GraceEntry = { since: string; promote: string; why: string }
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Tiny local semver compare — no dependency. Assumes plain `x.y.z`, no pre-release identifiers
|
|
36
|
+
* (basalt-ui bans majors and ships no pre-releases). Returns negative when `a` sorts before `b`,
|
|
37
|
+
* positive when after, zero when equal. Compares each component numerically, not lexically, so
|
|
38
|
+
* `'1.9.0' < '1.10.0'` and `'1.3.0' < '1.26.0'` — a string compare would get both backwards.
|
|
39
|
+
*/
|
|
40
|
+
export function compareSemver(a: string, b: string): number {
|
|
41
|
+
const partsA = a.split('.').map(Number)
|
|
42
|
+
const partsB = b.split('.').map(Number)
|
|
43
|
+
for (let i = 0; i < 3; i++) {
|
|
44
|
+
const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0)
|
|
45
|
+
if (diff !== 0) return diff > 0 ? 1 : -1
|
|
46
|
+
}
|
|
47
|
+
return 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The C16 gate itself, extracted so it can be exercised against a synthetic ledger as well as the
|
|
52
|
+
* real one. Throws naming the first offending entry when either invariant breaks: `since` must
|
|
53
|
+
* precede `promote` for every entry, and no entry's `promote` may be `<=` the given `version`.
|
|
54
|
+
*/
|
|
55
|
+
export function assertGraceLedger(ledger: Record<string, GraceEntry>, version: string): void {
|
|
56
|
+
for (const [id, entry] of Object.entries(ledger)) {
|
|
57
|
+
if (compareSemver(entry.since, entry.promote) >= 0) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`${id}: \`since\` (${entry.since}) must be before \`promote\` (${entry.promote}).`,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
if (compareSemver(version, entry.promote) >= 0) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`${id}: package.json is at ${version}, which has reached its promote version ` +
|
|
65
|
+
`${entry.promote} — promote to error or extend \`promote\` with a reason.`,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('compareSemver', () => {
|
|
72
|
+
it('orders multi-digit components numerically, not lexically', () => {
|
|
73
|
+
expect(compareSemver('1.9.0', '1.10.0')).toBeLessThan(0)
|
|
74
|
+
expect(compareSemver('1.10.0', '1.9.0')).toBeGreaterThan(0)
|
|
75
|
+
expect(compareSemver('1.3.0', '1.26.0')).toBeLessThan(0)
|
|
76
|
+
expect(compareSemver('1.26.0', '1.3.0')).toBeGreaterThan(0)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('treats equal versions as equal', () => {
|
|
80
|
+
expect(compareSemver('1.25.0', '1.25.0')).toBe(0)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('assertGraceLedger', () => {
|
|
85
|
+
it('does not throw on a well-formed entry not yet due', () => {
|
|
86
|
+
const ledger = { 'fake-kind': { since: '1.0.0', promote: '2.0.0', why: 'synthetic' } }
|
|
87
|
+
expect(() => assertGraceLedger(ledger, '1.25.0')).not.toThrow()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('throws once the given version reaches promote', () => {
|
|
91
|
+
const ledger = { 'fake-kind': { since: '1.0.0', promote: '1.25.0', why: 'synthetic' } }
|
|
92
|
+
expect(() => assertGraceLedger(ledger, '1.25.0')).toThrow(/fake-kind/)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('throws once the given version is past promote', () => {
|
|
96
|
+
const ledger = { 'fake-kind': { since: '1.0.0', promote: '1.9.0', why: 'synthetic' } }
|
|
97
|
+
expect(() => assertGraceLedger(ledger, '1.10.0')).toThrow(/fake-kind/)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('throws when since does not precede promote', () => {
|
|
101
|
+
const ledger = { 'fake-kind': { since: '1.5.0', promote: '1.5.0', why: 'synthetic' } }
|
|
102
|
+
expect(() => assertGraceLedger(ledger, '1.0.0')).toThrow(/since/)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('GRACE_PERIOD_KINDS — version gate (C16)', () => {
|
|
107
|
+
it('the real ledger passes the gate against the real package version', () => {
|
|
108
|
+
expect(() =>
|
|
109
|
+
assertGraceLedger(GRACE_PERIOD_KINDS as Record<string, GraceEntry>, CURRENT_VERSION),
|
|
110
|
+
).not.toThrow()
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// ── the release-time half: scripts/check-grace.ts ────────────────────────────
|
|
115
|
+
// Exercised as a PROCESS, because that is how `scripts/release.sh` consumes it — the exit code is
|
|
116
|
+
// the whole interface, and a script that printed the right words with status 0 would gate nothing.
|
|
117
|
+
|
|
118
|
+
const CHECK_GRACE = resolve(import.meta.dirname, '..', '..', 'scripts', 'check-grace.ts')
|
|
119
|
+
|
|
120
|
+
function runCheckGrace(...args: string[]): { code: number; stderr: string; stdout: string } {
|
|
121
|
+
const result = Bun.spawnSync(['bun', CHECK_GRACE, ...args])
|
|
122
|
+
return {
|
|
123
|
+
code: result.exitCode,
|
|
124
|
+
stderr: result.stderr.toString(),
|
|
125
|
+
stdout: result.stdout.toString(),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
describe('scripts/check-grace.ts', () => {
|
|
130
|
+
it('passes for a version below every promote', () => {
|
|
131
|
+
const { code, stdout } = runCheckGrace('1.0.0')
|
|
132
|
+
expect([code, stdout.includes('clear of all')]).toEqual([0, true])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('refuses a version that has reached a promote, naming the entries', () => {
|
|
136
|
+
const { code, stderr } = runCheckGrace('9.9.9')
|
|
137
|
+
expect(code).toBe(1)
|
|
138
|
+
expect(stderr).toContain('raw-selection-control')
|
|
139
|
+
expect(stderr).toContain('basalt/control-outside-home')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// 1.27.0 promoted five of the six wave-6 plugin rules and one of the two guard kinds; what is
|
|
143
|
+
// LEFT is the C1 pair (`raw-selection-control` + `basalt/control-outside-home`), re-dated to
|
|
144
|
+
// 1.28.0 against a measurement rather than a hunch — see either entry's `why`. So the release
|
|
145
|
+
// this file used to refuse now passes, and the refusal moved with the pair.
|
|
146
|
+
it('passes 1.27.0 — the five entries due there promoted', () => {
|
|
147
|
+
expect(runCheckGrace('1.27.0').code).toBe(0)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('refuses the version the remaining C1 pair is due in (1.28.0)', () => {
|
|
151
|
+
expect(runCheckGrace('1.28.0').code).toBe(1)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('exits 2 on a missing or malformed version rather than passing vacuously', () => {
|
|
155
|
+
expect(runCheckGrace().code).toBe(2)
|
|
156
|
+
expect(runCheckGrace('not-a-version').code).toBe(2)
|
|
157
|
+
})
|
|
158
|
+
})
|