basalt-ui 1.19.0 → 1.20.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 +15 -1
- package/CHANGELOG.md +27 -0
- package/MIGRATING.md +228 -0
- package/README.md +53 -9
- package/agent/rules/basalt-app.md +43 -0
- package/agent/rules/basalt-charts.md +26 -14
- package/agent/rules/basalt-mantine.md +25 -10
- package/agent/rules/basalt-router.md +22 -10
- package/agent/rules/basalt-tokens.md +75 -6
- package/agent/skills/basalt-app/SKILL.md +42 -6
- package/configs/check.yml +11 -4
- package/configs/lefthook.yml +21 -2
- package/configs/oxlint-plugin.js +515 -25
- package/configs/oxlint-plugin.test.ts +383 -10
- 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/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +1 -1
- package/dist/charts/primitives/ChartLegend.js +2 -2
- package/dist/charts/primitives/ChartTooltip.js +1 -1
- package/dist/cli/index.d.ts +107 -15
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +610 -72
- package/dist/cli/toolchain-wiring.test.d.ts +2 -0
- package/dist/cli/toolchain-wiring.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 +56 -2
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +326 -59
- 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/shell/app-mobile-nav.d.ts.map +1 -1
- package/dist/shell/app-mobile-nav.js +14 -14
- package/dist/shell/app-mobile-nav.module.css +44 -11
- package/dist/shell/app-mobile-nav.module.css.d.ts +1 -1
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.js +1 -1
- package/dist/tokens/index.d.ts +3 -3
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +2 -0
- 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.css +19 -17
- package/dist/vite-color-mix.js +2 -2
- 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/Donut.tsx +2 -1
- package/src/charts/primitives/ChartLegend.tsx +2 -2
- package/src/charts/primitives/ChartTooltip.tsx +1 -1
- package/src/cli/index.ts +1070 -99
- package/src/cli/init-sync.test.ts +19 -3
- package/src/cli/tokens-css.test.ts +27 -14
- package/src/cli/toolchain-wiring.test.ts +694 -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 +663 -11
- package/src/guard/index.ts +727 -94
- package/src/guard/types.ts +29 -4
- package/src/register.ts +32 -0
- package/src/shell/app-mobile-nav.module.css +44 -11
- package/src/shell/app-mobile-nav.module.css.d.ts +1 -1
- package/src/shell/app-mobile-nav.test.tsx +34 -0
- package/src/shell/app-mobile-nav.tsx +16 -5
- 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 +5 -3
- package/src/tokens/palette.ts +24 -18
- package/src/vite-color-mix.ts +2 -2
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,12 @@ 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. It is honoured
|
|
83
|
+
on the reported line, on a comment-only line directly above it, and — in CSS — from a trailing
|
|
84
|
+
comment back over the declaration it terminates. A bare `theme-allow` still waives everything but
|
|
85
|
+
reports `theme-allow-unscoped`. A word in the id slot that names no rule waives nothing — only a
|
|
86
|
+
genuinely bare comment is the blanket form.
|
|
87
|
+
|
|
80
88
|
> **Framework-internal only** — `bunx basalt-ui check-coverage` is a self-consistency gate for the
|
|
81
89
|
> basalt-ui repo itself (asserts SURFACES ↔ rule files ↔ skill files ↔ package.json exports). It
|
|
82
90
|
> is not a consumer command and will skip assertion 3 outside the framework repo.
|
|
@@ -85,3 +93,9 @@ colors without a `theme-allow` comment.
|
|
|
85
93
|
|
|
86
94
|
See `basalt-ui/llms.txt` (importable via the package specifier) for the full surface map: one
|
|
87
95
|
entry per published subpath with import specifier, description, layer, and optional peer packages.
|
|
96
|
+
|
|
97
|
+
## Upgrading across minors
|
|
98
|
+
|
|
99
|
+
`CHANGELOG.md` lists every release; `MIGRATING.md` lists what **broke** — removed and renamed
|
|
100
|
+
exports per minor, with the replacement. No majors ship here, so a minor bump can require code
|
|
101
|
+
changes. Read `MIGRATING.md` before jumping more than one minor.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
# [1.20.0](https://github.com/jkrumm/basalt-ui/compare/v1.19.1...v1.20.0) (2026-08-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* exempt generated LINES, not files, and stop flagging prose as a typo'd id ([a83abfb](https://github.com/jkrumm/basalt-ui/commit/a83abfb77888f31528f44e5923b26d3710ddf005))
|
|
7
|
+
* give the mobile-nav-pill demo row its own glyph ([4620590](https://github.com/jkrumm/basalt-ui/commit/4620590204a99afd07ec27eb923cd229acdfce84))
|
|
8
|
+
* hold basalt to its own scoped theme-allow contract ([b67718c](https://github.com/jkrumm/basalt-ui/commit/b67718cecf8e7ce60b61b7481bcdbd4795f79d40))
|
|
9
|
+
* make the escape hatch and the generated-file marker fail closed ([526cdb7](https://github.com/jkrumm/basalt-ui/commit/526cdb7f11395f27183828e34297b0ac6bc846c8))
|
|
10
|
+
* make the mobile-nav active pill track density and survive an icon-less app ([0a7d756](https://github.com/jkrumm/basalt-ui/commit/0a7d7560f9eae6db4c02ff029ba47088a084e511))
|
|
11
|
+
* snapshot the tokens-only guard export ([b63dc24](https://github.com/jkrumm/basalt-ui/commit/b63dc242bfd46359476f7fcae80957db244f727f))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* demo the mobile-nav active pill with and without an icon dependency ([9620905](https://github.com/jkrumm/basalt-ui/commit/9620905409efdf39bb10b40edaa128ce2b61c741))
|
|
17
|
+
* make the escape hatch usable and close the holes round 4 found ([5b8420d](https://github.com/jkrumm/basalt-ui/commit/5b8420d24647b84818dfbb65e9f0c52ea1e79c2c))
|
|
18
|
+
* scan the app shell, wire the tokens-only profile, ship __APP_VERSION__ ([61b1792](https://github.com/jkrumm/basalt-ui/commit/61b1792e8ea1bff3c59128ba65137b27d40df81a))
|
|
19
|
+
* stop the toolchain reporting green while enforcing nothing ([d3d8e34](https://github.com/jkrumm/basalt-ui/commit/d3d8e3434893516e62413acafdd166be13b86541))
|
|
20
|
+
|
|
21
|
+
## [1.19.1](https://github.com/jkrumm/basalt-ui/compare/v1.19.0...v1.19.1) (2026-08-20)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Bug Fixes
|
|
25
|
+
|
|
26
|
+
* cap the mobile bottom-sheet height to its content ([04093ac](https://github.com/jkrumm/basalt-ui/commit/04093ac902d894249ebea3e6381755497670e305))
|
|
27
|
+
|
|
1
28
|
# [1.19.0](https://github.com/jkrumm/basalt-ui/compare/v1.18.0...v1.19.0) (2026-08-20)
|
|
2
29
|
|
|
3
30
|
|
package/MIGRATING.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
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. Verified 2026-08-22; the
|
|
10
|
+
1.20.0 section below is written from the commits on `master` that produce it.
|
|
11
|
+
|
|
12
|
+
**No majors, by policy.** A rename or a removal ships as a plain `feat:` on the 1.x line, so a minor
|
|
13
|
+
bump can require code changes. Skipping several at once is the expensive case — read every section
|
|
14
|
+
between your version and the target.
|
|
15
|
+
|
|
16
|
+
> Reading the CHANGELOG: minors and majors are `#` (h1), patches are `##` (h2). Grepping for
|
|
17
|
+
> `## [1.` finds only the patches.
|
|
18
|
+
|
|
19
|
+
**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,
|
|
20
|
+
1.10.0, 1.13.0, 1.14.0, 1.16.0, 1.18.0 — and every patch. Additive-only subpaths: `./tokens.css`
|
|
21
|
+
at 1.3.0, `./agent-chat` at 1.10.0.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 1.20.0 — enforcement
|
|
26
|
+
|
|
27
|
+
**No export removed or renamed.** The whole delta is that things which used to pass now report. If
|
|
28
|
+
your build goes red on this upgrade, that is the release working; every new kind and rule lands
|
|
29
|
+
`warn`, so nothing here fails a build on its own.
|
|
30
|
+
|
|
31
|
+
| Change | What you'll see | What to do |
|
|
32
|
+
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
33
|
+
| `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 |
|
|
34
|
+
| 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` |
|
|
35
|
+
| Two new oxlint rules (`warn`) | `basalt/shadow-basalt-export`, `basalt/hand-rolled-shell` | import the shipped component instead of the fork |
|
|
36
|
+
| `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 |
|
|
37
|
+
| `doctor` `SKIPPED` exits non-zero, + 3 new hard checks | doctor goes red where it was green | that is the finding — see below |
|
|
38
|
+
|
|
39
|
+
**`theme-allow` has a new contract, and one comment shape stops waiving.** A bare `theme-allow`
|
|
40
|
+
still waives every kind, but now reports `theme-allow-unscoped`. Rescope it — and spell the id
|
|
41
|
+
right, because a word in the id slot that names no rule now waives NOTHING rather than degrading to
|
|
42
|
+
the blanket form:
|
|
43
|
+
|
|
44
|
+
```diff
|
|
45
|
+
-// theme-allow
|
|
46
|
+
+// theme-allow raw-surface — third-party widget needs a literal corner
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**The break: a reason with no separator introducing it.** `// theme-allow legacy vendor asset` used
|
|
50
|
+
to waive the line; it now waives nothing and the un-suppressed finding reports at its own severity
|
|
51
|
+
(`error` for `raw-hex`). The first word after the token is read as a rule id, and an id that names
|
|
52
|
+
no rule fails closed — that is the whole point, since the alternative is one mistyped character
|
|
53
|
+
silently widening a scoped waiver into a blanket one. No annotation in any of the seven consumer
|
|
54
|
+
repos writes that shape; every one of them introduces its reason with `—`, `–`, `-` or `:`. Add a
|
|
55
|
+
separator, or an id:
|
|
56
|
+
|
|
57
|
+
```diff
|
|
58
|
+
-// theme-allow legacy vendor asset
|
|
59
|
+
+// theme-allow raw-hex — legacy vendor asset
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Prose AFTER a resolved id is safe and needs no separator —
|
|
63
|
+
`// theme-allow raw-surface sub-scale legend corner` waives `raw-surface` and reports nothing. Only
|
|
64
|
+
a comma keeps the id list open past the first id.
|
|
65
|
+
|
|
66
|
+
Two placements that used to fail now work, both matching what the oxlint plugin always did: a
|
|
67
|
+
comment-ONLY line directly above the reported line (the only form JSX can express — the reported
|
|
68
|
+
line is usually a multi-line opening tag or a `{expr}` child), and in CSS a trailing annotation
|
|
69
|
+
reaching back over the declaration it terminates, which is what survives the shipped `oxfmt`
|
|
70
|
+
reflowing a long `background-color` so the hex lands above the comment.
|
|
71
|
+
|
|
72
|
+
**`basalt/hand-rolled-plot` no longer grants whole-file immunity.** Every assembly node is reported
|
|
73
|
+
and waived individually. A file-scoped exception now needs a written declaration naming the rule and
|
|
74
|
+
giving a reason, anywhere in the file: `// theme-allow hand-rolled-plot — two panes over one x scale`.
|
|
75
|
+
|
|
76
|
+
**`doctor` will go red.** `SKIPPED` is a third outcome beside pass/warn/fail and exits non-zero on
|
|
77
|
+
its own — "All checks passed" is only printable when every check RAN. Three new hard checks:
|
|
78
|
+
`basalt-resolves` (walks cwd → ancestors → workspace packages), `guard-scan` (would `check-theme`
|
|
79
|
+
cover more than zero files?), `oxlint-preset` (does `.oxlintrc.json` really extend the shipped
|
|
80
|
+
preset? JSONC is parsed, not rejected — `init` keeps an existing config, so one repo ran five minors
|
|
81
|
+
with the whole lint half off).
|
|
82
|
+
|
|
83
|
+
**New, additive:**
|
|
84
|
+
|
|
85
|
+
| Surface | Note |
|
|
86
|
+
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
87
|
+
| `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 |
|
|
88
|
+
| `basalt.include: [...]` | scan a named file outside `roots` — and the only route to a `.json`, which is never blanket-scanned |
|
|
89
|
+
| `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 |
|
|
90
|
+
| `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 |
|
|
91
|
+
| `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` |
|
|
92
|
+
| `__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 |
|
|
93
|
+
| `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 |
|
|
94
|
+
| `@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 |
|
|
95
|
+
|
|
96
|
+
`check-theme` also resolves `.html` / `.webmanifest` / `.json` as markup (colour kinds only), and
|
|
97
|
+
each root's PARENT now contributes its `index.html` and `public/` tree.
|
|
98
|
+
|
|
99
|
+
## 1.19.0 — nav
|
|
100
|
+
|
|
101
|
+
| Removed / renamed | Replacement | Note |
|
|
102
|
+
| ----------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
103
|
+
| `NavLinkRenderer` (type) | `defineNav` + `{...useNav(NAV)}` (`basalt-ui/router-tanstack`), or `SidebarItem.Anchor` | basalt now paints every nav pixel |
|
|
104
|
+
| `BasaltShellProps.renderNavLink`, `AppSidebarProps.renderNavLink` | same | |
|
|
105
|
+
| `BreadcrumbLinkRenderer` (type) | `AppBreadcrumbs.parentAnchor` | |
|
|
106
|
+
| `BasaltShellProps.renderBreadcrumbLink` | same | |
|
|
107
|
+
| `BasaltShellProps.sidebarFooterExtra` | `mobileNav.moreExtra` | its only host was the mobile drawer; it rendered nowhere on desktop |
|
|
108
|
+
| `AppSidebarProps.footerExtra`, `AppSidebarProps.onClose` | — | the full-height mobile sidebar drawer is deleted |
|
|
109
|
+
| `MobileNavItem` (type) | `MobileNavSlot` | |
|
|
110
|
+
| `MobileNavSection` (type) | `MobileNavGroup` | |
|
|
111
|
+
| `MobileNavLinkRenderer` (type) | `MobileNavModel` | |
|
|
112
|
+
| `SidebarSection.mobileTab` | `SidebarSection.mobile`: `'tab' \| 'more' \| 'hidden'` (`true`/`false` still accepted) | |
|
|
113
|
+
|
|
114
|
+
A consumer with no router needs no migration — `href` + `onClick` still work.
|
|
115
|
+
|
|
116
|
+
**Expect new type errors, and read them.** `renderNavLink` took `to` as an opaque value, which is
|
|
117
|
+
why consumers reached for `to={target.to as never}`. `defineNav` types it, so a destination missing
|
|
118
|
+
a required `search` now fails to compile — in one repo that surfaced two nav links that had been
|
|
119
|
+
shipping without required params. Fix with a click-time thunk (`search: () => Schema.parse({})`),
|
|
120
|
+
never `search: true`.
|
|
121
|
+
|
|
122
|
+
## 1.17.0 — behaviour only, no export removed
|
|
123
|
+
|
|
124
|
+
| Changed | Effect | Opt out |
|
|
125
|
+
| ---------------------------------------------------- | ------------------------------------------------------------ | -------------------------------- |
|
|
126
|
+
| `resolveAxisDomain` clamps before padding, not after | the axis top moves on any chart with an `autoMaxFloor` | lower the floor, or pin `domain` |
|
|
127
|
+
| `ChartFrame` `role="img"` → `role="group"` | `role="img"` pruned the keyboard slider out of the a11y tree | — |
|
|
128
|
+
|
|
129
|
+
## 1.15.0 — chart layer rebuilt
|
|
130
|
+
|
|
131
|
+
The largest delta in the 1.x line. No shims were shipped.
|
|
132
|
+
|
|
133
|
+
| Removed / renamed | Replacement | Note |
|
|
134
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
135
|
+
| `ChartHoverSync`, `ChartHoverSyncProps` | `globalCursorStore` / `createCursorStore` / `useChartCursor` / `useCursorState` — **no provider** | `ChartCursorScope` now _isolates_ a subtree; it is the inverse of the old provider, not a rename |
|
|
136
|
+
| `HoverContext`, `HoverCtx`, `useHoverSync`, `DEFAULT_NO_OP_SET_HOVER` | `useChartCursor`, `useCursorState`, `CursorState`, `CursorStore` | context → `useSyncExternalStore` |
|
|
137
|
+
| `ResponsiveChart`, `ResponsiveChartProps` | `CartesianChart` / `CartesianChartProps` (+ `autoMargin`, `PlotContext`, `PlotRect`) | now mandatory for every single-plot cartesian chart, enforced by `basalt/hand-rolled-plot` |
|
|
138
|
+
| `ChartTooltip` (tip-based), `useChartTooltip`, `useTooltipStyles` | `ChartTooltipFloat` + the `tooltip: CartesianTooltipConfig` prop | portal / flip / clamp done once |
|
|
139
|
+
| `BarsAxisConfig` | `AxisConfig` + `resolveAxisDomain` | |
|
|
140
|
+
| `ZonedLineTooltipLabel` | `tooltip` config on the kind | |
|
|
141
|
+
| `ZonedLine`/`MultiLine`: `yDomain`, `yAutoPad`, `yAutoMaxFloor`, `yAutoMinCeil`, `numTicksY`, `formatYTick` | one `y?: AxisConfig<T>` | `y` is **optional** where `yDomain` was required |
|
|
142
|
+
| `ZonedLine`/`MultiLine`: `formatValue`, `tooltipLabel`, `renderExtraTooltipRows` | `tooltip?: CartesianTooltipConfig` | **not** `y` — these are tooltip concerns |
|
|
143
|
+
| `ZonedLine`/`MultiLine`: `numTicksX` | `xTicks` | `xZones` added alongside |
|
|
144
|
+
| `Bars`: `formatValue`, `hideBarTooltipRows`, `leftAxis`, `rightAxis`, `marginLeft`, `numTicksX`, `tooltipLabel`, `renderExtraTooltipRows`, `renderPrefixTooltipRows` | `y` / `y2` (`AxisConfig`), `tooltip`, `SeriesStyle.tooltip`, measured `autoMargin` | passing `y2` is what makes a chart dual-axis; `chartMargin({ rightAxis })` is gone |
|
|
145
|
+
| `StackedArea`: `formatValue`, `numTicksX`, `numTicksY`, `yAutoMaxFloor`, `yLabel` | `y`, `tooltip`, `xTicks` | |
|
|
146
|
+
| `Heatmap.width` | measures itself; takes `height` / `aspectRatio` / `fill` | |
|
|
147
|
+
|
|
148
|
+
## 1.12.0 — agent-chat, behaviour only
|
|
149
|
+
|
|
150
|
+
| Changed | Effect |
|
|
151
|
+
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
152
|
+
| `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 |
|
|
153
|
+
| consumer `rehypePlugins` output is now sanitized, `clobberPrefix` is empty | a consumer injecting non-default elements must pass a matching `sanitizeSchema` |
|
|
154
|
+
|
|
155
|
+
## 1.11.0 — agent parts
|
|
156
|
+
|
|
157
|
+
Semver-breaking in a minor; the commit body says so.
|
|
158
|
+
|
|
159
|
+
| Removed / renamed | Replacement | Note |
|
|
160
|
+
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
161
|
+
| `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 |
|
|
162
|
+
| the error field named `error` | `errorText` | the SDK union has no `error` |
|
|
163
|
+
| flattened approval fields | nested `approval: ToolApproval`, carried verbatim | flattening dropped `isAutomatic` / `signature` |
|
|
164
|
+
| — | 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 |
|
|
165
|
+
| `ToolChip` threw `assertNever` on an unknown state | now falls back instead of throwing | |
|
|
166
|
+
|
|
167
|
+
## 1.0.0 — the Mantine pivot
|
|
168
|
+
|
|
169
|
+
| Removed / renamed | Replacement | Note |
|
|
170
|
+
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
171
|
+
| `./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()` |
|
|
172
|
+
| `./starlight` subpath | — | no replacement |
|
|
173
|
+
| the OKLCH foundation palette, ShadCN/Tremor/Starlight doctrine | the three-tier `--vx-*` system | see `docs/FRAMEWORK-FREE.md` for the token-only route |
|
|
174
|
+
|
|
175
|
+
Names that have no 1.x equivalent: the `--chart-blue-1..8` sequential ramp (1.x is categorical
|
|
176
|
+
`--vx-fill-*` only), the default font stacks as a token (they live in `styles.css`), `purple` as a
|
|
177
|
+
text color, `black`, `blue-400`/`green-400`.
|
|
178
|
+
|
|
179
|
+
## 1.0.1 — CLI binary renamed
|
|
180
|
+
|
|
181
|
+
`basalt` → **`basalt-ui`**. Never `bunx basalt`.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Lint and guard rules that tightened
|
|
186
|
+
|
|
187
|
+
Two independent mechanisms, each with its own ledger — read the ledger, not this table, for what is
|
|
188
|
+
in grace TODAY. `GuardKind` severities (`basalt-ui check-theme`) default to `error` and are
|
|
189
|
+
downgraded only by `GRACE_PERIOD_KINDS` (`src/guard/index.ts`). `basalt/*` oxlint rule severities
|
|
190
|
+
live in `configs/oxlint.json`, and since 1.20.0 their grace is tracked by `PLUGIN_RULE_GRACE`, a
|
|
191
|
+
named export beside the plugin — a test asserts it against the shipped preset in both directions, so
|
|
192
|
+
deleting an entry forces the level flip in the same commit.
|
|
193
|
+
|
|
194
|
+
| Rule | Landed | Became `error` |
|
|
195
|
+
| ---------------------------------- | ---------------- | ---------------------------------- |
|
|
196
|
+
| `mantine-shade-index` (guard kind) | 1.7.0 as `warn` | **1.11.0** |
|
|
197
|
+
| `basalt/raw-scroll-container` | ≤1.2.0 as `off` | `warn` 1.10.0 → **`error` 1.13.0** |
|
|
198
|
+
| `basalt/ai-sdk-major` | 1.10.0 as `warn` | **1.13.0** |
|
|
199
|
+
| `basalt/agent-no-raw-usechat` | 1.10.0 as `warn` | **1.13.0** |
|
|
200
|
+
| `basalt/agent-resume-guard` | 1.10.0 as `warn` | **1.13.0** |
|
|
201
|
+
| `basalt/raw-size-literal` | 1.7.0 as `warn` | **1.20.0** |
|
|
202
|
+
| `basalt/hand-rolled-plot` | 1.15.0 as `warn` | still `warn` — widened at 1.20.0 |
|
|
203
|
+
| `basalt/chart-legend-literal` | 1.15.0 as `warn` | still `warn` — widened at 1.20.0 |
|
|
204
|
+
| `basalt/shadow-basalt-export` | 1.20.0 as `warn` | may stay `warn` permanently |
|
|
205
|
+
| `basalt/hand-rolled-shell` | 1.20.0 as `warn` | — |
|
|
206
|
+
|
|
207
|
+
`card-with-border`, `inline-display`, `raw-html-layout`, `raw-form-control`, `raw-font-family` and
|
|
208
|
+
the other original guard kinds have been `error` since before 1.2.0 — they never had a grace minor.
|
|
209
|
+
Guard findings only gained a severity field at all in 1.4.0; before that every finding was fatal.
|
|
210
|
+
|
|
211
|
+
**Rule-id rename at 1.1.0:** `basalt/import-boundary` split into `basalt/visx-boundary`,
|
|
212
|
+
`basalt/visx-tooltip` and `basalt/token-layer-boundary` (the last is repo-local and deliberately not
|
|
213
|
+
in the shipped preset). A config still naming `import-boundary` after 1.1.0 disables nothing.
|
|
214
|
+
|
|
215
|
+
## Deprecated, not yet removed
|
|
216
|
+
|
|
217
|
+
The 32 camelCase `--vx-*` aliases deprecated in 1.5.0 are **still emitted at 1.20.0**.
|
|
218
|
+
`buildPaletteCss({ legacyAliases: false })` / `tokens:css --no-legacy-aliases` opts out now; a later
|
|
219
|
+
minor flips the default.
|
|
220
|
+
|
|
221
|
+
## Not verified
|
|
222
|
+
|
|
223
|
+
- Props declared inline on a component (`function X({ a }: { a: string })`) rather than on an
|
|
224
|
+
exported type are outside the diff method used here. Spot checks on the shell and charts modules
|
|
225
|
+
found none, but the negative is unproven.
|
|
226
|
+
- `AppBreadcrumbs.parentAnchor` as the replacement for `renderBreadcrumbLink` comes from the 1.19.0
|
|
227
|
+
commit body, not from a diffable exported type — `AppBreadcrumbsProps` is not in the public export
|
|
228
|
+
set at either tag.
|
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,50 @@ 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; 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"
|
|
190
202
|
}
|
|
191
203
|
}
|
|
192
204
|
```
|
|
193
205
|
|
|
194
206
|
`check-theme` fails loudly (instead of a false `✓`) when the configured roots resolve to zero
|
|
195
|
-
scanned files
|
|
196
|
-
|
|
207
|
+
scanned files, and `doctor`'s `guard-scan` check agrees with it. Each root's **parent** also
|
|
208
|
+
contributes its `index.html` and its `public/` tree — the Vite layout `basaltViteConfig` assumes,
|
|
209
|
+
and where a raw `theme-color` or webmanifest `background_color` actually lives. `.json` is never
|
|
210
|
+
blanket-scanned; `include` names one explicitly and is the only route to it. `profile:
|
|
211
|
+
"tokens-only"` turns off the 17 kinds whose remedy is a Mantine component or prop, and must be
|
|
212
|
+
declared (or `--tokens-only`) — it is deliberately never inferred from a missing `@mantine/core`,
|
|
213
|
+
which would silence half the guard on any repo keeping Mantine in a different workspace package.
|
|
214
|
+
Other keys (`exempt`, `severity`, `spacingSteps`, `forbiddenAccents`, …) are documented on the
|
|
215
|
+
`BasaltConfig` type.
|
|
216
|
+
|
|
217
|
+
Basalt-emitted LINES are skipped, not whole files. The file has to earn it first — a `.css` path,
|
|
218
|
+
the `@generated basalt-ui` header verbatim on line 1, the version + invocation line on line 2 — and
|
|
219
|
+
then each line does too: at brace depth 0 a selector or a self-closing comment, inside a block only
|
|
220
|
+
a `--vx-*` / `--basalt-*` declaration whose value carries no `;`, a `}`, or a comment. So the
|
|
221
|
+
stylesheet `tokens:css` just wrote is not reported back at you, pasting the marker into a `.tsx`
|
|
222
|
+
suppresses nothing, and an ordinary declaration smuggled into a sheet wearing the header is
|
|
223
|
+
reported on its own line.
|
|
224
|
+
|
|
225
|
+
**The escape hatch is scoped.** `theme-allow <rule-id> — <reason>` waives that one kind, on the
|
|
226
|
+
reported line, on a comment-only line directly above it (the only form JSX can express), or — in
|
|
227
|
+
CSS — from a trailing comment back over the declaration it terminates. A bare `theme-allow` still
|
|
228
|
+
waives everything but reports `theme-allow-unscoped`. The id slot is read strictly: a word there
|
|
229
|
+
that names no rule waives nothing, so an annotation covers exactly the ids it got right and a typo
|
|
230
|
+
is never the blanket form.
|
|
197
231
|
|
|
198
232
|
`doctor`'s `ai-major-parity` hard check fails a monorepo where workspace packages declare different
|
|
199
233
|
`ai` package majors — unless the split is intentional and written down. A producer pinned to an
|
|
@@ -241,7 +275,7 @@ deleted rather than silently surviving into the next real skew.
|
|
|
241
275
|
| `./connectivity` | coupled | `ConnectivityProvider` (aggregates browser/React-Query/SSE/health-check status), `useConnectivity`, `ConnectivityIndicator` — auto-mounted by `BasaltProvider` |
|
|
242
276
|
| `./content` | coupled | `Prose`, `CodeBlock`, `Callout`, `TableOfContents`, `ReadingProgress`, `Markdown`, `MermaidDiagram`, `ArticleLayout`, `ArticleCard` / `ArticleGrid`, `GuideLink` / `GuideDrawer`, `mdxComponents` |
|
|
243
277
|
| `./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
|
|
278
|
+
| `./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
279
|
| `./configs/*` | — | Raw toolchain presets — oxlint, oxfmt, tsconfig (base/react-app/node), lefthook |
|
|
246
280
|
| `./llms.txt` | — | Machine-readable surface map — one entry per published subpath with import specifier, description, layer, and optional peers |
|
|
247
281
|
|
|
@@ -606,11 +640,21 @@ bunx basalt-ui init # scaffold doctrine into a consumer repo
|
|
|
606
640
|
bunx basalt-ui sync # three-way diff against .basalt/manifest.json after a basalt-ui upgrade
|
|
607
641
|
bunx basalt-ui sync --check # CI drift gate — non-zero exit on any managed-file drift
|
|
608
642
|
bunx basalt-ui check-theme # palette guard — fails on colors that bypass the central --vx-* system
|
|
609
|
-
bunx basalt-ui doctor #
|
|
643
|
+
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/
|
|
644
|
+
bunx basalt-ui doctor --tokens-only # force the Mantine-free profile (auto-detected otherwise; --framework forces the full set)
|
|
645
|
+
bunx basalt-ui tokens:css --out src/tokens.css # emit the --vx-* stylesheet as a file you own
|
|
646
|
+
bunx basalt-ui fonts:css --out src/fonts.css # emit the shipped --basalt-font-* stacks
|
|
610
647
|
bunx basalt-ui info # human-readable surface map: subpath, layer, rule, skills, optional peers
|
|
611
648
|
bunx basalt-ui info --json # same map as stable JSON (InfoOutput shape)
|
|
649
|
+
bunx basalt-ui help # full usage; every subcommand also takes --help / -h
|
|
612
650
|
```
|
|
613
651
|
|
|
652
|
+
`doctor` reports **`SKIPPED`** as a third outcome beside pass/warn/fail and exits non-zero on it —
|
|
653
|
+
"All checks passed" is only printable when every check actually ran. `check-theme` and `doctor`
|
|
654
|
+
honour `BASALT_CWD`, and relocate to the single workspace package carrying a basalt config when
|
|
655
|
+
invoked from a repo root that has none (two candidates is reported as ambiguous, never guessed).
|
|
656
|
+
`tokens:css` and `fonts:css` both take `--check` as a CI drift gate.
|
|
657
|
+
|
|
614
658
|
`sync` strategy per file: unchanged since last write → overwrite; locally edited → skip (show diff); missing → recreate. `--force` overwrites local edits.
|
|
615
659
|
|
|
616
660
|
---
|
|
@@ -69,6 +69,49 @@ The one real caveat is a plugin that **emits files from its own `closeBundle`**:
|
|
|
69
69
|
`vite-plugin-pwa`'s, those files are written too late to be precached (the `rollup-plugin-copy`
|
|
70
70
|
class of bug). `vite-plugin-pwa` exposes `integration.closeBundleOrder` to sequence against it.
|
|
71
71
|
|
|
72
|
+
## What `basalt-ui init` writes into `package.json`
|
|
73
|
+
|
|
74
|
+
`init` is not only a file-scaffolder. It also patches two keys, and on an existing app it is a
|
|
75
|
+
**lint-debt event, not a no-op** — the shipped preset switches on whole oxlint plugins the repo was
|
|
76
|
+
never linted against, so previously-clean code lands with real findings on the first run. `init`
|
|
77
|
+
prints the plugin list; run `oxlint .` and triage it before your next commit rather than
|
|
78
|
+
blanket-disabling a plugin.
|
|
79
|
+
|
|
80
|
+
- **`basalt.roots`** — inferred from the real layout (workspace packages depending on basalt or
|
|
81
|
+
Mantine, else every workspace `src/`, else `src`). Everything derives from it: `check-theme`'s
|
|
82
|
+
scan, the seeded CI `oxfmt` globs, the default scan exemption. Correct it if your sources live
|
|
83
|
+
elsewhere; without it a workspace repo scaffolds a guard that scans zero files.
|
|
84
|
+
- **`scripts["lint:basalt"]`** — `oxlint . && basalt-ui check-theme`. Previously the docs told you
|
|
85
|
+
to wire this and nothing wrote it.
|
|
86
|
+
- **`basalt.profile: 'tokens-only'`** — for a consumer on the `--vx-*` layer with no Mantine. It
|
|
87
|
+
must be DECLARED (or passed as `--tokens-only`); `check-theme` deliberately never infers it,
|
|
88
|
+
because inferring from a missing `@mantine/core` would silence 17 kinds on any repo that keeps
|
|
89
|
+
Mantine in a different workspace package. `doctor` does infer it, because its profile only changes
|
|
90
|
+
which ADVICE it prints and never what it enforces — and it names the key to write down.
|
|
91
|
+
|
|
92
|
+
`init` keeps an existing `.oxlintrc.json` rather than clobbering it, and now names what keeping it
|
|
93
|
+
costs (the whole basalt lint half stays off). `--merge-lint` splices the preset's `extends` in; it
|
|
94
|
+
refuses on a commented config rather than deleting the comments.
|
|
95
|
+
|
|
96
|
+
Run `basalt-ui doctor` afterwards to confirm the wiring took. A check that cannot RUN is reported as
|
|
97
|
+
`SKIPPED` and exits non-zero on its own — "All checks passed" is only printable when every check ran.
|
|
98
|
+
|
|
99
|
+
## `index.html` and `public/` are scanned
|
|
100
|
+
|
|
101
|
+
`check-theme` resolves `.html` / `.webmanifest` / `.json` as markup (colour kinds only), and each
|
|
102
|
+
`basalt.roots` entry's PARENT contributes its `index.html` and its `public/` tree — exactly the Vite
|
|
103
|
+
layout above. So a raw hex in a `theme-color` meta or a webmanifest's `background_color` is a
|
|
104
|
+
finding now; those are the two colours nothing re-derives on a scheme change. Nothing else in the
|
|
105
|
+
parent is walked, and `.json` is never blanket-scanned — name one in `basalt.include`.
|
|
106
|
+
|
|
107
|
+
## `__APP_VERSION__`
|
|
108
|
+
|
|
109
|
+
`basaltViteConfig` injects the `__APP_VERSION__` define, and since 1.20.0 ships the ambient
|
|
110
|
+
declaration with it (`src/register.ts` → `dist/register.d.ts`, re-exported by the root barrel). An
|
|
111
|
+
app that imports anything from `basalt-ui` has it in scope — delete your hand-written ambient block
|
|
112
|
+
and its `oxlint-disable`. A subpath-only consumer does not get it, which is the same set as the
|
|
113
|
+
consumers not on this Vite preset anyway.
|
|
114
|
+
|
|
72
115
|
## CSS layer order (Mantine)
|
|
73
116
|
|
|
74
117
|
`basalt-ui/styles.css` must load AFTER every `@mantine/*/styles.layer.css` bundle — the layered
|
|
@@ -55,19 +55,31 @@ radial/matrix shape, is not optional — two `basalt` oxlint plugin rules
|
|
|
55
55
|
|
|
56
56
|
- **`basalt/hand-rolled-plot`** — rendering a chart-assembly primitive (`AxisLeftNumeric`,
|
|
57
57
|
`AxisRightNumeric`, `AxisBottomDate`, `HoverOverlay`, `Crosshair`) in a file that does not
|
|
58
|
-
compose `CartesianChart
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
compose `CartesianChart`. **Since 1.20.0 every assembly node is reported and waived on its own**
|
|
59
|
+
— one comment no longer grants the whole file permanent immunity, which is how a 604-line chart
|
|
60
|
+
file went unpoliced. A genuinely non-single-plot shape (multi-pane, radial, matrix) declares
|
|
61
|
+
itself with a written file declaration — a `theme-allow` that both names the rule and gives a
|
|
62
|
+
reason, anywhere in the file (`theme-allow hand-rolled-plot — two panes over one x scale`);
|
|
63
|
+
`DualPanel` carries the repo's only one. The file that DEFINES `CartesianChart` is exempt
|
|
64
|
+
definitionally (detected by declaration, not by path), since a rule saying "compose X" cannot
|
|
65
|
+
fire inside X.
|
|
66
|
+
- **`basalt/chart-legend-literal`** — a hand-written array literal passed to `ChartLegend`'s
|
|
67
|
+
`items`; **since 1.20.0 also a `.map()` over a non-`series` array**, because deriving from AN
|
|
68
|
+
array is not deriving from THE series. The legend must come from the same `series` the chart
|
|
69
|
+
draws (`deriveLegend`, or just let `ChartFrame`/`CartesianChart` do it), so it cannot go stale
|
|
66
70
|
naming a series the plot no longer draws.
|
|
67
71
|
|
|
68
|
-
Both ship
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
Both ship `warn` in the consumer preset (`configs/oxlint.json`) and `error` repo-local, per the
|
|
73
|
+
"Shipping a stricter guard — the grace minor" doctrine in `packages/basalt-ui/CLAUDE.md`. 1.20.0
|
|
74
|
+
WIDENS both, so both restart their grace rather than promoting — the live ledger with the promotion
|
|
75
|
+
note per rule is `PLUGIN_RULE_GRACE`, exported beside the plugin in `configs/oxlint-plugin.js`, and
|
|
76
|
+
a test asserts it against the shipped preset in both directions.
|
|
77
|
+
|
|
78
|
+
**A documented limit:** `hand-rolled-plot` keys on those visx primitives, so a chart drawn with DOM
|
|
79
|
+
nodes (divs sized in percent, an SVG assembled by hand) is structurally invisible to it. That is a
|
|
80
|
+
deliberate trade — every alternative detector tried either flagged basalt's own `Donut`/`Heatmap` or
|
|
81
|
+
an icon in a card header, and a noisy shipped rule gets switched off. Compose `CartesianChart`
|
|
82
|
+
because it is the contract, not because lint will catch you.
|
|
71
83
|
|
|
72
84
|
## Maps are not charts
|
|
73
85
|
|
|
@@ -85,8 +97,8 @@ ships no map kind.
|
|
|
85
97
|
an AST pass — it flags any `padding:`/`gap:`/`margin:` followed by a number in _any_ object
|
|
86
98
|
literal, in any file. A map's pixel geometry (`fitBounds({ padding: 48 })`, marker offsets) trips
|
|
87
99
|
it even though it isn't spacing, and hoisting the value to a module-scope const doesn't help — the
|
|
88
|
-
regex sees the line, not the binding.
|
|
89
|
-
answer, not a bug to file.
|
|
100
|
+
regex sees the line, not the binding. Scope a `theme-allow inline-spacing — map geometry` comment
|
|
101
|
+
to it; that's the sanctioned answer, not a bug to file.
|
|
90
102
|
|
|
91
103
|
## Every chart has
|
|
92
104
|
|
|
@@ -155,7 +167,7 @@ ships no map kind.
|
|
|
155
167
|
in the geometry; and because the domain value must be unique, two events at the same instant
|
|
156
168
|
collapse onto one position and one stops being drawn. `getX` returning a date string reads like a
|
|
157
169
|
time axis and is not one. Tracked as issue #52; a chart whose x is a measured quantity currently
|
|
158
|
-
needs a `basalt/hand-rolled-plot`
|
|
170
|
+
needs a `basalt/hand-rolled-plot` file declaration.
|
|
159
171
|
|
|
160
172
|
6. **The cursor is shared by default.** No provider needed — `useChartCursor` reads a module-level
|
|
161
173
|
external store (`useSyncExternalStore`), so every `CartesianChart`/`ChartFrame`-composed chart on
|
|
@@ -149,14 +149,26 @@ section divider, not card depth) are both fine.
|
|
|
149
149
|
consumer code must compose Mantine layout/surface primitives — `Box`, `Flex`, `Grid`,
|
|
150
150
|
`SimpleGrid`, `Stack`, `Group`, `Paper`, `Card` — instead of raw `<div>`/`<span>` with inline
|
|
151
151
|
`style`. Raw HTML with inline layout/surface styling defeats the token system.
|
|
152
|
-
- **
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
152
|
+
- **The second fix is a CSS Module, and it is the right one in list code.** What
|
|
153
|
+
`raw-html-layout` actually objects to is the inline `style` literal, not the `<div>`. Moving the
|
|
154
|
+
rule into a `.module.css` class that reads `var(--vx-*)` satisfies the guard, keeps the values
|
|
155
|
+
in the token system, and costs zero runtime — where a Mantine primitive costs one component
|
|
156
|
+
instance per row. In a row renderer that paints hundreds of items, or in a downstream library
|
|
157
|
+
that must not force `@mantine/core` on its consumers, take the CSS Module. `check-theme` scans
|
|
158
|
+
`.css` and `.module.css`, so the tokens stay policed there. The guard's fix text names only the
|
|
159
|
+
Mantine route; both are sanctioned.
|
|
160
|
+
- **Mechanical enforcement.** `basalt-ui check-theme` adds eight surface guard kinds (each a config
|
|
161
|
+
knob, default ON; scoped `theme-allow <rule-id> — <reason>` escape): `off-system-surface-var` (raw
|
|
162
|
+
ramp-step vars), `card-with-border` (`withBorder` on a `Card`/`Paper`), `mantine-shade-index` (a
|
|
163
|
+
shade-pinned color — see below), `raw-html-layout` (raw `<div>`/`<span>` with inline layout/surface
|
|
164
|
+
styling), `inline-spacing` (inline spacing literals), `inline-display` (inline `display` literals),
|
|
165
|
+
and since 1.20.0 `css-raw-surface` (the same surface kinds in their kebab CSS dialect, so
|
|
166
|
+
`border-radius: 4px` in a CSS module is no longer invisible beside a flagged `borderRadius: 2`) and
|
|
167
|
+
`hidden-inline-style` (`raw-html-layout`'s formatting-independent half — a multi-line opening tag,
|
|
168
|
+
or a style object hoisted to a const). The Mantine-free `src/charts/**` is the only place raw
|
|
169
|
+
`<div>` is allowed — and it must still use `VX.*` tokens. `oxlint` adds `basalt/card-inset`, which
|
|
170
|
+
catches the other half of the same card drift: an explicit `p`/`padding`/`radius` on a
|
|
171
|
+
`Card`/`Paper` instead of the xs/sm inset and the theme radius.
|
|
160
172
|
|
|
161
173
|
**Never pin a shade index on a color.** `c="yellow.7"`, `bg="blue.4"`, `var(--mantine-color-red-6)` —
|
|
162
174
|
each names one fixed swatch that is identical in light and dark, so a shade legible on the dark page
|
|
@@ -278,8 +290,11 @@ shadow-surfaces.test.ts` mechanically enumerates every site that applies either
|
|
|
278
290
|
|
|
279
291
|
## Composing the shell from sub-components
|
|
280
292
|
|
|
281
|
-
`BasaltShell` is the canonical single-mount
|
|
282
|
-
|
|
293
|
+
`BasaltShell` is the canonical single-mount, and since 1.20.0 `basalt/hand-rolled-shell` (`warn`)
|
|
294
|
+
says so mechanically: `AppShell.*` parts or a `Burger` in a file that does not render `BasaltShell`
|
|
295
|
+
is the layer the 1.19 nav contract replaces. It is file-scoped and reported once — which shell a
|
|
296
|
+
file uses is one decision. When a fully custom layout is genuinely needed, the shell sub-components
|
|
297
|
+
are available individually from `basalt-ui`:
|
|
283
298
|
|
|
284
299
|
- `AppSidebar` — desktop sidebar (sections, collapse, brand, settings/account extras)
|
|
285
300
|
- `MobileNav` — the bottom tab bar (takes a `MobileNavModel` from `projectMobileNav`)
|