basalt-ui 1.23.1 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +11 -6
- package/CHANGELOG.md +14 -0
- package/MIGRATING.md +138 -4
- package/README.md +123 -32
- package/agent/rules/basalt-data.md +45 -13
- package/agent/rules/basalt-query.md +57 -0
- package/agent/skills/basalt-app/SKILL.md +7 -0
- package/dist/cli/index.d.ts +4 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +101 -17
- package/dist/dashboard/empty-state.d.ts +6 -2
- package/dist/dashboard/empty-state.d.ts.map +1 -1
- package/dist/dashboard/empty-state.js +1 -1
- package/dist/dashboard/index.d.ts +1 -0
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +8 -0
- package/dist/dashboard/query-state.d.ts +96 -0
- package/dist/dashboard/query-state.d.ts.map +1 -0
- package/dist/dashboard/query-state.js +123 -0
- package/dist/dashboard/query-state.test.d.ts +2 -0
- package/dist/dashboard/query-state.test.d.ts.map +1 -0
- package/dist/data/data-table.d.ts +61 -4
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +41 -4
- package/dist/data/data-table.test.d.ts +2 -0
- package/dist/data/data-table.test.d.ts.map +1 -0
- package/dist/data/table.d.ts +1 -1
- package/dist/data/table.d.ts.map +1 -1
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +21 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/query/error-message.d.ts +41 -0
- package/dist/query/error-message.d.ts.map +1 -0
- package/dist/query/error-message.js +30 -0
- package/dist/query/error-message.test.d.ts +2 -0
- package/dist/query/error-message.test.d.ts.map +1 -0
- package/dist/query/index.d.ts +1 -19
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +2 -14
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.js +1 -1
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/cli/check-theme.test.ts +34 -0
- package/src/cli/index.ts +222 -44
- package/src/cli/tokens-css.test.ts +73 -6
- package/src/cli/toolchain-wiring.test.ts +146 -0
- package/src/dashboard/empty-state.tsx +9 -3
- package/src/dashboard/index.ts +11 -0
- package/src/dashboard/query-state.test.tsx +152 -0
- package/src/dashboard/query-state.tsx +272 -0
- package/src/data/data-table.test.tsx +158 -0
- package/src/data/data-table.tsx +123 -5
- package/src/data/table.ts +6 -1
- package/src/guard/check-source.test.ts +141 -0
- package/src/guard/index.ts +102 -8
- package/src/index.ts +9 -0
- package/src/query/error-message.test.ts +67 -0
- package/src/query/error-message.ts +70 -0
- package/src/query/index.ts +2 -34
- package/src/surfaces.ts +1 -1
package/AGENTS.md
CHANGED
|
@@ -8,7 +8,7 @@ Toolchain: Bun runtime, oxlint + oxfmt, conventional commits (empty scope), `mas
|
|
|
8
8
|
|
|
9
9
|
| Subpath | Layer | Purpose |
|
|
10
10
|
| --------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
-
| `basalt-ui` | mantine-coupled | BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, SettingsSection/SettingsRow/DangerZone)
|
|
11
|
+
| `basalt-ui` | mantine-coupled | BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, QueryState/LoadingState/ErrorState, SettingsSection/SettingsRow/DangerZone) |
|
|
12
12
|
| `basalt-ui/charts` | headless | CartesianChart + visx chart primitives, kinds, sparklines, hooks, and token re-exports (Mantine-free) |
|
|
13
13
|
| `basalt-ui/tokens` | headless | VX token refs, buildPaletteCss, defineSeries, seriesTokens, groupTokens, alpha (Mantine-free) |
|
|
14
14
|
| `basalt-ui/theme-lab` | mantine-coupled | ThemeLabControls, applyOverrides, COLOR_GROUPS — a low-level inspector for the non-derived structural tokens; identity/color tuning lives in DeriveControls |
|
|
@@ -63,7 +63,8 @@ duration/spring/ease literal in a `transition={{...}}` prop (enforced by `basalt
|
|
|
63
63
|
## Key commands
|
|
64
64
|
|
|
65
65
|
`init` and `tokens:css`/`fonts:css` are the only commands with no install to resolve, so `bunx` is
|
|
66
|
-
correct for them and
|
|
66
|
+
correct for them **and only while basalt is not installed here** — once it is, they run through the
|
|
67
|
+
local bin like everything else. See the note below.
|
|
67
68
|
|
|
68
69
|
```bash
|
|
69
70
|
bunx basalt-ui init # FIRST STEP: scaffold .claude/rules/ + .claude/skills/, managed CLAUDE.md block, DESIGN.md seed, toolchain seeds
|
|
@@ -72,8 +73,8 @@ basalt-ui check-theme # fail on off-palette colors in consumer source
|
|
|
72
73
|
basalt-ui sync # reconcile managed files (.claude/rules/, .claude/skills/, CLAUDE.md block)
|
|
73
74
|
basalt-ui sync --check # CI freshness gate — exits non-zero if any managed file drifted
|
|
74
75
|
basalt-ui doctor # integration health — SKIPPED is a third outcome and exits non-zero
|
|
75
|
-
bunx basalt-ui tokens:css # emit the --vx-* stylesheet (no React, no Mantine, no bundler)
|
|
76
|
-
bunx basalt-ui fonts:css # emit the shipped --basalt-font-* stacks
|
|
76
|
+
bunx basalt-ui tokens:css # emit the --vx-* stylesheet (no React, no Mantine, no bundler) — bunx ONLY if basalt is not installed
|
|
77
|
+
bunx basalt-ui fonts:css # emit the shipped --basalt-font-* stacks — same exception
|
|
77
78
|
basalt-ui info --json # stable JSON surface map
|
|
78
79
|
basalt-ui guard-hook # PreToolUse theme-guard adapter: reads a Write/Edit payload on stdin, denies off-palette writes
|
|
79
80
|
```
|
|
@@ -83,8 +84,12 @@ so above the usage block. Neither is ever ignored — `doctor --json` used to ru
|
|
|
83
84
|
|
|
84
85
|
`check-theme`, `doctor` and `sync` resolve their project identically: `BASALT_CWD` wins, then the
|
|
85
86
|
cwd, then the workspace packages the root declares, then a two-level layout scan (which is what a
|
|
86
|
-
repo with no `workspaces` field and the install one level down needs)
|
|
87
|
-
|
|
87
|
+
repo with no `workspaces` field and the install one level down needs), then an **ascend** to the
|
|
88
|
+
nearest ancestor carrying a basalt project, bounded by the repo root. Both directions announce the
|
|
89
|
+
relocation in one sentence — `check-theme` used to fabricate `roots: ["src"]` on the ascend path and
|
|
90
|
+
pass silently. Two candidates is ambiguous and reported, never guessed. `sync` on a `tokens-only`
|
|
91
|
+
consumer prints `n/a` and exits 0; from a sub-package it refreshes the parent install rather than
|
|
92
|
+
refusing, and still will not scaffold a second consumer.
|
|
88
93
|
|
|
89
94
|
**Run the local bin, not `bunx`.** `bunx` does not re-resolve a cached package, so it can execute
|
|
90
95
|
the version you just upgraded away from — one consumer filed a P0 against a 1.20.0 cache while
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [1.24.0](https://github.com/jkrumm/basalt-ui/compare/v1.23.1...v1.24.0) (2026-08-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* stop check-theme fabricating a config, and re-gate the provenance line ([99a45c0](https://github.com/jkrumm/basalt-ui/commit/99a45c0dc284850b4140a6ff7719dbade4795f09))
|
|
7
|
+
* stop raw-hex reading HTML entities, and teach the guard the SFC dialect ([f1b0562](https://github.com/jkrumm/basalt-ui/commit/f1b0562efeb718921509253f9ee215bf461c3c01)), closes [#123](https://github.com/jkrumm/basalt-ui/issues/123) [#125](https://github.com/jkrumm/basalt-ui/issues/125)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* playground routes for QueryState and the table body chrome ([e150a00](https://github.com/jkrumm/basalt-ui/commit/e150a0051bf743137751a3d5c17f202c0984aaa7))
|
|
13
|
+
* ship the query loading/error branch and the table body chrome ([963bf04](https://github.com/jkrumm/basalt-ui/commit/963bf047cf9ac4fac3cecceecbd0c378ee37f79d))
|
|
14
|
+
|
|
1
15
|
## [1.23.1](https://github.com/jkrumm/basalt-ui/compare/v1.23.0...v1.23.1) (2026-08-22)
|
|
2
16
|
|
|
3
17
|
|
package/MIGRATING.md
CHANGED
|
@@ -10,7 +10,7 @@ cross-checked against the repo's `scripts/export-surface.json` snapshot. **Every
|
|
|
10
10
|
was re-audited against the built declaration files at 1.20.0 (2026-08-22)** after round 5 caught one
|
|
11
11
|
row that was wrong. That pass corrected 4 table rows and 3 prose claims. Every section from 1.21.0
|
|
12
12
|
on was written against source, not against its commit messages — which is not a formality: one
|
|
13
|
-
1.23.0 commit message describes a change it did not make (see `
|
|
13
|
+
1.23.0 commit message describes a change it did not make (see `1.23.1` § Corrections). Check
|
|
14
14
|
the types, not this table, if the two disagree.
|
|
15
15
|
|
|
16
16
|
**The newest section is headed `## Unreleased`, and stays that way until npm serves it.** This file
|
|
@@ -40,6 +40,138 @@ version `CHANGELOG.md` does not record. This file is not in that set; the same d
|
|
|
40
40
|
by convention. Rename the section at release, or leave it — a reader can resolve `Unreleased`
|
|
41
41
|
against `CHANGELOG.md`, and could never resolve `1.21.1`.
|
|
42
42
|
|
|
43
|
+
**Nothing removed or renamed. Twelve new runtime exports on `.`/`./query`, plus table props.** Every
|
|
44
|
+
API entry is additive; the behaviour changes are in the guard and the CLI, listed after them.
|
|
45
|
+
|
|
46
|
+
### `QueryState` — the branch precedence, shipped as a component
|
|
47
|
+
|
|
48
|
+
`QueryState`, `LoadingState`, `ErrorState` (+ `QueryStateProps`, `QueryStateLike`,
|
|
49
|
+
`QueryStateVariant`, `QueryEmptyCopy`, `LoadingStateProps`, `ErrorStateProps`) on the root barrel,
|
|
50
|
+
beside `EmptyState`. `toErrorMessage` and `errorStatus` on `basalt-ui/query`.
|
|
51
|
+
|
|
52
|
+
basalt owned both ends of this file — `EmptyState` and `toErrorMessage` — and nothing in between, so
|
|
53
|
+
a consumer wrote the four-way switch and got it wrong in the direction the shape suggested:
|
|
54
|
+
image-share's library rendered `No images` on a **500** and a share detail rendered
|
|
55
|
+
`Share not found` on a dropped connection, until 204 hand-rolled lines stopped it. Open since round
|
|
56
|
+
4, re-reported in round 6.
|
|
57
|
+
|
|
58
|
+
- **A component, not a hook** — the product IS the precedence, and a hook returns the same four-way
|
|
59
|
+
switch to every call site.
|
|
60
|
+
- **It lives under `src/dashboard/`, not `src/query/`**: `check-dist-layering.mjs` asserts
|
|
61
|
+
`dist/query/index.js` reaches no `@mantine/*`, and this renders Mantine. `query` is typed as a
|
|
62
|
+
five-field structural subset (`QueryStateLike`), so basalt couples to no query-library version and
|
|
63
|
+
a hand-composed object is legal. The subset gives up the compiler, so the shape is asserted at
|
|
64
|
+
**runtime**: a missing `isError` throws naming the field, because a missing `isError` is precisely
|
|
65
|
+
the "500 renders _No images_" bug.
|
|
66
|
+
- `errorTitle` / `errorFallback` / `errorAction` apply to the no-cached-data error branch only; the
|
|
67
|
+
error-with-cached-data branch renders a fixed section banner above `children`.
|
|
68
|
+
- **`EmptyState.description` is now optional.** Five argo features wrapped the component solely
|
|
69
|
+
because a compact panel had to invent a second sentence. Existing calls are unaffected.
|
|
70
|
+
- `toErrorMessage(err, fallback?)` and `errorStatus(err)` had two live bugs the port found: an
|
|
71
|
+
opaque envelope rendered the literal `"{}"`, and `toErrorMessage(undefined)` returned the
|
|
72
|
+
`undefined` VALUE despite a `string` return type. Both fixed; a status is folded into the fallback
|
|
73
|
+
when the body decodes to nothing readable. Split into `src/query/error-message.ts` so the
|
|
74
|
+
dashboard decodes without importing the peer.
|
|
75
|
+
|
|
76
|
+
**Port result:** image-share, all 10 call sites plus a standalone `ErrorState`, **by changing one
|
|
77
|
+
import line each** — no renames, no prop changes, no casts. Total 2467 → 2221; code-only 2056 →
|
|
78
|
+
1882, **−174**; `query-state.tsx` 204 → 0.
|
|
79
|
+
|
|
80
|
+
### `BasaltDataTable` body chrome — and the honest number
|
|
81
|
+
|
|
82
|
+
New: `maxHeight`, `minWidth`, `stickyHeader`, `stickyHeaderOffset`, `verticalSpacing`,
|
|
83
|
+
`horizontalSpacing`, `withRowBorders`, `withTableBorder`, per-column `meta.align` and
|
|
84
|
+
`meta.numeral`; `striped` widens from `boolean` to `boolean | 'odd' | 'even'`. `withTableBorder`
|
|
85
|
+
defaults to `true` in basalt, overriding Mantine's `false`; every other one is a conditional
|
|
86
|
+
pass-through, so omitting it keeps Mantine's default.
|
|
87
|
+
|
|
88
|
+
**Porting argo's three tables onto them made them 341 → 370 lines. 29 LONGER.** argo named these
|
|
89
|
+
props as the reason the tables stayed hand-rolled, and adding them shortened nothing: column defs
|
|
90
|
+
cost more than JSX rows when every cell is bespoke — eight accessor blocks at 4–6 lines each against
|
|
91
|
+
an eight-`<Table.Td>` row at ~3. **The ask was mis-specified.** Adopt them for what they buy —
|
|
92
|
+
the `type="native"` footgun, alignment stated once instead of on both `th` and `td` six times in one
|
|
93
|
+
file, and sorting/filtering/pagination no longer consumer-owned — not for a line count. This is the
|
|
94
|
+
counterexample to the band kinds, and the reason the port-before-shipping rule earns its keep.
|
|
95
|
+
|
|
96
|
+
- `maxHeight` (or `minWidth`) renders **`Table.ScrollContainer type="native"`**, and
|
|
97
|
+
`agent/rules/basalt-data.md` now prescribes that same node for a bespoke table — so the blessed
|
|
98
|
+
lane and the escape cannot contradict, and `type="scrollarea"`, which breaks a sticky `thead`, is
|
|
99
|
+
unreachable through the props. (The prop's JSDoc claimed the docs already sanctioned it; they had
|
|
100
|
+
never named `Table.ScrollContainer` at all.)
|
|
101
|
+
- `meta.align` is a `ColumnMeta` module augmentation: a typo'd key is a tsc error, a wrong value
|
|
102
|
+
throws naming the column. `meta.numeral` is read only as `!== false` — an opt-OUT of the
|
|
103
|
+
mono-numeral cell style, never an opt-in.
|
|
104
|
+
- **Not shipped, known:** `emptyState` renders inside a `<td colSpan>` so the header row survives an
|
|
105
|
+
empty table — there is no `emptyState="replace"` mode. No per-column `enableSorting` of basalt's
|
|
106
|
+
own; TanStack's `ColumnDef.enableSorting` still reaches `getCanSort()` and works.
|
|
107
|
+
|
|
108
|
+
### Four false greens
|
|
109
|
+
|
|
110
|
+
1. **`check-theme` fabricated a config on the ascend path and passed silently.** From a package with
|
|
111
|
+
no `basalt` key it invented `roots: ["src"]` and reported the invention back under the name
|
|
112
|
+
`basalt.roots`. In `basalt-ui-obsidian`, run from `apps/demo`, that scanned **22 of 44** guarded
|
|
113
|
+
files and made `--audit-allows` report **0 live waivers in a repo carrying 1** — exit 0, no note.
|
|
114
|
+
The audit exists so `0 dead` cannot read as `0 dead anywhere`, and it could be made to say zero
|
|
115
|
+
by standing in the wrong directory. `resolveProjectDir` now ascends to the nearest ancestor
|
|
116
|
+
carrying a basalt project, bounded by the repo root, and announces it in the sentence descend
|
|
117
|
+
already used. **No `.git` above cwd means no ascend**, so a standalone consumer keeps the
|
|
118
|
+
built-in defaults exactly as before. After: 44 files, the real 1.
|
|
119
|
+
2. **`tokens:css --check` stopped verifying the regeneration command.** 1.23.1 blanked all of line 2
|
|
120
|
+
so a version bump would stop forcing a no-op commit — but line 2 also carries the exact
|
|
121
|
+
invocation line 1 tells the reader to regenerate with. Rewriting `--only core` to `--only all` in
|
|
122
|
+
that line **passed clean**. Only the version token is neutralized now; a line that does not parse
|
|
123
|
+
as a provenance line is compared verbatim, so a deleted or reworded header fails. The success
|
|
124
|
+
message also **parses** the versions instead of asserting them — it used to claim the file "still
|
|
125
|
+
names an older basalt-ui" without reading it, so `0.0.1-nonsense` earned the same sentence.
|
|
126
|
+
3. **`doctor`'s icons check was unreachable from the only directory where `doctor` exits 0.** On a
|
|
127
|
+
monorepo the root run omitted it with no `⊘ SKIPPED` line — the exact failure mode `SKIPPED` was
|
|
128
|
+
introduced to eliminate — while the app-package run failed on artefacts of standing in a
|
|
129
|
+
non-install package. It resolves the app package off `basalt.roots` now. No `basaltAppPlugin(`
|
|
130
|
+
anywhere and no `public/` is a pass that says so; a plugin call with no `public/` beside it is a
|
|
131
|
+
`⊘ SKIPPED`, which exits non-zero on its own.
|
|
132
|
+
4. **`SCANNABLE_EXT` gained `.astro`, `.jsx` and `.vue`.** rollhook's marketing site is Astro and its
|
|
133
|
+
two `.astro` templates are its entire markup layer — unguarded, while `check-theme` reported a
|
|
134
|
+
clean 4-file scan. It scans 6 now.
|
|
135
|
+
|
|
136
|
+
**Behaviour change to name: `sync` ascends too.** It shares the resolver, so from a sub-package it
|
|
137
|
+
relocates to the parent install and refreshes it — announced — rather than refusing. It still cannot
|
|
138
|
+
scaffold a second consumer: the refusal is keyed on the RESOLVED project and runs before the
|
|
139
|
+
`basalt.roots` backfill.
|
|
140
|
+
|
|
141
|
+
### Guard changes the widening exposed
|
|
142
|
+
|
|
143
|
+
- **`raw-hex` no longer matches inside an HTML numeric character reference.** `{` — the escaped
|
|
144
|
+
brace a template writes to show a literal `${…}` in prose — read as the hex colour `#123`. The
|
|
145
|
+
hole was in the KIND, not the extension: the same string produced the same findings in `.html`,
|
|
146
|
+
`.tsx`, `.css` and `.vue`, so `.astro` only walked into it first. The fix is precise, not blanket:
|
|
147
|
+
`HEX` rejects a full reference (`&#`, digits, `;`), so **`color: red&#fff` still flags** and
|
|
148
|
+
nothing is exempted by file type. Every neighbouring raw-text kind was checked and structurally
|
|
149
|
+
cannot share the blind spot — a character reference contains no `(`, and the rest anchor on a
|
|
150
|
+
property name, `var(`, or a JSX `=`.
|
|
151
|
+
- **A fourth guard syntax, `sfc`, for `.astro`/`.vue`.** They used to fall through to the `ts`
|
|
152
|
+
dialect, so `<!-- … -->` was never stripped: a `theme-allow` written in an HTML comment waived
|
|
153
|
+
nothing and a colour inside a commented-out block still reported. `sfc` strips both regions —
|
|
154
|
+
markup first, so an HTML comment holding an unterminated `/*` cannot open one that runs to EOF —
|
|
155
|
+
and keeps the **full 25-kind set**. A `markup` classification would have dropped 22 of them: an
|
|
156
|
+
`.astro` template is JSX-shaped and a `.vue` `<script setup>` is real TS. `.jsx` needs no branch.
|
|
157
|
+
Both the scan and `--audit-allows` now share one `stripGuardComments`, so they cannot disagree
|
|
158
|
+
about what a comment is.
|
|
159
|
+
- **Two limits, asserted rather than left ambiguous, both false-negative-only:** `css-raw-surface`
|
|
160
|
+
does not fire inside a `<style>` fence, and stripping is region-blind, so a `<!--` inside a script
|
|
161
|
+
string over-strips.
|
|
162
|
+
- **A known non-fix, deliberate:** an all-hex URL fragment or SVG reference (`href="#cafe"`,
|
|
163
|
+
`fill="url(#abcdef)"`) still reports. It is text-indistinguishable from a colour, so a fix would
|
|
164
|
+
cost real findings; `theme-allow` is the escape.
|
|
165
|
+
- **The widening ships at `error` with no `GRACE_PERIOD_KINDS` entry, and that table cannot express
|
|
166
|
+
it.** The table is keyed per KIND; widening `SCANNABLE_EXT` widens the file set for all 25 at
|
|
167
|
+
once. An entry for `raw-hex` — the kind that actually fired — would demote basalt's most
|
|
168
|
+
load-bearing kind to `warn` across every `.tsx` and `.css` in all seven consumers to buy runway on
|
|
169
|
+
a file type one consumer has. Measured: rollhook's marketing site scans 6 files with 0 findings,
|
|
170
|
+
and no other consumer holds a single `.astro`, `.vue` or `.jsx` file, so grace would have covered
|
|
171
|
+
zero incumbent violations.
|
|
172
|
+
|
|
173
|
+
## 1.23.1 — the band-state throw, the tag gate, a CLI that answers
|
|
174
|
+
|
|
43
175
|
**One type widened, nothing removed or renamed.** `BandStripSeries.formatValue` is now
|
|
44
176
|
`(d: T) => string | null`. `null` renders an em dash — an absent READING — which `''` never could:
|
|
45
177
|
`''` is a state whose label is the whole row. Every existing `(d) => string` still typechecks.
|
|
@@ -99,7 +231,9 @@ instead of dumping help and letting the dump read like a choice.
|
|
|
99
231
|
- **`tokens:css --check` blanks the provenance line (line 2) before comparing**, so a version bump
|
|
100
232
|
alone no longer forces a no-op commit in a tokens-only consumer, where the gate is byte-equality.
|
|
101
233
|
A stale provenance line is now a note on an otherwise-passing check. The `@generated` header is
|
|
102
|
-
still emitted byte-identical — the line stays, it just stops gating.
|
|
234
|
+
still emitted byte-identical — the line stays, it just stops gating. **Superseded in `Unreleased`:
|
|
235
|
+
blanking the WHOLE line also stopped gating the regeneration command it carries. Only the version
|
|
236
|
+
token is neutralized now.**
|
|
103
237
|
|
|
104
238
|
### Corrections to the record
|
|
105
239
|
|
|
@@ -229,8 +363,8 @@ install it found above instead. The refusal runs before the `basalt.roots` backf
|
|
|
229
363
|
the damage. Two ways to unblock it:
|
|
230
364
|
|
|
231
365
|
```bash
|
|
232
|
-
cd <the package holding .basalt/manifest.json> &&
|
|
233
|
-
BASALT_CWD=<that package>
|
|
366
|
+
cd <the package holding .basalt/manifest.json> && ./node_modules/.bin/basalt-ui sync
|
|
367
|
+
BASALT_CWD=<that package> ./node_modules/.bin/basalt-ui sync # or from anywhere
|
|
234
368
|
```
|
|
235
369
|
|
|
236
370
|
The summary line gained a `created` counter. **`recreated` now means what it always claimed** — the
|
package/README.md
CHANGED
|
@@ -292,31 +292,31 @@ deleted rather than silently surviving into the next real skew.
|
|
|
292
292
|
|
|
293
293
|
## Subpath exports
|
|
294
294
|
|
|
295
|
-
| Subpath | Mantine? | Purpose
|
|
296
|
-
| ------------------- | -------- |
|
|
297
|
-
| `.` | coupled | `BasaltProvider`, `createBasaltTheme` / `baseTheme` / `cssVariablesResolver`, `BasaltShell` + sidebar / mobile-nav / breadcrumbs / page-header, `NavCountBadge`, `SidebarAccount` + the provider-agnostic account contract (`BasaltAccountProps`/`State`/`Actions`), `ThreadWorkspace` + thread-chat components, shell types — the root entry still re-exports the thread-chat components; `./agent-chat` takes the same components standalone, without `BasaltProvider`, the shell, the dashboard composites, or `./connectivity`
|
|
298
|
-
| `./charts` | **free** | `CartesianChart` + visx primitives, kinds, sparklines, hooks, token re-exports
|
|
299
|
-
| `./tokens` | **free** | `VX` token refs, `buildPaletteCss`, `defineSeries`, `seriesTokens`, `groupTokens`, `alpha`, `ColorPair` / `SeriesMap` types
|
|
300
|
-
| `./theme-lab` | coupled | `ThemeLabControls`, `applyOverrides`, `loadOverrides`, `COLOR_GROUPS` for live theme inspection
|
|
301
|
-
| `./vite` | — | `basaltViteConfig(opts)` — Vite preset for basalt-ui consumer apps; `basaltAppPlugin(opts)` — PWA head, manifest, and icon metadata derived from the token palette
|
|
302
|
-
| `./guard` | **free** | `checkSource`, `GUARD_RULES`, `Finding` types — the headless theme-guard core; plus the annotation reader `--audit-allows` is built on: `findAllowAnnotations`, `neutralizeAllowAnnotation`, `NEUTRALIZED_ALLOW_TOKEN`, `PLUGIN_RULE_IDS`, `AllowAnnotationSite`
|
|
303
|
-
| `./query` | **free** | `createBasaltQueryClient`, transport-agnostic `unwrap`, lazy `BasaltQueryDevtools`
|
|
304
|
-
| `./router-tanstack` | **free** | TanStack Router bridge: `useBasaltNav` (active route) + `useRouterBreadcrumbs`
|
|
305
|
-
| `./forms` | coupled | Mantine form adapter: `useBasaltForm`, `field`, `FormErrorSummary`, `useFormDraft` (Standard Schema)
|
|
306
|
-
| `./notifications` | coupled | Mantine notifications: `notify` helpers, typed registry, persisted history, `NotificationBell`
|
|
307
|
-
| `./commands` | coupled | Typed command bus + overlay controller, `toSpotlightActions`, `ShortcutsHelp`, `BasaltOverlays`
|
|
308
|
-
| `./data` | coupled | Convenience barrel pulling both peer groups: `BasaltDataTable`, `BasaltVirtualList` (Mantine-rendered) — prefer `./data/table` / `./data/virtual` for per-feature opt-in
|
|
309
|
-
| `./data/table` | coupled | `BasaltDataTable` — sortable data table over TanStack Table, rendered with Mantine
|
|
310
|
-
| `./data/virtual` | coupled | `BasaltVirtualList` — windowed virtual list over TanStack Virtual, rendered with Mantine
|
|
311
|
-
| `./agent` | **free** | Headless streaming layer (`useAgentStream`, `PartList`) + multi-thread `createThreadsStore` / `useAgentThreadRuns` / outcome seam
|
|
312
|
-
| `./agent-chat` | coupled | Mantine-styled thread-chat components over `./agent`: `ThreadWorkspace`, `ThreadFeed` (`variant`/`renderRow`), `ThreadFeedRow` (inline-expanding Slack row, lazily mounted + kept mounted), `ThreadOutcomeCard`, `ThreadDetailPanel`, `Composer`, `ThreadTranscript` (`groupConsecutive`/`affordances`/`virtualize` — windowed transcripts open at the newest message unless `initialScroll: 'start'`; `virtualize` is enabled by the optional `@tanstack/react-virtual` peer, absent it degrades to an unwindowed, height-bound pane), `threadPartRenderers` — **requires `motion`**, not merely an optional peer; see [Requirements](#requirements)
|
|
313
|
-
| `./state` | **free** | `createPersistedState` (versioned localStorage) + `useOnlineStatus` — Mantine-free state primitives
|
|
314
|
-
| `./connectivity` | coupled | `ConnectivityProvider` (aggregates browser/React-Query/SSE/health-check status), `useConnectivity`, `ConnectivityIndicator` — auto-mounted by `BasaltProvider`
|
|
315
|
-
| `./content` | coupled | `Prose`, `CodeBlock`, `Callout`, `TableOfContents`, `ReadingProgress`, `Markdown`, `MermaidDiagram`, `ArticleLayout`, `ArticleCard` / `ArticleGrid`, `GuideLink` / `GuideDrawer`, `mdxComponents`
|
|
316
|
-
| `./styles.css` | — | `@layer basalt` base styles, iOS input safety net, font stack
|
|
317
|
-
| `./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)
|
|
318
|
-
| `./configs/*` | — | Raw toolchain presets — oxlint, oxfmt, tsconfig (base/react-app/node), lefthook
|
|
319
|
-
| `./llms.txt` | — | Machine-readable surface map — one entry per published subpath with import specifier, description, layer, and optional peers
|
|
295
|
+
| Subpath | Mantine? | Purpose |
|
|
296
|
+
| ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
297
|
+
| `.` | coupled | `BasaltProvider`, `createBasaltTheme` / `baseTheme` / `cssVariablesResolver`, `BasaltShell` + sidebar / mobile-nav / breadcrumbs / page-header, `NavCountBadge`, `SidebarAccount` + the provider-agnostic account contract (`BasaltAccountProps`/`State`/`Actions`), `ThreadWorkspace` + thread-chat components, dashboard composites (`DeltaBadge`, `StatCard`, `EmptyState`, `SettingsSection`/`SettingsRow`/`DangerZone`) and the query-branch trio `QueryState` / `LoadingState` / `ErrorState`, shell types — the root entry still re-exports the thread-chat components; `./agent-chat` takes the same components standalone, without `BasaltProvider`, the shell, the dashboard composites, or `./connectivity` |
|
|
298
|
+
| `./charts` | **free** | `CartesianChart` + visx primitives, kinds, sparklines, hooks, token re-exports |
|
|
299
|
+
| `./tokens` | **free** | `VX` token refs, `buildPaletteCss`, `defineSeries`, `seriesTokens`, `groupTokens`, `alpha`, `ColorPair` / `SeriesMap` types |
|
|
300
|
+
| `./theme-lab` | coupled | `ThemeLabControls`, `applyOverrides`, `loadOverrides`, `COLOR_GROUPS` for live theme inspection |
|
|
301
|
+
| `./vite` | — | `basaltViteConfig(opts)` — Vite preset for basalt-ui consumer apps; `basaltAppPlugin(opts)` — PWA head, manifest, and icon metadata derived from the token palette |
|
|
302
|
+
| `./guard` | **free** | `checkSource`, `GUARD_RULES`, `Finding` types — the headless theme-guard core; plus the annotation reader `--audit-allows` is built on: `findAllowAnnotations`, `neutralizeAllowAnnotation`, `NEUTRALIZED_ALLOW_TOKEN`, `PLUGIN_RULE_IDS`, `AllowAnnotationSite` |
|
|
303
|
+
| `./query` | **free** | `createBasaltQueryClient`, transport-agnostic `unwrap`, lazy `BasaltQueryDevtools`, `toErrorMessage` / `errorStatus` — the only route to those two; the root barrel does not re-export them |
|
|
304
|
+
| `./router-tanstack` | **free** | TanStack Router bridge: `useBasaltNav` (active route) + `useRouterBreadcrumbs` |
|
|
305
|
+
| `./forms` | coupled | Mantine form adapter: `useBasaltForm`, `field`, `FormErrorSummary`, `useFormDraft` (Standard Schema) |
|
|
306
|
+
| `./notifications` | coupled | Mantine notifications: `notify` helpers, typed registry, persisted history, `NotificationBell` |
|
|
307
|
+
| `./commands` | coupled | Typed command bus + overlay controller, `toSpotlightActions`, `ShortcutsHelp`, `BasaltOverlays` |
|
|
308
|
+
| `./data` | coupled | Convenience barrel pulling both peer groups: `BasaltDataTable`, `BasaltVirtualList` (Mantine-rendered) — prefer `./data/table` / `./data/virtual` for per-feature opt-in |
|
|
309
|
+
| `./data/table` | coupled | `BasaltDataTable` — sortable data table over TanStack Table, rendered with Mantine |
|
|
310
|
+
| `./data/virtual` | coupled | `BasaltVirtualList` — windowed virtual list over TanStack Virtual, rendered with Mantine |
|
|
311
|
+
| `./agent` | **free** | Headless streaming layer (`useAgentStream`, `PartList`) + multi-thread `createThreadsStore` / `useAgentThreadRuns` / outcome seam |
|
|
312
|
+
| `./agent-chat` | coupled | Mantine-styled thread-chat components over `./agent`: `ThreadWorkspace`, `ThreadFeed` (`variant`/`renderRow`), `ThreadFeedRow` (inline-expanding Slack row, lazily mounted + kept mounted), `ThreadOutcomeCard`, `ThreadDetailPanel`, `Composer`, `ThreadTranscript` (`groupConsecutive`/`affordances`/`virtualize` — windowed transcripts open at the newest message unless `initialScroll: 'start'`; `virtualize` is enabled by the optional `@tanstack/react-virtual` peer, absent it degrades to an unwindowed, height-bound pane), `threadPartRenderers` — **requires `motion`**, not merely an optional peer; see [Requirements](#requirements) |
|
|
313
|
+
| `./state` | **free** | `createPersistedState` (versioned localStorage) + `useOnlineStatus` — Mantine-free state primitives |
|
|
314
|
+
| `./connectivity` | coupled | `ConnectivityProvider` (aggregates browser/React-Query/SSE/health-check status), `useConnectivity`, `ConnectivityIndicator` — auto-mounted by `BasaltProvider` |
|
|
315
|
+
| `./content` | coupled | `Prose`, `CodeBlock`, `Callout`, `TableOfContents`, `ReadingProgress`, `Markdown`, `MermaidDiagram`, `ArticleLayout`, `ArticleCard` / `ArticleGrid`, `GuideLink` / `GuideDrawer`, `mdxComponents` |
|
|
316
|
+
| `./styles.css` | — | `@layer basalt` base styles, iOS input safety net, font stack |
|
|
317
|
+
| `./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) |
|
|
318
|
+
| `./configs/*` | — | Raw toolchain presets — oxlint, oxfmt, tsconfig (base/react-app/node), lefthook |
|
|
319
|
+
| `./llms.txt` | — | Machine-readable surface map — one entry per published subpath with import specifier, description, layer, and optional peers |
|
|
320
320
|
|
|
321
321
|
Named exports only — no default exports.
|
|
322
322
|
|
|
@@ -479,6 +479,62 @@ const [client] = useState(() => createBasaltQueryClient())
|
|
|
479
479
|
// queryFn: () => unwrap(api.users.get())
|
|
480
480
|
```
|
|
481
481
|
|
|
482
|
+
`toErrorMessage(err, fallback?)` and `errorStatus(err)` also live here — decode an unknown thrown
|
|
483
|
+
value into something renderable, folding an HTTP status into the fallback when the body decodes to
|
|
484
|
+
nothing usable (`''`, `'{}'`, `'[object Object]'`, …).
|
|
485
|
+
|
|
486
|
+
#### Rendering a query — `QueryState`
|
|
487
|
+
|
|
488
|
+
Imported from **`basalt-ui`**, not `basalt-ui/query`: it renders Mantine, and `./query` is a
|
|
489
|
+
guard-enforced Mantine-free entry.
|
|
490
|
+
|
|
491
|
+
```tsx
|
|
492
|
+
import { QueryState } from 'basalt-ui'
|
|
493
|
+
|
|
494
|
+
function Library() {
|
|
495
|
+
const q = useQuery(imageQueries.list()) // { data, isError, error, fetchStatus, refetch }
|
|
496
|
+
return (
|
|
497
|
+
<QueryState
|
|
498
|
+
query={q}
|
|
499
|
+
variant="page" // or "section"
|
|
500
|
+
errorTitle="Could not load images"
|
|
501
|
+
errorFallback="The request failed."
|
|
502
|
+
errorAction={<Button onClick={openSupport}>Get help</Button>}
|
|
503
|
+
empty={{ title: 'No images yet', description: 'Upload one to get started.' }}
|
|
504
|
+
isEmpty={(d) => d.items.length === 0}
|
|
505
|
+
>
|
|
506
|
+
{(data) => <ImageGrid items={data.items} />}
|
|
507
|
+
</QueryState>
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
**Ship it instead of writing the switch yourself.** Rendering only the empty branch is the mistake
|
|
513
|
+
this exists to retire: a consumer's library showed `No images` on a **500** and a share detail
|
|
514
|
+
showed `Share not found` on a dropped connection, because "nothing here" is what an app renders when
|
|
515
|
+
nobody wrote the other three branches. `LoadingState` and `ErrorState` ship beside it as the escape
|
|
516
|
+
hatch for a page that must place its branches in different DOM positions.
|
|
517
|
+
|
|
518
|
+
Branch precedence, in the order it is evaluated:
|
|
519
|
+
|
|
520
|
+
| Condition | Renders |
|
|
521
|
+
| ----------------------------------- | ------------------------------------------------------------------------- |
|
|
522
|
+
| `isError` and no `data` | `ErrorState` — `errorTitle` / `errorFallback` / `errorAction`, with Retry |
|
|
523
|
+
| no `data`, `fetchStatus === 'idle'` | the `empty` copy (or nothing, if `empty` is omitted) |
|
|
524
|
+
| no `data` | `loading`, else `LoadingState` |
|
|
525
|
+
| `data`, `isEmpty(data)` | the `empty` copy |
|
|
526
|
+
| `data` | `children` — a node, or `(data) => node` |
|
|
527
|
+
| `data` **and** `isError` | a section-variant _stale data_ banner above `children` |
|
|
528
|
+
|
|
529
|
+
The stale-data banner carries its own fixed copy (`Showing cached data`); `errorTitle` /
|
|
530
|
+
`errorFallback` / `errorAction` apply to the no-data error branch only.
|
|
531
|
+
|
|
532
|
+
`query` is typed as a five-field structural subset, not TanStack's `UseQueryResult` — a
|
|
533
|
+
hand-composed object is legal, and basalt couples to no query-library version. That subset removes
|
|
534
|
+
the compiler, so **the shape is checked at runtime and a missing `isError` throws**, naming the
|
|
535
|
+
field. That is deliberate: a missing `isError` is exactly the bug above, and degrading quietly would
|
|
536
|
+
reproduce it.
|
|
537
|
+
|
|
482
538
|
### `./router-tanstack` — TanStack Router bridge
|
|
483
539
|
|
|
484
540
|
```bash
|
|
@@ -547,9 +603,32 @@ bun add @tanstack/react-table @tanstack/react-virtual
|
|
|
547
603
|
import { BasaltDataTable, BasaltVirtualList, createColumnHelper } from 'basalt-ui/data'
|
|
548
604
|
|
|
549
605
|
const col = createColumnHelper<Row>()
|
|
550
|
-
const columns = [
|
|
551
|
-
|
|
552
|
-
|
|
606
|
+
const columns = [
|
|
607
|
+
col.accessor('name', { header: 'Name' }),
|
|
608
|
+
col.accessor('amount', { header: 'Amount', meta: { align: 'right' } }),
|
|
609
|
+
]
|
|
610
|
+
// <BasaltDataTable data={rows} columns={columns} maxHeight={320} stickyHeader />
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**Body chrome.** `maxHeight`, `minWidth`, `stickyHeader`, `stickyHeaderOffset`, `verticalSpacing`,
|
|
614
|
+
`horizontalSpacing`, `withRowBorders`, `withTableBorder` (basalt defaults this one to `true`),
|
|
615
|
+
`striped` (`boolean | 'odd' | 'even'`), and per-column `meta: { align, numeral }`.
|
|
616
|
+
|
|
617
|
+
- `maxHeight` (or `minWidth`) renders `Table.ScrollContainer type="native"` — the same node a
|
|
618
|
+
bespoke escape-hatch table should wrap itself in, so the two lanes produce identical DOM.
|
|
619
|
+
`type="native"` is required, not preferred: `ScrollArea`'s custom viewport is what a sticky
|
|
620
|
+
`<thead>` positions against, so the default type pins the header to the page viewport.
|
|
621
|
+
- `meta.align` is a `ColumnMeta` module augmentation: a typo'd key is a type error, and a value
|
|
622
|
+
outside `'left' | 'center' | 'right'` throws naming the column rather than left-aligning a money
|
|
623
|
+
column in silence. `meta: { numeral: false }` opts a numeric cell OUT of the mono-numeral style —
|
|
624
|
+
it is an opt-out only; `numeral: true` forces nothing.
|
|
625
|
+
- `emptyState` renders inside a `<td colSpan>`, so the header row survives an empty table. There is
|
|
626
|
+
no mode that replaces the whole table.
|
|
627
|
+
|
|
628
|
+
**Honest result:** porting argo's three tables onto these props made them 341 → **370** lines, 29
|
|
629
|
+
longer. Column defs cost more than JSX rows when every cell is bespoke. Adopt the props for what
|
|
630
|
+
they actually buy — the `type="native"` footgun, alignment stated once instead of on both `th` and
|
|
631
|
+
`td`, and sorting/filtering/pagination owned by basalt — not for a line count.
|
|
553
632
|
|
|
554
633
|
#### `./data/table` — BasaltDataTable only
|
|
555
634
|
|
|
@@ -711,8 +790,8 @@ basalt-ui sync --check # CI drift gate — non-zero exit on any manage
|
|
|
711
790
|
basalt-ui check-theme # palette guard — fails on colors that bypass the central --vx-* system
|
|
712
791
|
basalt-ui doctor # integration health: basalt-resolves, guard-scan, oxlint-preset, lefthook-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 an icon file basaltAppPlugin references and public/ does not have
|
|
713
792
|
basalt-ui doctor --tokens-only # force the Mantine-free profile (auto-detected otherwise; --framework forces the full set)
|
|
714
|
-
bunx basalt-ui tokens:css --out src/tokens.css # emit the --vx-* stylesheet as a file you own
|
|
715
|
-
bunx basalt-ui fonts:css --out src/fonts.css # emit the shipped --basalt-font-* stacks
|
|
793
|
+
bunx basalt-ui tokens:css --out src/tokens.css # emit the --vx-* stylesheet as a file you own — `bunx` ONLY if basalt is not installed here
|
|
794
|
+
bunx basalt-ui fonts:css --out src/fonts.css # emit the shipped --basalt-font-* stacks — same exception
|
|
716
795
|
basalt-ui info --json # surface map as stable JSON (InfoOutput shape); bare `info` is the human-readable form
|
|
717
796
|
basalt-ui help # full usage; every subcommand also takes --help / -h
|
|
718
797
|
```
|
|
@@ -740,6 +819,11 @@ script, CI step, hook and piece of doctor advice with the resolved `./node_modul
|
|
|
740
819
|
(`BASALT_BIN` overrides it, and `configs/lefthook.yml` keeps `bunx --no-install` as a deliberately
|
|
741
820
|
loud fallback). `basalt-ui --version` is the one-second check.
|
|
742
821
|
|
|
822
|
+
**Three commands are the sanctioned exception, and only when basalt is not installed in the repo:**
|
|
823
|
+
`init` (which predates the install by definition) and `tokens:css` / `fonts:css` (the framework-free
|
|
824
|
+
route, where there is nothing to install). In a repo that _does_ carry basalt — which includes every
|
|
825
|
+
tokens-only consumer that keeps it as a devDependency — run those through the local bin too.
|
|
826
|
+
|
|
743
827
|
`sync` strategy per file: unchanged since last write → overwrite; locally edited → skip (show diff);
|
|
744
828
|
missing → recreate. `--force` overwrites local edits. The summary counts `created` and `recreated`
|
|
745
829
|
apart: `recreated` means the ledger placed that file once and it went missing. On a
|
|
@@ -778,13 +862,20 @@ The `./configs/*` export gives consumer apps raw presets to `extends` or copy vi
|
|
|
778
862
|
|
|
779
863
|
## `basalt-ui sync` in CI
|
|
780
864
|
|
|
781
|
-
Wire the drift gate to catch doctrine falling behind after a basalt-ui upgrade
|
|
865
|
+
Wire the drift gate to catch doctrine falling behind after a basalt-ui upgrade. **The local bin,
|
|
866
|
+
never `bunx`** — this is what `init` seeds (`configs/check.yml`, rendered from where basalt actually
|
|
867
|
+
installed):
|
|
782
868
|
|
|
783
869
|
```yaml
|
|
784
870
|
# .github/workflows/check.yml (seeded by basalt-ui init)
|
|
785
|
-
- run:
|
|
871
|
+
- run: ./node_modules/.bin/basalt-ui sync --check
|
|
786
872
|
```
|
|
787
873
|
|
|
874
|
+
`sync --check` is the worst command to run from a stale `bunx` cache: it compares the consumer's
|
|
875
|
+
files against the **cached CLI's own** shipped doctrine, so it passes while the install has drifted.
|
|
876
|
+
Three consumer repos hand-corrected this exact line, and this snippet used to talk the next reader
|
|
877
|
+
straight back into it.
|
|
878
|
+
|
|
788
879
|
---
|
|
789
880
|
|
|
790
881
|
## Requirements
|
|
@@ -59,6 +59,23 @@ const columns = [
|
|
|
59
59
|
| `initialSorting` | `SortingState` | `[]` | Initial sort state; drives `useState` initializer |
|
|
60
60
|
| `onSortingChange` | `(s: SortingState) => void` | — | Called whenever sorting changes; omit for uncontrolled |
|
|
61
61
|
|
|
62
|
+
Body chrome — all conditional pass-throughs to Mantine `Table` except `withTableBorder`, which
|
|
63
|
+
basalt defaults to `true`:
|
|
64
|
+
|
|
65
|
+
| Prop | Type | Notes |
|
|
66
|
+
| --------------------------------------- | ------------------------------- | -------------------------------------------------------------------- |
|
|
67
|
+
| `maxHeight` | `number \| string` | Caps the body height — renders `Table.ScrollContainer type="native"` |
|
|
68
|
+
| `minWidth` | `number \| string` | Same container; `maxHeight` alone passes `minWidth={0}` |
|
|
69
|
+
| `stickyHeader` / `stickyHeaderOffset` | `boolean` / `number \| string` | Forwarded to Mantine; pair with `maxHeight` |
|
|
70
|
+
| `verticalSpacing` / `horizontalSpacing` | `MantineSpacing` | Cell padding |
|
|
71
|
+
| `withRowBorders` / `withTableBorder` | `boolean` | `withTableBorder` defaults `true` here, overriding Mantine's `false` |
|
|
72
|
+
| `meta: { align }` (per column) | `'left' \| 'center' \| 'right'` | Sets `textAlign` on BOTH the `th` and the `td` |
|
|
73
|
+
| `meta: { numeral: false }` (per column) | `boolean` | Opts a numeric cell OUT of the mono-numeral style. Opt-out only |
|
|
74
|
+
|
|
75
|
+
`meta.align` is a `ColumnMeta` module augmentation, so a typo'd key is a tsc error and a value
|
|
76
|
+
outside the union **throws** naming the column — a money column never silently left-aligns. Set it
|
|
77
|
+
once on the column def instead of repeating `textAlign: 'right'` on the header and the cell.
|
|
78
|
+
|
|
62
79
|
### Controlled sorting and URL sync
|
|
63
80
|
|
|
64
81
|
`initialSorting` seeds the internal `useState` and `onSortingChange` is called on every sort
|
|
@@ -122,18 +139,18 @@ col.display({ id: 'actions', header: 'Actions', cell: (ctx) => <Button>Delete {c
|
|
|
122
139
|
`ColumnDef<T>` defaults `TValue` to `unknown`, not `any`. Never cast column defs to `any` — use
|
|
123
140
|
the correct `ColumnDef<T>` type or `ColumnDef<T, unknown>`.
|
|
124
141
|
|
|
125
|
-
###
|
|
126
|
-
|
|
127
|
-
The 1.0 table ships sorting only. Natural extensions are **not** in scope yet:
|
|
142
|
+
### Also shipped, and easy to miss
|
|
128
143
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
|
|
144
|
+
Global search (`enableGlobalFilter`, `globalFilterPlaceholder`, `searchIcon`,
|
|
145
|
+
`initialGlobalFilter`, `onGlobalFilterChange`), faceted filters (`facets`), a toolbar slot
|
|
146
|
+
(`toolbarActions`), pagination client- or server-side (`enablePagination`, `pageSizeOptions`,
|
|
147
|
+
`initialPagination`, `onPaginationChange`, `manualPagination`, `rowCount`, `pageCount`) and column
|
|
148
|
+
pinning (`enablePinning`, `initialColumnPinning`). **Don't hand-roll any of these** — read the props
|
|
149
|
+
on the type, they are all there.
|
|
133
150
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
Still deferred: row selection, row expansion / sub-rows, and fully controlled (external) sorting —
|
|
152
|
+
`onSortingChange` is the sync seam. Add them when a concrete consumer need arises; keep the API
|
|
153
|
+
additive.
|
|
137
154
|
|
|
138
155
|
### The blessed lane vs the raw escape hatch
|
|
139
156
|
|
|
@@ -166,9 +183,24 @@ const table = useReactTable({ data: rows, columns, getCoreRowModel: getCoreRowMo
|
|
|
166
183
|
// table.getHeaderGroups() / table.getRowModel().rows, rendered with flexRender(...) and your own markup
|
|
167
184
|
```
|
|
168
185
|
|
|
169
|
-
Reach for the escape hatch only when `BasaltDataTable`'s props genuinely can't express the shape
|
|
170
|
-
|
|
171
|
-
|
|
186
|
+
Reach for the escape hatch only when `BasaltDataTable`'s props genuinely can't express the shape.
|
|
187
|
+
Pagination, server-side data and filtering are **not** among those cases any more — see above.
|
|
188
|
+
|
|
189
|
+
**Both lanes scroll through the same node.** An escape-hatch table that needs a capped body wraps
|
|
190
|
+
it in `<Table.ScrollContainer type="native" minWidth={0} maxHeight={320}>` — and that is byte for
|
|
191
|
+
byte what `BasaltDataTable`'s `maxHeight`/`minWidth` render. The blessed lane and the escape cannot
|
|
192
|
+
contradict each other on scrolling, and neither should reach for `type="scrollarea"`: `ScrollArea`'s
|
|
193
|
+
custom viewport is the positioning context a sticky `<thead>` resolves against, so the default type
|
|
194
|
+
pins the header to the page viewport instead of the table's box. (`basalt/raw-scroll-container`
|
|
195
|
+
takes no view here — it steers raw `overflow: auto`, and `Table.ScrollContainer` is a Mantine
|
|
196
|
+
component, not a raw scroll box.)
|
|
197
|
+
|
|
198
|
+
**Adopt the blessed lane for ownership, not for a line count.** Porting one consumer's three tables
|
|
199
|
+
onto these props made them 341 → **370** lines, 29 longer: eight accessor blocks at 4–6 lines each
|
|
200
|
+
cost more than an eight-`<Table.Td>` row at ~3 when every cell is bespoke. What the port bought was
|
|
201
|
+
the `type="native"` footgun, alignment stated once instead of on both `th` and `td` six times over,
|
|
202
|
+
and sorting/filtering/pagination no longer being yours to maintain. Expect that trade, not a
|
|
203
|
+
shrink.
|
|
172
204
|
|
|
173
205
|
## BasaltVirtualList
|
|
174
206
|
|