basalt-ui 1.21.0 → 1.23.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 +11 -1
- package/CHANGELOG.md +33 -0
- package/MIGRATING.md +205 -9
- package/README.md +63 -16
- package/agent/rules/basalt-app.md +39 -9
- package/agent/rules/basalt-charts.md +41 -6
- package/agent/rules/basalt-router.md +1 -1
- package/agent/rules/basalt-state.md +4 -4
- package/agent/rules/basalt-tokens.md +25 -4
- package/agent/skills/basalt-app/SKILL.md +4 -3
- package/agent/skills/basalt-charts/SKILL.md +23 -8
- package/agent/templates/DESIGN.md.tpl +3 -1
- package/configs/oxlint-plugin.js +86 -9
- package/configs/oxlint-plugin.test.ts +173 -0
- package/dist/charts/hooks/useBandPlot.d.ts +138 -0
- package/dist/charts/hooks/useBandPlot.d.ts.map +1 -0
- package/dist/charts/hooks/useBandPlot.js +127 -0
- package/dist/charts/index.d.ts +4 -0
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +14 -0
- package/dist/charts/kinds/BandStrip.d.ts +113 -0
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -0
- package/dist/charts/kinds/BandStrip.js +218 -0
- package/dist/charts/kinds/BandStrip.test.d.ts +2 -0
- package/dist/charts/kinds/BandStrip.test.d.ts.map +1 -0
- package/dist/charts/kinds/Bars.d.ts +5 -1
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +2 -0
- package/dist/charts/kinds/MirroredBars.d.ts +93 -0
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -0
- package/dist/charts/kinds/MirroredBars.js +315 -0
- package/dist/charts/kinds/MirroredBars.test.d.ts +2 -0
- package/dist/charts/kinds/MirroredBars.test.d.ts.map +1 -0
- package/dist/charts/kinds/MultiLine.d.ts +5 -1
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.js +2 -0
- package/dist/charts/kinds/StackedArea.d.ts +5 -1
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.js +2 -0
- package/dist/charts/kinds/ZonedLine.d.ts +5 -1
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.js +2 -0
- package/dist/charts/primitives/CartesianChart.d.ts +18 -2
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +5 -2
- package/dist/charts/primitives/HatchPattern.d.ts +31 -0
- package/dist/charts/primitives/HatchPattern.d.ts.map +1 -0
- package/dist/charts/primitives/HatchPattern.js +31 -0
- package/dist/cli/index.d.ts +9 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +275 -48
- package/dist/cli/shipped-versions.test.d.ts +2 -0
- package/dist/cli/shipped-versions.test.d.ts.map +1 -0
- package/dist/guard/index.d.ts +67 -0
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +70 -11
- package/dist/vite.d.ts +44 -2
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +71 -26
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/charts/hooks/useBandPlot.ts +302 -0
- package/src/charts/index.ts +14 -0
- package/src/charts/kinds/BandStrip.test.tsx +200 -0
- package/src/charts/kinds/BandStrip.tsx +364 -0
- package/src/charts/kinds/Bars.tsx +7 -1
- package/src/charts/kinds/DualPanel.test.tsx +10 -4
- package/src/charts/kinds/MirroredBars.test.tsx +193 -0
- package/src/charts/kinds/MirroredBars.tsx +455 -0
- package/src/charts/kinds/MultiLine.tsx +7 -1
- package/src/charts/kinds/StackedArea.tsx +7 -1
- package/src/charts/kinds/ZonedLine.tsx +7 -1
- package/src/charts/primitives/CartesianChart.test.tsx +86 -4
- package/src/charts/primitives/CartesianChart.tsx +28 -3
- package/src/charts/primitives/HatchPattern.tsx +55 -0
- package/src/cli/check-theme.test.ts +35 -0
- package/src/cli/help.test.ts +5 -1
- package/src/cli/index.ts +552 -79
- package/src/cli/shipped-versions.test.ts +83 -0
- package/src/cli/toolchain-wiring.test.ts +362 -6
- package/src/cli/waiver-audit.test.ts +88 -4
- package/src/guard/check-source.test.ts +293 -0
- package/src/guard/index.ts +209 -20
- package/src/vite.test.ts +112 -0
- package/src/vite.ts +147 -31
package/AGENTS.md
CHANGED
|
@@ -75,6 +75,14 @@ bunx basalt-ui info --json # stable JSON surface map
|
|
|
75
75
|
bunx basalt-ui guard-hook # PreToolUse theme-guard adapter: reads a Write/Edit payload on stdin, denies off-palette writes
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
`check-theme`, `doctor` and `sync` resolve their project identically: `BASALT_CWD` wins, then the
|
|
79
|
+
cwd, then the workspace packages the root declares, then a two-level layout scan (which is what a
|
|
80
|
+
repo with no `workspaces` field and the install one level down needs). Two candidates is ambiguous
|
|
81
|
+
and reported, never guessed. `sync` on a `tokens-only` consumer prints `n/a` and exits 0.
|
|
82
|
+
|
|
83
|
+
**Checking what an upgrade actually does? Run the local bin, not `bunx`.** `bunx` does not
|
|
84
|
+
re-resolve a cached package, so it can execute the version you just upgraded away from.
|
|
85
|
+
|
|
78
86
|
Register `guard-hook` in `.claude/settings.json` under `hooks.PreToolUse` with matcher
|
|
79
87
|
`Write|Edit|MultiEdit` and command `bunx basalt-ui guard-hook` so the agent can't write off-palette
|
|
80
88
|
colors without a `theme-allow` comment.
|
|
@@ -87,7 +95,9 @@ over the declaration it terminates. Whole-file scope is `theme-allow-file <id>
|
|
|
87
95
|
`theme-allow-file` waives nothing. A bare `theme-allow` still waives everything on its placement but
|
|
88
96
|
reports `theme-allow-unscoped`. A word in the id slot that names no rule waives nothing.
|
|
89
97
|
`.json`/`.webmanifest` use a `"basalt:theme-allow[-file]"` member. Audit them all with
|
|
90
|
-
`basalt-ui check-theme --audit-allows`, which exits 1 on a waiver that suppresses nothing.
|
|
98
|
+
`basalt-ui check-theme --audit-allows`, which exits 1 on a waiver that suppresses nothing. It judges
|
|
99
|
+
plugin-rule waivers too (since 1.22.0), by re-running oxlint — so it needs oxlint reachable;
|
|
100
|
+
otherwise the verdict is "cannot judge", never "dead".
|
|
91
101
|
|
|
92
102
|
> **Framework-internal only** — `bunx basalt-ui check-coverage` is a self-consistency gate for the
|
|
93
103
|
> basalt-ui repo itself (asserts SURFACES ↔ rule files ↔ skill files ↔ package.json exports). It
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
# [1.23.0](https://github.com/jkrumm/basalt-ui/compare/v1.22.0...v1.23.0) (2026-08-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* make sync resolve, and report, what the other two commands already do ([fcc5d3b](https://github.com/jkrumm/basalt-ui/commit/fcc5d3b862e90db03be9ae3daddbde56da15e415))
|
|
7
|
+
* make the two annotation parsers agree, and pin the whole shape grid ([5a25825](https://github.com/jkrumm/basalt-ui/commit/5a25825342930236e35a4c8ecd1157fc6c3c8eda))
|
|
8
|
+
* stop a non-finite absent share painting a band that never renders ([e8fae5b](https://github.com/jkrumm/basalt-ui/commit/e8fae5b205a9f74b31289591f8948f50a3e647eb))
|
|
9
|
+
* teach chart-in-raw-surface the two new kinds ([fc34a3b](https://github.com/jkrumm/basalt-ui/commit/fc34a3bed4e05083288dcd22fc1e4b2cc81a1863))
|
|
10
|
+
* teach unframed-chart the two new kinds too ([cb4e5b7](https://github.com/jkrumm/basalt-ui/commit/cb4e5b7977ff8a141bc4ec78d3c18b515fc41fed))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* give CartesianChart the x tick-VALUES seam its band siblings already have ([d94137d](https://github.com/jkrumm/basalt-ui/commit/d94137d20d496e8da1e0e0869c01d2d4c1577560))
|
|
16
|
+
* let basaltAppPlugin's icons option name a real icon file ([c0b182e](https://github.com/jkrumm/basalt-ui/commit/c0b182e580c6289b5018be9d840a588e29dea5d3))
|
|
17
|
+
* playground routes for BandStrip and MirroredBars ([ba6172d](https://github.com/jkrumm/basalt-ui/commit/ba6172d9ad40cf7b1e3c183e2598e79b5326c2e7))
|
|
18
|
+
* two banded chart kinds — BandStrip and MirroredBars ([0e4f5f7](https://github.com/jkrumm/basalt-ui/commit/0e4f5f7107e71be92eb3b6bec6ebf5587d5c607b))
|
|
19
|
+
|
|
20
|
+
# [1.22.0](https://github.com/jkrumm/basalt-ui/compare/v1.21.0...v1.22.0) (2026-08-22)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
* let `icons: false` reach the manifest it was only skipping in the head ([58abe27](https://github.com/jkrumm/basalt-ui/commit/58abe27be10e5baf7c4659aa79904a7646ff092d))
|
|
26
|
+
* scope the shadow-export tripwire to basalt consumers, and to components ([60716d4](https://github.com/jkrumm/basalt-ui/commit/60716d4ba00908c1a157f5568e192c6c50e438ec))
|
|
27
|
+
* stop sync scaffolding a second consumer, and give the audit the oxlint half ([45cf3d3](https://github.com/jkrumm/basalt-ui/commit/45cf3d340f081e61f3f10f852d12f79928993d2a))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
|
|
32
|
+
* give the waiver audit an annotation reader, and fix two shapes ([0dde850](https://github.com/jkrumm/basalt-ui/commit/0dde85052269780a61ead0688005db16e24f009a))
|
|
33
|
+
|
|
1
34
|
# [1.21.0](https://github.com/jkrumm/basalt-ui/compare/v1.20.0...v1.21.0) (2026-08-22)
|
|
2
35
|
|
|
3
36
|
|
package/MIGRATING.md
CHANGED
|
@@ -8,9 +8,13 @@ renamed exports per minor, with the replacement.
|
|
|
8
8
|
Reconstructed from `git diff` over the published export surface across `v1.0.0..v1.19.1`, then
|
|
9
9
|
cross-checked against the repo's `scripts/export-surface.json` snapshot. **Every replacement below
|
|
10
10
|
was re-audited against the built declaration files at 1.20.0 (2026-08-22)** after round 5 caught one
|
|
11
|
-
row that was wrong. That pass corrected 4 table rows and 3 prose claims. The 1.
|
|
12
|
-
written against source, not against
|
|
13
|
-
disagree.
|
|
11
|
+
row that was wrong. That pass corrected 4 table rows and 3 prose claims. The 1.21.0, 1.22.0 and
|
|
12
|
+
`Unreleased` sections were written against source, not against their commit messages. Check the
|
|
13
|
+
types, not this table, if the two disagree.
|
|
14
|
+
|
|
15
|
+
**The newest section is headed `## Unreleased`, and stays that way until npm serves it.** This file
|
|
16
|
+
is written before `semantic-release` picks the number, so a number written here is a guess — and it
|
|
17
|
+
was wrong three rounds running.
|
|
14
18
|
|
|
15
19
|
**No majors, by policy.** A rename or a removal ships as a plain `feat:` on the 1.x line, so a minor
|
|
16
20
|
bump can require code changes. Skipping several at once is the expensive case — read every section
|
|
@@ -25,7 +29,199 @@ at 1.3.0, `./agent-chat` at 1.10.0.
|
|
|
25
29
|
|
|
26
30
|
---
|
|
27
31
|
|
|
28
|
-
##
|
|
32
|
+
## Unreleased
|
|
33
|
+
|
|
34
|
+
**The heading has no number on purpose.** Three rounds running, the newest heading here named a
|
|
35
|
+
version npm never served — `1.20.1`, then `1.21.1` — because this file is written before
|
|
36
|
+
`semantic-release` computes the number, and a `feat:` in the batch turns the guess into a phantom.
|
|
37
|
+
`shipped-versions.test.ts` now fails any file `init`/`sync` copy into a consumer that names a
|
|
38
|
+
version `CHANGELOG.md` does not record. This file is not in that set; the same discipline applies
|
|
39
|
+
by convention. Rename the section at release, or leave it — a reader can resolve `Unreleased`
|
|
40
|
+
against `CHANGELOG.md`, and could never resolve `1.21.1`.
|
|
41
|
+
|
|
42
|
+
**No export removed or renamed; six new runtime exports and nine new types.** Nothing you wrote
|
|
43
|
+
changed. Every entry is additive, and the batch runs the other way from the last three: those made
|
|
44
|
+
the guard stricter, this one makes the framework more expressive.
|
|
45
|
+
|
|
46
|
+
**Two chart kinds `CartesianChart` structurally could not host.** It renders `AxisLeftNumeric`
|
|
47
|
+
unconditionally and builds x as `scalePoint` — positions, no widths. `BandStrip` has no y dimension
|
|
48
|
+
to axis; `MirroredBars` has two, and needs band widths. Both compose `ChartFrame` directly and
|
|
49
|
+
declare themselves with `theme-allow-file hand-rolled-plot`, alongside `DualPanel`.
|
|
50
|
+
|
|
51
|
+
| New on `basalt-ui/charts` | Note |
|
|
52
|
+
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| `BandStrip` (+ `BandStripProps`, `BandStripSeries`, `BandSpan`) | 1-D categorical bands. `getBand(d) => { state, fill?, absentFraction?, marker? }`; `series` IS the state set, so a strip cannot name a state it does not draw. `cursorResolution` defaults `'leading'` |
|
|
54
|
+
| `MirroredBars` (+ `MirroredBarsProps`, `MirroredBarPane`) | two bar panes, one x scale, one baseline, independent domains. `up`/`down` take `{ key, max?, autoMaxFloor?, ticks?, format }`; plus `upFraction` (the up pane's share of the band height, where the baseline sits), `getAbsentFraction`, `getBarOpacity` |
|
|
55
|
+
| `foldBands` (+ `BandFold`, `BandTooltipConfig`, `BandTooltipRowContext`) | the width-driven fold both kinds run, exported so a consumer can test their `merge` against the real grouping |
|
|
56
|
+
| `HatchPattern`, `hatchFill`, `hatchSizeFor` | the absence fill |
|
|
57
|
+
|
|
58
|
+
The shared choreography lives in an internal `useBandPlot` — **not exported**, deliberately: it is
|
|
59
|
+
the kinds' contract with each other, not a public seam.
|
|
60
|
+
|
|
61
|
+
**`MirroredBars` reverses a recorded decision, and the recorded reason for it was wrong.** The old
|
|
62
|
+
entry in `docs/CHARTS-SPEC.md` read _"no two-bar-pane kind with independent per-pane scales"_, with
|
|
63
|
+
_"a second consumer asks"_ as its trigger. That trigger never fired. Round 4 framed the blocker as
|
|
64
|
+
independent SCALES; `DualPanel` already had `topYDomain`/`bottomYDomain`, so that was never it. The
|
|
65
|
+
real blockers are that `DualPanel`'s top pane is a LINE pane and its bottom takes one SIGNED
|
|
66
|
+
`getBar` over a symmetric domain. A decision recorded against the wrong blocker outlives its own
|
|
67
|
+
refutation.
|
|
68
|
+
|
|
69
|
+
**`xTickValues?: (keys, xMax) => readonly string[]` on `CartesianChart`**, forwarded by `Bars`,
|
|
70
|
+
`MultiLine`, `StackedArea`, `ZonedLine` and both band kinds. It resolves AHEAD of `xTicks`, which is
|
|
71
|
+
unchanged and still works; omit both and ticks are chosen to fit (`smartTicks`), exactly as before.
|
|
72
|
+
Reach for it on a dense time axis: the tick choosers append the final key unconditionally, so a
|
|
73
|
+
COUNT that does not land on the last index paints two labels on top of each other at the right edge
|
|
74
|
+
— at every count, not at an unlucky one. On the consumer that reported it, the local tick helper
|
|
75
|
+
went 200 → 160 lines; it shrinks, it does not disappear.
|
|
76
|
+
|
|
77
|
+
**`basaltAppPlugin`'s `icons` takes an array now.**
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
icons?: false | { dir?: string } | readonly BasaltAppIcon[]
|
|
81
|
+
// BasaltAppIcon = { src, sizes?, type?, purpose?, rel? }
|
|
82
|
+
icons: [{ src: '/favicon.svg', sizes: 'any', type: 'image/svg+xml' }]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Every entry becomes a manifest icon; an entry reaches the head only when it names a `rel` (`'icon' |
|
|
86
|
+
'shortcut icon' | 'apple-touch-icon' | 'mask-icon'`) — which is what lets an app whose `index.html`
|
|
87
|
+
already links its favicon take the generated manifest without a duplicate tag. An empty array reads
|
|
88
|
+
as `false`, i.e. no `icons` member rather than an empty one. `{ dir }` and the default are
|
|
89
|
+
byte-identical to before. **If you kept a hand-written `manifest.webmanifest` because the plugin
|
|
90
|
+
could not name your icon, you can delete it now** — with its permanent `theme-allow-file` — the
|
|
91
|
+
generated manifest reproduces it member for member, plus an `id` a hand-written copy usually lacks.
|
|
92
|
+
|
|
93
|
+
**CLI: a repo root with no `workspaces` field was invisible to everything.** With the install one
|
|
94
|
+
level down and nothing declaring it, `check-theme` printed "no off-palette colors" having scanned
|
|
95
|
+
**zero files** and `doctor` inferred `tokens-only` for a full Mantine consumer. `resolveProjectDir`
|
|
96
|
+
now falls back to a bounded two-level layout scan when there is nothing declared to read; a declared
|
|
97
|
+
`workspaces` still wins, and 2+ candidates stays ambiguous. `BASALT_CWD` is honoured by all three
|
|
98
|
+
commands now, not two.
|
|
99
|
+
|
|
100
|
+
- **`sync` is profile-aware.** A `"basalt": { "profile": "tokens-only" }` consumer has no scaffold
|
|
101
|
+
to reconcile, so `sync` prints `n/a` and exits 0 — `sync --check` is now wirable into a
|
|
102
|
+
tokens-only repo's CI. It used to refuse and prescribe `basalt-ui init`, the one command that
|
|
103
|
+
would have written a competing install.
|
|
104
|
+
- **`DESIGN.md` version rot is healed, not re-stamped.** The stamp was never a constant: `DESIGN.md`
|
|
105
|
+
is a **seed**, written once and never reconciled, so the same line read 1.0.0 / 1.9.0 / 1.21.0 /
|
|
106
|
+
1.22.0 under one install. Reported as four doc bugs over three rounds; it was one. The template
|
|
107
|
+
names no version and `sync` rewrites openers already written.
|
|
108
|
+
|
|
109
|
+
**The `theme-allow` shape grid is enumerated, not collected.** The reported fifth hole was three:
|
|
110
|
+
the closer-alone-on-its-line shape that got reported, a `MAX_COMMENT_BLOCK_LINES = 8` budget
|
|
111
|
+
truncating the walk inside a ~12-line docblock (not a JSX shape at all), and the plugin requiring
|
|
112
|
+
the annotation's comment to be the LAST one above the node — so a reason wrapped onto a second `//`,
|
|
113
|
+
a shape argo writes, reported under oxlint while the guard waived it. The grid now runs the four
|
|
114
|
+
axes that vary (comment style × token position × where the closer falls × what follows), pinned row
|
|
115
|
+
for row in `src/guard/check-source.test.ts` (37 supported + 8 asserted-unsupported) and
|
|
116
|
+
`configs/oxlint-plugin.test.ts` (32 + 8; the guard's extra five rows are CSS/HTML/JSON dialects
|
|
117
|
+
oxlint never sees). Zero disagreements, down from five — **and no waiver tally moved in any of the
|
|
118
|
+
seven consumer repos.**
|
|
119
|
+
|
|
120
|
+
Asserting the unsupported cells is the point: it stops "unsupported" and "silently broken" reading
|
|
121
|
+
the same. What does NOT waive, in both halves:
|
|
122
|
+
|
|
123
|
+
| Shape | Why |
|
|
124
|
+
| ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
125
|
+
| a blank line between annotation and code — after `//`, after `{/* */}`, or after a `{/*` whose closer sits alone | a blank line is how you say "this comment is not about the next statement" |
|
|
126
|
+
| the token mid-sentence in a line comment, a docblock gutter, or a JSX expression comment | prose that MENTIONS the token is not an annotation — the reason it must START its comment |
|
|
127
|
+
| the token inside a string literal | same |
|
|
128
|
+
| above a multi-line OPENING tag, with the finding on a later attribute line | a waiver reaches the first line below its comment, not an arbitrary line further down |
|
|
129
|
+
|
|
130
|
+
**Corrected consumer findings.** _"`sync` scaffolds 19–20 files into a consumer root"_ **did not
|
|
131
|
+
reproduce** — those runs used a stale `bunx` cache of 1.20.0, and `bunx` does not re-resolve a
|
|
132
|
+
package it already has. Check an upgrade against the local bin. Round 6's _"`doctor` exits 0 on hard
|
|
133
|
+
failures"_ was a pipe artifact (`$?` read after `| tail`) and was already withdrawn.
|
|
134
|
+
|
|
135
|
+
## 1.22.0 — the toolchain stops overclaiming
|
|
136
|
+
|
|
137
|
+
**No export removed or renamed; four added.** Nothing you wrote changed. Every entry below is a
|
|
138
|
+
check that reported an answer it had not earned, so a green CI on 1.21.0 was, for several of them,
|
|
139
|
+
green over an empty set.
|
|
140
|
+
|
|
141
|
+
**`sync` refuses where it used to scaffold.** Run from a sub-package that depends on basalt-ui but
|
|
142
|
+
holds no `.basalt/manifest.json` — `apps/dashboard`, `apps/web` — it printed `0 updated, 20
|
|
143
|
+
recreated`, wrote a second `basalt` key, and stood up a complete competing install beside the real
|
|
144
|
+
one at the repo root. argo and rb both reverted it by hand. It now resolves its project exactly as
|
|
145
|
+
`check-theme` and `doctor` do, and **exits 1** when the resolved project has no manifest, naming the
|
|
146
|
+
install it found above instead. The refusal runs before the `basalt.roots` backfill, which was half
|
|
147
|
+
the damage. Two ways to unblock it:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
cd <the package holding .basalt/manifest.json> && bunx basalt-ui sync
|
|
151
|
+
BASALT_CWD=<that package> bunx basalt-ui sync # or from anywhere
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The summary line gained a `created` counter. **`recreated` now means what it always claimed** — the
|
|
155
|
+
ledger placed that file once and it went missing. Twenty first-time writes were never that.
|
|
156
|
+
|
|
157
|
+
**`--audit-allows` judges plugin-rule annotations.** They used to print "not a check-theme kind" and
|
|
158
|
+
drop out of the exit-1 gate, so the gate covered an empty set wherever the waivers were plugin
|
|
159
|
+
rules: argo's whole tally was `0 live, 0 dead, 8 outside reach`, and 11 of linewatch's 14 went the
|
|
160
|
+
same way. Each is now probed by re-running oxlint over that one file with the annotation
|
|
161
|
+
neutralized. Judged now: argo 8 of 8, linewatch 14 of 14, basalt's own tree 23 of 23.
|
|
162
|
+
|
|
163
|
+
**It requires oxlint to be reachable.** The probe writes one neutralized sibling file and re-runs
|
|
164
|
+
oxlint over it (oxlint has no stdin mode), removing it in a `finally`. Where oxlint cannot run, the
|
|
165
|
+
annotation is reported as **"cannot judge"**, never as dead. The report now also prints the scope it
|
|
166
|
+
audited — `0 dead` over `basalt.roots` was reading as `0 dead anywhere`.
|
|
167
|
+
|
|
168
|
+
**`doctor`'s `lefthook-preset` check asks a different question.** It tested whether the config text
|
|
169
|
+
contained the `extends` string. It now asks whether the gate EXISTS, via `lefthook dump`, which
|
|
170
|
+
resolves `extends`, `include` and per-command `root:`. linewatch wires all three jobs with
|
|
171
|
+
`root: 'web/'` precisely because `extends` merges commands _without_ their working directory — it
|
|
172
|
+
was correctly configured, got warned, and the old advice would have broken it. Three outcomes: a
|
|
173
|
+
broken `extends` target is still a hard fail, a provably absent gate is a warn, and where
|
|
174
|
+
`lefthook dump` could not run you get an advisory warning naming what it could not see.
|
|
175
|
+
|
|
176
|
+
**`basalt/shadow-basalt-export` narrowed twice.** It now gates on `isBasaltScopedFile` like every
|
|
177
|
+
other rule in the file, and needs a component-SHAPED declaration — a function, an arrow, a
|
|
178
|
+
`memo`/`forwardRef` wrapper, or a class extending one. It had been firing on a `SlugTracker` class
|
|
179
|
+
in a React-free package carrying no basalt-ui dependency, telling it to import from basalt-ui. The
|
|
180
|
+
stated limit is unchanged: exact-name-only, a tripwire, not coverage.
|
|
181
|
+
|
|
182
|
+
**`basaltAppPlugin({ icons: false })` now omits the manifest's `icons` member too.** It skipped the
|
|
183
|
+
head `<link>` icons and emitted the two PNG manifest entries anyway, so `{ manifest: true, icons:
|
|
184
|
+
false }` shipped an installable app pointing at two 404s. The manifest is now **honest** about
|
|
185
|
+
icons — it is not yet **sufficient**. If you went hybrid over the 404s, dropping the hand-written
|
|
186
|
+
half here costs you every icon unless your `public/` matches basalt's six filenames: at this
|
|
187
|
+
release `icons` was still `false | { dir?: string }` over those six, so an app whose icon is
|
|
188
|
+
`favicon.svg` could pick between a manifest naming two PNGs it never builds and a manifest with no
|
|
189
|
+
`icons` member at all. The array form that fixes it is in `## Unreleased`. The option's JSDoc said
|
|
190
|
+
"skips the head `<link>` icons"; it now says what the option does.
|
|
191
|
+
|
|
192
|
+
**Two `theme-allow` comment shapes were silently broken and now work.** Plainly: this is the fourth
|
|
193
|
+
hole found in this one contract in three rounds. A thirteen-shape matrix — every shape a consumer
|
|
194
|
+
had been SEEN to write — is pinned in both halves, `src/guard/check-source.test.ts` and
|
|
195
|
+
`configs/oxlint-plugin.test.ts`. **The claim that followed it here, that the two parsers could
|
|
196
|
+
therefore no longer disagree, was false when it was written**: a list of collected anecdotes cannot
|
|
197
|
+
close a contract. Three more holes were open at this release. See `## Unreleased`.
|
|
198
|
+
|
|
199
|
+
| Shape | What it did | Now |
|
|
200
|
+
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
|
201
|
+
| `/** theme-allow <id> — <why> */` on one line | waived under oxlint, reported under `check-theme` | both halves honour it |
|
|
202
|
+
| `{/*` + token on the next line + `*/}` | comment-stripping left a bare `}`, so the line read as CODE and the annotation was classified trailing — scoped to a brace | scoped to the node below it, as written |
|
|
203
|
+
|
|
204
|
+
**linewatch writes that wrapped shape for every hand-composed chart axis**, so any _guard_ kind
|
|
205
|
+
annotated that way was silently unwaivable. It only appeared to work because the rules it names
|
|
206
|
+
(`hand-rolled-plot`) live in the oxlint plugin, whose placement test is comment-node-based and never
|
|
207
|
+
saw the brace.
|
|
208
|
+
|
|
209
|
+
**New, additive — `./guard` gains four runtime exports**, the reader half of the audit:
|
|
210
|
+
|
|
211
|
+
| Export | Note |
|
|
212
|
+
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
213
|
+
| `findAllowAnnotations(text, relPath, cfg)` | every annotation as written, ids split into `guardKinds` / `pluginRules` / `unknownRules`. Shares `collectAllowAnnotations` with `checkSource`, so it cannot list a line the scan does not honour |
|
|
214
|
+
| `neutralizeAllowAnnotation(text, line, cfg)` | one annotation rewritten to the token below, every other left intact — the probe half of an audit |
|
|
215
|
+
| `NEUTRALIZED_ALLOW_TOKEN` | so a guard probe and an oxlint probe neutralize identically |
|
|
216
|
+
| `PLUGIN_RULE_IDS` | the ids only oxlint can judge |
|
|
217
|
+
|
|
218
|
+
The `AllowAnnotationSite` type ships alongside them.
|
|
219
|
+
|
|
220
|
+
**Known gaps, reported and not fixed:** `--audit-allows` says nothing about `basalt.exempt` — a
|
|
221
|
+
whole file removed from the scan, the broadest exception the config surface has — and its
|
|
222
|
+
`scoped to …` line does not distinguish `theme-allow` from `theme-allow-file`.
|
|
223
|
+
|
|
224
|
+
## 1.21.0 — the `theme-allow` grammar
|
|
29
225
|
|
|
30
226
|
**No export removed or renamed.** One break, and it is in the escape hatch itself: **file scope must
|
|
31
227
|
now be spelled `theme-allow-file`.** At 1.20.0 an annotation that named a rule and gave a reason —
|
|
@@ -73,7 +269,7 @@ manifest, `basaltAppPlugin` is the first remedy, since a hand-copied hex drifts
|
|
|
73
269
|
| Change | What to do |
|
|
74
270
|
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
75
271
|
| `createSearchParamStore` / `createMultiSearchParamStore` gain `linkSearch` | replace every nav link's `search: { <param>: '<literal>' }` with `search: <store>.linkSearch`, passed BY REFERENCE. A module-scope literal pins the fallback on every click — argo's reader had ZERO call sites, so "remember my window" had never worked |
|
|
76
|
-
| `BasaltShell` collapse moves to `createPersistedState` | the key is now `basalt:<storageKey>` holding `{ v, value }`; read it with `readPersistedValue(storageKey, 1)`. A one-time migration adopts the raw pre-1.
|
|
272
|
+
| `BasaltShell` collapse moves to `createPersistedState` | the key is now `basalt:<storageKey>` holding `{ v, value }`; read it with `readPersistedValue(storageKey, 1)`. A one-time migration adopts the raw pre-1.21.0 value, so the sidebar does not re-expand on upgrade |
|
|
77
273
|
|
|
78
274
|
**New, additive:**
|
|
79
275
|
|
|
@@ -157,7 +353,7 @@ the rule AND giving a reason is what `hasFileDeclaration` matches, at any line
|
|
|
157
353
|
intended for one node silences the file. Dropping the reason keeps it node-scoped in the oxlint
|
|
158
354
|
plugin, but then `check-theme` reports `theme-allow-unscoped ("no reason")`. The two halves of the
|
|
159
355
|
contract intersect at exactly one legal shape and that shape is whole-file. Write the declaration in
|
|
160
|
-
the component's docblock, where it reads as the file-level decision it is. **Fixed in 1.
|
|
356
|
+
the component's docblock, where it reads as the file-level decision it is. **Fixed in 1.21.0** —
|
|
161
357
|
see that section; the declaration moves to `theme-allow-file`.
|
|
162
358
|
|
|
163
359
|
**`doctor` will go red.** `SKIPPED` is a third outcome beside pass/warn/fail and exits non-zero on
|
|
@@ -293,9 +489,9 @@ deleting an entry forces the level flip in the same commit.
|
|
|
293
489
|
| `basalt/agent-no-raw-usechat` | 1.10.0 as `warn` | **1.13.0** |
|
|
294
490
|
| `basalt/agent-resume-guard` | 1.10.0 as `warn` | **1.13.0** |
|
|
295
491
|
| `basalt/raw-size-literal` | 1.7.0 as `warn` | **1.20.0** |
|
|
296
|
-
| `basalt/hand-rolled-plot` | 1.15.0 as `warn` | still `warn` — widened again at 1.
|
|
492
|
+
| `basalt/hand-rolled-plot` | 1.15.0 as `warn` | still `warn` — widened again at 1.21.0; grace restarts with the widening |
|
|
297
493
|
| `basalt/chart-legend-literal` | 1.15.0 as `warn` | still `warn` — widened at 1.20.0 |
|
|
298
|
-
| `basalt/shadow-basalt-export` | 1.20.0 as `warn` | may stay `warn` permanently — widened at 1.
|
|
494
|
+
| `basalt/shadow-basalt-export` | 1.20.0 as `warn` | may stay `warn` permanently — widened at 1.21.0, narrowed at 1.22.0 |
|
|
299
495
|
| `basalt/hand-rolled-shell` | 1.20.0 as `warn` | — |
|
|
300
496
|
|
|
301
497
|
`card-with-border`, `inline-display`, `raw-html-layout`, `raw-form-control`, `raw-font-family` and
|
|
@@ -308,7 +504,7 @@ in the shipped preset). A config still naming `import-boundary` after 1.1.0 disa
|
|
|
308
504
|
|
|
309
505
|
## Deprecated, not yet removed
|
|
310
506
|
|
|
311
|
-
The 32 camelCase `--vx-*` aliases deprecated in 1.5.0 are **still emitted at 1.
|
|
507
|
+
The 32 camelCase `--vx-*` aliases deprecated in 1.5.0 are **still emitted at 1.21.0**.
|
|
312
508
|
`buildPaletteCss({ legacyAliases: false })` / `tokens:css --no-legacy-aliases` opts out now; a later
|
|
313
509
|
minor flips the default.
|
|
314
510
|
|
package/README.md
CHANGED
|
@@ -224,7 +224,7 @@ Other keys (`exempt`, `severity`, `spacingSteps`, `forbiddenAccents`, …) are d
|
|
|
224
224
|
**`exemptRules`** turns one kind off along a path. A pattern is a relative path
|
|
225
225
|
(`public/site.webmanifest`, or `src/agent` for everything under it), a glob (`*` stops at `/`, `**`
|
|
226
226
|
does not, and a slash-free glob is also tried against the basename, so `*.module.css` works), or a
|
|
227
|
-
bare path segment — the legacy shape, and the one nobody guesses, since before 1.
|
|
227
|
+
bare path segment — the legacy shape, and the one nobody guesses, since before 1.21.0 it was the
|
|
228
228
|
ONLY shape and a real relative path matched nothing and said nothing. The object form records the
|
|
229
229
|
reason, which is what a `theme-allow` always carried and this key could not. A pattern that
|
|
230
230
|
suppresses nothing is now reported by a normal run and fails `--audit-allows`.
|
|
@@ -242,7 +242,7 @@ stylesheet `tokens:css` just wrote is not reported back at you, pasting the mark
|
|
|
242
242
|
suppresses nothing, and an ordinary declaration smuggled into a sheet wearing the header is
|
|
243
243
|
reported on its own line.
|
|
244
244
|
|
|
245
|
-
**The escape hatch is scoped, and since 1.
|
|
245
|
+
**The escape hatch is scoped, and since 1.21.0 the two scopes are spelled apart:**
|
|
246
246
|
|
|
247
247
|
```text
|
|
248
248
|
theme-allow → this node/line, EVERY rule (reports theme-allow-unscoped)
|
|
@@ -252,7 +252,7 @@ theme-allow-file <id>[, <id>…] — <why> → the WHOLE FILE, those rules
|
|
|
252
252
|
```
|
|
253
253
|
|
|
254
254
|
An annotation must **start** its comment (after `//`, `/*`, `<!--`, a block gutter `*`, or
|
|
255
|
-
whitespace) — before 1.
|
|
255
|
+
whitespace) — before 1.21.0 any comment merely mentioning the token parsed as the bare blanket form,
|
|
256
256
|
so a file documenting its own waivers disarmed itself. Placements: the reported line, a comment-only
|
|
257
257
|
line directly above it (the only form JSX can express, and it reaches the first CODE line below,
|
|
258
258
|
walking through the rest of its comment block), or — in CSS — a trailing comment reaching back over
|
|
@@ -260,8 +260,13 @@ the declaration it terminates. The id slot is read strictly: a word there that n
|
|
|
260
260
|
nothing, so a typo is never the blanket form.
|
|
261
261
|
|
|
262
262
|
`basalt-ui check-theme --audit-allows` reports every annotation and every `exemptRules` entry with
|
|
263
|
-
what it still suppresses, proved by re-running the
|
|
264
|
-
|
|
263
|
+
what it still suppresses, proved by re-running the scan with that one waiver neutralized. Exits 1 on
|
|
264
|
+
a dead waiver, so it is usable as a CI gate. Since 1.22.0 it runs **both** halves — `checkSource` for
|
|
265
|
+
a guard kind, oxlint over one neutralized sibling file for a plugin rule — so a waiver naming
|
|
266
|
+
`hand-rolled-plot` is judged rather than skipped. **It needs oxlint reachable**; where it is not, the
|
|
267
|
+
verdict is "cannot judge", never "dead". The report prints the scope it audited, because `0 dead`
|
|
268
|
+
over `basalt.roots` is not `0 dead anywhere`. Two known gaps: `basalt.exempt` is not audited at all,
|
|
269
|
+
and the `scoped to …` line does not distinguish `theme-allow` from `theme-allow-file`.
|
|
265
270
|
|
|
266
271
|
`doctor`'s `ai-major-parity` hard check fails a monorepo where workspace packages declare different
|
|
267
272
|
`ai` package majors — unless the split is intentional and written down. A producer pinned to an
|
|
@@ -294,7 +299,7 @@ deleted rather than silently surviving into the next real skew.
|
|
|
294
299
|
| `./tokens` | **free** | `VX` token refs, `buildPaletteCss`, `defineSeries`, `seriesTokens`, `groupTokens`, `alpha`, `ColorPair` / `SeriesMap` types |
|
|
295
300
|
| `./theme-lab` | coupled | `ThemeLabControls`, `applyOverrides`, `loadOverrides`, `COLOR_GROUPS` for live theme inspection |
|
|
296
301
|
| `./vite` | — | `basaltViteConfig(opts)` — Vite preset for basalt-ui consumer apps; `basaltAppPlugin(opts)` — PWA head, manifest, and icon metadata derived from the token palette |
|
|
297
|
-
| `./guard` | **free** | `checkSource`, `GUARD_RULES`, `Finding` types — the headless theme-guard core
|
|
302
|
+
| `./guard` | **free** | `checkSource`, `GUARD_RULES`, `Finding` types — the headless theme-guard core; plus the annotation reader `--audit-allows` is built on: `findAllowAnnotations`, `neutralizeAllowAnnotation`, `NEUTRALIZED_ALLOW_TOKEN`, `PLUGIN_RULE_IDS`, `AllowAnnotationSite` |
|
|
298
303
|
| `./query` | **free** | `createBasaltQueryClient`, transport-agnostic `unwrap`, lazy `BasaltQueryDevtools` |
|
|
299
304
|
| `./router-tanstack` | **free** | TanStack Router bridge: `useBasaltNav` (active route) + `useRouterBreadcrumbs` |
|
|
300
305
|
| `./forms` | coupled | Mantine form adapter: `useBasaltForm`, `field`, `FormErrorSummary`, `useFormDraft` (Standard Schema) |
|
|
@@ -436,8 +441,20 @@ import { CartesianChart, LinePath, VX } from 'basalt-ui/charts'
|
|
|
436
441
|
</CartesianChart>
|
|
437
442
|
```
|
|
438
443
|
|
|
439
|
-
Shipped kinds (`Bars`, `MultiLine`, `StackedArea`, `ZonedLine`, `Donut`, `DualPanel`, `Heatmap
|
|
440
|
-
are that same composition, pre-wired.
|
|
444
|
+
Shipped kinds (`Bars`, `MultiLine`, `StackedArea`, `ZonedLine`, `Donut`, `DualPanel`, `Heatmap`,
|
|
445
|
+
`BandStrip`, `MirroredBars`) are that same composition, pre-wired. The last five compose
|
|
446
|
+
`ChartFrame` directly, because a ring, a matrix, two panes or a strip is not a single plot rect:
|
|
447
|
+
**`BandStrip`** draws 1-D categorical bands with no y dimension at all (`CartesianChart` renders a
|
|
448
|
+
left numeric axis unconditionally, so it could never host one), and **`MirroredBars`** draws two
|
|
449
|
+
bar panes over one x scale and one baseline, each in its own domain. Both fold their domain by
|
|
450
|
+
width, hatch the share of a slot nothing measured, and share the page cursor like every other
|
|
451
|
+
chart.
|
|
452
|
+
|
|
453
|
+
Which x ticks get painted is `xTickValues?: (keys, xMax) => readonly string[]`, on
|
|
454
|
+
`CartesianChart` and forwarded by `Bars`/`MultiLine`/`StackedArea`/`ZonedLine` and both band kinds.
|
|
455
|
+
It resolves ahead of the `xTicks` COUNT, which is unchanged; omit both and ticks are chosen to fit.
|
|
456
|
+
Reach for values on a dense time axis — the tick chooser appends the final key unconditionally, so
|
|
457
|
+
a count that misses the last index collides two labels at the right edge at every count.
|
|
441
458
|
|
|
442
459
|
### `./query` — TanStack Query adapter
|
|
443
460
|
|
|
@@ -639,8 +656,15 @@ bun add -D vite-plugin-pwa workbox-build workbox-window # only if serviceWorke
|
|
|
639
656
|
Without the peer installed, `serviceWorker: true` degrades to a one-line console warning (no
|
|
640
657
|
service worker, no crash) — everything else (theme-color meta, anti-FOUC background,
|
|
641
658
|
`site.webmanifest`, favicon/apple-touch-icon links, OG/Twitter defaults) works with zero extra
|
|
642
|
-
dependencies.
|
|
643
|
-
|
|
659
|
+
dependencies.
|
|
660
|
+
|
|
661
|
+
`icons` takes `false | { dir?: string } | readonly BasaltAppIcon[]`. The array form names the icons
|
|
662
|
+
the app actually has, with the manifest's own field names (`src`, `sizes`, `type`, `purpose`) plus
|
|
663
|
+
an optional `rel` — every entry becomes a manifest icon, and only an entry naming a `rel` reaches
|
|
664
|
+
the head. `icons: [{ src: '/favicon.svg', sizes: 'any', type: 'image/svg+xml' }]` is a complete,
|
|
665
|
+
installable icon set for a single-page app. An empty array reads as `false`; `{ dir }` and the
|
|
666
|
+
default are byte-identical to what they always emitted. Full doctrine, plugin ordering, and the six
|
|
667
|
+
default icon filenames: `agent/rules/basalt-app.md`.
|
|
644
668
|
|
|
645
669
|
---
|
|
646
670
|
|
|
@@ -684,12 +708,35 @@ bunx basalt-ui help # full usage; every subcommand also takes --hel
|
|
|
684
708
|
```
|
|
685
709
|
|
|
686
710
|
`doctor` reports **`SKIPPED`** as a third outcome beside pass/warn/fail and exits non-zero on it —
|
|
687
|
-
"All checks passed" is only printable when every check actually ran.
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
711
|
+
"All checks passed" is only printable when every check actually ran. **`check-theme`, `doctor` and
|
|
712
|
+
`sync` all resolve their project the same way**: `BASALT_CWD` wins, then the cwd itself, then the
|
|
713
|
+
workspace packages the root declares, then a two-level layout scan. Two candidates is reported as
|
|
714
|
+
ambiguous, never guessed. The scan is what a repo declaring **no `workspaces` field** needs — with
|
|
715
|
+
the install one level down and nothing declaring it, every command answered about the root and
|
|
716
|
+
found nothing there: `check-theme` printed "no off-palette colors" having scanned zero files, and
|
|
717
|
+
`doctor` inferred `tokens-only` for a full Mantine consumer. `tokens:css` and `fonts:css` both take
|
|
718
|
+
`--check` as a CI drift gate.
|
|
719
|
+
|
|
720
|
+
**Verify an upgrade against the local bin, not `bunx`.** `bunx` does not re-resolve a package it
|
|
721
|
+
has already cached, so a `bunx basalt-ui …` run can execute a version you upgraded away from —
|
|
722
|
+
which is exactly how a fixed defect gets re-reported. Prefer `./node_modules/.bin/basalt-ui` (or
|
|
723
|
+
`bun run` a script that does) when the point of the run is to check what the installed version
|
|
724
|
+
does.
|
|
725
|
+
|
|
726
|
+
`sync` strategy per file: unchanged since last write → overwrite; locally edited → skip (show diff);
|
|
727
|
+
missing → recreate. `--force` overwrites local edits. The summary counts `created` and `recreated`
|
|
728
|
+
apart: `recreated` means the ledger placed that file once and it went missing. On a
|
|
729
|
+
`"basalt": { "profile": "tokens-only" }` consumer there is no scaffold to reconcile, so `sync`
|
|
730
|
+
prints `n/a` and exits 0 — `sync --check` is wirable into a tokens-only repo's CI without a
|
|
731
|
+
special case. `sync` also heals a `DESIGN.md` opener that still names a version: the file is a
|
|
732
|
+
**seed**, written once and never reconciled, so any number stamped into it was stale by the next
|
|
733
|
+
release.
|
|
734
|
+
|
|
735
|
+
**`sync` refreshes an existing install; it does not create one.** It resolves its project exactly as
|
|
736
|
+
`check-theme` and `doctor` do, and since 1.22.0 **exits 1** when the resolved project has no
|
|
737
|
+
`.basalt/manifest.json`, naming the install it found above instead of scaffolding a second one
|
|
738
|
+
beside it. Run it at the package that holds the manifest, or point `BASALT_CWD` at that package.
|
|
739
|
+
Creating an install is `basalt-ui init`'s decision.
|
|
693
740
|
|
|
694
741
|
---
|
|
695
742
|
|
|
@@ -98,12 +98,17 @@ Run `basalt-ui doctor` afterwards to confirm the wiring took. A check that canno
|
|
|
98
98
|
|
|
99
99
|
### The lefthook preset overrides YOU, not the other way round
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
`doctor`'s `lefthook-preset` check asks whether a pre-commit gate EXISTS — not whether your config
|
|
102
|
+
contains the extends string. Since 1.22.0 it asks `lefthook dump`, which resolves `extends`,
|
|
103
|
+
`include` and per-command `root:`. **Wiring the jobs yourself passes**: linewatch runs all three with
|
|
104
|
+
`root: 'web/'` precisely because `extends` merges commands _without_ their working directory, and the
|
|
105
|
+
old text match warned there and prescribed a change that would have broken it.
|
|
106
|
+
|
|
107
|
+
A broken `extends` target is still a hard fail — lefthook merges a missing target into **zero**
|
|
108
|
+
commands and exits 0, so `lefthook dump` looks clean and the repo has no gate with nothing saying so.
|
|
109
|
+
A provably absent gate is a warn; a repo where `lefthook dump` could not run gets an advisory warning
|
|
110
|
+
naming what it could not see. n/a with no lefthook at all. `sync` reports the same seam, since it is
|
|
111
|
+
the command an upgrade actually runs and neither file is one basalt owns.
|
|
107
112
|
|
|
108
113
|
**An `extends` target WINS on a colliding key.** Declare `pre-commit.commands.oxfmt.run` (or
|
|
109
114
|
`glob:`) in your own file and the preset's value is what runs — yours is discarded silently. Only
|
|
@@ -160,7 +165,7 @@ site-wide OG/Twitter defaults from `options.seo`.
|
|
|
160
165
|
plugins: [react(), ...basaltAppPlugin({ name: 'Argo', colorScheme: 'auto' })]
|
|
161
166
|
```
|
|
162
167
|
|
|
163
|
-
Before 1.
|
|
168
|
+
Before 1.21.0 the anti-FOUC rule emitted `html{color-scheme:dark}` **unlayered**, which outranks
|
|
164
169
|
Mantine's own `@layer mantine` declaration regardless of specificity — a light-scheme consumer got
|
|
165
170
|
dark scrollbars, dark `<select>` popups and dark date pickers permanently, with no opt-out. The rule
|
|
166
171
|
is now scoped to `html:not([data-mantine-color-scheme])`, so it expires the instant MantineProvider
|
|
@@ -190,9 +195,34 @@ Generate the set with `@vite-pwa/assets-generator` (or realfavicongenerator.net)
|
|
|
190
195
|
source image — it lives in the CONSUMER's own devDependencies, not basalt-ui's. `basalt-ui doctor`
|
|
191
196
|
warns (does not fail) when `public/` exists but is missing one of these files.
|
|
192
197
|
|
|
198
|
+
**If your `public/` does not match those six filenames, name your icons instead.** `icons` takes
|
|
199
|
+
`false | { dir?: string } | readonly BasaltAppIcon[]`, and an array uses the manifest's own field
|
|
200
|
+
names verbatim:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
basaltAppPlugin({
|
|
204
|
+
name: 'rb',
|
|
205
|
+
icons: [{ src: '/favicon.svg', sizes: 'any', type: 'image/svg+xml' }],
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`BasaltAppIcon` is `{ src, sizes?, type?, purpose?, rel? }`, `rel` being one of `'icon'`,
|
|
210
|
+
`'shortcut icon'`, `'apple-touch-icon'`, `'mask-icon'`. **Every entry becomes a manifest icon; an
|
|
211
|
+
entry reaches the head only when it names a `rel`** — which is what lets an app whose `index.html`
|
|
212
|
+
already links its favicon take the generated manifest without a duplicate tag. An empty array reads
|
|
213
|
+
as `false` (no `icons` member, rather than an empty one). `{ dir }` and the default are byte-identical
|
|
214
|
+
to what they always emitted.
|
|
215
|
+
|
|
216
|
+
One SVG at `sizes: 'any'` is a complete, installable icon set for a single-page app — and it is the
|
|
217
|
+
shape every non-scaffolded consumer already has. If you keep a hand-written `manifest.webmanifest`
|
|
218
|
+
only because the plugin could not name your icon, delete it: the generated manifest reproduces it
|
|
219
|
+
member for member, including the two hexes derived from `SURFACE.bg` rather than hand-copied, and it
|
|
220
|
+
takes the permanent `theme-allow-file` on that file with it.
|
|
221
|
+
|
|
193
222
|
`basaltAppPlugin` also emits `site.webmanifest` (served in dev too) with explicit `id`/`scope`/
|
|
194
|
-
`start_url` — pass `manifest: false` to skip it, or `icons: false` to skip the
|
|
195
|
-
|
|
223
|
+
`start_url` — pass `manifest: false` to skip it, or `icons: false` to skip the icons. Since 1.22.0
|
|
224
|
+
`icons: false` omits BOTH the head `<link>` tags and the manifest's `icons` member; before that it
|
|
225
|
+
skipped only the head, so a manifest shipped naming two PNGs the app never builds.
|
|
196
226
|
|
|
197
227
|
## Service worker (opt-in)
|
|
198
228
|
|