basalt-ui 1.19.1 → 1.21.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 +19 -1
- package/CHANGELOG.md +39 -0
- package/MIGRATING.md +326 -0
- package/README.md +87 -9
- package/agent/rules/basalt-app.md +84 -0
- package/agent/rules/basalt-charts.md +32 -17
- package/agent/rules/basalt-content.md +4 -0
- package/agent/rules/basalt-mantine.md +25 -10
- package/agent/rules/basalt-router.md +79 -15
- package/agent/rules/basalt-state.md +21 -3
- package/agent/rules/basalt-tokens.md +132 -6
- package/agent/skills/basalt-app/SKILL.md +45 -6
- package/agent/skills/basalt-charts/SKILL.md +5 -3
- package/agent/templates/CLAUDE-block.md.tpl +11 -5
- package/configs/check.yml +11 -4
- package/configs/lefthook.yml +59 -7
- package/configs/oxlint-plugin.js +633 -30
- package/configs/oxlint-plugin.test.ts +594 -15
- package/configs/oxlint.json +10 -1
- package/dist/agent/stick-to-bottom.js +1 -1
- package/dist/agent-chat/thread-detail-panel.js +1 -1
- package/dist/agent-chat/thread-message.js +2 -2
- package/dist/agent-chat/tool-chip.js +1 -1
- package/dist/charts/kinds/Bars.d.ts +1 -1
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.d.ts +1 -1
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +1 -1
- package/dist/charts/kinds/DualPanel.d.ts +1 -1
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/Heatmap.d.ts +1 -1
- package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.d.ts +1 -1
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.d.ts +1 -1
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.d.ts +1 -1
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +2 -2
- package/dist/charts/primitives/ChartTooltip.js +1 -1
- package/dist/cli/index.d.ts +146 -20
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +901 -101
- package/dist/cli/toolchain-wiring.test.d.ts +2 -0
- package/dist/cli/toolchain-wiring.test.d.ts.map +1 -0
- package/dist/cli/waiver-audit.test.d.ts +2 -0
- package/dist/cli/waiver-audit.test.d.ts.map +1 -0
- package/dist/dashboard/delta-badge.js +2 -2
- package/dist/dashboard/stat-card.js +1 -1
- package/dist/data/virtual-list.js +5 -5
- package/dist/guard/index.d.ts +101 -2
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +474 -62
- package/dist/guard/types.d.ts +25 -5
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/register.d.ts +25 -0
- package/dist/register.d.ts.map +1 -1
- package/dist/router-tanstack/multi-search-param-store.d.ts +13 -0
- package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/multi-search-param-store.js +4 -1
- package/dist/router-tanstack/search-param-store.d.ts +29 -5
- package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/search-param-store.js +31 -3
- package/dist/router-tanstack/search-param-store.test.d.ts +2 -0
- package/dist/router-tanstack/search-param-store.test.d.ts.map +1 -0
- package/dist/shell/app-mobile-nav.module.css +11 -3
- package/dist/shell/index.d.ts +12 -5
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +24 -6
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.js +1 -1
- package/dist/tokens/index.d.ts +5 -3
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +7 -1
- package/dist/tokens/palette.d.ts +35 -27
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +24 -18
- package/dist/tokens/text-scale.test.d.ts +2 -0
- package/dist/tokens/text-scale.test.d.ts.map +1 -0
- package/dist/tokens.css +21 -17
- package/dist/vite-color-mix.js +2 -2
- package/dist/vite.d.ts +24 -2
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +54 -13
- package/llms.txt +3 -3
- package/package.json +4 -3
- package/src/agent/stick-to-bottom.tsx +1 -1
- package/src/agent-chat/thread-detail-panel.tsx +1 -1
- package/src/agent-chat/thread-message.tsx +3 -3
- package/src/agent-chat/tool-chip.tsx +1 -1
- package/src/charts/kinds/Bars.tsx +1 -1
- package/src/charts/kinds/Donut.tsx +3 -2
- package/src/charts/kinds/DualPanel.tsx +2 -2
- package/src/charts/kinds/Heatmap.tsx +1 -1
- package/src/charts/kinds/MultiLine.tsx +1 -1
- package/src/charts/kinds/StackedArea.tsx +1 -1
- package/src/charts/kinds/ZonedLine.tsx +1 -1
- package/src/charts/primitives/ChartLegend.tsx +2 -2
- package/src/charts/primitives/ChartTooltip.tsx +1 -1
- package/src/cli/index.ts +1614 -164
- package/src/cli/init-sync.test.ts +21 -3
- package/src/cli/template-ledger.test.ts +1 -1
- package/src/cli/tokens-css.test.ts +27 -14
- package/src/cli/toolchain-wiring.test.ts +856 -0
- package/src/cli/waiver-audit.test.ts +131 -0
- package/src/dashboard/delta-badge.tsx +3 -3
- package/src/dashboard/stat-card.tsx +1 -1
- package/src/data/virtual-list.tsx +8 -8
- package/src/guard/check-source.test.ts +1104 -12
- package/src/guard/index.ts +1106 -103
- package/src/guard/types.ts +29 -4
- package/src/register.ts +32 -0
- package/src/router-tanstack/multi-search-param-store.ts +16 -1
- package/src/router-tanstack/search-param-store.test.ts +135 -0
- package/src/router-tanstack/search-param-store.ts +90 -8
- package/src/shell/app-mobile-nav.module.css +11 -3
- package/src/shell/app-mobile-nav.tsx +1 -1
- package/src/shell/index.test.tsx +52 -0
- package/src/shell/index.tsx +64 -13
- package/src/surfaces.ts +1 -1
- package/src/theme/density-relations.test.ts +8 -5
- package/src/theme/spacing.test.ts +2 -0
- package/src/theme-toggle/index.tsx +2 -2
- package/src/tokens/index.ts +28 -3
- package/src/tokens/palette.ts +24 -18
- package/src/tokens/text-scale.test.ts +51 -0
- package/src/vite-color-mix.ts +2 -2
- package/src/vite.test.ts +125 -6
- package/src/vite.ts +148 -20
package/AGENTS.md
CHANGED
|
@@ -67,7 +67,9 @@ bunx basalt-ui init # FIRST STEP: scaffold .claude/rules/ + .claude
|
|
|
67
67
|
bunx basalt-ui check-theme # fail on off-palette colors in consumer source
|
|
68
68
|
bunx basalt-ui sync # reconcile managed files (.claude/rules/, .claude/skills/, CLAUDE.md block)
|
|
69
69
|
bunx basalt-ui sync --check # CI freshness gate — exits non-zero if any managed file drifted
|
|
70
|
-
bunx basalt-ui doctor #
|
|
70
|
+
bunx basalt-ui doctor # integration health — SKIPPED is a third outcome and exits non-zero
|
|
71
|
+
bunx basalt-ui tokens:css # emit the --vx-* stylesheet (no React, no Mantine, no bundler)
|
|
72
|
+
bunx basalt-ui fonts:css # emit the shipped --basalt-font-* stacks
|
|
71
73
|
bunx basalt-ui info # print published surface map
|
|
72
74
|
bunx basalt-ui info --json # stable JSON surface map
|
|
73
75
|
bunx basalt-ui guard-hook # PreToolUse theme-guard adapter: reads a Write/Edit payload on stdin, denies off-palette writes
|
|
@@ -77,6 +79,16 @@ Register `guard-hook` in `.claude/settings.json` under `hooks.PreToolUse` with m
|
|
|
77
79
|
`Write|Edit|MultiEdit` and command `bunx basalt-ui guard-hook` so the agent can't write off-palette
|
|
78
80
|
colors without a `theme-allow` comment.
|
|
79
81
|
|
|
82
|
+
**The escape hatch is `theme-allow <rule-id> — <reason>`**, scoped to that one kind and to that one
|
|
83
|
+
node/line. It must START its comment (after `//`, `/*`, `<!--`, a block gutter `*`, or whitespace),
|
|
84
|
+
and is honoured on the reported line, on a comment-only line directly above it (reaching the first
|
|
85
|
+
CODE line below, through the rest of its comment block), and — in CSS — from a trailing comment back
|
|
86
|
+
over the declaration it terminates. Whole-file scope is `theme-allow-file <id>… — <why>`; a bare
|
|
87
|
+
`theme-allow-file` waives nothing. A bare `theme-allow` still waives everything on its placement but
|
|
88
|
+
reports `theme-allow-unscoped`. A word in the id slot that names no rule waives nothing.
|
|
89
|
+
`.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.
|
|
91
|
+
|
|
80
92
|
> **Framework-internal only** — `bunx basalt-ui check-coverage` is a self-consistency gate for the
|
|
81
93
|
> basalt-ui repo itself (asserts SURFACES ↔ rule files ↔ skill files ↔ package.json exports). It
|
|
82
94
|
> is not a consumer command and will skip assertion 3 outside the framework repo.
|
|
@@ -85,3 +97,9 @@ colors without a `theme-allow` comment.
|
|
|
85
97
|
|
|
86
98
|
See `basalt-ui/llms.txt` (importable via the package specifier) for the full surface map: one
|
|
87
99
|
entry per published subpath with import specifier, description, layer, and optional peer packages.
|
|
100
|
+
|
|
101
|
+
## Upgrading across minors
|
|
102
|
+
|
|
103
|
+
`CHANGELOG.md` lists every release; `MIGRATING.md` lists what **broke** — removed and renamed
|
|
104
|
+
exports per minor, with the replacement. No majors ship here, so a minor bump can require code
|
|
105
|
+
changes. Read `MIGRATING.md` before jumping more than one minor.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
# [1.21.0](https://github.com/jkrumm/basalt-ui/compare/v1.20.0...v1.21.0) (2026-08-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* delete a waiver that stopped covering anything ([3984281](https://github.com/jkrumm/basalt-ui/commit/3984281fff6313c225bdf1e185de4a427d2ef4df))
|
|
7
|
+
* emit 0.1, not 0.10, from the shadow palette itself ([48adfff](https://github.com/jkrumm/basalt-ui/commit/48adfffcc9ad7713cacc07c1fc090b219fd175fa))
|
|
8
|
+
* let the real color scheme win, and keep <meta charset> in its window ([8708c85](https://github.com/jkrumm/basalt-ui/commit/8708c85f1488b25e6a059ee879dcadf6350278a2))
|
|
9
|
+
* make an annotation start its comment, and let it scope to one node ([a4cf78c](https://github.com/jkrumm/basalt-ui/commit/a4cf78c546d291c33a27bf94d7636758072a1005))
|
|
10
|
+
* persist sidebar collapse through basalt's own state primitive ([c576040](https://github.com/jkrumm/basalt-ui/commit/c576040172dfeb8d66b52909bbcf55b7b1f91c70))
|
|
11
|
+
* resolve the paths the wiring checks only matched, and audit the waivers ([c595181](https://github.com/jkrumm/basalt-ui/commit/c5951817ed01d7729f031bba2f3da50c3bf60666))
|
|
12
|
+
* restore the palette fixture to emitter bytes ([605e240](https://github.com/jkrumm/basalt-ui/commit/605e240387a417f57ae8070f97bad79d97545d60))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* give the type ladder the two rungs consumers could only waive ([29b9ef3](https://github.com/jkrumm/basalt-ui/commit/29b9ef3767282dffb51fb52138d6ae4f1d24dd62))
|
|
18
|
+
* make the search-param store's reader reachable from the API ([6924970](https://github.com/jkrumm/basalt-ui/commit/6924970f410b82ba5cfc65fd1d5088124934fc8f))
|
|
19
|
+
|
|
20
|
+
# [1.20.0](https://github.com/jkrumm/basalt-ui/compare/v1.19.1...v1.20.0) (2026-08-22)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
* exempt generated LINES, not files, and stop flagging prose as a typo'd id ([a83abfb](https://github.com/jkrumm/basalt-ui/commit/a83abfb77888f31528f44e5923b26d3710ddf005))
|
|
26
|
+
* give the mobile-nav-pill demo row its own glyph ([4620590](https://github.com/jkrumm/basalt-ui/commit/4620590204a99afd07ec27eb923cd229acdfce84))
|
|
27
|
+
* hold basalt to its own scoped theme-allow contract ([b67718c](https://github.com/jkrumm/basalt-ui/commit/b67718cecf8e7ce60b61b7481bcdbd4795f79d40))
|
|
28
|
+
* make the escape hatch and the generated-file marker fail closed ([526cdb7](https://github.com/jkrumm/basalt-ui/commit/526cdb7f11395f27183828e34297b0ac6bc846c8))
|
|
29
|
+
* make the mobile-nav active pill track density and survive an icon-less app ([0a7d756](https://github.com/jkrumm/basalt-ui/commit/0a7d7560f9eae6db4c02ff029ba47088a084e511))
|
|
30
|
+
* snapshot the tokens-only guard export ([b63dc24](https://github.com/jkrumm/basalt-ui/commit/b63dc242bfd46359476f7fcae80957db244f727f))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Features
|
|
34
|
+
|
|
35
|
+
* demo the mobile-nav active pill with and without an icon dependency ([9620905](https://github.com/jkrumm/basalt-ui/commit/9620905409efdf39bb10b40edaa128ce2b61c741))
|
|
36
|
+
* make the escape hatch usable and close the holes round 4 found ([5b8420d](https://github.com/jkrumm/basalt-ui/commit/5b8420d24647b84818dfbb65e9f0c52ea1e79c2c))
|
|
37
|
+
* scan the app shell, wire the tokens-only profile, ship __APP_VERSION__ ([61b1792](https://github.com/jkrumm/basalt-ui/commit/61b1792e8ea1bff3c59128ba65137b27d40df81a))
|
|
38
|
+
* stop the toolchain reporting green while enforcing nothing ([d3d8e34](https://github.com/jkrumm/basalt-ui/commit/d3d8e3434893516e62413acafdd166be13b86541))
|
|
39
|
+
|
|
1
40
|
## [1.19.1](https://github.com/jkrumm/basalt-ui/compare/v1.19.0...v1.19.1) (2026-08-20)
|
|
2
41
|
|
|
3
42
|
|
package/MIGRATING.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# Migrating basalt-ui
|
|
2
|
+
|
|
3
|
+
`CHANGELOG.md` ships in this package and lists every release. It cannot tell you what **broke**:
|
|
4
|
+
semantic-release writes one line per commit, and "make the mobile bar navigate, driven by one typed
|
|
5
|
+
nav definition" does not say `ChartHoverSync` was deleted. This file is that half — removed and
|
|
6
|
+
renamed exports per minor, with the replacement.
|
|
7
|
+
|
|
8
|
+
Reconstructed from `git diff` over the published export surface across `v1.0.0..v1.19.1`, then
|
|
9
|
+
cross-checked against the repo's `scripts/export-surface.json` snapshot. **Every replacement below
|
|
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.20.1 section was
|
|
12
|
+
written against source, not against its commit messages. Check the types, not this table, if the two
|
|
13
|
+
disagree.
|
|
14
|
+
|
|
15
|
+
**No majors, by policy.** A rename or a removal ships as a plain `feat:` on the 1.x line, so a minor
|
|
16
|
+
bump can require code changes. Skipping several at once is the expensive case — read every section
|
|
17
|
+
between your version and the target.
|
|
18
|
+
|
|
19
|
+
> Reading the CHANGELOG: minors and majors are `#` (h1), patches are `##` (h2). Grepping for
|
|
20
|
+
> `## [1.` finds only the patches.
|
|
21
|
+
|
|
22
|
+
**Minors with no public API delta:** 1.1.0, 1.3.0, 1.4.0, 1.5.0, 1.6.0, 1.7.0, 1.8.0, 1.9.0,
|
|
23
|
+
1.10.0, 1.13.0, 1.14.0, 1.16.0, 1.18.0 — and every patch. Additive-only subpaths: `./tokens.css`
|
|
24
|
+
at 1.3.0, `./agent-chat` at 1.10.0.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 1.20.1 — the `theme-allow` grammar
|
|
29
|
+
|
|
30
|
+
**No export removed or renamed.** One break, and it is in the escape hatch itself: **file scope must
|
|
31
|
+
now be spelled `theme-allow-file`.** At 1.20.0 an annotation that named a rule and gave a reason —
|
|
32
|
+
the exact shape the rule's own message asks for — was promoted to a whole-file declaration, which is
|
|
33
|
+
why per-node scoping never actually shipped. The two forms are now distinct:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
theme-allow → this node/line, EVERY rule (reports theme-allow-unscoped)
|
|
37
|
+
theme-allow <id>[, <id>…] [— <why>] → this node/line, those rules
|
|
38
|
+
theme-allow-file <id>[, <id>…] — <why> → the WHOLE FILE, those rules; a bare one waives NOTHING
|
|
39
|
+
"basalt:theme-allow[-file]": "<id>… — <why>" → the same two, for JSON / .webmanifest
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**The migration is one word per file declaration.** Measured across the consumer sweep: linewatch
|
|
43
|
+
0 → 11 findings, argo 0 → 6, rb 0 → 0 — all `warn`, so **no build changes colour**.
|
|
44
|
+
|
|
45
|
+
```diff
|
|
46
|
+
-// theme-allow hand-rolled-plot — two panes over one x scale
|
|
47
|
+
+// theme-allow-file hand-rolled-plot — two panes over one x scale
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**An annotation must now START its comment** — after `//`, `/*`, `<!--`, a block-comment gutter `*`,
|
|
51
|
+
or nothing but whitespace. Both parsers used a bare substring search, so a comment that merely
|
|
52
|
+
_mentioned_ the token parsed as the legacy blanket form and switched every rule off on the line
|
|
53
|
+
below. linewatch documented its own waivers in a docblock and thereby disarmed the file — a false
|
|
54
|
+
NEGATIVE, and the reason this ships as a break rather than a grace period. Every annotation anyone
|
|
55
|
+
actually writes still qualifies; a sentence about the escape hatch no longer waives anything.
|
|
56
|
+
|
|
57
|
+
**A comment-only annotation now reaches the first CODE line below it**, walking through the rest of
|
|
58
|
+
its own comment block. Before, a reason that wrapped onto a second line, or a docblock's `*/`,
|
|
59
|
+
absorbed the waiver and the natural shape silently waived nothing — argo hit that three times in one
|
|
60
|
+
upgrade. A blank line still ends the block.
|
|
61
|
+
|
|
62
|
+
**`.json` / `.webmanifest` finally have a waiver.** They have been scanned since 1.20.0 and cannot
|
|
63
|
+
hold a comment, so their findings were unwaivable and the printed remedy prescribed something
|
|
64
|
+
impossible; both consumers fell back to a blanket `exemptRules`. Use a member key — but for a
|
|
65
|
+
manifest, `basaltAppPlugin` is the first remedy, since a hand-copied hex drifts from the palette:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{ "basalt:theme-allow-file": "raw-hex — a PWA manifest theme_color must be a literal hex" }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Two API-shaped fixes worth acting on:**
|
|
72
|
+
|
|
73
|
+
| Change | What to do |
|
|
74
|
+
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
75
|
+
| `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.20.1 value, so the sidebar does not re-expand on upgrade |
|
|
77
|
+
|
|
78
|
+
**New, additive:**
|
|
79
|
+
|
|
80
|
+
| Surface | Note |
|
|
81
|
+
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `check-theme --audit-allows` | every waiver and every `exemptRules` entry with what it still suppresses, proved by re-running the guard with that one occurrence neutralized. Exits 1 on a dead waiver — wire it into CI |
|
|
83
|
+
| `basalt.exemptRules` takes paths, globs and a reason | relative paths and directory prefixes (`public/site.webmanifest`, `src/agent`), globs (`*` stops at `/`, `**` does not, a slash-free glob also matches the basename), and `{ paths, reason }`. Entries that suppress nothing are now reported |
|
|
84
|
+
| `doctor` → `lefthook-preset` | hard-fails a broken `extends` target. lefthook merges a missing target into ZERO commands and exits 0, so a stale path leaves a repo with no pre-commit gate and a clean `lefthook dump`. `sync` reports the same seam |
|
|
85
|
+
| `basaltAppPlugin({ colorScheme })` | `'dark'` (default) / `'light'` / `'auto'` / `false`. Set it to whatever the app passes as `defaultColorScheme` — before this, a light-scheme consumer got dark native controls permanently |
|
|
86
|
+
| `VX.text.nano` (10px) + `VX.text.display` (30px) | the two rungs `inline-font-size` could previously only be waived for. A 20px rung was rejected: 21/20 = 1.05 is below the ladder's 1.06×–1.17× band, so `h2` is the remedy |
|
|
87
|
+
| `sync` backfills `basalt.roots` | only `init` wrote it, so every existing consumer sat on the undeclared `src` default while `guard-scan` passed. A declared value is never overwritten |
|
|
88
|
+
| `basalt/shadow-basalt-export` reads all nine barrels | the charts layer included. Still exact-name-only — a **tripwire, not coverage** |
|
|
89
|
+
|
|
90
|
+
Also: `check-theme`'s `inline-spacing` no longer reads a unitless number in a plain options bag as
|
|
91
|
+
CSS (`fitBounds({ padding: 48 })` stops reporting); the shipped `oxfmt` pre-commit job drops back to
|
|
92
|
+
`*.{ts,tsx,js,jsx,css}` and gains `--no-error-on-unmatched-pattern`; `tokens:css` emits `0.1` rather
|
|
93
|
+
than `0.10`, so a committed sheet stops failing prettier — re-run the command to pick it up.
|
|
94
|
+
|
|
95
|
+
**The lefthook preset overrides YOU, not the other way round.** An `extends` target wins on a
|
|
96
|
+
colliding key: declare `pre-commit.commands.oxfmt.run` (or `glob:`) in your own file and **yours**
|
|
97
|
+
is the one silently discarded. Only keys the preset does not define merge in. The guard job runs
|
|
98
|
+
`${BASALT_BIN:-bunx --no-install basalt-ui}`; that shell default is the sanctioned seam, set via
|
|
99
|
+
`env:`, which does merge.
|
|
100
|
+
|
|
101
|
+
## 1.20.0 — enforcement
|
|
102
|
+
|
|
103
|
+
**No export removed or renamed.** The whole delta is that things which used to pass now report. If
|
|
104
|
+
your build goes red on this upgrade, that is the release working. Every new **kind** and **rule**
|
|
105
|
+
lands `warn` — but two other changes in this release do fail a build: `basalt/raw-size-literal`
|
|
106
|
+
promotes to `error`, and the widened markup scan reads `index.html` / `.webmanifest`, where a raw
|
|
107
|
+
hex is an `error`-severity colour kind. Two consumers exited 1 on exactly that.
|
|
108
|
+
|
|
109
|
+
| Change | What you'll see | What to do |
|
|
110
|
+
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
111
|
+
| `basalt/raw-size-literal` `warn` → **`error`** | CSS-length strings on `size`/`fz`/`fontSize` now fail lint | use a token (`size="sm"`); `warn` since 1.7.0, zero violations across all seven consumers |
|
|
112
|
+
| Five new guard kinds (`warn`) | `theme-allow-unscoped`, `surface-shadow-override`, `css-raw-surface`, `inline-font-size`, `hidden-inline-style` | see below; promotion is tracked in `GRACE_PERIOD_KINDS` |
|
|
113
|
+
| Two new oxlint rules (`warn`) | `basalt/shadow-basalt-export`, `basalt/hand-rolled-shell` | import the shipped component instead of the fork |
|
|
114
|
+
| `basalt/hand-rolled-plot`, `basalt/chart-legend-literal` widened | more sites report; both stay `warn` | a widened rule does not promote in the minor that widens it |
|
|
115
|
+
| `doctor` `SKIPPED` exits non-zero, + 3 new hard checks | doctor goes red where it was green | that is the finding — see below |
|
|
116
|
+
|
|
117
|
+
**`theme-allow` has a new contract, and one comment shape stops waiving.** A bare `theme-allow`
|
|
118
|
+
still waives every kind, but now reports `theme-allow-unscoped`. Rescope it — and spell the id
|
|
119
|
+
right, because a word in the id slot that names no rule now waives NOTHING rather than degrading to
|
|
120
|
+
the blanket form:
|
|
121
|
+
|
|
122
|
+
```diff
|
|
123
|
+
-// theme-allow
|
|
124
|
+
+// theme-allow raw-surface — third-party widget needs a literal corner
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**The break: a reason with no separator introducing it.** `// theme-allow legacy vendor asset` used
|
|
128
|
+
to waive the line; it now waives nothing and the un-suppressed finding reports at its own severity
|
|
129
|
+
(`error` for `raw-hex`). The first word after the token is read as a rule id, and an id that names
|
|
130
|
+
no rule fails closed — that is the whole point, since the alternative is one mistyped character
|
|
131
|
+
silently widening a scoped waiver into a blanket one. No annotation in any of the seven consumer
|
|
132
|
+
repos writes that shape; every one of them introduces its reason with `—`, `–`, `-` or `:`. Add a
|
|
133
|
+
separator, or an id:
|
|
134
|
+
|
|
135
|
+
```diff
|
|
136
|
+
-// theme-allow legacy vendor asset
|
|
137
|
+
+// theme-allow raw-hex — legacy vendor asset
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Prose AFTER a resolved id is safe and needs no separator —
|
|
141
|
+
`// theme-allow raw-surface sub-scale legend corner` waives `raw-surface` and reports nothing. Only
|
|
142
|
+
a comma keeps the id list open past the first id.
|
|
143
|
+
|
|
144
|
+
Two placements that used to fail now work, both matching what the oxlint plugin always did: a
|
|
145
|
+
comment-ONLY line directly above the reported line (the only form JSX can express — the reported
|
|
146
|
+
line is usually a multi-line opening tag or a `{expr}` child), and in CSS a trailing annotation
|
|
147
|
+
reaching back over the declaration it terminates, which is what survives the shipped `oxfmt`
|
|
148
|
+
reflowing a long `background-color` so the hex lands above the comment.
|
|
149
|
+
|
|
150
|
+
**`basalt/hand-rolled-plot` waivers must now be written deliberately.** Every assembly node is
|
|
151
|
+
reported individually, and a waiver is no longer picked up off whatever comment happened to sit on
|
|
152
|
+
the file's first assembly node — it needs a written declaration naming the rule and giving a reason,
|
|
153
|
+
anywhere in the file: `// theme-allow hand-rolled-plot — two panes over one x scale`.
|
|
154
|
+
|
|
155
|
+
**But the waiver is still whole-file, and per-node scoping is not expressible at 1.20.0.** Naming
|
|
156
|
+
the rule AND giving a reason is what `hasFileDeclaration` matches, at any line — so a comment
|
|
157
|
+
intended for one node silences the file. Dropping the reason keeps it node-scoped in the oxlint
|
|
158
|
+
plugin, but then `check-theme` reports `theme-allow-unscoped ("no reason")`. The two halves of the
|
|
159
|
+
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.20.1** —
|
|
161
|
+
see that section; the declaration moves to `theme-allow-file`.
|
|
162
|
+
|
|
163
|
+
**`doctor` will go red.** `SKIPPED` is a third outcome beside pass/warn/fail and exits non-zero on
|
|
164
|
+
its own — "All checks passed" is only printable when every check RAN. Three new hard checks:
|
|
165
|
+
`basalt-resolves` (walks cwd → ancestors → workspace packages), `guard-scan` (would `check-theme`
|
|
166
|
+
cover more than zero files?), `oxlint-preset` (does `.oxlintrc.json` really extend the shipped
|
|
167
|
+
preset? JSONC is parsed, not rejected — `init` keeps an existing config, so one repo ran five minors
|
|
168
|
+
with the whole lint half off).
|
|
169
|
+
|
|
170
|
+
**New, additive:**
|
|
171
|
+
|
|
172
|
+
| Surface | Note |
|
|
173
|
+
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
174
|
+
| `basalt.profile: 'tokens-only'` / `--tokens-only` | disables the 17 kinds whose remedy is a Mantine component, prop or the React theme factory. `check-theme` requires it DECLARED; `doctor` infers it, because its profile only changes advice, never enforcement |
|
|
175
|
+
| `basalt.include: [...]` | scan a named file outside `roots` — and the only route to a `.json`, which is never blanket-scanned |
|
|
176
|
+
| `basalt.roots` + a `lint:basalt` script | written by `init` from the real layout; `init` on an existing app is a lint-debt event, not a no-op |
|
|
177
|
+
| `tokens:css --check`, `--selector-class <c>` (+ `--light-class`) | drift gate; the Tailwind `<html class="dark">` convention. There is no `scheme: { class }` API — the class form is CLI-only |
|
|
178
|
+
| `fonts:css [--out] [--check]` | the shipped `--basalt-font-*` stacks as plain CSS, read out of `styles.css` — the only route to basalt's typefaces without the Mantine-coupled `styles.css` |
|
|
179
|
+
| `__APP_VERSION__` ambient declaration | ships via `src/register.ts`, re-exported by the root barrel: delete your hand-written ambient block. A subpath-only consumer does not get it |
|
|
180
|
+
| `BASALT_CWD` | `check-theme`/`doctor` honour it, and relocate to the single workspace package carrying a basalt config when invoked from a root that has none |
|
|
181
|
+
| `@generated basalt-ui` header | `tokens:css`/`fonts:css` output carries it on line 1, the version + invocation line on line 2, and `check-theme`, in a `.css` file with that exact header, skips the LINES that are basalt custom properties, selectors, `}` or self-closing comments — this is what fixed 116 violations reported inside the stylesheet `tokens:css` had just written. Committed output emitted by 1.19.1 has no header: re-run the command to get the skip |
|
|
182
|
+
|
|
183
|
+
`check-theme` also resolves `.html` / `.webmanifest` / `.json` as markup (colour kinds only), and
|
|
184
|
+
each root's PARENT now contributes its `index.html` and `public/` tree.
|
|
185
|
+
|
|
186
|
+
## 1.19.0 — nav
|
|
187
|
+
|
|
188
|
+
| Removed / renamed | Replacement | Note |
|
|
189
|
+
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
190
|
+
| `NavLinkRenderer` (type) | `defineNav` + `{...useNav(NAV)}` (`basalt-ui/router-tanstack`), or `SidebarItem.Anchor` | basalt now paints every nav pixel |
|
|
191
|
+
| `BasaltShellProps.renderNavLink`, `AppSidebarProps.renderNavLink` | same | |
|
|
192
|
+
| `BreadcrumbLinkRenderer` (type) | `AppBreadcrumbs.parentAnchor` | |
|
|
193
|
+
| `BasaltShellProps.renderBreadcrumbLink` | same | |
|
|
194
|
+
| `BasaltShellProps.sidebarFooterExtra` | `mobileNav.moreExtra` | its only host was the mobile drawer; it rendered nowhere on desktop |
|
|
195
|
+
| `AppSidebarProps.footerExtra`, `AppSidebarProps.onClose` | — | the full-height mobile sidebar drawer is deleted |
|
|
196
|
+
| `MobileNavItem` (type) | `MobileNavSlot` | |
|
|
197
|
+
| `MobileNavSection` (type) | `MobileNavGroup` | |
|
|
198
|
+
| `MobileNavLinkRenderer` (type) | `MobileNavModel` | |
|
|
199
|
+
| `SidebarSection.mobileTab` | `SidebarSection.mobile?: false \| NavSectionMobile` — an OBJECT (`{ tab: true, label?, icon? }`), or `false` to hide the section | `'tab' \| 'more' \| 'hidden'` is `SidebarItem.mobile` (`NavMobilePlacement`, `true` ≡ `'tab'`, `false` ≡ `'hidden'`) — a different prop on a different type |
|
|
200
|
+
|
|
201
|
+
A consumer with no router needs no migration — `href` + `onClick` still work.
|
|
202
|
+
|
|
203
|
+
**Expect new type errors, and read them.** `renderNavLink` took `to` as an opaque value, which is
|
|
204
|
+
why consumers reached for `to={target.to as never}`. `defineNav` types it, so a destination missing
|
|
205
|
+
a required `search` now fails to compile — in one repo that surfaced two nav links that had been
|
|
206
|
+
shipping without required params. Fix with a click-time thunk (`search: () => Schema.parse({})`),
|
|
207
|
+
never `search: true`.
|
|
208
|
+
|
|
209
|
+
## 1.17.0 — behaviour only, no export removed
|
|
210
|
+
|
|
211
|
+
| Changed | Effect | Opt out |
|
|
212
|
+
| ---------------------------------------------------- | ------------------------------------------------------------ | -------------------------------- |
|
|
213
|
+
| `resolveAxisDomain` clamps before padding, not after | the axis top moves on any chart with an `autoMaxFloor` | lower the floor, or pin `domain` |
|
|
214
|
+
| `ChartFrame` `role="img"` → `role="group"` | `role="img"` pruned the keyboard slider out of the a11y tree | — |
|
|
215
|
+
|
|
216
|
+
## 1.15.0 — chart layer rebuilt
|
|
217
|
+
|
|
218
|
+
The largest delta in the 1.x line. No shims were shipped.
|
|
219
|
+
|
|
220
|
+
| Removed / renamed | Replacement | Note |
|
|
221
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
222
|
+
| `ChartHoverSync`, `ChartHoverSyncProps` | `globalCursorStore` / `createCursorStore` / `useChartCursor` / `useCursorState` — **no provider** | `ChartCursorScope` now _isolates_ a subtree; it is the inverse of the old provider, not a rename |
|
|
223
|
+
| `HoverContext`, `HoverCtx`, `useHoverSync`, `DEFAULT_NO_OP_SET_HOVER` | `useChartCursor`, `useCursorState`, `CursorState`, `CursorStore` | context → `useSyncExternalStore` |
|
|
224
|
+
| `ResponsiveChart`, `ResponsiveChartProps` | `CartesianChart` / `CartesianChartProps` (+ `autoMargin`, `PlotContext`, `PlotRect`) | now mandatory for every single-plot cartesian chart, enforced by `basalt/hand-rolled-plot` |
|
|
225
|
+
| `ChartTooltip` (tip-based), `useChartTooltip`, `useTooltipStyles` | `ChartTooltipFloat` + the `tooltip: CartesianTooltipConfig` prop | portal / flip / clamp done once |
|
|
226
|
+
| `BarsAxisConfig` | `AxisConfig` + `resolveAxisDomain` | |
|
|
227
|
+
| `ZonedLineTooltipLabel` | `tooltip` config on the kind | |
|
|
228
|
+
| `ZonedLine`/`MultiLine`: `yDomain`, `yAutoPad`, `yAutoMaxFloor`, `yAutoMinCeil`, `numTicksY`, `formatYTick` | one `y?: AxisConfig<T>` | `y` is **optional** where `yDomain` was required |
|
|
229
|
+
| `ZonedLine`/`MultiLine`: `tooltipLabel`, `renderExtraTooltipRows` | `tooltip.label`, `tooltip.extraRows` (`CartesianTooltipConfig`) | there is no `appendRows`; the field is `extraRows` |
|
|
230
|
+
| `ZonedLine`/`MultiLine`: `formatValue` | **`y.format`** — or per-series `ChartSeries.formatValue` for one row | `CartesianTooltipConfig` has no value formatter: the axis format IS the tooltip value format |
|
|
231
|
+
| `ZonedLine`/`MultiLine`: `numTicksX` | `xTicks` | `xZones` added alongside |
|
|
232
|
+
| `Bars`: `formatValue`, `hideBarTooltipRows`, `leftAxis`, `rightAxis`, `marginLeft`, `numTicksX`, `tooltipLabel`, `renderExtraTooltipRows`, `renderPrefixTooltipRows` | `y` / `y2` (`AxisConfig`), `xTicks`, `tooltip.{label,extraRows,prependRows}`, per-bar `BarsBar.formatValue` / `.tooltip`, measured `autoMargin` + `margin` | passing `y2` is what makes a chart dual-axis. `chartMargin({ rightAxis })` is **not** removed — it is still exported from `basalt-ui` and `basalt-ui/charts`; it is simply no longer needed, since the right gutter follows from measurement |
|
|
233
|
+
| `StackedArea`: `formatValue`, `numTicksX`, `numTicksY`, `yAutoMaxFloor`, `yLabel` | `y` (incl. `y.format`), `xTicks`; per-series `ChartSeries.formatValue` | `StackedAreaProps` has **no** `tooltip` prop — its tooltip is entirely derived from `series` |
|
|
234
|
+
| `Heatmap.width` | measures itself; takes `height` / `aspectRatio` / `fill` | |
|
|
235
|
+
|
|
236
|
+
## 1.12.0 — agent-chat, behaviour only
|
|
237
|
+
|
|
238
|
+
| Changed | Effect |
|
|
239
|
+
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
240
|
+
| `streaming` no longer selects the image allowlist | **security-relevant.** Model output must now pass `contentTrust="untrusted"`; leaving it unset silently reopens prompt-injection image exfiltration |
|
|
241
|
+
| consumer `rehypePlugins` output is now sanitized, `clobberPrefix` is empty | a consumer injecting non-default elements must pass a matching `sanitizeSchema` |
|
|
242
|
+
|
|
243
|
+
## 1.11.0 — agent parts
|
|
244
|
+
|
|
245
|
+
Semver-breaking in a minor; the commit body says so.
|
|
246
|
+
|
|
247
|
+
| Removed / renamed | Replacement | Note |
|
|
248
|
+
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
249
|
+
| `ToolCallPart` flat `{ type, toolName, input, output? }` | `ToolCallPart` as a 7-arm union discriminated on `state` (`input-streaming` … `output-error`) | mirrors the AI SDK v7 lifecycle |
|
|
250
|
+
| the error field named `error` | `errorText` | the SDK union has no `error` |
|
|
251
|
+
| flattened approval fields | nested `approval: ToolApproval`, carried verbatim | flattening dropped `isAutomatic` / `signature` |
|
|
252
|
+
| — | every `AgentPart` now extends `PartBase` with a **required `id`**; `withPartIds` mints them on drafts | a 1.10-shaped part rehydrated from localStorage no longer throws, but id-less parts collide |
|
|
253
|
+
| `ToolChip` threw `assertNever` on an unknown state | now falls back instead of throwing | |
|
|
254
|
+
|
|
255
|
+
## 1.0.0 — the Mantine pivot
|
|
256
|
+
|
|
257
|
+
| Removed / renamed | Replacement | Note |
|
|
258
|
+
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
259
|
+
| `./css` subpath (877 lines: 307 custom props, 19 utilities, a reset, the Tailwind entrypoint) | `basalt-ui tokens:css` / `basalt-ui/tokens.css` — **but only from 1.3.0** | between 1.0.0 and 1.2.x the only CSS door was `styles.css` + `buildPaletteCss()` |
|
|
260
|
+
| `./starlight` subpath | — | no replacement |
|
|
261
|
+
| the OKLCH foundation palette, ShadCN/Tremor/Starlight doctrine | the three-tier `--vx-*` system | see `docs/FRAMEWORK-FREE.md` for the token-only route |
|
|
262
|
+
|
|
263
|
+
Names that have no 1.x equivalent: the `--chart-blue-1..8` sequential ramp (1.x is categorical
|
|
264
|
+
`--vx-fill-*` only), the default font stacks as a token (they live in `styles.css`), `purple` as a
|
|
265
|
+
text color, `black`, `blue-400`/`green-400`.
|
|
266
|
+
|
|
267
|
+
**The typefaces changed at 1.0, not just their delivery.** 0.4.2 shipped Instrument Sans; 1.x ships
|
|
268
|
+
Nunito Sans (body) + Hubot Sans (condensed headings), mono unchanged at JetBrains Mono. `fonts:css`
|
|
269
|
+
emits the 1.x stacks — for a 0.4.2 migrant that is a **rebrand**, not a restoration. It also emits
|
|
270
|
+
`--basalt-font-head-stretch: 88%`, tuned for Hubot Sans specifically; pointed at another face it
|
|
271
|
+
silently condenses it.
|
|
272
|
+
|
|
273
|
+
## 1.0.1 — CLI binary renamed
|
|
274
|
+
|
|
275
|
+
`basalt` → **`basalt-ui`**. Never `bunx basalt`.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Lint and guard rules that tightened
|
|
280
|
+
|
|
281
|
+
Two independent mechanisms, each with its own ledger — read the ledger, not this table, for what is
|
|
282
|
+
in grace TODAY. `GuardKind` severities (`basalt-ui check-theme`) default to `error` and are
|
|
283
|
+
downgraded only by `GRACE_PERIOD_KINDS` (`src/guard/index.ts`). `basalt/*` oxlint rule severities
|
|
284
|
+
live in `configs/oxlint.json`, and since 1.20.0 their grace is tracked by `PLUGIN_RULE_GRACE`, a
|
|
285
|
+
named export beside the plugin — a test asserts it against the shipped preset in both directions, so
|
|
286
|
+
deleting an entry forces the level flip in the same commit.
|
|
287
|
+
|
|
288
|
+
| Rule | Landed | Became `error` |
|
|
289
|
+
| ---------------------------------- | ---------------- | ------------------------------------------------------------------------ |
|
|
290
|
+
| `mantine-shade-index` (guard kind) | 1.7.0 as `warn` | **1.11.0** |
|
|
291
|
+
| `basalt/raw-scroll-container` | ≤1.2.0 as `off` | `warn` 1.10.0 → **`error` 1.13.0** |
|
|
292
|
+
| `basalt/ai-sdk-major` | 1.10.0 as `warn` | **1.13.0** |
|
|
293
|
+
| `basalt/agent-no-raw-usechat` | 1.10.0 as `warn` | **1.13.0** |
|
|
294
|
+
| `basalt/agent-resume-guard` | 1.10.0 as `warn` | **1.13.0** |
|
|
295
|
+
| `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.20.1; grace restarts with the widening |
|
|
297
|
+
| `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.20.1 |
|
|
299
|
+
| `basalt/hand-rolled-shell` | 1.20.0 as `warn` | — |
|
|
300
|
+
|
|
301
|
+
`card-with-border`, `inline-display`, `raw-html-layout`, `raw-form-control`, `raw-font-family` and
|
|
302
|
+
the other original guard kinds have been `error` since before 1.2.0 — they never had a grace minor.
|
|
303
|
+
Guard findings only gained a severity field at all in 1.4.0; before that every finding was fatal.
|
|
304
|
+
|
|
305
|
+
**Rule-id rename at 1.1.0:** `basalt/import-boundary` split into `basalt/visx-boundary`,
|
|
306
|
+
`basalt/visx-tooltip` and `basalt/token-layer-boundary` (the last is repo-local and deliberately not
|
|
307
|
+
in the shipped preset). A config still naming `import-boundary` after 1.1.0 disables nothing.
|
|
308
|
+
|
|
309
|
+
## Deprecated, not yet removed
|
|
310
|
+
|
|
311
|
+
The 32 camelCase `--vx-*` aliases deprecated in 1.5.0 are **still emitted at 1.20.1**.
|
|
312
|
+
`buildPaletteCss({ legacyAliases: false })` / `tokens:css --no-legacy-aliases` opts out now; a later
|
|
313
|
+
minor flips the default.
|
|
314
|
+
|
|
315
|
+
## Not verified
|
|
316
|
+
|
|
317
|
+
- Props declared inline on a component (`function X({ a }: { a: string })`) rather than on an
|
|
318
|
+
exported type are outside the diff method used here. Spot checks on the shell and charts modules
|
|
319
|
+
found none, but the negative is unproven.
|
|
320
|
+
- The 0.4.2 `./css` line counts in the 1.0.0 table (877 lines / 307 custom props / 19 utilities) come
|
|
321
|
+
from the removal commit, not from a re-read of the 0.4.2 tarball.
|
|
322
|
+
|
|
323
|
+
`AppBreadcrumbs.parentAnchor` was listed here through 1.20.0 and is now verified: it is declared on
|
|
324
|
+
the shipped `dist/shell/app-breadcrumbs.d.ts` (`parentAnchor?: NavAnchor`, with `parentHref` as the
|
|
325
|
+
no-router fallback). The type is inline on the component rather than an exported `AppBreadcrumbsProps`,
|
|
326
|
+
which is why the export-surface diff never saw it.
|
package/README.md
CHANGED
|
@@ -65,6 +65,15 @@ That's the whole install — there is no plugin, no marketplace, and no second v
|
|
|
65
65
|
- A thin `DESIGN.md` seed — your app's deltas (series dictionary, identity, deviations)
|
|
66
66
|
- Toolchain seeds: `.oxlintrc.json` and `lefthook.yml` as `extends` stubs into `node_modules/basalt-ui/configs/` (the presets auto-update with the package), plus `.oxfmtrc.json` and `.github/workflows/check.yml` as starting copies
|
|
67
67
|
- `.basalt/manifest.json` — sha256 per managed file + the basalt-ui version, for `sync` three-way diff and `doctor`
|
|
68
|
+
- Two `package.json` patches: `basalt.roots` inferred from the real layout, and a `lint:basalt` script
|
|
69
|
+
|
|
70
|
+
`init` on an **existing** app is a lint-debt event, not a no-op: the shipped preset switches on
|
|
71
|
+
whole oxlint plugins the repo was never linted against, so previously-clean code lands with real
|
|
72
|
+
findings on the first run. `init` names those plugins, and names every file it kept along with what
|
|
73
|
+
keeping it costs — an `.oxlintrc.json` that does not extend the preset means the entire basalt lint
|
|
74
|
+
half is inert and nothing else says so (`--merge-lint` splices the `extends` in; it refuses on a
|
|
75
|
+
commented config rather than deleting the comments). Run `oxlint .` and triage before your next
|
|
76
|
+
commit.
|
|
68
77
|
|
|
69
78
|
Every file is either **managed** (basalt owns it, `sync` refreshes it, local edits are skipped and
|
|
70
79
|
reported — exactly the files Claude reads, which cannot load from `node_modules`) or a **seed**
|
|
@@ -175,25 +184,84 @@ half — see the `./vite` adapter battery under [Adapter batteries](#adapter-bat
|
|
|
175
184
|
|
|
176
185
|
```ts
|
|
177
186
|
// Lint — theme guard is the teeth behind the token doctrine
|
|
178
|
-
// package.json scripts:
|
|
187
|
+
// package.json scripts (init seeds this as "lint:basalt"):
|
|
179
188
|
"lint": "oxlint . && basalt-ui check-theme"
|
|
180
189
|
```
|
|
181
190
|
|
|
182
|
-
`check-theme` scans `src/` by default
|
|
183
|
-
|
|
191
|
+
`check-theme` scans `src/` by default; `init` writes a real `"basalt"` key from your actual layout,
|
|
192
|
+
and everything derives from it — the guard's scan, the seeded CI `oxfmt` globs, the default scan
|
|
193
|
+
exemption:
|
|
184
194
|
|
|
185
195
|
```json
|
|
186
|
-
// package.json
|
|
196
|
+
// package.json — `roots` is written by init and backfilled by sync; the rest are yours to add
|
|
187
197
|
{
|
|
188
198
|
"basalt": {
|
|
189
|
-
"roots": ["src"]
|
|
199
|
+
"roots": ["apps/web/src"],
|
|
200
|
+
"include": ["app/manifest.json"],
|
|
201
|
+
"profile": "tokens-only",
|
|
202
|
+
"exemptRules": {
|
|
203
|
+
"inline-display": ["agent"],
|
|
204
|
+
"raw-hex": {
|
|
205
|
+
"paths": ["public/site.webmanifest"],
|
|
206
|
+
"reason": "a PWA manifest theme_color MUST be a literal hex — JSON cannot reference a CSS var"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
190
209
|
}
|
|
191
210
|
}
|
|
192
211
|
```
|
|
193
212
|
|
|
194
213
|
`check-theme` fails loudly (instead of a false `✓`) when the configured roots resolve to zero
|
|
195
|
-
scanned files
|
|
196
|
-
|
|
214
|
+
scanned files, and `doctor`'s `guard-scan` check agrees with it. Each root's **parent** also
|
|
215
|
+
contributes its `index.html` and its `public/` tree — the Vite layout `basaltViteConfig` assumes,
|
|
216
|
+
and where a raw `theme-color` or webmanifest `background_color` actually lives. `.json` is never
|
|
217
|
+
blanket-scanned; `include` names one explicitly and is the only route to it. `profile:
|
|
218
|
+
"tokens-only"` turns off the 17 kinds whose remedy is a Mantine component or prop, and must be
|
|
219
|
+
declared (or `--tokens-only`) — it is deliberately never inferred from a missing `@mantine/core`,
|
|
220
|
+
which would silence half the guard on any repo keeping Mantine in a different workspace package.
|
|
221
|
+
Other keys (`exempt`, `severity`, `spacingSteps`, `forbiddenAccents`, …) are documented on the
|
|
222
|
+
`BasaltConfig` type.
|
|
223
|
+
|
|
224
|
+
**`exemptRules`** turns one kind off along a path. A pattern is a relative path
|
|
225
|
+
(`public/site.webmanifest`, or `src/agent` for everything under it), a glob (`*` stops at `/`, `**`
|
|
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.20.1 it was the
|
|
228
|
+
ONLY shape and a real relative path matched nothing and said nothing. The object form records the
|
|
229
|
+
reason, which is what a `theme-allow` always carried and this key could not. A pattern that
|
|
230
|
+
suppresses nothing is now reported by a normal run and fails `--audit-allows`.
|
|
231
|
+
|
|
232
|
+
`sync` **backfills `roots`** when the key is absent — only `init` used to write it, so every
|
|
233
|
+
existing consumer stayed on the undeclared `src` default while `guard-scan` passed anyway. A
|
|
234
|
+
declared value is never overwritten; a layout it does not cover is reported as one line, because
|
|
235
|
+
`roots` being a subset is the normal case in a monorepo, not drift.
|
|
236
|
+
|
|
237
|
+
Basalt-emitted LINES are skipped, not whole files. The file has to earn it first — a `.css` path,
|
|
238
|
+
the `@generated basalt-ui` header verbatim on line 1, the version + invocation line on line 2 — and
|
|
239
|
+
then each line does too: at brace depth 0 a selector or a self-closing comment, inside a block only
|
|
240
|
+
a `--vx-*` / `--basalt-*` declaration whose value carries no `;`, a `}`, or a comment. So the
|
|
241
|
+
stylesheet `tokens:css` just wrote is not reported back at you, pasting the marker into a `.tsx`
|
|
242
|
+
suppresses nothing, and an ordinary declaration smuggled into a sheet wearing the header is
|
|
243
|
+
reported on its own line.
|
|
244
|
+
|
|
245
|
+
**The escape hatch is scoped, and since 1.20.1 the two scopes are spelled apart:**
|
|
246
|
+
|
|
247
|
+
```text
|
|
248
|
+
theme-allow → this node/line, EVERY rule (reports theme-allow-unscoped)
|
|
249
|
+
theme-allow <id>[, <id>…] [— <why>] → this node/line, those rules
|
|
250
|
+
theme-allow-file <id>[, <id>…] — <why> → the WHOLE FILE, those rules; a bare one waives NOTHING
|
|
251
|
+
"basalt:theme-allow[-file]": "<id>… — <why>" → the same two, for JSON / .webmanifest
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
An annotation must **start** its comment (after `//`, `/*`, `<!--`, a block gutter `*`, or
|
|
255
|
+
whitespace) — before 1.20.1 any comment merely mentioning the token parsed as the bare blanket form,
|
|
256
|
+
so a file documenting its own waivers disarmed itself. Placements: the reported line, a comment-only
|
|
257
|
+
line directly above it (the only form JSX can express, and it reaches the first CODE line below,
|
|
258
|
+
walking through the rest of its comment block), or — in CSS — a trailing comment reaching back over
|
|
259
|
+
the declaration it terminates. The id slot is read strictly: a word there that names no rule waives
|
|
260
|
+
nothing, so a typo is never the blanket form.
|
|
261
|
+
|
|
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 guard with that one waiver neutralized. Exits 1
|
|
264
|
+
on a dead waiver, so it is usable as a CI gate.
|
|
197
265
|
|
|
198
266
|
`doctor`'s `ai-major-parity` hard check fails a monorepo where workspace packages declare different
|
|
199
267
|
`ai` package majors — unless the split is intentional and written down. A producer pinned to an
|
|
@@ -241,7 +309,7 @@ deleted rather than silently surviving into the next real skew.
|
|
|
241
309
|
| `./connectivity` | coupled | `ConnectivityProvider` (aggregates browser/React-Query/SSE/health-check status), `useConnectivity`, `ConnectivityIndicator` — auto-mounted by `BasaltProvider` |
|
|
242
310
|
| `./content` | coupled | `Prose`, `CodeBlock`, `Callout`, `TableOfContents`, `ReadingProgress`, `Markdown`, `MermaidDiagram`, `ArticleLayout`, `ArticleCard` / `ArticleGrid`, `GuideLink` / `GuideDrawer`, `mdxComponents` |
|
|
243
311
|
| `./styles.css` | — | `@layer basalt` base styles, iOS input safety net, font stack |
|
|
244
|
-
| `./tokens.css` | **free** | Prebuilt `--vx-*` stylesheet — the default `buildPaletteCss()` output as a plain file, for a consumer with
|
|
312
|
+
| `./tokens.css` | **free** | Prebuilt `--vx-*` stylesheet — the default `buildPaletteCss()` output as a plain file, for a consumer with a bundler but no React or Mantine. **With no bundler, use the `basalt-ui tokens:css` CLI instead** and commit the emitted file: importing this subpath needs bare-specifier resolution. See [`docs/FRAMEWORK-FREE.md`](https://github.com/jkrumm/basalt-ui/blob/master/docs/FRAMEWORK-FREE.md) |
|
|
245
313
|
| `./configs/*` | — | Raw toolchain presets — oxlint, oxfmt, tsconfig (base/react-app/node), lefthook |
|
|
246
314
|
| `./llms.txt` | — | Machine-readable surface map — one entry per published subpath with import specifier, description, layer, and optional peers |
|
|
247
315
|
|
|
@@ -606,11 +674,21 @@ bunx basalt-ui init # scaffold doctrine into a consumer repo
|
|
|
606
674
|
bunx basalt-ui sync # three-way diff against .basalt/manifest.json after a basalt-ui upgrade
|
|
607
675
|
bunx basalt-ui sync --check # CI drift gate — non-zero exit on any managed-file drift
|
|
608
676
|
bunx basalt-ui check-theme # palette guard — fails on colors that bypass the central --vx-* system
|
|
609
|
-
bunx basalt-ui doctor #
|
|
677
|
+
bunx basalt-ui doctor # integration health: basalt-resolves, guard-scan, oxlint-preset, manifest presence, node_modules-vs-manifest version, spacing-scale drift, and a cross-package `ai` major-version parity check (HARD failure — walks every workspace manifest, exemptable via `basalt.aiMajorSkewReason` — see above) + warn on missing basaltAppPlugin icon files under public/
|
|
678
|
+
bunx basalt-ui doctor --tokens-only # force the Mantine-free profile (auto-detected otherwise; --framework forces the full set)
|
|
679
|
+
bunx basalt-ui tokens:css --out src/tokens.css # emit the --vx-* stylesheet as a file you own
|
|
680
|
+
bunx basalt-ui fonts:css --out src/fonts.css # emit the shipped --basalt-font-* stacks
|
|
610
681
|
bunx basalt-ui info # human-readable surface map: subpath, layer, rule, skills, optional peers
|
|
611
682
|
bunx basalt-ui info --json # same map as stable JSON (InfoOutput shape)
|
|
683
|
+
bunx basalt-ui help # full usage; every subcommand also takes --help / -h
|
|
612
684
|
```
|
|
613
685
|
|
|
686
|
+
`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. `check-theme` and `doctor`
|
|
688
|
+
honour `BASALT_CWD`, and relocate to the single workspace package carrying a basalt config when
|
|
689
|
+
invoked from a repo root that has none (two candidates is reported as ambiguous, never guessed).
|
|
690
|
+
`tokens:css` and `fonts:css` both take `--check` as a CI drift gate.
|
|
691
|
+
|
|
614
692
|
`sync` strategy per file: unchanged since last write → overwrite; locally edited → skip (show diff); missing → recreate. `--force` overwrites local edits.
|
|
615
693
|
|
|
616
694
|
---
|