basalt-ui 2.0.2 → 2.0.3

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/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [2.0.3](https://github.com/jkrumm/basalt-ui/compare/v2.0.2...v2.0.3) (2026-10-14)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * follow the router import in app-wiring and let CI run make check ([47b6152](https://github.com/jkrumm/basalt-ui/commit/47b61526ab3ae959c1d9e4595916782defb0454f))
7
+ * label the provider's nav commands and drop duplicate nav entries ([08ec6ee](https://github.com/jkrumm/basalt-ui/commit/08ec6eef320f1ae8e072584c48a903f672222364))
8
+ * show nav badges in the collapsed rail ([5e09ea0](https://github.com/jkrumm/basalt-ui/commit/5e09ea07f05822f853c118a19c2cf72fe593501a))
9
+
1
10
  ## [2.0.2](https://github.com/jkrumm/basalt-ui/compare/v2.0.1...v2.0.2) (2026-10-14)
2
11
 
3
12
 
package/MIGRATING.md CHANGED
@@ -66,18 +66,18 @@ There is one row per SURFACE, not per export, and a surface earns a row once any
66
66
  shipped lefthook preset both changed in 1.30.0 with no row that owned them — which is how a
67
67
  consumer read this index, found nothing, and diffed a KPI row that had silently gone 4-up → 2-up.
68
68
 
69
- | Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | 1.30.2 | 1.31.0 | 1.32.0 | 1.32.1 | 1.33.0 | 1.34.0 | 1.34.1 | 1.35.0 | 1.36.0 | 1.36.1 | 1.37.0 | 1.37.1 | 1.38.0 | 1.38.1 | 1.39.0 | 1.40.0 | 1.40.1 | 1.40.2 | 1.40.3 | 1.41.0 | 2.0.0 | 2.0.1 | 2.0.2 | Unreleased |
70
- | ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------- | ------ | ------ | -------------------------------------------------------------------------------------------- | ------ | ------ | ------ | ------ | ------ | ----------------------------------------------- | ------ | ------ | ------ | ------ | ------------------------------------------------------------ | ---------- | --- | --- | --- | --- |
71
- | **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — | `--vx-hit` replaces the touch vars; `SIZE_CLASSES` (§ 1.31.0) | — | `SIZE_CLASSES`/`CONTAINER_CLASSES` actually exported (§ Fixes) | `CONTAINER_GRID_BREAKPOINTS`; `raw-breakpoint` trusts it (§ 1.33.0) | `CONTAINER_KEYS` (§ 1.34.0) | — | — | — | — | — | — | — | | `appShellInset` 20 → 16 (§ 1.38.1) | — | — | — | — | — | — | — | — | — | — |
72
- | **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — | container-class tier + `height` keys (§ 1.31.0) | — | `ResponsiveChartHeight` `sm` → key (§ Container recipes) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
73
- | **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — | `theme.breakpoints` derived, `useSizeClass`, `data-basalt-host`, `useBreakpoint` on notice (§ 1.31.0) | `useBreakpoint` removal → 1.33.0; `useSizeClass` is shell-only (§ 1.32.0) | shell-less `shell/` gutter, `AppShell` breakpoints, `useSizeClass` for overlays (§§ Shell-less, `useSizeClass`) | — | `useBreakpoint` removed (§ 1.34.0) | — | — | `useCoarsePointer()` (§ `useCoarsePointer()`) | — | — | — | — | | content gutter 16, block 8 from `sm` (§ 1.38.1) | — | — | — | — | — | — | — | — | — | — |
74
- | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — | `NumberFilter clearable`; `createSearchStore` `resets` (§ State and controls) | — | — | — | `FormSection`/`FormRow`/`FormGroup`/`FormActions` move here (§ The form layout moves) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
75
- | **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | — | — | `FormGroup` row no longer stretches (§ Fixes) | — | — | — | — | the layout + `useForm`/`schemaResolver` deprecated, removed 1.37.0 (§ The form layout moves) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
76
- | **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | — | — | `renderCard` documented (§ `renderCard`) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
77
- | **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
78
- | **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
79
- | **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | the managed block moves `CLAUDE.md` → `AGENTS.md` (§ 1.41.0) | — | — | — | — |
80
- | **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — | — | `raw-breakpoint`: the container-grid answer, `error` at 1.33.0 (§ 1.32.0) | `FormRow`/`FormGroup` homes in both lanes; `basalt-main` dropped (§ Fixes) | C1 + breakpoint pairs → `error`; `unknown-vx-token` `warn` (§ Guards) | — | — | — | `unknown-vx-token` → `error`; the token factories throw (§ Guards) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
69
+ | Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | 1.30.2 | 1.31.0 | 1.32.0 | 1.32.1 | 1.33.0 | 1.34.0 | 1.34.1 | 1.35.0 | 1.36.0 | 1.36.1 | 1.37.0 | 1.37.1 | 1.38.0 | 1.38.1 | 1.39.0 | 1.40.0 | 1.40.1 | 1.40.2 | 1.40.3 | 1.41.0 | 2.0.0 | 2.0.1 | 2.0.2 | 2.0.3 | Unreleased |
70
+ | ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------- | ------ | ------ | -------------------------------------------------------------------------------------------- | ------ | ------ | ------ | ------ | ------ | ----------------------------------------------- | ------ | ------ | ------ | ------ | ------------------------------------------------------------ | ---------- | --- | --- | --- | --- | --- |
71
+ | **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — | `--vx-hit` replaces the touch vars; `SIZE_CLASSES` (§ 1.31.0) | — | `SIZE_CLASSES`/`CONTAINER_CLASSES` actually exported (§ Fixes) | `CONTAINER_GRID_BREAKPOINTS`; `raw-breakpoint` trusts it (§ 1.33.0) | `CONTAINER_KEYS` (§ 1.34.0) | — | — | — | — | — | — | — | | `appShellInset` 20 → 16 (§ 1.38.1) | — | — | — | — | — | — | — | — | — | — | — |
72
+ | **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — | container-class tier + `height` keys (§ 1.31.0) | — | `ResponsiveChartHeight` `sm` → key (§ Container recipes) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
73
+ | **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — | `theme.breakpoints` derived, `useSizeClass`, `data-basalt-host`, `useBreakpoint` on notice (§ 1.31.0) | `useBreakpoint` removal → 1.33.0; `useSizeClass` is shell-only (§ 1.32.0) | shell-less `shell/` gutter, `AppShell` breakpoints, `useSizeClass` for overlays (§§ Shell-less, `useSizeClass`) | — | `useBreakpoint` removed (§ 1.34.0) | — | — | `useCoarsePointer()` (§ `useCoarsePointer()`) | — | — | — | — | | content gutter 16, block 8 from `sm` (§ 1.38.1) | — | — | — | — | — | — | — | — | — | — | — |
74
+ | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — | `NumberFilter clearable`; `createSearchStore` `resets` (§ State and controls) | — | — | — | `FormSection`/`FormRow`/`FormGroup`/`FormActions` move here (§ The form layout moves) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
75
+ | **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | — | — | `FormGroup` row no longer stretches (§ Fixes) | — | — | — | — | the layout + `useForm`/`schemaResolver` deprecated, removed 1.37.0 (§ The form layout moves) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
76
+ | **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | — | — | `renderCard` documented (§ `renderCard`) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
77
+ | **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
78
+ | **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
79
+ | **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | the managed block moves `CLAUDE.md` → `AGENTS.md` (§ 1.41.0) | — | — | — | — | — |
80
+ | **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — | — | `raw-breakpoint`: the container-grid answer, `error` at 1.33.0 (§ 1.32.0) | `FormRow`/`FormGroup` homes in both lanes; `basalt-main` dropped (§ Fixes) | C1 + breakpoint pairs → `error`; `unknown-vx-token` `warn` (§ Guards) | — | — | — | `unknown-vx-token` → `error`; the token factories throw (§ Guards) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
81
81
 
82
82
  ---
83
83
 
@@ -992,16 +992,34 @@ optional, subpath-required peers.
992
992
  _Nothing yet. Author the next section under this heading as `## Unreleased — <title>`; the
993
993
  release renames it and opens a new one here._
994
994
 
995
+ ## 2.0.3 — argo shell and CI friction
996
+
997
+ - `doctor`'s `app-wiring` follows the entry's relative imports (three deep): `createBasaltRouter`
998
+ may be called in `src/lib/router.ts` (or any module `main.tsx` imports), not only in `main.tsx`.
999
+ - The seeded `.github/workflows/check.yml` runs `make check` when the repo's Makefile has a
1000
+ `check:` target, after `doctor` and `sync --check` (which always run, so drift is never
1001
+ skippable); otherwise it keeps the built-in lint, theme and format steps. `init` decides once, on
1002
+ seeding. An already-seeded workflow is yours and unchanged: replace its lint/theme/format steps
1003
+ with `- run: make check` to adopt it.
1004
+ - The collapsed `BasaltShell` rail shows a dot on the icon of any nav item with a count or badge,
1005
+ and the link's accessible name carries the count (e.g. "Calendar, 3"). Expanded rendering is
1006
+ unchanged.
1007
+ - `BasaltProvider` takes `navCommandLabel?: (item) => string` to label the Spotlight `nav.<id>`
1008
+ entries it generates from the nav (e.g. "Go to …"); default unchanged. A `defineCommands` entry
1009
+ with id `nav.<id>` now replaces the generated one instead of showing twice.
1010
+
995
1011
  ## 2.0.2
996
1012
 
997
- _Nothing to migrate in this release._
1013
+ - `doctor` and `sync` are monorepo-aware. The manifest, `.claude/` and the AGENTS block stay at the
1014
+ install root; the app-scoped half (`tsconfig-base`, `app-wiring`, the `src/main.tsx`,
1015
+ `src/lib/nav.tsx` and `src/routes/__root.tsx` seeds) targets the app package: `basalt.app`
1016
+ (relative path, e.g. `"apps/dashboard"`) in the root package.json, else the one workspace package
1017
+ that depends on basalt-ui and has `src/main.tsx` or `index.html`, else the cwd. Two candidates
1018
+ fail with a message naming `basalt.app`. `doctor` run inside the app package finds the root
1019
+ manifest, and warns about a router seed an earlier `sync` left at the root — delete it.
998
1020
 
999
1021
  ## 2.0.1
1000
1022
 
1001
- _Nothing to migrate in this release._
1002
-
1003
- ## Next patch — doctor reads the peers and the config
1004
-
1005
1023
  - `doctor` gains `exact-peers`: for each exact-pinned peer of the installed basalt-ui (the three
1006
1024
  `@fontsource-variable/*`, `motion`, `remend`, …) it compares your installed version and prints a
1007
1025
  one-line `bun add <pkg>@<version>` fix on a mismatch. `bun add` accepts the mismatch silently.
@@ -1012,17 +1030,6 @@ _Nothing to migrate in this release._
1012
1030
  - Corrected the § 2.0 "Upgrading from 1.x" step 2: delete only the core, notifications and
1013
1031
  spotlight layer imports; extension packages (`@mantine/dates`, `@mantine/schedule`) stay, after
1014
1032
  `basalt-ui/styles.css`.
1015
- - `doctor` and `sync` are monorepo-aware. The manifest, `.claude/` and the AGENTS block stay at the
1016
- install root; the app-scoped half (`tsconfig-base`, `app-wiring`, the `src/main.tsx`,
1017
- `src/lib/nav.tsx` and `src/routes/__root.tsx` seeds) targets the app package: `basalt.app`
1018
- (relative path, e.g. `"apps/dashboard"`) in the root package.json, else the one workspace package
1019
- that depends on basalt-ui and has `src/main.tsx` or `index.html`, else the cwd. Two candidates
1020
- fail with a message naming `basalt.app`. `doctor` run inside the app package finds the root
1021
- manifest, and warns about a router seed an earlier `sync` left at the root — delete it.
1022
-
1023
- ## 2.0.0
1024
-
1025
- _Nothing to migrate in this release._
1026
1033
 
1027
1034
  ## 1.41.0 — the managed block moves to AGENTS.md
1028
1035
 
package/configs/check.yml CHANGED
@@ -13,6 +13,10 @@
13
13
  # from the working directory (bun's isolated linker puts it beside the package that depends
14
14
  # on it, not at the root) bunx silently downloads a DIFFERENT copy from npm and the gate
15
15
  # runs a version nobody pinned.
16
+ # The gate is the consumer's when it has one: with a Makefile `check:` target the workflow runs
17
+ # `make check` (after `doctor` and `sync --check`, which always run first so drift can never be
18
+ # skipped by a custom gate); without one it runs the built-in lint + theme guard + format steps.
19
+ # `init` decides once, when it seeds this file. Add gates to your `make check`, not to this file.
16
20
  name: check
17
21
  on:
18
22
  pull_request:
@@ -28,11 +32,7 @@ jobs:
28
32
  with:
29
33
  bun-version: latest
30
34
  - run: bun install --frozen-lockfile
31
- - run: bunx oxlint .
32
- - run: '{{BASALT_BIN}} doctor'
33
- - run: '{{BASALT_BIN}} check-theme'
34
- - run: '{{BASALT_BIN}} sync --check'
35
- - run: bunx oxfmt {{ROOTS_GLOBS}} '!**/dist/**' --check
35
+ # {{GATE_STEPS}}
36
36
  # Enable once your app defines these package.json scripts:
37
37
  # - run: bun run typecheck
38
38
  # - run: bun run test
@@ -22,7 +22,7 @@ export declare function droppedSubpathsCheck(cwd: string, cfg: BasaltConfig, ins
22
22
  export declare function seedTsconfigExtends(cwd: string): string | null;
23
23
  /**
24
24
  * `app-wiring`: the entry module `index.html` loads (default src/main.tsx) mounts `BasaltProvider`
25
- * on a `createBasaltRouter` router, and the router tree is generated: the vite config calls a
25
+ * on a `createBasaltRouter` router (called in the entry or a module it imports), and the router tree is generated: the vite config calls a
26
26
  * binding imported from `@tanstack/router-plugin` in `plugins`, or a `routeTree.gen.*` already sits
27
27
  * beside the entry (or in src/). `basaltViteConfig` does not register that plugin, so naming it
28
28
  * proves nothing.
@@ -1 +1 @@
1
- {"version":3,"file":"doctor-upgrade.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-upgrade.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,MAAM,MAAM,KAAK,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAA;AAgB9D,wEAAwE;AACxE,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,KAAG,MACc,CAAA;AA+CvD,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAqBpD;AA+CD,4FAA4F;AAC5F,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,KAAK,CAyBhF;AA4CD,mGAAmG;AACnG,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,aAAa,GACrB,KAAK,CA8BP;AAUD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkC9D;AAoDD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CA+BjD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAezD;AAaD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,KAAK,CAwB1E"}
1
+ {"version":3,"file":"doctor-upgrade.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-upgrade.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,MAAM,MAAM,KAAK,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAA;AAgB9D,wEAAwE;AACxE,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,KAAG,MACc,CAAA;AA+CvD,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAqBpD;AA+CD,4FAA4F;AAC5F,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,KAAK,CAyBhF;AA4CD,mGAAmG;AACnG,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,aAAa,GACrB,KAAK,CA8BP;AAUD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkC9D;AAgGD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAkCjD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAezD;AAaD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,KAAK,CAwB1E"}
@@ -234,6 +234,39 @@ function registersRouterPlugin(src) {
234
234
  );
235
235
  });
236
236
  }
237
+ const RELATIVE_IMPORT = /(?:\b(?:import|export)\b[^'";]*?\bfrom|\bimport)\s*['"](\.{1,2}\/[^'"]+)['"]/g;
238
+ function resolveRelative(from, spec) {
239
+ const base = resolve(dirname(from), spec.replace(/\.[jt]sx?$/, ""));
240
+ return [
241
+ resolve(dirname(from), spec),
242
+ `${base}.ts`,
243
+ `${base}.tsx`,
244
+ `${base}/index.ts`,
245
+ `${base}/index.tsx`
246
+ ].find((f) => /\.[jt]sx?$/.test(f) && existsSync(f)) ?? null;
247
+ }
248
+ function routerCreatedInImports(cwd, entry) {
249
+ const seen = /* @__PURE__ */ new Set([resolve(cwd, entry)]);
250
+ let frontier = [...seen];
251
+ for (let depth = 0; depth < 3 && frontier.length > 0; depth++) {
252
+ const next = [];
253
+ for (const file of frontier) {
254
+ for (const m of (readIfExists(file) ?? "").matchAll(RELATIVE_IMPORT)) {
255
+ const dep = resolveRelative(file, m[1] ?? "");
256
+ if (dep === null || seen.has(dep)) continue;
257
+ seen.add(dep);
258
+ const { code, inLiteral } = tokenize(readIfExists(dep) ?? "");
259
+ const called = [...code.matchAll(/\bcreateBasaltRouter\b/g)].some(
260
+ (c) => !inLiteral(c.index)
261
+ );
262
+ if (called) return true;
263
+ next.push(dep);
264
+ }
265
+ }
266
+ frontier = next;
267
+ }
268
+ return false;
269
+ }
237
270
  function appWiringCheck(cwd) {
238
271
  return guarded("app-wiring", () => {
239
272
  const entry = (htmlEntry(readIfExists(resolve(cwd, "index.html")) ?? "") ?? "src/main.tsx").replace(/^\//, "");
@@ -243,7 +276,9 @@ function appWiringCheck(cwd) {
243
276
  }
244
277
  const { code, inLiteral } = tokenize(text);
245
278
  const used = (name) => [...code.matchAll(new RegExp(`\\b${name}\\b`, "g"))].some((m) => !inLiteral(m.index));
246
- const missing = ["BasaltProvider", "createBasaltRouter"].filter((name) => !used(name));
279
+ const missing = ["BasaltProvider", "createBasaltRouter"].filter(
280
+ (name) => !used(name) && !(name === "createBasaltRouter" && routerCreatedInImports(cwd, entry))
281
+ );
247
282
  if (missing.length > 0) {
248
283
  return {
249
284
  fail: `app-wiring: ${entry} does not use ${missing.join(" or ")}. Fix: make it match ${SEED_HINT} (\`init\` seeds it only when the file is absent).`
@@ -152,6 +152,8 @@ type TemplateVars = {
152
152
  * `bunx basalt-ui` only as the pre-install fallback.
153
153
  */
154
154
  BASALT_BIN: string;
155
+ /** The CI gate steps after install: `make check` when the repo owns one, else the built-in set. */
156
+ GATE_STEPS: string;
155
157
  };
156
158
  export declare const MANIFEST_PATH = ".basalt/manifest.json";
157
159
  /** The host file the managed block is spliced into (and its manifest key). */
@@ -170,6 +172,12 @@ export declare function readFrameworkVersion(pkgRoot: string): string;
170
172
  * cached package, so it lets a consumer pin one version and be gated by another).
171
173
  */
172
174
  export declare function basaltBinCommand(install: BasaltInstall, cwd: string): string;
175
+ /**
176
+ * The consumer owns the CI gate when its Makefile has a `check:` target: `doctor` and `sync --check`
177
+ * stay as steps of their own (drift is never skippable), then `make check` replaces the built-in
178
+ * lint/theme/format steps. Without one, the built-in steps run. Rendered at `{{GATE_STEPS}}`.
179
+ */
180
+ export declare function ciGateSteps(cwd: string): string;
173
181
  /** True when both paths resolve to one file (a symlinked pair). A missing path is simply "no". */
174
182
  export declare function sameFile(a: string, b: string): boolean;
175
183
  /** True when the host text carries a managed block (begin marker present). */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -268,6 +268,18 @@ function basaltBinCommand(install, cwd) {
268
268
  const binDir = resolve(install.dir, "..", ".bin");
269
269
  return `${relativePosix(cwd, binDir).replace(/^\.\//, "")}/basalt-ui`;
270
270
  }
271
+ function ciGateSteps(cwd) {
272
+ const owned = /^check\s*:/m.test(readIfExists(resolve(cwd, "Makefile")) ?? "");
273
+ const drift = ["- run: '{{BASALT_BIN}} doctor'", "- run: '{{BASALT_BIN}} sync --check'"];
274
+ const steps = owned ? [...drift, "- run: make check"] : [
275
+ "- run: bunx oxlint .",
276
+ drift[0],
277
+ "- run: '{{BASALT_BIN}} check-theme'",
278
+ drift[1],
279
+ "- run: bunx oxfmt {{ROOTS_GLOBS}} '!**/dist/**' --check"
280
+ ];
281
+ return steps.join("\n ");
282
+ }
271
283
  function buildTemplateVars(pkgRoot, cwd, cfg) {
272
284
  const install = findBasaltInstall(cwd);
273
285
  return {
@@ -278,14 +290,16 @@ function buildTemplateVars(pkgRoot, cwd, cfg) {
278
290
  ROOTS_GLOBS: resolveRoots(cfg).map(toRootGlob).join(" "),
279
291
  OXLINT_PRESET_PATH: shippedAssetPath(install, cwd, OXLINT_FILES.preset),
280
292
  LEFTHOOK_PRESET_PATH: shippedAssetPath(install, cwd, "configs/lefthook.yml"),
281
- BASALT_BIN: basaltBinCommand(install, cwd)
293
+ BASALT_BIN: basaltBinCommand(install, cwd),
294
+ GATE_STEPS: ciGateSteps(cwd)
282
295
  };
283
296
  }
284
297
  function fillTemplate(tpl, vars) {
285
- return tpl.replace(/\{\{(\w+)\}\}/g, (m, key) => {
298
+ const fill = (text) => text.replace(/\{\{(\w+)\}\}/g, (m, key) => {
286
299
  const v = vars[key];
287
300
  return v ?? m;
288
301
  });
302
+ return fill(tpl.replace("# {{GATE_STEPS}}", vars.GATE_STEPS));
289
303
  }
290
304
  function findBlockRegion(host) {
291
305
  const start = host.indexOf(BLOCK_BEGIN_PREFIX);
@@ -1000,6 +1014,7 @@ export {
1000
1014
  basaltBinCommand,
1001
1015
  basaltPresetEntry,
1002
1016
  checkTheme,
1017
+ ciGateSteps,
1003
1018
  classify,
1004
1019
  conflictingProfileFlags,
1005
1020
  declaredProfile,
@@ -5,6 +5,7 @@ import type { BuildPaletteOpts } from '../tokens/core.js';
5
5
  import type { SizeClass } from '../shell/use-size-class.js';
6
6
  import type { Telemetry } from '../telemetry.js';
7
7
  import type { BasaltRouter } from './create-router.js';
8
+ import type { FlatNavItem } from '../router-tanstack/nav.js';
8
9
  export type BasaltProviderProps = {
9
10
  /** From `createBasaltRouter`; the provider mounts it and reads its `queryClient`. */
10
11
  router: BasaltRouter;
@@ -41,6 +42,12 @@ export type BasaltProviderProps = {
41
42
  * can answer. Default `'compact'`. SSR only; an SSR desktop app SHOULD pass it.
42
43
  */
43
44
  sizeClassHint?: SizeClass;
45
+ /**
46
+ * Label for the `nav.<id>` Spotlight entries generated from `defineNav`, e.g.
47
+ * `(item) => `Go to ${item.label}``. Default: the destination's own label. A `defineCommands`
48
+ * entry with id `nav.<id>` replaces the generated one outright.
49
+ */
50
+ navCommandLabel?: (item: FlatNavItem) => string;
44
51
  } & Omit<MantineProviderProps, 'children' | 'theme' | 'defaultColorScheme' | 'cssVariablesResolver'>;
45
52
  type RootBoundaryProps = {
46
53
  children: ReactNode;
@@ -76,6 +83,6 @@ export declare function composeInjectedCss(base: string, fontsCss: string, radiu
76
83
  * closes the gap between hydration and the next client paint.
77
84
  */
78
85
  export declare function useHostAttribute(): void;
79
- export declare function BasaltProvider({ router, theme, telemetry, fallback, injectPalette, paletteOptions, defaultColorScheme, nonce, sizeClassHint, ...rest }: BasaltProviderProps): import("react").JSX.Element;
86
+ export declare function BasaltProvider({ router, theme, telemetry, fallback, injectPalette, paletteOptions, defaultColorScheme, nonce, sizeClassHint, navCommandLabel, ...rest }: BasaltProviderProps): import("react").JSX.Element;
80
87
  export {};
81
88
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/provider/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AASzD,OAAO,EAAE,SAAS,EAAqE,MAAM,OAAO,CAAA;AACpG,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAStD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,KAAK,EAAE,YAAY,EAAuB,MAAM,iBAAiB,CAAA;AAIxE,MAAM,MAAM,mBAAmB,GAAG;IAChC,qFAAqF;IACrF,MAAM,EAAE,YAAY,CAAA;IACpB,qFAAqF;IACrF,KAAK,CAAC,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAA;IACrC;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,CAAC,CAAA;IACtD;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,gBAAgB,CAAA;IACjC,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,oBAAoB,CAAC,oBAAoB,CAAC,CAAA;IAC/D;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,SAAS,CAAA;CAC1B,GAAG,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,OAAO,GAAG,oBAAoB,GAAG,sBAAsB,CAAC,CAAA;AAIpG,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE,SAAS,CAAA;IACnB,IAAI,EAAE,SAAS,CAAA;IACf,QAAQ,EAAE,mBAAmB,CAAC,UAAU,CAAC,CAAA;CAC1C,CAAA;AAED,KAAK,aAAa,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAA;AAwB1D,mGAAmG;AACnG,qBAAa,YAAa,SAAQ,SAAS,CAAC,iBAAiB,EAAE,aAAa,CAAC;gBAC/D,KAAK,EAAE,iBAAiB;IAKpC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa;IAIrD,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI;IAczD,MAAM,IAAI,SAAS;CAO7B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM,CAER;AAYD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAmBvC;AAqMD,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,KAAK,EACL,SAAS,EACT,QAAQ,EACR,aAAoB,EACpB,cAAc,EACd,kBAA2B,EAC3B,KAAK,EACL,aAAyB,EACzB,GAAG,IAAI,EACR,EAAE,mBAAmB,+BA6ErB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/provider/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AASzD,OAAO,EAAE,SAAS,EAAqE,MAAM,OAAO,CAAA;AACpG,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAStD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,KAAK,EAAE,YAAY,EAAuB,MAAM,iBAAiB,CAAA;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAGzD,MAAM,MAAM,mBAAmB,GAAG;IAChC,qFAAqF;IACrF,MAAM,EAAE,YAAY,CAAA;IACpB,qFAAqF;IACrF,KAAK,CAAC,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAA;IACrC;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,CAAC,CAAA;IACtD;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,gBAAgB,CAAA;IACjC,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,oBAAoB,CAAC,oBAAoB,CAAC,CAAA;IAC/D;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,SAAS,CAAA;IACzB;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,CAAA;CAChD,GAAG,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,OAAO,GAAG,oBAAoB,GAAG,sBAAsB,CAAC,CAAA;AAIpG,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE,SAAS,CAAA;IACnB,IAAI,EAAE,SAAS,CAAA;IACf,QAAQ,EAAE,mBAAmB,CAAC,UAAU,CAAC,CAAA;CAC1C,CAAA;AAED,KAAK,aAAa,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAA;AAwB1D,mGAAmG;AACnG,qBAAa,YAAa,SAAQ,SAAS,CAAC,iBAAiB,EAAE,aAAa,CAAC;gBAC/D,KAAK,EAAE,iBAAiB;IAKpC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa;IAIrD,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI;IAczD,MAAM,IAAI,SAAS;CAO7B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM,CAER;AAYD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAmBvC;AAqMD,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,KAAK,EACL,SAAS,EACT,QAAQ,EACR,aAAoB,EACpB,cAAc,EACd,kBAA2B,EAC3B,KAAK,EACL,aAAyB,EACzB,eAAe,EACf,GAAG,IAAI,EACR,EAAE,mBAAmB,+BA6ErB"}
@@ -242,6 +242,7 @@ function BasaltProvider({
242
242
  defaultColorScheme = "dark",
243
243
  nonce,
244
244
  sizeClassHint = "compact",
245
+ navCommandLabel,
245
246
  ...rest
246
247
  }) {
247
248
  useDuplicateProviderGuard();
@@ -259,8 +260,8 @@ function BasaltProvider({
259
260
  const [devTool, setDevTool] = useState(null);
260
261
  const [actions, setActions] = useState([]);
261
262
  const refreshActions = useCallback(
262
- () => setActions(buildSpotlightActions(router, setDevTool)),
263
- [router]
263
+ () => setActions(buildSpotlightActions(router, setDevTool, navCommandLabel)),
264
+ [router, navCommandLabel]
264
265
  );
265
266
  useEffect(refreshActions, [refreshActions]);
266
267
  const resolvedTheme = useLabTheme(theme);
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * The provider's Spotlight list: the `defineCommands` registry, then the `defineNav` destinations
3
- * (via `flattenNav`), then the three dev-dock commands in dev. A snapshot rebuilt each time Spotlight
3
+ * (via `flattenNav`, skipping any id a registered `nav.<id>` command already owns), then the three dev-dock commands in dev. A snapshot rebuilt each time Spotlight
4
4
  * opens, so a command's `when()` is current. Not for use during render (`detectMac` reads `navigator`).
5
5
  */
6
6
  import type { SpotlightActionData } from '@mantine/spotlight';
7
7
  import type { AnyRouter } from '@tanstack/react-router';
8
+ import type { FlatNavItem } from '../router-tanstack/nav.js';
8
9
  import type { BasaltDevDockTool } from '../dev-dock.js';
9
- export declare function buildSpotlightActions(router: AnyRouter, openDevTool: (tool: BasaltDevDockTool) => void): SpotlightActionData[];
10
+ export declare function buildSpotlightActions(router: AnyRouter, openDevTool: (tool: BasaltDevDockTool) => void, navCommandLabel?: (item: FlatNavItem) => string): SpotlightActionData[];
10
11
  //# sourceMappingURL=spotlight-actions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spotlight-actions.d.ts","sourceRoot":"","sources":["../../src/provider/spotlight-actions.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAIvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AASpD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,GAC7C,mBAAmB,EAAE,CA8CvB"}
1
+ {"version":3,"file":"spotlight-actions.d.ts","sourceRoot":"","sources":["../../src/provider/spotlight-actions.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAIvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AASpD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,EAC9C,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,GAC9C,mBAAmB,EAAE,CAiDvB"}
@@ -7,9 +7,10 @@ const DEV_TOOLS = [
7
7
  { tool: "query", label: "Query devtools" },
8
8
  { tool: "theme", label: "Theme lab" }
9
9
  ];
10
- function buildSpotlightActions(router, openDevTool) {
10
+ function buildSpotlightActions(router, openDevTool, navCommandLabel) {
11
11
  const actions = [];
12
- for (const [id, cmd] of Object.entries(getActiveCommands())) {
12
+ const commands = getActiveCommands();
13
+ for (const [id, cmd] of Object.entries(commands)) {
13
14
  if (cmd.when !== void 0 && !cmd.when()) continue;
14
15
  actions.push({
15
16
  id,
@@ -27,9 +28,10 @@ function buildSpotlightActions(router, openDevTool) {
27
28
  if (nav !== void 0) {
28
29
  for (const item of flattenNav(nav)) {
29
30
  if (item.disabled === true) continue;
31
+ if (`nav.${item.id}` in commands) continue;
30
32
  actions.push({
31
33
  id: `nav.${item.id}`,
32
- label: item.label,
34
+ label: navCommandLabel?.(item) ?? item.label,
33
35
  group: item.groupLabel,
34
36
  leftSection: item.icon,
35
37
  // `link` is the destination's own `linkOptions`, so a store-backed `search` thunk restores
@@ -1 +1 @@
1
- {"version":3,"file":"app-sidebar.d.ts","sourceRoot":"","sources":["../../src/shell/app-sidebar.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAElD,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAe,cAAc,EAAE,MAAM,aAAa,CAAA;AAI5E,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAE1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAgBjF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C;;;;OAIG;IACH,KAAK,EAAE,WAAW,GAAG;QAAE,IAAI,CAAC,EAAE,eAAe,EAAE,CAAA;KAAE,CAAA;IACjD,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,SAAS,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,IAAI,CAAA;IAC5B;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAA;IACtC;;;;;;;;;;;;;;;;OAgBG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IACvC;;;;OAIG;IACH,OAAO,CAAC,EAAE,kBAAkB,CAAA;IAC5B;;;;OAIG;IACH,MAAM,CAAC,EAAE,mBAAmB,GAAG;QAAE,OAAO,CAAC,EAAE,oBAAoB,CAAA;KAAE,CAAA;IACjE;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;CACxB,CAAA;AAmTD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,+BA2KhD"}
1
+ {"version":3,"file":"app-sidebar.d.ts","sourceRoot":"","sources":["../../src/shell/app-sidebar.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAElD,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAe,cAAc,EAAE,MAAM,aAAa,CAAA;AAI5E,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAE1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAgBjF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C;;;;OAIG;IACH,KAAK,EAAE,WAAW,GAAG;QAAE,IAAI,CAAC,EAAE,eAAe,EAAE,CAAA;KAAE,CAAA;IACjD,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,SAAS,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,IAAI,CAAA;IAC5B;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAA;IACtC;;;;;;;;;;;;;;;;OAgBG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IACvC;;;;OAIG;IACH,OAAO,CAAC,EAAE,kBAAkB,CAAA;IAC5B;;;;OAIG;IACH,MAAM,CAAC,EAAE,mBAAmB,GAAG;QAAE,OAAO,CAAC,EAAE,oBAAoB,CAAA;KAAE,CAAA;IACjE;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;CACxB,CAAA;AAiUD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,+BA2KhD"}
@@ -88,14 +88,21 @@ function NavLinkBody({
88
88
  collapsed = false
89
89
  }) {
90
90
  const Anchor = item.Anchor;
91
+ const icon = iconOrInitial(item.icon, item.label);
92
+ const railDot = collapsed && (item.badge !== void 0 && item.badge !== null || Boolean(item.count));
91
93
  const shared = {
92
94
  classNames: { root: classes.link },
93
95
  label: item.label,
94
- leftSection: iconOrInitial(item.icon, item.label),
96
+ leftSection: railDot ? /* @__PURE__ */ jsxs("span", { className: classes.navIconDotHost, children: [
97
+ icon,
98
+ /* @__PURE__ */ jsx("span", { className: classes.blockDot, "data-basalt-rail-dot": true, "aria-hidden": true })
99
+ ] }) : icon,
95
100
  // Only the collapsed rail hides the label (CSS), leaving the icon alone — name the link there.
96
101
  // Expanded, the visible text IS the name and carries the count badge; an `aria-label` would
97
102
  // override both and drop the count ("Inbox 5" → "Inbox").
98
- ...collapsed && { "aria-label": item.label },
103
+ ...collapsed && {
104
+ "aria-label": item.count ? `${item.label}, ${item.count}` : item.label
105
+ },
99
106
  rightSection: item.badge ?? (item.count ? /* @__PURE__ */ jsx(NavCountBadge, { count: item.count }) : void 0),
100
107
  active,
101
108
  // Basalt is the one that stamps `aria-current`, not the theme and not a router `<Link>`'s own
@@ -472,6 +472,12 @@
472
472
  box-shadow: 0 0 0 2px var(--mantine-color-body);
473
473
  }
474
474
 
475
+ /* Host for a nav row's rail dot: the dot anchors on the icon itself, not the 48px row. */
476
+ .navIconDotHost {
477
+ position: relative;
478
+ display: inline-flex;
479
+ }
480
+
475
481
  .blockItems {
476
482
  display: flex;
477
483
  flex-direction: column;
@@ -29,6 +29,7 @@ declare const classes: {
29
29
  readonly blockHeaderButton: string
30
30
  readonly blockIcon: string
31
31
  readonly blockDot: string
32
+ readonly navIconDotHost: string
32
33
  readonly blockItems: string
33
34
  readonly blockRow: string
34
35
  readonly blockRowStatic: string
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # basalt-ui v2.0.2
1
+ # basalt-ui v2.0.3
2
2
  # Opinionated framework for Mantine-based React apps — theme, app shell, TanStack Charts, toolchain presets
3
3
  #
4
4
  # Entry points: use the exact import specifier below; do not guess.
@@ -13,7 +13,7 @@
13
13
  # A subpath's `Install:` line is its required peers; `OptionalPeers:` are import()-only, install on use.
14
14
 
15
15
  Package: basalt-ui
16
- Version: 2.0.2
16
+ Version: 2.0.3
17
17
 
18
18
  ## basalt-ui
19
19
  Import: import { ... } from 'basalt-ui'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basalt-ui",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Opinionated framework for Mantine-based React apps — theme, app shell, TanStack Charts, toolchain presets",
5
5
  "keywords": [
6
6
  "charts",
@@ -352,9 +352,53 @@ function registersRouterPlugin(src: string): boolean {
352
352
  })
353
353
  }
354
354
 
355
+ const RELATIVE_IMPORT =
356
+ /(?:\b(?:import|export)\b[^'";]*?\bfrom|\bimport)\s*['"](\.{1,2}\/[^'"]+)['"]/g
357
+
358
+ /** The source file a relative specifier names: as written, `.ts`/`.tsx` appended, or a directory index. */
359
+ function resolveRelative(from: string, spec: string): string | null {
360
+ const base = resolve(dirname(from), spec.replace(/\.[jt]sx?$/, ''))
361
+ return (
362
+ [
363
+ resolve(dirname(from), spec),
364
+ `${base}.ts`,
365
+ `${base}.tsx`,
366
+ `${base}/index.ts`,
367
+ `${base}/index.tsx`,
368
+ ].find((f) => /\.[jt]sx?$/.test(f) && existsSync(f)) ?? null
369
+ )
370
+ }
371
+
372
+ /**
373
+ * Whether a module the entry imports (relative imports, three deep) calls `createBasaltRouter` —
374
+ * the router often lives in a singleton module other code also imports.
375
+ */
376
+ function routerCreatedInImports(cwd: string, entry: string): boolean {
377
+ const seen = new Set([resolve(cwd, entry)])
378
+ let frontier = [...seen]
379
+ for (let depth = 0; depth < 3 && frontier.length > 0; depth++) {
380
+ const next: string[] = []
381
+ for (const file of frontier) {
382
+ for (const m of (readIfExists(file) ?? '').matchAll(RELATIVE_IMPORT)) {
383
+ const dep = resolveRelative(file, m[1] ?? '')
384
+ if (dep === null || seen.has(dep)) continue
385
+ seen.add(dep)
386
+ const { code, inLiteral } = tokenize(readIfExists(dep) ?? '')
387
+ const called = [...code.matchAll(/\bcreateBasaltRouter\b/g)].some(
388
+ (c) => !inLiteral(c.index),
389
+ )
390
+ if (called) return true
391
+ next.push(dep)
392
+ }
393
+ }
394
+ frontier = next
395
+ }
396
+ return false
397
+ }
398
+
355
399
  /**
356
400
  * `app-wiring`: the entry module `index.html` loads (default src/main.tsx) mounts `BasaltProvider`
357
- * on a `createBasaltRouter` router, and the router tree is generated: the vite config calls a
401
+ * on a `createBasaltRouter` router (called in the entry or a module it imports), and the router tree is generated: the vite config calls a
358
402
  * binding imported from `@tanstack/router-plugin` in `plugins`, or a `routeTree.gen.*` already sits
359
403
  * beside the entry (or in src/). `basaltViteConfig` does not register that plugin, so naming it
360
404
  * proves nothing.
@@ -371,7 +415,10 @@ export function appWiringCheck(cwd: string): Check {
371
415
  const { code, inLiteral } = tokenize(text)
372
416
  const used = (name: string): boolean =>
373
417
  [...code.matchAll(new RegExp(`\\b${name}\\b`, 'g'))].some((m) => !inLiteral(m.index))
374
- const missing = ['BasaltProvider', 'createBasaltRouter'].filter((name) => !used(name))
418
+ const missing = ['BasaltProvider', 'createBasaltRouter'].filter(
419
+ (name) =>
420
+ !used(name) && !(name === 'createBasaltRouter' && routerCreatedInImports(cwd, entry)),
421
+ )
375
422
  if (missing.length > 0) {
376
423
  return {
377
424
  fail:
package/src/cli/index.ts CHANGED
@@ -474,6 +474,8 @@ type TemplateVars = {
474
474
  * `bunx basalt-ui` only as the pre-install fallback.
475
475
  */
476
476
  BASALT_BIN: string
477
+ /** The CI gate steps after install: `make check` when the repo owns one, else the built-in set. */
478
+ GATE_STEPS: string
477
479
  }
478
480
 
479
481
  // The block markers the AGENTS.md template emits. The begin marker carries the framework version
@@ -536,6 +538,26 @@ export function basaltBinCommand(install: BasaltInstall, cwd: string): string {
536
538
  return `${relativePosix(cwd, binDir).replace(/^\.\//, '')}/basalt-ui`
537
539
  }
538
540
 
541
+ /**
542
+ * The consumer owns the CI gate when its Makefile has a `check:` target: `doctor` and `sync --check`
543
+ * stay as steps of their own (drift is never skippable), then `make check` replaces the built-in
544
+ * lint/theme/format steps. Without one, the built-in steps run. Rendered at `{{GATE_STEPS}}`.
545
+ */
546
+ export function ciGateSteps(cwd: string): string {
547
+ const owned = /^check\s*:/m.test(readIfExists(resolve(cwd, 'Makefile')) ?? '')
548
+ const drift = ["- run: '{{BASALT_BIN}} doctor'", "- run: '{{BASALT_BIN}} sync --check'"]
549
+ const steps = owned
550
+ ? [...drift, '- run: make check']
551
+ : [
552
+ '- run: bunx oxlint .',
553
+ drift[0],
554
+ "- run: '{{BASALT_BIN}} check-theme'",
555
+ drift[1],
556
+ "- run: bunx oxfmt {{ROOTS_GLOBS}} '!**/dist/**' --check",
557
+ ]
558
+ return steps.join('\n ')
559
+ }
560
+
539
561
  /** Build the template-variable map from the framework root + consumer cwd/config. */
540
562
  function buildTemplateVars(pkgRoot: string, cwd: string, cfg: BasaltConfig): TemplateVars {
541
563
  const install = findBasaltInstall(cwd)
@@ -548,15 +570,20 @@ function buildTemplateVars(pkgRoot: string, cwd: string, cfg: BasaltConfig): Tem
548
570
  OXLINT_PRESET_PATH: shippedAssetPath(install, cwd, OXLINT_FILES.preset),
549
571
  LEFTHOOK_PRESET_PATH: shippedAssetPath(install, cwd, 'configs/lefthook.yml'),
550
572
  BASALT_BIN: basaltBinCommand(install, cwd),
573
+ GATE_STEPS: ciGateSteps(cwd),
551
574
  }
552
575
  }
553
576
 
554
577
  /** Substitute every `{{KEY}}` placeholder from the variable map; unknown keys are left verbatim. */
555
578
  function fillTemplate(tpl: string, vars: TemplateVars): string {
556
- return tpl.replace(/\{\{(\w+)\}\}/g, (m, key: string) => {
557
- const v = (vars as Record<string, string>)[key]
558
- return v ?? m
559
- })
579
+ const fill = (text: string): string =>
580
+ text.replace(/\{\{(\w+)\}\}/g, (m, key: string) => {
581
+ const v = (vars as Record<string, string>)[key]
582
+ return v ?? m
583
+ })
584
+ // The marker is a YAML comment so the template stays valid YAML; GATE_STEPS itself carries
585
+ // placeholders, so it expands before the generic pass.
586
+ return fill(tpl.replace('# {{GATE_STEPS}}', vars.GATE_STEPS))
560
587
  }
561
588
 
562
589
  /**
@@ -41,6 +41,7 @@ import type { Telemetry } from '../telemetry'
41
41
  import type { BasaltDevDockTool } from '../dev-dock'
42
42
  import type { BasaltRouter, BasaltRouterContext } from './create-router'
43
43
  import { useRegistryHotkeys } from './hotkeys'
44
+ import type { FlatNavItem } from '../router-tanstack/nav'
44
45
  import { buildSpotlightActions } from './spotlight-actions'
45
46
 
46
47
  export type BasaltProviderProps = {
@@ -79,6 +80,12 @@ export type BasaltProviderProps = {
79
80
  * can answer. Default `'compact'`. SSR only; an SSR desktop app SHOULD pass it.
80
81
  */
81
82
  sizeClassHint?: SizeClass
83
+ /**
84
+ * Label for the `nav.<id>` Spotlight entries generated from `defineNav`, e.g.
85
+ * `(item) => `Go to ${item.label}``. Default: the destination's own label. A `defineCommands`
86
+ * entry with id `nav.<id>` replaces the generated one outright.
87
+ */
88
+ navCommandLabel?: (item: FlatNavItem) => string
82
89
  } & Omit<MantineProviderProps, 'children' | 'theme' | 'defaultColorScheme' | 'cssVariablesResolver'>
83
90
 
84
91
  // ── Root error boundary ───────────────────────────────────────────────────────────────────────────
@@ -408,6 +415,7 @@ export function BasaltProvider({
408
415
  defaultColorScheme = 'dark',
409
416
  nonce,
410
417
  sizeClassHint = 'compact',
418
+ navCommandLabel,
411
419
  ...rest
412
420
  }: BasaltProviderProps) {
413
421
  useDuplicateProviderGuard()
@@ -432,8 +440,8 @@ export function BasaltProvider({
432
440
  // appears, not frozen at mount — and `detectMac` (inside the builder) never runs during render.
433
441
  const [actions, setActions] = useState<SpotlightActionData[]>([])
434
442
  const refreshActions = useCallback(
435
- () => setActions(buildSpotlightActions(router, setDevTool)),
436
- [router],
443
+ () => setActions(buildSpotlightActions(router, setDevTool, navCommandLabel)),
444
+ [router, navCommandLabel],
437
445
  )
438
446
  useEffect(refreshActions, [refreshActions])
439
447
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The provider's Spotlight list: the `defineCommands` registry, then the `defineNav` destinations
3
- * (via `flattenNav`), then the three dev-dock commands in dev. A snapshot rebuilt each time Spotlight
3
+ * (via `flattenNav`, skipping any id a registered `nav.<id>` command already owns), then the three dev-dock commands in dev. A snapshot rebuilt each time Spotlight
4
4
  * opens, so a command's `when()` is current. Not for use during render (`detectMac` reads `navigator`).
5
5
  */
6
6
  import type { SpotlightActionData } from '@mantine/spotlight'
@@ -8,6 +8,7 @@ import type { AnyRouter } from '@tanstack/react-router'
8
8
  import { getActiveCommands } from '../commands/define-commands'
9
9
  import { detectMac, formatShortcutDisplay } from '../commands/shortcut-format'
10
10
  import { flattenNav, getActiveNav } from '../router-tanstack/nav'
11
+ import type { FlatNavItem } from '../router-tanstack/nav'
11
12
  import type { BasaltDevDockTool } from '../dev-dock'
12
13
  import { runCommandSafely } from './hotkeys'
13
14
 
@@ -20,10 +21,12 @@ const DEV_TOOLS: ReadonlyArray<{ tool: BasaltDevDockTool; label: string }> = [
20
21
  export function buildSpotlightActions(
21
22
  router: AnyRouter,
22
23
  openDevTool: (tool: BasaltDevDockTool) => void,
24
+ navCommandLabel?: (item: FlatNavItem) => string,
23
25
  ): SpotlightActionData[] {
24
26
  const actions: SpotlightActionData[] = []
27
+ const commands = getActiveCommands()
25
28
 
26
- for (const [id, cmd] of Object.entries(getActiveCommands())) {
29
+ for (const [id, cmd] of Object.entries(commands)) {
27
30
  if (cmd.when !== undefined && !cmd.when()) continue
28
31
  actions.push({
29
32
  id,
@@ -42,9 +45,11 @@ export function buildSpotlightActions(
42
45
  if (nav !== undefined) {
43
46
  for (const item of flattenNav(nav)) {
44
47
  if (item.disabled === true) continue
48
+ // A registered `nav.<id>` command owns that destination: the consumer wins, no duplicate.
49
+ if (`nav.${item.id}` in commands) continue
45
50
  actions.push({
46
51
  id: `nav.${item.id}`,
47
- label: item.label,
52
+ label: navCommandLabel?.(item) ?? item.label,
48
53
  group: item.groupLabel,
49
54
  leftSection: item.icon,
50
55
  // `link` is the destination's own `linkOptions`, so a store-backed `search` thunk restores
@@ -472,6 +472,12 @@
472
472
  box-shadow: 0 0 0 2px var(--mantine-color-body);
473
473
  }
474
474
 
475
+ /* Host for a nav row's rail dot: the dot anchors on the icon itself, not the 48px row. */
476
+ .navIconDotHost {
477
+ position: relative;
478
+ display: inline-flex;
479
+ }
480
+
475
481
  .blockItems {
476
482
  display: flex;
477
483
  flex-direction: column;
@@ -29,6 +29,7 @@ declare const classes: {
29
29
  readonly blockHeaderButton: string
30
30
  readonly blockIcon: string
31
31
  readonly blockDot: string
32
+ readonly navIconDotHost: string
32
33
  readonly blockItems: string
33
34
  readonly blockRow: string
34
35
  readonly blockRowStatic: string
@@ -207,14 +207,28 @@ function NavLinkBody({
207
207
  collapsed?: boolean
208
208
  }) {
209
209
  const Anchor = item.Anchor
210
+ const icon = iconOrInitial(item.icon, item.label)
211
+ // The rail hides the right section (where the badge lives), so a badged row keeps a dot on its
212
+ // icon instead — the count rides the accessible name since the number itself has no room.
213
+ const railDot =
214
+ collapsed && ((item.badge !== undefined && item.badge !== null) || Boolean(item.count))
210
215
  const shared = {
211
216
  classNames: { root: classes.link },
212
217
  label: item.label,
213
- leftSection: iconOrInitial(item.icon, item.label),
218
+ leftSection: railDot ? (
219
+ <span className={classes.navIconDotHost}>
220
+ {icon}
221
+ <span className={classes.blockDot} data-basalt-rail-dot aria-hidden />
222
+ </span>
223
+ ) : (
224
+ icon
225
+ ),
214
226
  // Only the collapsed rail hides the label (CSS), leaving the icon alone — name the link there.
215
227
  // Expanded, the visible text IS the name and carries the count badge; an `aria-label` would
216
228
  // override both and drop the count ("Inbox 5" → "Inbox").
217
- ...(collapsed && { 'aria-label': item.label }),
229
+ ...(collapsed && {
230
+ 'aria-label': item.count ? `${item.label}, ${item.count}` : item.label,
231
+ }),
218
232
  rightSection: item.badge ?? (item.count ? <NavCountBadge count={item.count} /> : undefined),
219
233
  active,
220
234
  // Basalt is the one that stamps `aria-current`, not the theme and not a router `<Link>`'s own