basalt-ui 1.24.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 +25 -0
- package/MIGRATING.md +547 -21
- package/README.md +46 -33
- 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/query-state.d.ts +1 -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 +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 +102 -20
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +166 -65
- package/dist/data/table.d.ts +1 -1
- package/dist/data/table.d.ts.map +1 -1
- 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/query-state.tsx +1 -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 +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 +282 -6
- package/src/data/data-table.tsx +429 -94
- package/src/data/table.ts +2 -0
- 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 -269
- 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
package/MIGRATING.md
CHANGED
|
@@ -32,13 +32,497 @@ at 1.3.0, `./agent-chat` at 1.10.0.
|
|
|
32
32
|
|
|
33
33
|
## Unreleased
|
|
34
34
|
|
|
35
|
-
**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
**One export removed and two deprecated — see § Stores below; two shell PROPS removed — see
|
|
36
|
+
§ Sidebar. Two other behaviour changes: the grace ledgers changed shape, and nine
|
|
37
|
+
long-stale entries (D4, `docs/archive/CONTROLS-SYNTHESIS.md`) are promoted.** C16
|
|
38
|
+
(`docs/CONTROLS-SPEC.md` §1) is the new law behind both: a grace entry now carries `{ since,
|
|
39
|
+
promote, why }` (semver strings) instead of a bare promotion-note string, and a version-gated test
|
|
40
|
+
fails the build once `package.json`'s version reaches an entry's `promote` while the entry is still
|
|
41
|
+
in the ledger — and `make release` refuses to cut a release whose COMPUTED version has reached one,
|
|
42
|
+
which is the half that fires before the release rather than one minor after it. That is what stops a
|
|
43
|
+
rule sitting at `warn` for five minors with nothing tracking the promise, which is exactly what
|
|
44
|
+
happened to the nine rows below.
|
|
45
|
+
|
|
46
|
+
**`PLUGIN_RULE_GRACE`** (`configs/oxlint-plugin.js`) **and `GRACE_PERIOD_KINDS`**
|
|
47
|
+
(`src/guard/index.ts`) no longer carry any PRE-EXISTING entry — every one either promoted to `error`
|
|
48
|
+
(the table below) or moved to the new, permanent `PLUGIN_RULE_ADVISORY` ledger
|
|
49
|
+
(`shadow-basalt-export`). Both ledgers now hold only the wave-6 control guards listed under
|
|
50
|
+
§ Guards — six plugin rules and two guard kinds, each `{ since: '1.26.0', promote: '1.27.0' }` —
|
|
51
|
+
which the C16 gate forces to promote or be deleted at 1.27.0. A theme-allow escape written against
|
|
52
|
+
any of these still works unchanged; only the SEVERITY of an unwaived finding moves from `warn` to
|
|
53
|
+
`error`.
|
|
54
|
+
|
|
55
|
+
| Rule / kind (promoted) | What now errors | Escape hatch |
|
|
56
|
+
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| `basalt/hand-rolled-plot` | An unwaived chart-assembly node (`AxisLeftNumeric`, `AxisRightNumeric`, `AxisBottomDate`, `HoverOverlay`, `Crosshair`) outside a file composing `CartesianChart` | `theme-allow hand-rolled-plot — <why>` per node, or `theme-allow-file hand-rolled-plot — <why>` for a declared exception |
|
|
58
|
+
| `basalt/chart-legend-literal` | A hand-written array literal passed to `ChartLegend`'s `items` | Derive the legend from `series` (`deriveLegend`, or let `ChartFrame`/`CartesianChart` do it), or `theme-allow chart-legend-literal — <why>` |
|
|
59
|
+
| `basalt/hand-rolled-shell` | A hand-rolled app-shell assembly node instead of `BasaltShell` | `theme-allow hand-rolled-shell — <why>`, or `theme-allow-file` for the whole file |
|
|
60
|
+
| `theme-allow-unscoped` (guard kind) | A bare `// theme-allow` with no rule id | Name the kind(s): `theme-allow <kind> — <why>` |
|
|
61
|
+
| `surface-shadow-override` (guard kind) | A hand-composed `boxShadow` reaching a `--vx-*` token | Use the shipped shadow tokens, or waive with a reason |
|
|
62
|
+
| `css-raw-surface` (guard kind) | A raw surface color in kebab CSS (`.module.css`) | Same remedy as the JS/TSX form — a `--vx-*` var, or waive |
|
|
63
|
+
| `inline-font-size` (guard kind) | A hardcoded numeric font size in an inline `style` object | `VX.text.*` / `--vx-text-*`, or waive |
|
|
64
|
+
| `hidden-inline-style` (guard kind) | A style object defined once and spread into `style={...}` (evades the guard's inline-object scan) | Inline the object at the call site, or waive |
|
|
65
|
+
|
|
66
|
+
**`basalt/shadow-basalt-export` is unaffected** — it moves ledgers (`PLUGIN_RULE_GRACE` →
|
|
67
|
+
`PLUGIN_RULE_ADVISORY`), not severity. It stays `warn` in the shipped preset, permanently, and is
|
|
68
|
+
never subject to the C16 gate: see "Lint and guard rules that tightened" below for why.
|
|
69
|
+
|
|
70
|
+
Consumers who never overrode `basalt.severity` for any of the eight kinds/rules above and never
|
|
71
|
+
waived them see these findings for the first time as `error`; measure against the shipped preset
|
|
72
|
+
before upgrading if that's a concern (`basalt-ui check-theme --audit-allows`, `oxlint .`).
|
|
73
|
+
|
|
74
|
+
### Composers — WidgetHeader-backed renames (docs/CONTROLS-SPEC.md §2.2)
|
|
75
|
+
|
|
76
|
+
`StatCard`, `ChartCard`, `Section` (new), `SettingsSection`/`DangerZone` and `BasaltDataTable` now
|
|
77
|
+
each compose the wave-1 `WidgetHeader` primitive for their title row instead of hand-rolling one —
|
|
78
|
+
several props renamed or were added to line up with it.
|
|
79
|
+
|
|
80
|
+
| Component | Removed / renamed | Replacement |
|
|
81
|
+
| -------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `StatCard` | `label` | `title` |
|
|
83
|
+
| `StatCard` | `menu` | `actions` (wrapped in `CtlSlot`) |
|
|
84
|
+
| `StatCard` | — | adds `icon`, `info`, `subtitle` (forwarded to the composed `WidgetHeader` — the info glyph beside the title, the muted unit line under the hero row), `sparklinePlacement?: 'bleed' \| 'right'` (default `'bleed'`, collapses to it below `sm`) |
|
|
85
|
+
| `ChartCard` | `tooltip` | `info` |
|
|
86
|
+
| `ChartCard` | `extra` | `actions` (carries `data-basalt-tier="widget"`, no `CtlSlot` — `./charts` stays Mantine-free) |
|
|
87
|
+
| `ChartCard` | `title: string` (required) | `title?: string` — the header now renders only when title/info/value/actions/icon/count is set |
|
|
88
|
+
| `ChartCard` | — | adds `icon`, `value`, `delta`, `deltaPeriod`, `count` |
|
|
89
|
+
| `SettingsSection` / `DangerZone` | `description` | `subtitle` |
|
|
90
|
+
| `SettingsSection` / `DangerZone` | — | adds `actions` (wrapped in `CtlSlot`) |
|
|
91
|
+
| `BasaltDataTable` | `toolbarActions` | `actions?: ReactNode` — the toolbar (search + facets + `actions`) is now wrapped in `CtlSlot`. Typed `ReactNode`, not `BarAction[]` — a table toolbar is not a `PageBar`/`Section`, and `ActionGroup`'s shell-extras semantics (primary/secondary, mobile folding) must not leak into it |
|
|
92
|
+
| `BasaltDataTable` | fixed `w={220/200/180/110}` on toolbar/pagination controls | removed — the controls size by content |
|
|
93
|
+
| `BasaltDataTable` | — | adds `title`, `icon`, `subtitle` — when `title` is set, renders `WidgetHeader tier="widget"` above the toolbar, `count` always `table.getRowCount()` (C11) |
|
|
94
|
+
| `BasaltDataTable` | `facets` rendered as raw `Select`/`MultiSelect` | rendered as `EnumFilter`/`MultiSelectFilter` pills inside a `FilterSet` (`docs/CONTROLS-SPEC.md` §3) — a synthetic `"All"` option stands in for a single-select facet's cleared state, since a closed enum field has no member for "unset" |
|
|
95
|
+
|
|
96
|
+
New export: **`Section`** (`.`, Mantine-coupled) — `WidgetHeaderProps` minus `tier`, plus `tabs?`,
|
|
97
|
+
`collapsible?`, `persistKey?`, `defaultOpen?`, `summary?`, `id?`, `children`. No `variant`, no
|
|
98
|
+
border/background — one shaded container level per page belongs to the cards inside a `Section`, not
|
|
99
|
+
to `Section` itself. Fold state persists at `basalt:section:<persistKey>` via `createPersistedState`
|
|
100
|
+
when `persistKey` is given, else local `useState`; the header stays drawn when collapsed, only the
|
|
101
|
+
body unmounts. `defaultOpen` (default `true`) picks the state a FIRST visit lands on and is outranked
|
|
102
|
+
by a persisted value, so a section the reader closed stays closed. `summary` renders under the header
|
|
103
|
+
and survives a collapse — a folded section still states its headline figures, which is what makes
|
|
104
|
+
folding it a real option rather than a way to lose the numbers.
|
|
105
|
+
|
|
106
|
+
### Homes — `PageBar` replaces the page-action portal
|
|
107
|
+
|
|
108
|
+
**Three exports removed with no deprecation window, one prop retyped, three tokens and one CSS
|
|
109
|
+
class deleted.** `PageActions` gave a page a `ReactNode` slot in the header and nothing else: no
|
|
110
|
+
second row, no overflow policy, no mobile projection. Every consumer then hand-rolled the rest —
|
|
111
|
+
a sticky filter row measured into a `--lw-header-h`, a horizontally scrolling action strip, a
|
|
112
|
+
responsive twin under `visibleFrom`/`hiddenFrom`. `PageBar` is that whole shape, typed
|
|
113
|
+
(`docs/CONTROLS-SPEC.md` §2.1).
|
|
114
|
+
|
|
115
|
+
| Removed / changed | Replacement |
|
|
116
|
+
| -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| `PageActions` | `<PageBar actions={{ primary, secondary }} sync={…} tabs={…} filters={…} filtersEnd={…} />` |
|
|
118
|
+
| `PageActionsOutlet` | none — the outlet is internal to `BasaltShell` now |
|
|
119
|
+
| `PageHeaderProvider` | none — the provider is internal to `BasaltShell` now |
|
|
120
|
+
| `BasaltShellProps.globalActions: ReactNode` | `GlobalAction[]` — `{ key, node, mobile?: 'bar' \| 'more' \| 'hidden' }`; the first two default to `'bar'` |
|
|
121
|
+
| tokens `appShellHeaderMobileHeight`, `appHeaderMobileActionsHeight`, `stickyHeaderClearanceMobile` | `appShellHeaderHeight` / `stickyHeaderClearance` — one value at every viewport |
|
|
122
|
+
| vars `--vx-space-app-header-mobile-actions-height`, `--vx-space-sticky-header-clearance-mobile` | `--vx-space-sticky-header-clearance` |
|
|
123
|
+
| CSS `.pageActions` (`shell/app-header.module.css`) and its `nowrap` override | none — the portal target no longer scrolls; overflow folds into `ActionGroup`'s `More`/kebab |
|
|
124
|
+
|
|
125
|
+
What changes at runtime even if you only rename:
|
|
126
|
+
|
|
127
|
+
- **The app-shell header is 48px on a phone too.** It used to wrap to two rows (97px) and reserve
|
|
128
|
+
the second one on every route, empty or not (law C14). A page-level filter/tab row now renders
|
|
129
|
+
in the page FLOW, sticky under the header, so a route with no filters is 49px shorter on mobile.
|
|
130
|
+
- **The header never scrolls sideways.** Actions past three fold into a `More` menu on desktop and
|
|
131
|
+
into one kebab below `sm` — computed from `BarAction[]`, which is why the slot had to become data
|
|
132
|
+
(law C7). There is exactly ONE kebab per header, and `PageBar`'s ROW 1 owns it: the shell's
|
|
133
|
+
`mobile: 'more'` global actions land there, and so do `filtersEnd`'s items (row 2 shows those on
|
|
134
|
+
desktop only — below `sm` row 2 is tabs + the first pill + `Filters (n)`, per spec §2.1). A route
|
|
135
|
+
with no `PageBar` row-1 actions gets the kebab from the shell instead. An `ActionGroup` you mount
|
|
136
|
+
yourself — in a `Section` or `ChartCard` `actions` slot — never inherits the global rows.
|
|
137
|
+
- **`--basalt-page-bar-h` is published on `documentElement`** (ResizeObserver where available,
|
|
138
|
+
`height > 0` guard)
|
|
139
|
+
whenever a row 2 renders, and removed when it unmounts. It is written in the LAYOUT phase, so it
|
|
140
|
+
exists at the first paint — a cold load of `/page#anchor` lands below the bar rather than under
|
|
141
|
+
it. A `100dvh` body becomes
|
|
142
|
+
`calc(100dvh - var(--app-shell-header-height) - var(--basalt-page-bar-h, 0px))` — delete any
|
|
143
|
+
hand-rolled measure-and-publish effect and any hardcoded header-height fallback.
|
|
144
|
+
- **A shell-less app reads `title`/`icon`.** Inside a `BasaltShell` the breadcrumb names the page
|
|
145
|
+
and both are ignored; outside one they lead row 1, and the whole bar sticks at `top: 0`.
|
|
146
|
+
- **`className` lands on the bar ROOT in both forms** — the shell-less
|
|
147
|
+
`[data-basalt-page-bar="standalone"]`, or row 2's sticky wrapper `[data-basalt-page-bar="shell"]`.
|
|
148
|
+
It is the seam for the two things only the consumer's own layout knows: bleeding the sticky bar
|
|
149
|
+
across a container's gutters, and a hairline under it. Scope that CSS through the class; a global
|
|
150
|
+
`[data-basalt-page-bar]` rule reaches every page in the app, including the ones wanting neither.
|
|
151
|
+
|
|
152
|
+
`ActionGroup`, `OverflowMenu` and `SyncButton` ship on the new `basalt-ui/controls` subpath; only
|
|
153
|
+
the TYPES a `PageBar`/`BasaltShell` prop mentions (`BarAction`, `ActionGroupProps`, `GlobalAction`)
|
|
154
|
+
are re-exported from the root.
|
|
155
|
+
|
|
156
|
+
### Stores — `createSearchStore` replaces the enum-only pair
|
|
157
|
+
|
|
158
|
+
`createSearchParamStore` and `createMultiSearchParamStore` still work: both are now `@deprecated`
|
|
159
|
+
thin wrappers over `createSearchStore`, returning the same four members
|
|
160
|
+
(`validateSearch`, `useStore`, `readStored`, `linkSearch`) with the same signatures and reading the
|
|
161
|
+
same single-value localStorage envelope. **They are removed in 1.29.0.**
|
|
162
|
+
|
|
163
|
+
| Removed / deprecated | Replacement |
|
|
164
|
+
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
165
|
+
| `createSearchParamStore({ key, param, values, fallback })` | `createSearchStore({ key, fields: { <param>: field.enum(values, fallback) } })` — `@deprecated`, removed 1.29.0 |
|
|
166
|
+
| `createMultiSearchParamStore({ key, param, values })` | `createSearchStore({ key, fields: { <param>: field.multi(values, fallback) } })` — `@deprecated`, removed 1.29.0 |
|
|
167
|
+
| `<store>.useStore()` | `<store>.field.<name>.use()` — reads the URL FIRST, then the mirror; writes both lanes |
|
|
168
|
+
| `useOnlineStatus` (`.` and `./state`) | `useConnectivity()` (`.`, auto-mounted by `BasaltProvider`) — **removed, not deprecated** (A12) |
|
|
169
|
+
| `createSearchSchemaStore` | never shipped; `createSearchStore` is what those "planned" paragraphs were waiting for |
|
|
170
|
+
|
|
171
|
+
New on `./router-tanstack`: `createSearchStore`, `field` (`enum` / `multi` / `range` / `number` /
|
|
172
|
+
`boolean` / `string`), and the `FieldHandle` / `AnyField` / `FieldValue` / `RangeValue` / lane types.
|
|
173
|
+
New on `./state` (router-free, same field vocabulary): `createLocalStore`, `field`, those same types.
|
|
174
|
+
`FieldHandle` is what every 1.26.0 control takes instead of `value`/`onChange`
|
|
175
|
+
(`docs/CONTROLS-SPEC.md` §3–§4).
|
|
176
|
+
|
|
177
|
+
Three things to know when you migrate a real store:
|
|
178
|
+
|
|
179
|
+
- **A deep link now wins over the mirror.** `?range=7d` reads back `7d` even when localStorage says
|
|
180
|
+
`30d`. The old `useStore` read localStorage, so a shared link opened on the wrong window — the bug
|
|
181
|
+
three consumers shipped (A8). If you were compensating for that by hand, delete the compensation.
|
|
182
|
+
- **The storage layout changed for the NEW factory only.** `createSearchStore` keeps one entry per
|
|
183
|
+
STORE (`basalt:<key>` = `{ fieldName: value }`); the old factories kept one bare value per param.
|
|
184
|
+
A store migrated from a wrapper to `createSearchStore` therefore ignores whatever the browser
|
|
185
|
+
already had for that key and starts on its fallback once. The wrappers themselves keep the old
|
|
186
|
+
layout byte-for-byte, so an un-migrated consumer sees no reset — including a stored EMPTY multi
|
|
187
|
+
selection, which still reads back as empty: "an empty array means absent" is a URL rule in the
|
|
188
|
+
deprecated store, never a storage rule, so a deliberately cleared filter stays cleared.
|
|
189
|
+
- **A write from outside the owning route persists only** — it no longer needs a hand-rolled
|
|
190
|
+
`navigate` beside it, and it no longer silently writes a param the route does not validate.
|
|
191
|
+
`validateSearch` picks the persisted value up on the next visit (A1). The one combination with
|
|
192
|
+
nowhere to go — a `persist: false` field written from a route that does not validate it — warns
|
|
193
|
+
once per field in dev rather than dropping the write silently.
|
|
194
|
+
|
|
195
|
+
**Every navigate a store issues carries `resetScroll: false`** — a field write and `useReset`
|
|
196
|
+
alike. A filter lives halfway down a page as often as it lives in the bar, and the router treats a
|
|
197
|
+
same-route search write as a navigation, so the default scrolled the reader back to the top on every
|
|
198
|
+
change. Delete any `resetScroll: false` you were passing beside a hand-rolled `navigate`.
|
|
199
|
+
|
|
200
|
+
Lanes are declared once per field: `{ url: false }` is the local-only lane (per-chart selects, a
|
|
201
|
+
compact toggle — or use `createLocalStore`), `{ persist: false }` the URL-only lane (pagination, a
|
|
202
|
+
deliberately unpersisted threshold), `{ history: 'push' }` opts one field into a history entry.
|
|
203
|
+
**`{ url: false, persist: false }` is the IN-MEMORY lane** rather than the one combination that
|
|
204
|
+
dropped its write: shared across every mount of that store for the session, gone on reload, never
|
|
205
|
+
written to or read from localStorage. It behaves identically in both factories — `createSearchStore`
|
|
206
|
+
counts such a field in `useActiveCount()` and clears it in `useReset()`, and it never appears in
|
|
207
|
+
`useValues()`/`validateSearch`, which are the URL lane by definition. In a `createLocalStore` the
|
|
208
|
+
`url` flag is ignored, so `{ persist: false }` alone lands there. The dev warning about a write with
|
|
209
|
+
nowhere to go now fires only for its remaining real case: a `{ persist: false }` field that IS on the
|
|
210
|
+
URL lane, written from a route that does not validate the param. `createLocalStore` also carries **`labels()`**,
|
|
211
|
+
chainable and identical to `createSearchStore`'s, so `SelectFilter`/`ViewTabs` read option labels off
|
|
212
|
+
a local store too.
|
|
213
|
+
A `field.range` keeps THREE URL params (preset + `from` + `to`, renamable via `params`), so existing
|
|
214
|
+
deep links and loaders keep their shape, and `field.<name>.toWindow(v)` replaces a hand-rolled
|
|
215
|
+
`presetToParams`.
|
|
216
|
+
|
|
217
|
+
### Sidebar — `sidebarBlocks` replaces the two `ReactNode` extras
|
|
218
|
+
|
|
219
|
+
**Two props removed with no deprecation window, and two widened.** `sidebarNavExtra` and
|
|
220
|
+
`mobileNav.moreExtra` were the same gap solved twice and badly: the first rendered on desktop only
|
|
221
|
+
and vanished in the collapsed rail, the second was one anonymous row at the bottom of the More
|
|
222
|
+
surface, and neither could be projected because basalt could not see inside a `ReactNode`. Every
|
|
223
|
+
consumer that wanted an "Awaiting action" list therefore wrote it twice, once per prop, and got no
|
|
224
|
+
rail badge either way. `SidebarBlock[]` is that whole shape as data (law C13,
|
|
225
|
+
`docs/CONTROLS-SPEC.md` §2.3).
|
|
226
|
+
|
|
227
|
+
| Removed / changed | Replacement |
|
|
228
|
+
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
229
|
+
| `BasaltShellProps.sidebarNavExtra` | `sidebarBlocks={[{ kind: 'custom', key, node }]}` — same DOM position (last child of the nav scroll region), same CSS-only hiding in the rail, still desktop-only |
|
|
230
|
+
| `AppSidebarProps.navExtra` | `blocks={[{ kind: 'custom', key, node }]}` |
|
|
231
|
+
| `MobileNavConfig.moreExtra` | `sidebarBlocks={[{ kind: 'list', key, label, items }]}` — one More-sheet row (`Awaiting action · 3`) opening a nested sheet of its items, instead of one unlabelled row of raw content |
|
|
232
|
+
| `useNav(NAV, { moreExtra })` | drop the option; declare the block on `BasaltShell` |
|
|
233
|
+
| `BasaltShellProps.brand` | widened to `BrandConfig & { menu?: AccountMenuItem[] }` — additive; `menu` makes the brand row a `Name ▾` workspace switcher |
|
|
234
|
+
| `BasaltShellProps.search` | widened to `SidebarSearchConfig & { actions?: SidebarSearchActions }` — additive; one or two icon-only buttons right of the ⌘K row |
|
|
235
|
+
|
|
236
|
+
What changes at runtime even if you declare no blocks at all:
|
|
237
|
+
|
|
238
|
+
- **Nav-section folds are now PERSISTED**, at `basalt:sidebar-section:<label-slug>` (e.g.
|
|
239
|
+
`basalt:sidebar-section:tools`). They were a `useState` keyed by label, so every reload re-opened
|
|
240
|
+
a section the user had closed. `defaultCollapsed` is now the SEED for that key rather than a value
|
|
241
|
+
that overrides the user on every mount. Block folds live at `basalt:sidebar-block:<key>`; both are
|
|
242
|
+
the standard `createPersistedState` envelope (`{ v: 1, value: boolean }`), readable with
|
|
243
|
+
`readPersistedValue` from `basalt-ui/state`. A "Show more" toggle is deliberately NOT persisted.
|
|
244
|
+
- **`settingsMenuItems` renders FLAT at three entries or fewer** — one link row each, instead of a
|
|
245
|
+
gear "Settings" menu you had to open to see two rows. Four or more keeps the menu. `brand.version`
|
|
246
|
+
rides the flat rows as a faint label and the dropdown as a `Menu.Label`, as before. New
|
|
247
|
+
`BasaltShellProps.settingsMenu?: 'auto' | 'flat' | 'menu'` (`AppSidebarProps` too, default
|
|
248
|
+
`'auto'` = that count rule) forces the form: pass `'menu'` when the three rows are CONTROLS rather
|
|
249
|
+
than destinations — a theme radio group and a devtools switch read as a widget pile flat, and the
|
|
250
|
+
count cannot tell them from three links.
|
|
251
|
+
- **The footer is one wrapper deeper.** `settingsMenuItems` without `account` used to render its
|
|
252
|
+
`Group` as the footer element itself; both now sit inside one footer `Stack` alongside any
|
|
253
|
+
`placement: 'bottom'` block. No visual change — relevant only to a stylesheet reaching in by
|
|
254
|
+
structure.
|
|
255
|
+
- **`useNav` now marks exactly one destination `active`** — the deepest matching route wins, instead
|
|
256
|
+
of every prefix-matching row (a parent AND its child) reading active at once. A parent on the
|
|
257
|
+
winner's path gets the new `SidebarItem.ancestor` instead: never active, never `aria-current`, only
|
|
258
|
+
a `data-ancestor` hook for CSS. A consumer that styled off two simultaneously-active rows will now
|
|
259
|
+
see one.
|
|
260
|
+
|
|
261
|
+
New types on `.`: `SidebarBlock`, `SidebarListBlock`, `SidebarProgressBlock`, `SidebarCustomBlock`,
|
|
262
|
+
`SidebarBlockItem`, `SidebarBlockTone`, `SidebarSearchActions`. `SidebarItem` gained `ancestor?:
|
|
263
|
+
boolean`.
|
|
264
|
+
|
|
265
|
+
### `DeltaBadge` — plain-element DOM, same props
|
|
266
|
+
|
|
267
|
+
Nothing removed or renamed — a DOM contract change only, ahead of `WidgetHeader`
|
|
268
|
+
(`docs/CONTROLS-SPEC.md` §2.2) composing it Mantine-free.
|
|
269
|
+
|
|
270
|
+
| Component | What changed | Why it matters |
|
|
271
|
+
| ------------ | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
272
|
+
| `DeltaBadge` | Renders a plain `<span>`, no `mantine-Badge-root`/`mantine-Badge-label` classes anymore | Same props, tone and format API — but a selector or snapshot keyed on Mantine's Badge classes breaks |
|
|
273
|
+
|
|
274
|
+
### Controls — `ArticleFilterBar` replaced by store-bound controls
|
|
275
|
+
|
|
276
|
+
Two new subpaths, `basalt-ui/controls` and `basalt-ui/controls-dates`, and ONE removal. The removed
|
|
277
|
+
component was controlled (`value`/`onChange`, its own `visibleFrom`/`hiddenFrom` twin); its
|
|
278
|
+
replacements take a `FieldHandle` and own the URL write, the localStorage mirror and the responsive
|
|
279
|
+
swap themselves (`docs/CONTROLS-SPEC.md` §3, laws C2/C9).
|
|
280
|
+
|
|
281
|
+
| Removed | Replacement | Import |
|
|
282
|
+
| ----------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
283
|
+
| `ArticleFilterBar` | `FilterSet` + `ViewTabs` (category axis) + `MultiSelectFilter` (tags) | `basalt-ui/controls`, and unchanged from `basalt-ui/content` |
|
|
284
|
+
| `ArticleFilterBarProps` | `FilterSetProps` / `ViewTabsProps` / `MultiSelectFilterProps` | same |
|
|
285
|
+
|
|
286
|
+
All three replacements are re-exported from `basalt-ui/content` as well, so a content-only consumer
|
|
287
|
+
changes the component names and the props, not the import path.
|
|
288
|
+
|
|
289
|
+
Porting one call site:
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
// before — the page owned the state, the labels and the mobile swap
|
|
293
|
+
const [category, setCategory] = useState('all')
|
|
294
|
+
<ArticleFilterBar categories={CATEGORIES} category={category} onCategoryChange={setCategory}
|
|
295
|
+
tags={ARTICLE_TAGS} selectedTags={tags} onTagsChange={setTags} />
|
|
296
|
+
|
|
297
|
+
// after — one store, two controls, no useState and no responsive branch
|
|
298
|
+
const articles = createSearchStore({
|
|
299
|
+
key: 'articles',
|
|
300
|
+
fields: { category: field.enum(CATEGORY_VALUES, 'all'), tags: field.multi(ARTICLE_TAGS, []) },
|
|
301
|
+
}).labels({ category: CATEGORY_LABELS })
|
|
302
|
+
|
|
303
|
+
<ViewTabs field={articles.field.category} />
|
|
304
|
+
<FilterSet>
|
|
305
|
+
<MultiSelectFilter field={articles.field.tags} label="All tags" />
|
|
306
|
+
</FilterSet>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
New on `basalt-ui/controls`: `FilterSet`, `RangeFilter`, `CompareFilter`, `SelectFilter`,
|
|
310
|
+
`MultiSelectFilter`, `SearchFilter`, `ToggleFilter`, `ViewTabs`, `COMPARE_VALUES`, and the
|
|
311
|
+
action/sync family. New on `basalt-ui/controls-dates`: `DateRangePicker`.
|
|
312
|
+
|
|
313
|
+
**`SyncButton`'s `scope` now decides the SHAPE, not only where you mount it.** `scope: 'global'`
|
|
314
|
+
renders icon-only at every viewport (an `ActionIcon` with the spinning glyph, age and error in the
|
|
315
|
+
tooltip, `label` as the accessible name) — the shell header shares 48px with the breadcrumb and
|
|
316
|
+
`PageBar` row 1, and a labelled button there is what pushes a page's own actions into the kebab.
|
|
317
|
+
`scope: 'page'` is unchanged on desktop (labelled, age inline) and drops to icon-only below `sm`,
|
|
318
|
+
which is what `docs/CONTROLS-SPEC.md` §3 always specified. One mount either way, CSS-only swap (law
|
|
319
|
+
C9). Consequence to know: the accessible name is now `aria-label` in BOTH forms, so a test asserting
|
|
320
|
+
the name as `Sync 2m ago` (the age used to land in it) now reads `Sync`. Nothing to change at a call
|
|
321
|
+
site.
|
|
322
|
+
|
|
323
|
+
**`SelectFilter` and `MultiSelectFilter` take `options?: readonly FilterOption[]`** — a runtime
|
|
324
|
+
catalogue (`{ value, label, disabled? }`) that OVERRIDES `field.options` whole rather than merging,
|
|
325
|
+
so a value the catalogue has dropped loses its row. It is what a label carrying live data needs
|
|
326
|
+
(`EUR · 1.08`), and on `SelectFilter` it also opens a second field shape: a `FieldHandle<StringField>`
|
|
327
|
+
is legal WITH `options` — an id set no enum can close over, a project picker fed from a query — and
|
|
328
|
+
a type error without it. `FilterOption` is new on `basalt-ui/controls`. Additive; nothing to change.
|
|
329
|
+
|
|
330
|
+
**`FilterSet` renders the mobile `Filters (n)` pill only when a child is actually folded.** With
|
|
331
|
+
every child inside the `inline` budget (one filter at the default `inline: 1`) the sheet would hold a
|
|
332
|
+
copy of what is already on screen, so neither the trigger nor the drawer mounts. Nothing to change —
|
|
333
|
+
but a mobile test asserting the pill exists on a one-filter page now has to fold something
|
|
334
|
+
(`inline={0}`) or drop the assertion. `RangeFilter.label` (default `'Range'`) names the sheet heading
|
|
335
|
+
and the preset track's aria name; the pill itself still reads the VALUE.
|
|
336
|
+
|
|
337
|
+
**`data-numeric` is now a package-wide law, not a per-component rule.** `basalt-ui/styles.css`
|
|
338
|
+
declares `[data-numeric] { font-family: var(--basalt-font-mono); font-variant-numeric: tabular-nums }`
|
|
339
|
+
inside `@layer basalt`, so the attribute works on ANY element — `<Text data-numeric>`, a table cell,
|
|
340
|
+
a count tag — and not only on a `SegmentedControl`, where it was previously a module-scoped rule that
|
|
341
|
+
made the attribute look broken everywhere else. Nothing to change: adding the attribute is opt-in,
|
|
342
|
+
and the two module rules that also set a component `font-size` still apply on top. This retires the
|
|
343
|
+
per-consumer `theme-allow` + inline `fontFamily`/`fontSize` hack for numeric labels.
|
|
344
|
+
|
|
345
|
+
**`@mantine/dates` is a new OPTIONAL peer, and only `basalt-ui/controls-dates` needs it.**
|
|
346
|
+
`basalt-ui/controls` resolves and renders without it — proven from the packed tarball by
|
|
347
|
+
`scripts/pack-test.sh`. Pass the picker into `RangeFilter` rather than importing it from a shared
|
|
348
|
+
module:
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
import { DateRangePicker } from 'basalt-ui/controls-dates'
|
|
352
|
+
;<RangeFilter field={analytics.field.range} customPicker={DateRangePicker} />
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
A consumer that uses `DateRangePicker` installs `@mantine/dates` and imports
|
|
356
|
+
`@mantine/dates/styles.layer.css` with the other Mantine layer bundles, before `basalt-ui/styles.css`.
|
|
357
|
+
|
|
358
|
+
### Guards — the control rules (docs/CONTROLS-SPEC.md §6)
|
|
359
|
+
|
|
360
|
+
**Nothing removed. Eight new oxlint rules and two new guard kinds; two of the ten land `error`, the
|
|
361
|
+
rest `warn` under a dated C16 grace entry.** Every one honours `theme-allow <id> — <why>` on the
|
|
362
|
+
node and `theme-allow-file <id> — <why>` on the file, the same grammar as the rest.
|
|
363
|
+
|
|
364
|
+
| Rule / kind | Fires on | Ships |
|
|
365
|
+
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
|
366
|
+
| `basalt/hand-rolled-filter` | A raw Mantine `Select`/`SegmentedControl`/… handed to a TIERED home slot (`actions`/`filters`/`tabs`/`sync`/`filtersEnd` on `PageBar`/`Section`/`WidgetHeader`/`ChartCard`/`StatCard`/`BasaltDataTable`/`SettingsSection`/`FilterSet`) | error |
|
|
367
|
+
| `basalt/page-bar-budget` | A second `PageBar` in the SAME returned tree, >4 `actions.secondary`, >3 `Section` actions, a second filled `Button`/`ActionIcon` in one slot (outside an overlay) | error |
|
|
368
|
+
| `basalt/control-outside-home` | The same raw control with no home at all — exempt under a settings row / overlay / composer, in an `@mantine/form` file, or in a file that DEFINES a basalt control | warn → 1.27.0 |
|
|
369
|
+
| `basalt/control-size-literal` | `size`/`w`/`fullWidth`/`visibleFrom`/`hiddenFrom` on anything inside a home slot (the slot sets the tier) | warn → 1.27.0 |
|
|
370
|
+
| `basalt/in-body-page-title` | `<Title order={1\|2}>` outside prose/overlay context and outside a `content/` path | warn → 1.27.0 |
|
|
371
|
+
| `basalt/responsive-twin` | The same control mounted twice, one `visibleFrom="X"` and one `hiddenFrom="X"` | warn → 1.27.0 |
|
|
372
|
+
| `basalt/search-literal-link` | A `search:` object literal in a `linkOptions()` inside `defineNav()`/`navGroup()` | warn → 1.27.0 |
|
|
373
|
+
| `basalt/use-search-from-literal` | `useSearch({ from: '<route>' })` | warn → 1.27.0 |
|
|
374
|
+
| `in-body-page-title` (guard kind) | The text lane of the same law — SAME id, so one annotation waives both lanes | warn → 1.27.0 |
|
|
375
|
+
| `raw-selection-control` (guard kind) | The text lane of `control-outside-home`, approximated by a 12-line host-tag window | warn → 1.27.0 |
|
|
376
|
+
|
|
377
|
+
**A `SettingsRow`'s `control` is not a tiered slot, so nothing in this table fires inside one.**
|
|
378
|
+
It is law C1's third home — the form row — and a form keeps Mantine's `md` tier
|
|
379
|
+
(`controlHeight` 42, unchanged). A raw `Select`/`Switch`/`Button` bound to a setting is correct
|
|
380
|
+
there, and its `size` prop is what holds the row at the form tier, so neither `hand-rolled-filter`
|
|
381
|
+
nor `control-size-literal` reaches into it; `control-outside-home` treats a settings row as a home
|
|
382
|
+
and stays silent as well. Consumer settings pages need no change.
|
|
383
|
+
|
|
384
|
+
**Three scoping facts, because each one is a rule NOT firing on code you already have.** The home
|
|
385
|
+
tag must resolve to a component imported from `basalt-ui` (or a basalt subpath) — your own
|
|
386
|
+
`Section`, `PageBar` or `StatCard` is not a tiered home, and nothing in this table reads it as one;
|
|
387
|
+
the one accepted gap is basalt's `Section` re-exported through your own barrel, which the rules
|
|
388
|
+
cannot see. An OVERLAY colocated in a slot (`Modal`, `Drawer`, `Popover.Dropdown`, `Menu.Dropdown`,
|
|
389
|
+
`SettingsRow`) is exempt — a `New` button beside the `<Modal>` it opens is how both are written, and
|
|
390
|
+
the overlay's own controls are not in the bar. And `control-size-literal` only reaches what the
|
|
391
|
+
slot's theme actually re-tiers: the raw filters, the basalt controls, and Mantine's
|
|
392
|
+
`Button`/`ActionIcon`/`Input`/`TextInput`/`Select`/`MultiSelect`/`SegmentedControl`/`NativeSelect` —
|
|
393
|
+
an icon's `size`, a count `Badge`, a `Loader`, an `Avatar` or a `Modal size="lg"` never report.
|
|
394
|
+
|
|
395
|
+
**Two existing rules widened, neither with a new id.** `basalt/raw-scroll-container` now also fires
|
|
396
|
+
on `overflowX: 'auto' \| 'scroll'` and on `<ScrollArea scrollbars="x">` **inside a home SLOT**
|
|
397
|
+
(`actions`/`filters`/`tabs`/`sync`/`filtersEnd`) — law C7, overflow folds into a `More` menu or a
|
|
398
|
+
`Filters (n)` sheet rather than scrolling sideways. A home's BODY is out of scope: a wide table, a
|
|
399
|
+
pinned-column grid or a horizontally scrolling code block in a `Section`/`ChartCard` is page
|
|
400
|
+
content, not a sideways-scrolling row of controls, and outside a slot `overflowX` is still not
|
|
401
|
+
policed at all.
|
|
402
|
+
oxlint severities are per rule ID, not per branch, so this widening inherits the rule's existing
|
|
403
|
+
`error` level with no grace runway of its own — the honest limitation, and the reason it is scoped to
|
|
404
|
+
homes rather than shipped repo-wide. `basalt/shadow-basalt-export` gains a rename table
|
|
405
|
+
(`PageHeader`/`FilterBar` → `PageBar`, `WindowSelector`/`RangeSelector`/`DateFilter` →
|
|
406
|
+
`RangeFilter`, `PageSection`/`SectionTitle`/`SectionHeading` → `Section`, `ViewSwitch`/`ViewToggle`
|
|
407
|
+
→ `ViewTabs`, `RefreshButton`/`SyncControl`/`SyncStatusButton` → `SyncButton`, `HeroCard`/`HeroStats`
|
|
408
|
+
→ `StatCard`), so a fork that RENAMED the export is visible too. It stays a permanent advisory
|
|
409
|
+
`warn`.
|
|
410
|
+
|
|
411
|
+
**`profile: 'tokens-only'` now disables 19 kinds, not 17** — both new kinds are Mantine-coupled.
|
|
412
|
+
|
|
413
|
+
**`SURFACES` gains `pluginRules`** (`src/surfaces.ts`, internal — not a published subpath): every
|
|
414
|
+
doctrine surface names the oxlint rules that enforce it, every registered rule maps to exactly one
|
|
415
|
+
surface, every guard KIND maps to at least one, and `basalt-ui check-coverage` asserts all three —
|
|
416
|
+
so a generated coverage header can neither over- nor under-report its lane. `check-coverage` also
|
|
417
|
+
takes `--write` / `--check` for the generated `<!-- basalt:coverage -->` header of each
|
|
418
|
+
`agent/rules/*.md` file; a rule file with no block yet is reported, not failed.
|
|
419
|
+
|
|
420
|
+
### Controls — the pill reads the VALUE, and four defaults moved
|
|
421
|
+
|
|
422
|
+
**A filter pill's TEXT is now always the selected option's label, at the field's default value too.**
|
|
423
|
+
It used to fall back to the filter's NAME while `field.isDefault(value)` held, which is how a bar
|
|
424
|
+
could read `Compare` over a field holding `'previous'` while the popover showed `Previous period`
|
|
425
|
+
selected. `label` is the popover/sheet heading and the accessible name; it is never printed on the
|
|
426
|
+
pill. Affects `SelectFilter`, `CompareFilter` and every control built on the shared enum body;
|
|
427
|
+
`RangeFilter` already read its value, and `MultiSelectFilter` keeps its own law (the group label
|
|
428
|
+
while the selection is empty or complete, `N <noun>` otherwise).
|
|
429
|
+
|
|
430
|
+
**Nothing to change at a call site.** What changes is the string a test or a screenshot sees: a
|
|
431
|
+
`SelectFilter field={currency} label="Currency"` at fallback `USD` now reads `USD`, not `Currency`.
|
|
432
|
+
If a bar reads badly at rest, the fix is the OPTION label (`store.labels()`), not the filter's name.
|
|
433
|
+
|
|
434
|
+
| Component / prop | Was | Now |
|
|
435
|
+
| ------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
436
|
+
| `CompareFilter` option labels | the raw values until `store.labels()` | `No comparison` / `Previous period` / `Same period last year`, exported as `COMPARE_LABELS`. A label a consumer set (`label !== value`) still wins. |
|
|
437
|
+
| `BasaltDataTable` → `withTableBorder` | `true` | `false`. Pass `true` for a table that needs a frame. The head rule and the between-row rules are untouched; the LAST row's rule is now dropped (it was an outer border). |
|
|
438
|
+
| `BasaltDataTable` single-select facet's "all" row | `All` | `Any <facet.label lowercased>` — the pill now reads it, and three facets all reading `All` said nothing. |
|
|
439
|
+
| `ViewTabs` phone `Select` (past 3 options) | greedy (216px measured, truncating a title) | capped at 9rem |
|
|
440
|
+
|
|
441
|
+
**New: `ControlGroup`** (`basalt-ui/controls`) — joins adjacent controls that act on ONE thing
|
|
442
|
+
(`‹ Today ›`, `− 1 +`) into a single box: no gap, one shared hairline per pair, radius on the outer
|
|
443
|
+
ends only. `gap?: 'none' | 'tight'`. `BarActionItem` gains `group?: true`, and a run of adjacent
|
|
444
|
+
`group: true` actions in an `ActionGroup` renders as one `ControlGroup`; a joined member that ships an
|
|
445
|
+
`icon` renders ICON-ONLY with its label demoted to the accessible name. `ActionGroup` also joins
|
|
446
|
+
adjacent ICON-ONLY entries on the mobile bar with no flag. `basalt/shadow-basalt-export` now names
|
|
447
|
+
`ButtonGroup` / `ButtonRow` / `JoinedButtons` as renamed forks of it.
|
|
448
|
+
|
|
449
|
+
### The `ctl` tier now covers Radio, Checkbox and Switch
|
|
450
|
+
|
|
451
|
+
All three defaulted to Mantine's `sm` — a 20px indicator beside the tier's 13.5px option label — in
|
|
452
|
+
every filter popover and in the mobile sheet. They are now in `CTL_THEME` (with their `.Group`s) and
|
|
453
|
+
`cssVariablesResolver` declares their `-ctl` vars: 16px indicator, 18×30 switch.
|
|
454
|
+
|
|
455
|
+
**One consequence worth knowing.** Mantine resolves a Radio's size as `props.size ? props.size :
|
|
456
|
+
ctx.size`, and a theme `defaultProps.size` is indistinguishable from an explicit prop to that check
|
|
457
|
+
— so inside a `<CtlSlot>` (any home's slot) a `<Radio.Group size="lg">` no longer sizes its children;
|
|
458
|
+
they stay `ctl`. A group that genuinely needs another size belongs in a BODY, which no slot wraps.
|
|
459
|
+
|
|
460
|
+
`basalt/control-size-literal` widened to cover the six new tags, so a `size` on a Radio/Checkbox/
|
|
461
|
+
Switch inside a home slot now reports. It is still `warn` on its 1.26.0 → 1.27.0 runway (it has never
|
|
462
|
+
shipped at any level, so widening it restarts nothing).
|
|
463
|
+
|
|
464
|
+
### Icons — one box, no call-site geometry
|
|
465
|
+
|
|
466
|
+
Every `icon` prop in the framework (`BarAction`, the filter pills and their trailing affordance,
|
|
467
|
+
`WidgetHeader`/`StatCard`, `SyncButton`, sidebar-block rows, the overflow fold's own glyphs) now
|
|
468
|
+
renders through ONE internal slot: a fixed `--vx-space-icon-size` (16px default) square that restates
|
|
469
|
+
the glyph's `width`/`height` at 100% and takes it off the text baseline. **No API change** — but a
|
|
470
|
+
consumer's `<svg width="24">` that used to set a control's row height now renders at 16px, and an
|
|
471
|
+
`<svg>` with no `width`/`viewBox` at all no longer paints at 300×150. Per-tier sizes come from the
|
|
472
|
+
box var (`WidgetHeader` sets 14px at `tier="widget"`), never from a prop.
|
|
473
|
+
|
|
474
|
+
### Mobile nav — at most ONE slot is active
|
|
475
|
+
|
|
476
|
+
A slot's activeness now reads exactly the destinations it COVERS. It used to roll up over `children`
|
|
477
|
+
unconditionally while an item slot covered only its own key, so at `/dashboard/sessions` the
|
|
478
|
+
`Dashboard` tab lit through the rollup and the `More` tab lit for `Sessions` sitting in its overflow
|
|
479
|
+
— two `aria-current="page"` tabs at once. A SECTION tab still lights for a nested destination (it
|
|
480
|
+
covers its whole tree); an ITEM tab with children no longer does, and the overflow slot that can
|
|
481
|
+
actually reach the open route lights instead.
|
|
482
|
+
|
|
483
|
+
`useNav`'s anchors now pass `activeOptions: { exact: true }` to TanStack's `Link`. A `Link` spreads
|
|
484
|
+
`{ 'data-status': 'active', 'aria-current': 'page' }` last — after `activeProps` and after every
|
|
485
|
+
caller prop — so its own fuzzy match could not be overridden from outside and re-created the very
|
|
486
|
+
double-highlight `useNav`'s resolver exists to prevent. basalt's own law is unchanged: an item whose
|
|
487
|
+
exact route is never visited still lights on a descendant route.
|
|
488
|
+
|
|
489
|
+
### Layout — two collisions and a gap
|
|
490
|
+
|
|
491
|
+
- **The app header**: exactly one side of row 1 is elastic, and it is the breadcrumb (the side whose
|
|
492
|
+
content can ellipsize). The control side is `flex: 0 0 auto` with no `min-width: 0` below it. Both
|
|
493
|
+
sides were shrinkable, and at 390px the sync button overflowed 30px INTO the page title, because a
|
|
494
|
+
`justify-content: flex-end` flex container overflows towards its start.
|
|
495
|
+
- **`WidgetHeader`'s hero row**: the value → `DeltaBadge` gap is the 8px rhythm step
|
|
496
|
+
(`--vx-space-stack-sm`), not 4px, and the badge is centred on the value rather than baseline-aligned
|
|
497
|
+
to it.
|
|
498
|
+
- **`WidgetHeader`'s title row** wraps below `sm`, so an `actions` slot too wide to sit beside the
|
|
499
|
+
title takes its own line instead of squeezing the heading to an ellipsis. A 30px kebab still fits
|
|
500
|
+
and still sits on the row. **Its metric row wraps too**: in a 2-up card grid at 390px
|
|
501
|
+
`$1294.9k ▲18.9%` rendered as `$129… ▲18.9%`, and truncating the number to protect the delta about
|
|
502
|
+
that number is the wrong way round. A short value still keeps its badge on the line.
|
|
503
|
+
- **`StatCard`'s `actions` slot is the 24px tier now, not 30px.** `CtlSlot` gained `tier?: 'ctl' |
|
|
504
|
+
'widget'` (default `'ctl'`, so no existing call site changes), and `StatCard` mounts its header slot
|
|
505
|
+
at `'widget'` — the `size="icon"` step `--vx-space-control-height-widget` has always named as
|
|
506
|
+
"`WidgetHeader tier="widget"` actions". A raw `ActionIcon` there rendered at the 30px `ctl` tier
|
|
507
|
+
inside a 28px header row and grew it to 30, so a KPI card WITH a kebab sat 2px below the card
|
|
508
|
+
beside it and every number in it with it. A consumer passing `<ActionIcon size="ctl">` explicitly
|
|
509
|
+
still gets 30px — and its card is 2px taller than its neighbours again, which is now visible rather
|
|
510
|
+
than accidental. The slot's `data-basalt-tier` marker reads `widget` there, the same value
|
|
511
|
+
`ChartCard` already writes by hand.
|
|
512
|
+
- **The breadcrumb's ANCESTOR crumbs are hidden below `sm`** — only the page crumb renders. At 390px
|
|
513
|
+
the elastic side truncated `Overview / Dashboard` to `O… / D…`: two ellipses and a separator in
|
|
514
|
+
place of the one word that says where the reader is. CSS-only (law C9), so no flash and no hook.
|
|
515
|
+
`AppBreadcrumbs` gained one wrapper element around those crumbs; its props are unchanged.
|
|
516
|
+
|
|
517
|
+
### Cursor — the shared cursor now partitions by x-domain kind, automatically
|
|
518
|
+
|
|
519
|
+
Nothing removed. `CursorState` gained one field.
|
|
520
|
+
|
|
521
|
+
| Component | What changed | Why it matters |
|
|
522
|
+
| ------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
523
|
+
| `CursorState` | gains `kind: DomainKind \| null` | No consumer action needed. The shared cursor now classifies each chart's x-domain (`'time' \| 'linear' \| 'band'`, derived from its own keys) and a broadcast whose kind differs from a chart's own is invisible to it — a categorical chart can no longer be mistaken for a time chart. `ChartCursorScope` is no longer required to keep a categorical chart from following a time chart; it still isolates two charts that share the same domain kind but are semantically unrelated. |
|
|
524
|
+
|
|
525
|
+
## 1.24.0 — `QueryState`, table body chrome, four false greens
|
|
42
526
|
|
|
43
527
|
**Nothing removed or renamed. Twelve new runtime exports on `.`/`./query`, plus table props.** Every
|
|
44
528
|
API entry is additive; the behaviour changes are in the guard and the CLI, listed after them.
|
|
@@ -85,7 +569,7 @@ New: `maxHeight`, `minWidth`, `stickyHeader`, `stickyHeaderOffset`, `verticalSpa
|
|
|
85
569
|
defaults to `true` in basalt, overriding Mantine's `false`; every other one is a conditional
|
|
86
570
|
pass-through, so omitting it keeps Mantine's default.
|
|
87
571
|
|
|
88
|
-
**Porting argo's three tables onto them made them 341 → 370 lines. 29 LONGER.** argo named these
|
|
572
|
+
**Porting argo's three tables onto them made them 341 → 370–379 lines. 29–38 LONGER.** argo named these
|
|
89
573
|
props as the reason the tables stayed hand-rolled, and adding them shortened nothing: column defs
|
|
90
574
|
cost more than JSX rows when every cell is bespoke — eight accessor blocks at 4–6 lines each against
|
|
91
575
|
an eight-`<Table.Td>` row at ~3. **The ask was mis-specified.** Adopt them for what they buy —
|
|
@@ -170,6 +654,32 @@ scaffold a second consumer: the refusal is keyed on the RESOLVED project and run
|
|
|
170
654
|
and no other consumer holds a single `.astro`, `.vue` or `.jsx` file, so grace would have covered
|
|
171
655
|
zero incumbent violations.
|
|
172
656
|
|
|
657
|
+
## 1.25.0 — `manualPagination` imposes a contract
|
|
658
|
+
|
|
659
|
+
**Nothing removed or renamed — a behaviour change, additive props only.** `manualPagination` made
|
|
660
|
+
`data` one server page but left every other client-side control armed: sorting reordered that page
|
|
661
|
+
under a header chevron while "Showing 1–25 of 412" presented it as a sort of all 412 — a plausible,
|
|
662
|
+
wrong answer with nothing on screen to give it away. argo found it on 1.24.0 and worked around it
|
|
663
|
+
with an explicit `enableSorting={false}`.
|
|
664
|
+
|
|
665
|
+
Adopting `manualPagination` now imposes a contract, checked from props at render:
|
|
666
|
+
|
|
667
|
+
| With `manualPagination` | Resolve it with |
|
|
668
|
+
| ------------------------- | ------------------------------------------------------------------------------------ |
|
|
669
|
+
| the pagination bar itself | `enablePagination` (without it `manualPagination` is inert) + `rowCount`/`pageCount` |
|
|
670
|
+
| sorting | the new `manualSorting` + sort in `onSortingChange`, or `enableSorting={false}` |
|
|
671
|
+
| `enableGlobalFilter` | the new `manualFiltering` + filter in `onGlobalFilterChange` |
|
|
672
|
+
| `facets` | `manualFiltering` + the new `onColumnFiltersChange` |
|
|
673
|
+
|
|
674
|
+
Unresolved, it **throws in dev** naming every breach at once; a production bundle degrades instead —
|
|
675
|
+
no sort headers, no filter controls, no "of N" it cannot stand behind, plus one `console.error`. A
|
|
676
|
+
bare `<BasaltDataTable data columns />` (no `manualPagination`) is byte-identical — no opt-out
|
|
677
|
+
needed for a client-side table.
|
|
678
|
+
|
|
679
|
+
Sibling defect fixed with it: the empty-state branch keyed off `data.length`, so a search or page
|
|
680
|
+
index matching nothing left `data` non-empty and rendered a `<tbody>` with no rows AND no message.
|
|
681
|
+
It now keys off the rendered row model.
|
|
682
|
+
|
|
173
683
|
## 1.23.1 — the band-state throw, the tag gate, a CLI that answers
|
|
174
684
|
|
|
175
685
|
**One type widened, nothing removed or renamed.** `BandStripSeries.formatValue` is now
|
|
@@ -583,7 +1093,7 @@ with the whole lint half off).
|
|
|
583
1093
|
|
|
584
1094
|
| Surface | Note |
|
|
585
1095
|
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
586
|
-
| `basalt.profile: 'tokens-only'` / `--tokens-only` | disables the
|
|
1096
|
+
| `basalt.profile: 'tokens-only'` / `--tokens-only` | disables the 19 kinds whose remedy is a Mantine component, prop or the React theme factory. `check-theme` requires it DECLARED; `doctor` infers it, because its profile only changes advice, never enforcement |
|
|
587
1097
|
| `basalt.include: [...]` | scan a named file outside `roots` — and the only route to a `.json`, which is never blanket-scanned |
|
|
588
1098
|
| `basalt.roots` + a `lint:basalt` script | written by `init` from the real layout; `init` on an existing app is a lint-debt event, not a no-op |
|
|
589
1099
|
| `tokens:css --check`, `--selector-class <c>` (+ `--light-class`) | drift gate; the Tailwind `<html class="dark">` convention. There is no `scheme: { class }` API — the class form is CLI-only |
|
|
@@ -697,23 +1207,39 @@ live in `configs/oxlint.json`, and since 1.20.0 their grace is tracked by `PLUGI
|
|
|
697
1207
|
named export beside the plugin — a test asserts it against the shipped preset in both directions, so
|
|
698
1208
|
deleting an entry forces the level flip in the same commit.
|
|
699
1209
|
|
|
700
|
-
| Rule
|
|
701
|
-
|
|
|
702
|
-
| `mantine-shade-index` (guard kind)
|
|
703
|
-
| `basalt/raw-scroll-container`
|
|
704
|
-
| `basalt/ai-sdk-major`
|
|
705
|
-
| `basalt/agent-no-raw-usechat`
|
|
706
|
-
| `basalt/agent-resume-guard`
|
|
707
|
-
| `basalt/raw-size-literal`
|
|
708
|
-
| `basalt/hand-rolled-plot`
|
|
709
|
-
| `basalt/chart-legend-literal`
|
|
710
|
-
| `basalt/shadow-basalt-export`
|
|
711
|
-
| `basalt/hand-rolled-shell`
|
|
1210
|
+
| Rule | Landed | Became `error` |
|
|
1211
|
+
| -------------------------------------- | ---------------- | --------------------------------------------------------------------- |
|
|
1212
|
+
| `mantine-shade-index` (guard kind) | 1.7.0 as `warn` | **1.11.0** |
|
|
1213
|
+
| `basalt/raw-scroll-container` | ≤1.2.0 as `off` | `warn` 1.10.0 → **`error` 1.13.0** |
|
|
1214
|
+
| `basalt/ai-sdk-major` | 1.10.0 as `warn` | **1.13.0** |
|
|
1215
|
+
| `basalt/agent-no-raw-usechat` | 1.10.0 as `warn` | **1.13.0** |
|
|
1216
|
+
| `basalt/agent-resume-guard` | 1.10.0 as `warn` | **1.13.0** |
|
|
1217
|
+
| `basalt/raw-size-literal` | 1.7.0 as `warn` | **1.20.0** |
|
|
1218
|
+
| `basalt/hand-rolled-plot` | 1.21.0 as `warn` | **Unreleased** (grace had restarted at the 1.21.0 widening) |
|
|
1219
|
+
| `basalt/chart-legend-literal` | 1.20.0 as `warn` | **Unreleased** |
|
|
1220
|
+
| `basalt/shadow-basalt-export` | 1.20.0 as `warn` | ADVISORY — permanent `warn`, see below, never subject to the C16 gate |
|
|
1221
|
+
| `basalt/hand-rolled-shell` | 1.20.0 as `warn` | **Unreleased** |
|
|
1222
|
+
| `theme-allow-unscoped` (guard kind) | 1.20.0 as `warn` | **Unreleased** |
|
|
1223
|
+
| `surface-shadow-override` (guard kind) | 1.20.0 as `warn` | **Unreleased** |
|
|
1224
|
+
| `css-raw-surface` (guard kind) | 1.20.0 as `warn` | **Unreleased** |
|
|
1225
|
+
| `inline-font-size` (guard kind) | 1.20.0 as `warn` | **Unreleased** |
|
|
1226
|
+
| `hidden-inline-style` (guard kind) | 1.20.0 as `warn` | **Unreleased** |
|
|
712
1227
|
|
|
713
1228
|
`card-with-border`, `inline-display`, `raw-html-layout`, `raw-form-control`, `raw-font-family` and
|
|
714
1229
|
the other original guard kinds have been `error` since before 1.2.0 — they never had a grace minor.
|
|
715
1230
|
Guard findings only gained a severity field at all in 1.4.0; before that every finding was fatal.
|
|
716
1231
|
|
|
1232
|
+
**Both ledgers changed shape in the Unreleased minor (C16, `docs/CONTROLS-SPEC.md` §1).** An entry
|
|
1233
|
+
used to be a bare promotion-note string with no expiry a machine could check — which is exactly how
|
|
1234
|
+
the nine rows above sat at `warn` for five minors although the doctrine below says "one minor". Both
|
|
1235
|
+
`PLUGIN_RULE_GRACE` (`configs/oxlint-plugin.js`) and `GRACE_PERIOD_KINDS` (`src/guard/index.ts`) now
|
|
1236
|
+
hold `{ since, promote, why }` (semver strings), and a version-gated test fails the build once
|
|
1237
|
+
`package.json`'s version reaches an entry's `promote` while the entry is still there — see
|
|
1238
|
+
"Unreleased" above for the full change. `shadow-basalt-export` moved to a sibling ledger,
|
|
1239
|
+
`PLUGIN_RULE_ADVISORY`, which the gate never checks: it was already documented as a possible
|
|
1240
|
+
permanent `warn`, and the version-gate doctrine only fits an entry that has an honest promotion
|
|
1241
|
+
date.
|
|
1242
|
+
|
|
717
1243
|
**Rule-id rename at 1.1.0:** `basalt/import-boundary` split into `basalt/visx-boundary`,
|
|
718
1244
|
`basalt/visx-tooltip` and `basalt/token-layer-boundary` (the last is repo-local and deliberately not
|
|
719
1245
|
in the shipped preset). A config still naming `import-boundary` after 1.1.0 disables nothing.
|