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,264 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
source: basalt-ui
|
|
3
|
-
description: Notification conventions for basalt-ui apps — notify helpers, typed registry, persisted history store, bell/center, and BasaltNotifications overlay from the shipped ./notifications battery. @mantine/notifications is an optional peer.
|
|
4
|
-
paths:
|
|
5
|
-
- 'src/**'
|
|
6
|
-
- 'apps/**/src/**'
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Basalt Notifications
|
|
10
|
-
|
|
11
|
-
basalt-ui ships `./notifications` — a Mantine notification adapter battery providing `notify`,
|
|
12
|
-
`notifySuccess/Error/Warning/Info`, `notifyPromise`, `defineNotifications`, `emit`,
|
|
13
|
-
`useNotificationHistory`, `NotificationBell`, `NotificationCenter`, and `BasaltNotifications`
|
|
14
|
-
on top of `@mantine/notifications`. `@mantine/notifications` is an **optional peer** — install it
|
|
15
|
-
before using this battery:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
bun add @mantine/notifications
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Mount BasaltNotifications
|
|
22
|
-
|
|
23
|
-
Render **one** `<BasaltNotifications />` inside `BasaltProvider`, before the router. The overlay is
|
|
24
|
-
opt-in — it is NOT auto-mounted from `BasaltProvider` to keep the peer out of the root barrel.
|
|
25
|
-
|
|
26
|
-
```tsx
|
|
27
|
-
import { BasaltNotifications } from 'basalt-ui/notifications'
|
|
28
|
-
import { BasaltProvider } from 'basalt-ui'
|
|
29
|
-
|
|
30
|
-
createRoot(root).render(
|
|
31
|
-
<BasaltProvider>
|
|
32
|
-
<BasaltNotifications />
|
|
33
|
-
<App />
|
|
34
|
-
</BasaltProvider>,
|
|
35
|
-
)
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
`BasaltNotifications` wraps Mantine's `<Notifications />` with basalt defaults:
|
|
39
|
-
`position="bottom-right"`, `autoClose=4000`, `limit=5`. Pass props to override:
|
|
40
|
-
|
|
41
|
-
```tsx
|
|
42
|
-
<BasaltNotifications position="top-right" autoClose={3000} limit={3} />
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
NOTE: `BasaltOverlays` (from `basalt-ui/commands`) is the shipped composable mount that wires
|
|
46
|
-
notifications together with spotlight/modals in one place. A standalone `<BasaltNotifications />`
|
|
47
|
-
remains available for apps that don't use the overlays layer.
|
|
48
|
-
|
|
49
|
-
## notify
|
|
50
|
-
|
|
51
|
-
`notify(opts)` is the core helper. It maps intent → Mantine color name, sets the correct aria
|
|
52
|
-
`role` for screen reader announcement, applies per-intent `autoClose` defaults, and records the
|
|
53
|
-
toast to the persisted history store. Returns the notification id string.
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
import { notify } from 'basalt-ui/notifications'
|
|
57
|
-
|
|
58
|
-
notify({ intent: 'success', message: 'Profile saved' })
|
|
59
|
-
notify({ intent: 'error', message: 'Upload failed', title: 'Error', autoClose: false })
|
|
60
|
-
notify({ intent: 'warning', message: 'Session expires soon', dedupKey: 'session-warn' })
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Intent → Mantine color + aria role + autoClose
|
|
64
|
-
|
|
65
|
-
| Intent | `color=` | `role=` | Default autoClose |
|
|
66
|
-
| ------- | ---------- | --------------------- | ------------------------- |
|
|
67
|
-
| success | `'green'` | `'status'` (polite) | 2000ms |
|
|
68
|
-
| info | `'blue'` | `'status'` (polite) | 3000ms |
|
|
69
|
-
| warning | `'yellow'` | `'alert'` (assertive) | 5000ms |
|
|
70
|
-
| error | `'red'` | `'alert'` (assertive) | `false` (until dismissed) |
|
|
71
|
-
|
|
72
|
-
**Never pass raw Mantine colors directly.** Always use `notify`'s `intent` field — the mapping is
|
|
73
|
-
the canonical source so screen-reader announcement and autoClose stay consistent.
|
|
74
|
-
|
|
75
|
-
### Two-layer dedup
|
|
76
|
-
|
|
77
|
-
1. **Mantine id-dedup**: passing the same `id` to `notifications.show` is a no-op on screen
|
|
78
|
-
(Mantine's built-in dedup). Pass `id` to prevent a toast from appearing twice.
|
|
79
|
-
2. **dedupKey window**: a module-level guard skips any `notify` call using the same `dedupKey`
|
|
80
|
-
within 1500ms — useful for event-driven triggers that may fire rapidly.
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
// Fire rapidly — only the first call within 1500ms shows:
|
|
84
|
-
notify({ intent: 'warning', message: 'Rate limit', dedupKey: 'rate-limit-warn' })
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## Convenience wrappers
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
import { notifySuccess, notifyError, notifyWarning, notifyInfo } from 'basalt-ui/notifications'
|
|
91
|
-
|
|
92
|
-
notifySuccess('Changes saved')
|
|
93
|
-
notifyError('Upload failed', { title: 'Storage error' })
|
|
94
|
-
notifyWarning('Session expires in 5 minutes')
|
|
95
|
-
notifyInfo('New version available', { autoClose: 10000 })
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Each wrapper calls `notify` with the matching intent. The second arg takes all `NotifyOptions`
|
|
99
|
-
fields except `intent` and `message`.
|
|
100
|
-
|
|
101
|
-
## notifyPromise
|
|
102
|
-
|
|
103
|
-
For async operations — shows a loading toast, then updates to success or error on settle.
|
|
104
|
-
Uses `notifications.show` for loading, then `notifications.update` for the final state (required
|
|
105
|
-
by Mantine to replace an existing id — `show` with the same id is a no-op).
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
import { notifyPromise } from 'basalt-ui/notifications'
|
|
109
|
-
|
|
110
|
-
await notifyPromise(uploadFile(file), {
|
|
111
|
-
loading: 'Uploading…',
|
|
112
|
-
success: 'Upload complete',
|
|
113
|
-
error: 'Upload failed',
|
|
114
|
-
})
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
The promise is passed through — `await notifyPromise(...)` resolves/rejects as normal. The final
|
|
118
|
-
state is recorded to the notification history store (loading state is not recorded).
|
|
119
|
-
|
|
120
|
-
## defineNotifications + emit
|
|
121
|
-
|
|
122
|
-
`defineNotifications` is a const-generic factory (mirrors `defineSeries` from `./tokens`) for
|
|
123
|
-
typed notification kind registries. Augment `BasaltRegister.notifications` once with the spec map
|
|
124
|
-
type, then use `emit` — unknown kinds are tsc errors.
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
// notifications.ts — define and augment (app-side, once):
|
|
128
|
-
import { defineNotifications } from 'basalt-ui/notifications'
|
|
129
|
-
|
|
130
|
-
export const NOTIFICATIONS = defineNotifications({
|
|
131
|
-
'upload:success': { intent: 'success', toMessage: (p: { name: string }) => `Uploaded ${p.name}` },
|
|
132
|
-
'upload:error': { intent: 'error', toMessage: (_: unknown) => 'Upload failed' },
|
|
133
|
-
'session:warn': { intent: 'warning', toMessage: () => 'Session expires soon' },
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
declare module 'basalt-ui' {
|
|
137
|
-
interface BasaltRegister {
|
|
138
|
-
notifications: typeof NOTIFICATIONS
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
```ts
|
|
144
|
-
// usage.ts — typed emit, unknown kinds fail tsc:
|
|
145
|
-
import { emit } from 'basalt-ui/notifications'
|
|
146
|
-
|
|
147
|
-
emit('upload:success', { name: 'photo.jpg' }) // ✓ typed payload
|
|
148
|
-
emit('upload:error', null) // ✓
|
|
149
|
-
emit('nonexistent', null) // ✗ tsc error
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
`emit` resolves `intent` + calls `toMessage(payload)` from the registered spec, then calls
|
|
153
|
-
`notify()`. Pass extra `NotifyOptions` as a third argument to override spec defaults.
|
|
154
|
-
|
|
155
|
-
## defineNotification (single-spec helper)
|
|
156
|
-
|
|
157
|
-
Mirror of `defineOverlay` from `./commands` — types a single notification spec without registering
|
|
158
|
-
it. Use when splitting notification definitions across files before merging into `defineNotifications`.
|
|
159
|
-
|
|
160
|
-
```ts
|
|
161
|
-
import { defineNotification, defineNotifications } from 'basalt-ui/notifications'
|
|
162
|
-
|
|
163
|
-
// Type an isolated spec constant — useful when splitting across files:
|
|
164
|
-
const uploadSuccess = defineNotification({
|
|
165
|
-
intent: 'success',
|
|
166
|
-
toMessage: (p: { name: string }) => `Uploaded ${p.name}`,
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
// Merge into the full registry (defineNotifications does the actual registration):
|
|
170
|
-
export const NOTIFICATIONS = defineNotifications({ 'upload:success': uploadSuccess })
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
WARNING: `defineNotification` only TYPES — it does NOT register. Only `defineNotifications(map)`
|
|
174
|
-
registers. Calling `emit` without a prior `defineNotifications` is a no-op at runtime.
|
|
175
|
-
|
|
176
|
-
### `NotificationSpec` has no `title`/`icon` — by design
|
|
177
|
-
|
|
178
|
-
`NotificationSpec` is `{ intent, toMessage? }` — deliberately no `title`/`icon` field. Those stay
|
|
179
|
-
per-call `NotifyOptions` (`emit(kind, payload, { title: '…' })` or `notify({ ..., title: '…' })`),
|
|
180
|
-
so a title that varies per call site isn't forced into the registry. If every call to a given kind
|
|
181
|
-
repeats the same title, pass it at each call site — there's no registry-level default to fall back
|
|
182
|
-
on.
|
|
183
|
-
|
|
184
|
-
### Colors beyond the 4 intents live outside the registry
|
|
185
|
-
|
|
186
|
-
`notify`/`emit` only resolve `intent` to one of the four Mantine colors (§ Intent → Mantine color
|
|
187
|
-
above) — there's no `intent` override and no raw-color escape in `NotifyOptions`. A feature needing
|
|
188
|
-
per-category colored toasts beyond `success`/`info`/`warning`/`error` (e.g. achievement toasts keyed
|
|
189
|
-
by achievement type) cannot ride the typed registry. The documented stance: call Mantine's raw
|
|
190
|
-
`notifications.show({ color, ... })` directly, outside `notify`/`emit`. The cost is explicit — a
|
|
191
|
-
raw `notifications.show` toast is invisible to the persisted history store, the bell, and the
|
|
192
|
-
notification center, since only `notify`/`notifyPromise` record to history.
|
|
193
|
-
|
|
194
|
-
## Notification history store
|
|
195
|
-
|
|
196
|
-
Every `notify()` / `notifyPromise()` call records to a module-level persisted external store
|
|
197
|
-
(own `useSyncExternalStore` + versioned `{ v, value }` envelope under `basalt:notifications` in
|
|
198
|
-
localStorage). The store is **not** hook-scoped — `add`, `markRead`, `markAllRead`, `clear` are
|
|
199
|
-
module-level and callable outside React.
|
|
200
|
-
|
|
201
|
-
Ring-buffer cap: 50 items. Items store `{ id, intent, title?, message, createdAt, readAt? }` —
|
|
202
|
-
**message and title are strings** (ReactNode is not serializable). Cross-tab sync via the
|
|
203
|
-
`storage` event.
|
|
204
|
-
|
|
205
|
-
### useNotificationHistory
|
|
206
|
-
|
|
207
|
-
```ts
|
|
208
|
-
import { useNotificationHistory } from 'basalt-ui/notifications'
|
|
209
|
-
|
|
210
|
-
const { items, unreadCount, markRead, markAllRead, clear } = useNotificationHistory()
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
| Field | Type | Description |
|
|
214
|
-
| --------------- | --------------------------- | ------------------------------------------ |
|
|
215
|
-
| `items` | `NotificationHistoryItem[]` | All history (newest first, ring-buffer 50) |
|
|
216
|
-
| `unreadCount` | `number` | Items with no `readAt` |
|
|
217
|
-
| `markRead(id)` | `(id: string) => void` | Mark one item read |
|
|
218
|
-
| `markAllRead()` | `() => void` | Mark all items read |
|
|
219
|
-
| `clear()` | `() => void` | Clear all history |
|
|
220
|
-
|
|
221
|
-
## NotificationBell
|
|
222
|
-
|
|
223
|
-
A Mantine `ActionIcon` + `Indicator` showing unread count. Opens a `NotificationCenter` in a
|
|
224
|
-
`Popover`. Marks all items read when the center opens. Designed for the shell `globalActions` slot.
|
|
225
|
-
|
|
226
|
-
```tsx
|
|
227
|
-
import { NotificationBell } from 'basalt-ui/notifications'
|
|
228
|
-
import { BasaltShell } from 'basalt-ui'
|
|
229
|
-
;<BasaltShell globalActions={<NotificationBell />} {...rest} />
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
The bell renders a bell SVG icon (no `@tabler/icons` dependency — icons are ReactNode per house
|
|
233
|
-
rules). The unread indicator is capped at `9+` for counts above 9.
|
|
234
|
-
|
|
235
|
-
## NotificationCenter
|
|
236
|
-
|
|
237
|
-
Lists history items (title, message, relative time, unread dot) with "Mark all read" and "Clear"
|
|
238
|
-
actions. Consumes `useNotificationHistory`. Composable — mount it inside any Popover, Drawer, or
|
|
239
|
-
panel:
|
|
240
|
-
|
|
241
|
-
```tsx
|
|
242
|
-
import { NotificationCenter } from 'basalt-ui/notifications'
|
|
243
|
-
import { Drawer } from '@mantine/core'
|
|
244
|
-
;<Drawer opened={opened} onClose={close} title="Notifications">
|
|
245
|
-
<NotificationCenter maxHeight={500} />
|
|
246
|
-
</Drawer>
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
`NotificationBell` uses `NotificationCenter` internally. Mount `NotificationCenter` directly only
|
|
250
|
-
when you need a custom container (e.g. a full-page notifications view or a custom Drawer).
|
|
251
|
-
|
|
252
|
-
## Deferred (not yet shipped)
|
|
253
|
-
|
|
254
|
-
The following features are advisory notes for future work — they are NOT part of the current battery:
|
|
255
|
-
|
|
256
|
-
- **Web Push** (`usePushSubscription` + `sw.js` + Elysia push route): server-side push delivery
|
|
257
|
-
for background notifications. Requires a service worker, a push endpoint, and a vapid key.
|
|
258
|
-
- **Visibility-aware delivery buffering**: buffer toasts while the tab is hidden (`document.hidden`)
|
|
259
|
-
and flush on `visibilitychange` — prevents lost toasts when the tab is backgrounded.
|
|
260
|
-
- **IntersectionObserver per-item mark-seen**: replace the mark-read-on-open approach with
|
|
261
|
-
per-item IntersectionObserver so only viewed items are marked read (higher fidelity).
|
|
262
|
-
- **Cross-device read-sync**: propagate `readAt` via an Elysia endpoint so read state syncs
|
|
263
|
-
across browser sessions (requires auth and a backend store).
|
|
264
|
-
- **Quiet hours**: suppress non-error toasts during a configurable time window.
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
source: basalt-ui
|
|
3
|
-
description: TanStack Query conventions for basalt-ui apps — query factories, Suspense reads, and mutation/invalidation. basalt-ui ships ./query (createBasaltQueryClient + unwrap + devtools); this rule covers the advisory consumer patterns on top.
|
|
4
|
-
paths:
|
|
5
|
-
- 'src/lib/queries/**'
|
|
6
|
-
- 'apps/**/src/lib/queries/**'
|
|
7
|
-
- 'src/routes/**'
|
|
8
|
-
- 'apps/**/src/routes/**'
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Basalt Query — TanStack Query Conventions
|
|
12
|
-
|
|
13
|
-
basalt-ui ships `./query` — a thin TanStack Query adapter providing `createBasaltQueryClient`,
|
|
14
|
-
`unwrap`, `BasaltQueryDevtools`, and convenience re-exports (`QueryClientProvider`,
|
|
15
|
-
`QueryErrorResetBoundary`, `useQueryErrorResetBoundary`). @tanstack/react-query is a **required**
|
|
16
|
-
peer — `BasaltProvider` hard-requires it at build time.
|
|
17
|
-
|
|
18
|
-
`./query` also re-exports the primary fetch hooks so consumers never need to dual-import from both
|
|
19
|
-
`basalt-ui/query` and `@tanstack/react-query`:
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
import {
|
|
23
|
-
useQuery,
|
|
24
|
-
useSuspenseQuery,
|
|
25
|
-
useMutation,
|
|
26
|
-
useQueryClient,
|
|
27
|
-
useInfiniteQuery,
|
|
28
|
-
queryOptions,
|
|
29
|
-
} from 'basalt-ui/query'
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
This rule covers the advisory consumer patterns on top of that adapter. Nothing in the framework
|
|
33
|
-
enforces these conventions beyond the `unwrap` seam.
|
|
34
|
-
|
|
35
|
-
## Query factories
|
|
36
|
-
|
|
37
|
-
Each resource has a factory in `src/lib/queries/<resource>.ts`:
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
import { queryOptions, useSuspenseQuery } from 'basalt-ui/query'
|
|
41
|
-
import { unwrap } from 'basalt-ui/query'
|
|
42
|
-
import { api } from '../api-client'
|
|
43
|
-
|
|
44
|
-
export const resourceQueries = {
|
|
45
|
-
list: (params: ListParams) =>
|
|
46
|
-
queryOptions({
|
|
47
|
-
queryKey: ['resource', 'list', params],
|
|
48
|
-
queryFn: () => unwrap(api.resource.get({ query: params })),
|
|
49
|
-
}),
|
|
50
|
-
summary: () =>
|
|
51
|
-
queryOptions({
|
|
52
|
-
queryKey: ['resource', 'summary'],
|
|
53
|
-
queryFn: () => unwrap(api.resource.summary.get()),
|
|
54
|
-
}),
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
- Key hierarchy: `[resource, action, ...params]`.
|
|
59
|
-
- `queryOptions` and all hooks import from `basalt-ui/query` — do not dual-import from
|
|
60
|
-
`@tanstack/react-query` directly.
|
|
61
|
-
- `queryFn` unwraps typed responses via `unwrap()` — throws on the error branch so failures surface
|
|
62
|
-
to the nearest error boundary or TanStack Query's error state. Also throws when `data` is `null`
|
|
63
|
-
with no error (e.g. a 204 No Content or a silent transport failure that returns
|
|
64
|
-
`{ data: null, error: null }`). Both cases are bugs the caller must address.
|
|
65
|
-
- Never use raw strings as query keys in components — always import from the factory.
|
|
66
|
-
|
|
67
|
-
## Reading data in components
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
import { useSuspenseQuery } from 'basalt-ui/query'
|
|
71
|
-
|
|
72
|
-
const { data } = useSuspenseQuery(resourceQueries.summary())
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
- Always `useSuspenseQuery` inside loader-prefetched routes (never `useQuery`) — pairs with the
|
|
76
|
-
router's `ensureQueryData` (see basalt-router.md).
|
|
77
|
-
- Wrap pages or panels in `<Suspense fallback={…}>` at the route level.
|
|
78
|
-
|
|
79
|
-
## Rendering a query — use `QueryState`, don't write the switch
|
|
80
|
-
|
|
81
|
-
```tsx
|
|
82
|
-
import { QueryState } from 'basalt-ui' // NOT basalt-ui/query — it renders Mantine
|
|
83
|
-
|
|
84
|
-
function Library() {
|
|
85
|
-
const q = useQuery(imageQueries.list())
|
|
86
|
-
return (
|
|
87
|
-
<QueryState
|
|
88
|
-
query={q}
|
|
89
|
-
variant="page" // or "section" for a panel inside a page
|
|
90
|
-
errorTitle="Could not load images"
|
|
91
|
-
empty={{ title: 'No images yet', description: 'Upload one to get started.' }}
|
|
92
|
-
isEmpty={(d) => d.items.length === 0}
|
|
93
|
-
>
|
|
94
|
-
{(data) => <ImageGrid items={data.items} />}
|
|
95
|
-
</QueryState>
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
**Rendering only the empty branch is the bug this retires.** A consumer's library showed
|
|
101
|
-
`No images` on a **500**, and a share detail showed `Share not found` on a dropped connection —
|
|
102
|
-
"nothing here" is what an app renders when nobody wrote the other three branches. Swapping the
|
|
103
|
-
import replaced 204 hand-rolled lines across 10 call sites there, with no prop or JSX change.
|
|
104
|
-
|
|
105
|
-
Precedence, in evaluation order:
|
|
106
|
-
|
|
107
|
-
| Condition | Renders |
|
|
108
|
-
| ----------------------------------- | ------------------------------------------------------------------------- |
|
|
109
|
-
| `isError`, no `data` | `ErrorState` — `errorTitle` / `errorFallback` / `errorAction`, with Retry |
|
|
110
|
-
| no `data`, `fetchStatus === 'idle'` | the `empty` copy (nothing, if `empty` is omitted) |
|
|
111
|
-
| no `data` | `loading`, else `LoadingState` |
|
|
112
|
-
| `data`, `isEmpty(data)` | the `empty` copy |
|
|
113
|
-
| `data` | `children` — a node, or `(data) => node` |
|
|
114
|
-
| `data` **and** `isError` | a fixed _Showing cached data_ banner above `children` |
|
|
115
|
-
|
|
116
|
-
`errorTitle` / `errorFallback` / `errorAction` reach the no-data error branch only; the stale-data
|
|
117
|
-
banner carries its own copy. `empty` is `{ title, description?, icon?, action? }` — `description`
|
|
118
|
-
is optional, so a compact panel needs no invented second sentence.
|
|
119
|
-
|
|
120
|
-
**`useQuery`, not `useSuspenseQuery`.** A suspense read never hands you an undefined `data` or an
|
|
121
|
-
error branch — it throws to the nearest boundary — so `QueryState` has nothing to decide there.
|
|
122
|
-
Suspense + an error boundary and `QueryState` are the two ways to render a query; pick one per read.
|
|
123
|
-
|
|
124
|
-
`LoadingState` and `ErrorState` ship beside it as the escape hatch for a page that must place its
|
|
125
|
-
branches in different DOM positions.
|
|
126
|
-
|
|
127
|
-
**`query` is a structural subset, checked at runtime.** It needs `{ data, isError, error,
|
|
128
|
-
fetchStatus, refetch }` — a TanStack `UseQueryResult` satisfies it, and a hand-composed object is
|
|
129
|
-
legal. Because the subset gives up the compiler's help, a missing `isError` **throws** naming the
|
|
130
|
-
field rather than rendering a false claim about the data.
|
|
131
|
-
|
|
132
|
-
Decoding an unknown error into copy is `toErrorMessage(err, fallback?)` from `basalt-ui/query`,
|
|
133
|
-
with `errorStatus(err)` for the status; `toErrorMessage` folds the status into the fallback when the
|
|
134
|
-
body decodes to nothing readable, so an opaque envelope never renders as `{}`.
|
|
135
|
-
|
|
136
|
-
## Mutations
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
const mutation = useMutation({
|
|
140
|
-
mutationFn: (body) => unwrap(api.resource.post({ body })),
|
|
141
|
-
onSuccess: () => {
|
|
142
|
-
queryClient.invalidateQueries({ queryKey: ['resource'] })
|
|
143
|
-
notifications.show({ message: 'Saved', color: 'green' })
|
|
144
|
-
},
|
|
145
|
-
})
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
- Invalidate by resource prefix (first key segment) — catches list + summary at once.
|
|
149
|
-
- Show a notification on success; let errors bubble to the global error boundary.
|
|
150
|
-
- For the in-button success flip (loading spinner → green `IconCheck`), drive it off `mutation.isPending`
|
|
151
|
-
/ `mutation.isSuccess` — see the interaction-feedback section in basalt-mantine.md.
|
|
152
|
-
|
|
153
|
-
## DevTools
|
|
154
|
-
|
|
155
|
-
Use `BasaltQueryDevtools` from `basalt-ui/query` — it is lazy, production-excluded, and safe to
|
|
156
|
-
import unconditionally:
|
|
157
|
-
|
|
158
|
-
```tsx
|
|
159
|
-
import { BasaltQueryDevtools, QueryClientProvider } from 'basalt-ui/query'
|
|
160
|
-
|
|
161
|
-
function Root() {
|
|
162
|
-
return (
|
|
163
|
-
<QueryClientProvider client={client}>
|
|
164
|
-
{children}
|
|
165
|
-
<BasaltQueryDevtools initialIsOpen={false} />
|
|
166
|
-
</QueryClientProvider>
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Never import `@tanstack/react-query-devtools` directly in app code — it is an optional peer and
|
|
172
|
-
the lazy wrapper handles the production guard for you.
|
|
173
|
-
|
|
174
|
-
## Eden footguns (hard authoring rules)
|
|
175
|
-
|
|
176
|
-
Two patterns silently degrade Eden Treaty inference to `any` — both must be avoided:
|
|
177
|
-
|
|
178
|
-
1. **Non-chained route definitions**: Elysia routes MUST be method-chained on the `app` instance.
|
|
179
|
-
A standalone `app.get(...)` (reassigned or called without chaining back to the root) drops the
|
|
180
|
-
`App` type to `any`, which propagates into the Treaty client and loses all type safety.
|
|
181
|
-
|
|
182
|
-
2. **Mismatched tsconfig path aliases**: The client package and the server package MUST share or
|
|
183
|
-
extend one root `tsconfig` so path aliases (`~/*`, `@/*`, etc.) resolve identically in both.
|
|
184
|
-
A mismatch causes Eden's type extraction to fail silently and degrade to `any`.
|
|
185
|
-
|
|
186
|
-
Both footguns produce no TypeScript error at the point of breakage — they only manifest as `any`
|
|
187
|
-
types on the Treaty client, making them hard to spot. Check these first when Eden types regress.
|