basalt-ui 1.31.0 → 1.32.1

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.
Files changed (102) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/MIGRATING.md +212 -20
  3. package/agent/rules/basalt-mantine.md +4 -4
  4. package/configs/oxlint-plugin.js +240 -59
  5. package/configs/oxlint-plugin.test.ts +329 -1
  6. package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
  7. package/dist/charts/kinds/BandStrip.js +18 -66
  8. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  9. package/dist/charts/kinds/DualPanel.js +5 -26
  10. package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
  11. package/dist/charts/kinds/MirroredBars.js +10 -68
  12. package/dist/charts/kinds/band-chrome.d.ts +31 -0
  13. package/dist/charts/kinds/band-chrome.d.ts.map +1 -0
  14. package/dist/charts/kinds/band-chrome.js +71 -0
  15. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  16. package/dist/charts/primitives/CartesianChart.js +49 -76
  17. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  18. package/dist/charts/primitives/ChartCard.js +5 -23
  19. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  20. package/dist/charts/primitives/ChartFrame.js +23 -40
  21. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  22. package/dist/charts/primitives/ChartLegend.js +5 -133
  23. package/dist/charts/primitives/ChartTooltip.d.ts +5 -0
  24. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  25. package/dist/charts/primitives/ChartTooltip.js +16 -1
  26. package/dist/charts/primitives/HoverOverlay.d.ts +24 -9
  27. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  28. package/dist/charts/primitives/HoverOverlay.js +17 -1
  29. package/dist/charts/primitives/chart-card-context.d.ts +17 -13
  30. package/dist/charts/primitives/chart-card-context.d.ts.map +1 -1
  31. package/dist/charts/primitives/chart-card-context.js +10 -5
  32. package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
  33. package/dist/charts/primitives/chart-frame-layout.js +25 -31
  34. package/dist/charts/primitives/legend-disclosure.d.ts +27 -0
  35. package/dist/charts/primitives/legend-disclosure.d.ts.map +1 -0
  36. package/dist/charts/primitives/legend-disclosure.js +153 -0
  37. package/dist/controls/controls.module.css +5 -0
  38. package/dist/controls/sync-button.module.css +1 -0
  39. package/dist/dashboard/stat-group.module.css +19 -64
  40. package/dist/dashboard/widget-grid.module.css +11 -38
  41. package/dist/data/data-table-state.d.ts +83 -0
  42. package/dist/data/data-table-state.d.ts.map +1 -0
  43. package/dist/data/data-table-state.js +139 -0
  44. package/dist/data/data-table.d.ts +40 -2
  45. package/dist/data/data-table.d.ts.map +1 -1
  46. package/dist/data/data-table.js +179 -277
  47. package/dist/forms/form-layout.d.ts +6 -1
  48. package/dist/forms/form-layout.d.ts.map +1 -1
  49. package/dist/forms/form-layout.module.css +6 -13
  50. package/dist/guard/index.d.ts +2 -2
  51. package/dist/guard/index.d.ts.map +1 -1
  52. package/dist/guard/index.js +10 -14
  53. package/dist/notifications/center.d.ts.map +1 -1
  54. package/dist/provider/index.d.ts.map +1 -1
  55. package/dist/provider/index.js +12 -10
  56. package/dist/shell/aside-docking.d.ts +31 -0
  57. package/dist/shell/aside-docking.d.ts.map +1 -0
  58. package/dist/shell/aside-docking.js +19 -0
  59. package/dist/shell/index.d.ts +0 -11
  60. package/dist/shell/index.d.ts.map +1 -1
  61. package/dist/shell/index.js +13 -14
  62. package/dist/shell/page-bar.d.ts +24 -0
  63. package/dist/shell/page-bar.d.ts.map +1 -1
  64. package/dist/shell/use-breakpoint.d.ts +1 -1
  65. package/dist/theme/floating.module.css +1 -0
  66. package/dist/theme-lab/derive-controls.d.ts.map +1 -1
  67. package/dist/tokens/index.d.ts +2 -0
  68. package/dist/tokens/index.d.ts.map +1 -1
  69. package/dist/tokens/index.js +4 -0
  70. package/llms.txt +2 -2
  71. package/package.json +2 -1
  72. package/src/charts/kinds/BandStrip.tsx +18 -64
  73. package/src/charts/kinds/DualPanel.tsx +4 -24
  74. package/src/charts/kinds/MirroredBars.tsx +12 -66
  75. package/src/charts/kinds/band-chrome.tsx +91 -0
  76. package/src/charts/primitives/CartesianChart.tsx +79 -122
  77. package/src/charts/primitives/ChartCard.tsx +5 -27
  78. package/src/charts/primitives/ChartFrame.tsx +37 -76
  79. package/src/charts/primitives/ChartLegend.tsx +7 -154
  80. package/src/charts/primitives/ChartTooltip.tsx +16 -0
  81. package/src/charts/primitives/HoverOverlay.tsx +52 -21
  82. package/src/charts/primitives/chart-card-context.ts +25 -16
  83. package/src/charts/primitives/chart-frame-layout.ts +44 -56
  84. package/src/charts/primitives/legend-disclosure.tsx +177 -0
  85. package/src/controls/controls.module.css +5 -0
  86. package/src/controls/sync-button.module.css +1 -0
  87. package/src/dashboard/stat-group.module.css +19 -64
  88. package/src/dashboard/widget-grid.module.css +11 -38
  89. package/src/data/data-table-state.ts +218 -0
  90. package/src/data/data-table.tsx +134 -218
  91. package/src/forms/form-layout.module.css +6 -13
  92. package/src/forms/form-layout.tsx +6 -1
  93. package/src/guard/index.ts +38 -34
  94. package/src/notifications/center.tsx +3 -0
  95. package/src/provider/index.tsx +18 -17
  96. package/src/shell/aside-docking.ts +54 -0
  97. package/src/shell/index.tsx +15 -37
  98. package/src/shell/page-bar.tsx +24 -0
  99. package/src/shell/use-breakpoint.ts +2 -2
  100. package/src/theme/floating.module.css +1 -0
  101. package/src/theme-lab/derive-controls.tsx +2 -0
  102. package/src/tokens/index.ts +5 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,37 @@
1
+ ## [1.32.1](https://github.com/jkrumm/basalt-ui/compare/v1.32.0...v1.32.1) (2026-09-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * align guard lanes on FormRow/FormGroup, drop basalt-main ([17e673d](https://github.com/jkrumm/basalt-ui/commit/17e673d421c3e2f93be01c2b8d24e10e56d5b71c))
7
+ * bottom-align FormGroup's row direction so a trailing button isn't stretched ([e5ca2ab](https://github.com/jkrumm/basalt-ui/commit/e5ca2ab5e433e4e3ef15f505913c672b4912f0e9))
8
+ * export the size- and container-class tables from basalt-ui/tokens ([14fb784](https://github.com/jkrumm/basalt-ui/commit/14fb784586c3348a3647280e74ea905b2df0091b))
9
+ * point every § Unreleased reference at the section the release names ([bc17710](https://github.com/jkrumm/basalt-ui/commit/bc1771001048f2883607d6917e6f94cc397865be))
10
+ * resolve consts before unwrapping raw-breakpoint conditionals ([4f9cc79](https://github.com/jkrumm/basalt-ui/commit/4f9cc7912ae0805eb5e574f8ed8a8f9c068e0ddf))
11
+
12
+ # [1.32.0](https://github.com/jkrumm/basalt-ui/compare/v1.31.0...v1.32.0) (2026-09-30)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * build BasaltDataTable cards only in card mode, guard the routed-prop lookup ([4024e83](https://github.com/jkrumm/basalt-ui/commit/4024e8333c83cb4808507929f47246209e67a00a))
18
+ * exempt a container Grid only when its breakpoints map resolves ([d6d9bda](https://github.com/jkrumm/basalt-ui/commit/d6d9bdaa7eb693a01166d121fdc5bb95002bb09d))
19
+ * honour maxHeight in BasaltDataTable's card projection ([3b59a2f](https://github.com/jkrumm/basalt-ui/commit/3b59a2f5304969e0dcadfa77b1ff375fd0f1d048))
20
+ * key StatGroup and WidgetGrid on the container-class law ([db6e130](https://github.com/jkrumm/basalt-ui/commit/db6e130d050d562926fce63e7f1a5e3d0aeb45f8))
21
+ * move raw-media-query's promotion to 1.33.0 with its AST twin ([ac2641c](https://github.com/jkrumm/basalt-ui/commit/ac2641c4ac1fcb42b2381758ae9a2b20809ce630))
22
+ * move useBreakpoint's removal to 1.33.0 and correct its replacement ([a2c356b](https://github.com/jkrumm/basalt-ui/commit/a2c356bd1979960b8b9fc26eb8341cc84d3e88ee))
23
+ * narrow the container-grid exemption to the props Mantine routes ([d547022](https://github.com/jkrumm/basalt-ui/commit/d5470226008984fba39849062c0ee0abbac178cc))
24
+ * prune ignored dirs during the doc-drift repo walk instead of after it ([a4647dc](https://github.com/jkrumm/basalt-ui/commit/a4647dcd380f74f7ac73989fa6857d2041d16a23))
25
+ * read only width clauses when judging a shell/ media query ([3f14b7f](https://github.com/jkrumm/basalt-ui/commit/3f14b7f342c64c9f2b7b1b04887930f37f695f01))
26
+ * say legend.maxRows changed unit in its deprecation warning ([29ad3a5](https://github.com/jkrumm/basalt-ui/commit/29ad3a5183648a0edc71250b22a996fa818edfb8))
27
+ * stop warning legend-slot contention on a same-commit owner hand-off ([af18cfa](https://github.com/jkrumm/basalt-ui/commit/af18cfad7c3d9747a7cc6c0a117db34bbb8cd531))
28
+
29
+
30
+ ### Features
31
+
32
+ * give basalt/raw-breakpoint a reachable container-grid answer ([2bf4a22](https://github.com/jkrumm/basalt-ui/commit/2bf4a2256fff9b08adcd6c3716b92993b4fdb40a))
33
+ * project BasaltDataTable rows as cards on the table's own width ([f201b2a](https://github.com/jkrumm/basalt-ui/commit/f201b2ac81726fc47b2adb5660fd42ce2c881b3e))
34
+
1
35
  # [1.31.0](https://github.com/jkrumm/basalt-ui/compare/v1.30.2...v1.31.0) (2026-09-28)
2
36
 
3
37
 
package/MIGRATING.md CHANGED
@@ -66,18 +66,18 @@ There is one row per SURFACE, not per export, and a surface earns a row once any
66
66
  shipped lefthook preset both changed in 1.30.0 with no row that owned them — which is how a
67
67
  consumer read this index, found nothing, and diffed a KPI row that had silently gone 4-up → 2-up.
68
68
 
69
- | Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | 1.30.2 | 1.31.0 | Unreleased |
70
- | ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------- | ---------- |
71
- | **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — | `--vx-hit` replaces the touch vars; `SIZE_CLASSES` (§ Unreleased) | — |
72
- | **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — | container-class tier + `height` keys (§ Unreleased) | — |
73
- | **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — | `theme.breakpoints` derived, `useSizeClass`, `data-basalt-host`, `useBreakpoint` on notice (§ Unreleased) | — |
74
- | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — |
75
- | **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | — | — |
76
- | **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | — | — |
77
- | **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — | — | — |
78
- | **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — | — | — |
79
- | **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — | — | — |
80
- | **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — | — | — |
69
+ | Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | 1.30.2 | 1.31.0 | 1.32.0 | 1.32.1 | Unreleased |
70
+ | ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------- |
71
+ | **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — | `--vx-hit` replaces the touch vars; `SIZE_CLASSES` (§ 1.31.0) | — | `SIZE_CLASSES`/`CONTAINER_CLASSES` actually exported (§ Fixes) | — |
72
+ | **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — | container-class tier + `height` keys (§ 1.31.0) | — | `ResponsiveChartHeight` `sm` → key (§ Container recipes) | — |
73
+ | **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — | `theme.breakpoints` derived, `useSizeClass`, `data-basalt-host`, `useBreakpoint` on notice (§ 1.31.0) | `useBreakpoint` removal → 1.33.0; `useSizeClass` is shell-only (§ 1.32.0) | shell-less `shell/` gutter, `AppShell` breakpoints, `useSizeClass` for overlays (§§ Shell-less, `useSizeClass`) | — |
74
+ | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — | — |
75
+ | **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | — | — | `FormGroup` row no longer stretches (§ Fixes) | — |
76
+ | **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | — | — | `renderCard` documented (§ `renderCard`) | — |
77
+ | **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — | — | — | — | — |
78
+ | **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — | — | — | — | — |
79
+ | **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — | — | — | — | — |
80
+ | **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — | — | `raw-breakpoint`: the container-grid answer, `error` at 1.33.0 (§ 1.32.0) | `FormRow`/`FormGroup` homes in both lanes; `basalt-main` dropped (§ Fixes) | — |
81
81
 
82
82
  ---
83
83
 
@@ -86,6 +86,196 @@ consumer read this index, found nothing, and diffed a KPI row that had silently
86
86
  _Nothing yet. Author the next section under this heading as `## Unreleased — <title>`; the
87
87
  release renames it and opens a new one here._
88
88
 
89
+ ## 1.32.1 — the 1.32.0 recipe resolves, and the gaps consumers hit upgrading to it
90
+
91
+ A patch: no new API beyond restoring what 1.31.0/1.32.0 already documented. Eight consumers
92
+ upgraded to 1.32.0; this section is what they had to find out by reading source.
93
+
94
+ ### Fixes you can now delete a workaround for
95
+
96
+ | What | Delete |
97
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
98
+ | **`SIZE_CLASSES`, `CONTAINER_CLASSES`, `resolveContainerClass`, `ContainerClass` are exported from `basalt-ui/tokens`.** The 1.31.0/1.32.0 sections, the `raw-breakpoint` message and the `BasaltDataTable` JSDoc all named that import; it did not resolve. A test now fails the build when a fenced import in this file names a symbol its subpath does not export. | the local `container-classes.ts` mirror or the literal `'480px'`/`800` — import them |
99
+ | **`FormRow` / `FormGroup` are control homes in `check-theme` too.** The oxlint plugin already treated them as homes; the text lane's `raw-selection-control` did not, so a correctly migrated form row warned (and would have errored at 1.33.0). A parity test now pins both lanes' home lists to one set. | every `theme-allow raw-selection-control — … FormRow/FormGroup …` waiver (grep `WORKAROUND: homed in a FormRow`) |
100
+ | **`basalt-main` is no longer an accepted `@container` name.** Nothing ever declared it, so a query against it never matched. Declare your own container (`container: <name> / inline-size` on the element) or use `basalt-grid` on the parent. | `@container basalt-main …` — rewrite against a container you declare |
101
+ | **The `raw-media-query` message no longer over-claims.** Only `basalt-card` and unnamed `@container` queries are judged against the 0/240/480/800 table; any other declared name is accepted at any width. Same behaviour, honest text. | — |
102
+ | **`raw-breakpoint` exempts a `tests?/` directory** (e.g. a `tests/setup/dom.ts` `matchMedia` polyfill), not only `*.test.*`/`__tests__`. The message now says `cols={{ base: 3 }}` is just `cols={3}`, and a `(hover: none)` read gets a pointer-tier hint. | the polyfill's `theme-allow raw-breakpoint` |
103
+ | **`FormGroup direction="row"` no longer stretches a trailing button** (`align-items: flex-end`). | a `style={{ alignSelf: … }}` on the button |
104
+
105
+ **This patch also TIGHTENS two checks — expect NEW warnings after upgrading, still `warn` until
106
+ 1.33.0:** `raw-breakpoint` now sees a responsive object inside a conditional, logical or
107
+ const-resolved prop value (`span={wide ? { base: 12, md: 4 } : 12}` used to slip through), and
108
+ `raw-media-query` rejects `@container basalt-main`. (The `FormRow`/`FormGroup` change only removes
109
+ warnings.)
110
+
111
+ **After any guard narrows, audit your waivers:** `basalt-ui check-theme --audit-allows` lists every
112
+ `theme-allow` and whether it still suppresses anything. Dead ones (rb carried five
113
+ `in-body-page-title`, linewatch one `bound-control-outside-home`) go.
114
+
115
+ **Scope — the breakpoint law is Mantine/CSS-scoped.** `raw-breakpoint` is an oxlint rule over JSX
116
+ and `raw-media-query` a `check-theme` kind over `.css`. A tokens-only (Tailwind) consumer has only
117
+ the `check-theme` lane; nothing here asks it to adopt `type="container"` grids.
118
+
119
+ ### Container recipes 1.32.0 left implicit
120
+
121
+ **Swap two children at a container width in ONE query** — narrow default, flip both sides inside
122
+ one `min-width` rule. A `max-width: 799.9px` / `min-width: 800px` pair leaves a sub-pixel band where
123
+ both or neither render.
124
+
125
+ ```css
126
+ .root {
127
+ container: basalt-grid / inline-size;
128
+ }
129
+ .wide {
130
+ display: none;
131
+ }
132
+ @container basalt-grid (min-width: 800px) {
133
+ .narrow {
134
+ display: none;
135
+ }
136
+ .wide {
137
+ display: block;
138
+ }
139
+ }
140
+ ```
141
+
142
+ **A plain Mantine `Table` (not `BasaltDataTable`)** has no column fold or card projection; the
143
+ sanctioned remedy is the same CSS module — declare the container on the wrapper and hide columns or
144
+ swap to a list inside a `min-width` query. `BasaltDataTable` needs the optional
145
+ `@tanstack/react-table` peer; don't add it only to get the fold.
146
+
147
+ **Layouts that need a step above `wide` (800px).** There is no fifth class; one ships when two named
148
+ consumers need the same width. Until then, verified against the 1.32.0 plugin:
149
+
150
+ | Where | Off-class width (e.g. `1200px`) |
151
+ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
152
+ | `SimpleGrid type="container"` px key — `cols={{ base: 1, '1200px': 5 }}` | passes: `raw-breakpoint` does not judge px keys, only theme names (`sm`) |
153
+ | `Grid type="container"` `breakpoints` map value — `xl: '1200px'` | passes: the map's values are not judged |
154
+ | `@container basalt-card` or unnamed, in CSS | flagged — `theme-allow raw-media-query — <why this layout needs 1200px>` |
155
+ | `@container <your-own-name>` or `basalt-grid`, in CSS | passes today; a later minor may judge `basalt-grid` — prefer a reasoned `theme-allow` over relying on the unjudged name |
156
+
157
+ Either way, write the reason next to the width: it is the evidence the fifth class is waiting on.
158
+
159
+ **`ResponsiveChartHeight` `sm` (768px) → which key.** `wide` (800) is 32px later and keeps the old
160
+ step's meaning ("tablet or wider"); `regular` (480) opens 288px earlier. Pick by which side of the old
161
+ width the height was for.
162
+
163
+ ### `BasaltDataTable` `renderCard` (1.32.0, undocumented here until now)
164
+
165
+ The law lives in the prop's JSDoc (`src/data/data-table.tsx`, shipped in `dist/data/table.d.ts`).
166
+ The points consumers tripped on:
167
+
168
+ - Swaps at the table's OWN width `< CONTAINER_CLASSES.regular` (480px), not the viewport.
169
+ - Card order is the table's processed order — sort, filter, page. For a domain order, pass
170
+ `initialSorting` or sort `data`; there is no second sort.
171
+ - `minWidth` disables the COLUMN fold (it declares a horizontal floor) and is ignored by the card
172
+ list. `stickyHeader` alone keeps the fold; pair it with `maxHeight` to stick at every width.
173
+ - Activation is click or **Enter** — not Space (the browser's page scroll on a focused non-button).
174
+
175
+ ### Shell-less apps and the 1.31.0 breakpoint move
176
+
177
+ 1.31.0 moved `sm` from 48em to **52.5em** (840px). What its checklist missed:
178
+
179
+ - **Re-key your own literals:** grep `.css` for `47.99375em` / `48em` → `52.49375em` / `52.5em`.
180
+ - **A shell-less app's gutter / sticky-bar CSS belongs under your own `shell/` directory** at
181
+ 52.5em: any `shell/`-pathed CSS may write a width `@media` at a `SIZE_CLASSES` boundary; anywhere
182
+ else it is `raw-media-query`.
183
+ - **A consumer-owned Mantine `AppShell`** resolves `navbar.breakpoint` / `aside.breakpoint` through
184
+ `theme.breakpoints`, so `breakpoint: 'sm'` collapses at 840px now, not 768px.
185
+ - **In the `medium` class the sidebar defaults to the rail**, and the rail hides a `kind: 'custom'`
186
+ block entirely (a list block keeps a dot only with both `icon` and `count`). Anything a
187
+ `medium`-width user needs must not live only in a custom block.
188
+ - **`PageBar` without a shell:** see the shell-less example in its JSDoc — `title` leads the one
189
+ sticky bar, and `classNames.root` is where the gutter bleed goes.
190
+
191
+ ### `useSizeClass()` outside the shell — two sanctioned uses
192
+
193
+ Layout stays a container decision; an overlay's form (`Drawer` side, sheet vs dialog) and content
194
+ paired with a `ViewTabs` option `only: 'sm-down'` may read it. Law and wording:
195
+ `agent/rules/basalt-mantine.md` (a shipped rule, placed by `basalt-ui sync`).
196
+
197
+ ## 1.32.0 — `basalt/raw-breakpoint` gets its answer
198
+
199
+ ### The C1 pair (`control-outside-home` / `raw-selection-control`) — `error` at 1.33.0, the last extension
200
+
201
+ Re-measured 2026-09-30: basalt's own tree 0, the fleet ~33 (image-gen 11, rb 7, image-share 6,
202
+ weatherorb 5, email-gateway 4, argo 2). Clear them on 1.32.0 while they still warn — the remedies
203
+ are unchanged (§ 1.31.0 below: a `PageBar` home, `basalt-ui/forms` rows, bound `FieldHandle`s, or a
204
+ reasoned `theme-allow`). 1.33.0 promotes all four ids — this pair plus `raw-breakpoint` and
205
+ `raw-media-query` — together.
206
+
207
+ ### `basalt/raw-breakpoint`
208
+
209
+ 1.31.0 shipped this rule (`warn`) with a message that said "reach for a container query" and named
210
+ no primitive, and this file never mentioned it. It flags four shapes; each has a concrete target
211
+ now. **Severity: `warn`, promotes to `error` at 1.33.0** (moved from 1.32.0 — this is the first
212
+ minor with a documented answer). A `theme-allow raw-breakpoint — <reason>` waives one occurrence.
213
+
214
+ **Its CSS twin, the `raw-media-query` guard kind** (`basalt-ui check-theme`), enforces the same law
215
+ in `.css`: a width `@media` outside `shell/**`, an `@container` name other than `basalt-card` /
216
+ `basalt-form` / `basalt-grid` (`basalt-main` was listed here too; dropped in 1.32.1, nothing declared it) or at a width off a `CONTAINER_CLASSES` boundary, and
217
+ a pointer query other than `coarse`/`fine`. Same `warn`, and it moved with the rule — **`error` at
218
+ 1.33.0** (was 1.32.0). `theme-allow raw-media-query — <reason>` waives one line.
219
+
220
+ **The law.** `useSizeClass()` is for **shell chrome**. A page swapping its OWN layout — a column
221
+ count, a table for a card list, a row for a stack — is a **container** decision, keyed on
222
+ `CONTAINER_CLASSES` (`basalt-ui/tokens`: compact 240 · regular 480 · wide 800), not the viewport.
223
+ The primitive is Mantine 9's own `type="container"` grid. Verified against `@mantine/core` 9.3
224
+ source, which is what the rule's exemption is built on:
225
+
226
+ - `SimpleGrid type="container"` wraps itself in a `container: simple-grid / inline-size` div (no
227
+ ancestor needed) and writes **each key verbatim** into `@container simple-grid (min-width: <key>)`.
228
+ Keys are CSS lengths — `'480px'`, not `480` (unitless is invalid) and not `sm` (never resolved
229
+ through `theme.breakpoints`; emits a dead query). The rule flags `sm`-style keys there.
230
+ - `Grid type="container"` only goes container **when it also gets a `breakpoints` map**; without
231
+ one it silently renders viewport media queries (flagged). The map is typed
232
+ `Record<MantineSize, string>` — all five `xs`–`xl`, no custom names — and `Grid.Col` keys resolve
233
+ through it, so `sm` there means whatever width the map gives it (exempt).
234
+
235
+ | Shape (what it flags) | Replace with |
236
+ | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
237
+ | responsive object on a Mantine prop — `SimpleGrid cols={{ base, sm }}` | `SimpleGrid type="container"` with px keys (below) |
238
+ | `Grid.Col span={{ base, lg }}` | `Grid type="container"` + a `breakpoints` map onto container widths; spans keyed through it |
239
+ | `Flex direction={{ base: 'column', sm: 'row' }}` | the container `SimpleGrid` (equal columns); a plain `Group` (wraps by default) for intrinsic-width items; or a CSS module: `container: basalt-grid / inline-size` on the parent, `@container basalt-grid (min-width: 480px)` on the child |
240
+ | `visibleFrom`/`hiddenFrom` outside a shell home or a control's C9 swap | the same container rule, `display` toggled in the CSS module — or drop the swap |
241
+ | `useMediaQuery`/`useMatches`/`useViewportSize` (`@mantine/*`), `window.matchMedia`/`innerWidth` | shell chrome: `useSizeClass()`; a page's layout: one of the above |
242
+
243
+ ```tsx
244
+ // before — viewport breakpoints
245
+ <SimpleGrid cols={{ base: 1, sm: 3 }}>…</SimpleGrid>
246
+ <Grid><Grid.Col span={{ base: 12, lg: 4 }}>…</Grid.Col></Grid>
247
+
248
+ // after — the grid's own width
249
+ import { CONTAINER_CLASSES as C } from 'basalt-ui/tokens'
250
+ <SimpleGrid type="container" cols={{ base: 1, [`${C.regular}px`]: 3 }}>…</SimpleGrid>
251
+ const px = (n: number) => `${n}px`
252
+ const containerBreakpoints = { xs: px(C.compact), sm: px(C.regular), md: px(C.wide), lg: px(C.wide), xl: px(C.wide) }
253
+ <Grid type="container" breakpoints={containerBreakpoints}>
254
+ <Grid.Col span={{ base: 12, md: 4 }}>…</Grid.Col>
255
+ </Grid>
256
+ ```
257
+
258
+ Pick the container key by the grid's own width, not by the viewport key it replaces: a grid inside a
259
+ page with an aside or a sidebar is narrower than the viewport, which is the point.
260
+
261
+ ### `StatGroup` / `WidgetGrid` — `sm` opens at 800px, container name `basalt-grid`
262
+
263
+ Both grids now key on the container-class law basalt ships: the `sm` column count opens at the
264
+ `wide` class (**800px** of the grid's own width, was 768); the full `cols` count still opens at
265
+ 1200px. A grid 768–799px wide now renders its base count (StatGroup 2, WidgetGrid 1). The
266
+ container they declare is renamed `basalt-stat-group`/`basalt-widget-grid` → **`basalt-grid`** — only
267
+ a consumer rule written against the old names needs editing. The `@supports not (container-type)`
268
+ viewport fallbacks (these two and `FormRow`'s) are gone: every browser that parses basalt's
269
+ `color-mix()` also parses `@container`.
270
+
271
+ ### `useBreakpoint` — removal moved to 1.33.0, and the 1.31.0 replacement line was half wrong
272
+
273
+ `useBreakpoint('sm')` → `useSizeClass() !== 'compact'` holds for **shell chrome only**. A page
274
+ swapping its own layout on it (argo's table ↔ card list) is a container decision — see above. Note
275
+ that `useSizeClass()` answers `'compact'` on the first frame (the server snapshot,
276
+ `<BasaltProvider sizeClassHint>`) where `useBreakpoint` returned `undefined`, so an
277
+ `isLg === true` guard written for that `undefined` can go.
278
+
89
279
  ## 1.31.0 — size classes and the pointer tier
90
280
 
91
281
  `theme.breakpoints` is now derived from `SIZE_CLASSES` (compact < 840, medium 840–1199, expanded
@@ -93,7 +283,9 @@ release renames it and opens a new one here._
93
283
  it out. Mapping: `xs` = `sm` = 52.5em (medium opens), `md` = `lg` = `xl` = 75em (expanded opens) — the
94
284
  keys collapse. So `xs` moves 36em → 52.5em, `sm` 48em → 52.5em (an iPad portrait is now bottom-bar chrome), `md`
95
285
  62em → 75em, `lg` stays 75em and `xl` 88em → 75em. Check every `visibleFrom`/`hiddenFrom`/`useBreakpoint`
96
- on those keys: `xs` no longer differs from `sm`, and `md`/`lg`/`xl` now switch at the same width.
286
+ on those keys: `xs` no longer differs from `sm`, and `md`/`lg`/`xl` now switch at the same width. Outside a
287
+ shell home those props are `basalt/raw-breakpoint` findings — move them to a container grid rather
288
+ than re-keying them (see that section above).
97
289
 
98
290
  | Removed | Replacement |
99
291
  | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -149,10 +341,10 @@ everywhere).
149
341
  `storageKey`. Opt out with a controlled `collapsed` prop; a persisted `false` (the user opened it
150
342
  once) pins it open.
151
343
 
152
- **On notice — `useBreakpoint` (removed next minor).** `@deprecated`, still works. Replace
153
- `useBreakpoint('sm')` with `useSizeClass() !== 'compact'`, and `useBreakpoint('lg')` with
154
- `useSizeClass() === 'expanded'`. A `max`-edge or non-shell width read is a container concern: use
155
- `@container` in CSS.
344
+ **On notice — `useBreakpoint` (removal moved to 1.33.0).** `@deprecated`, still works. For shell
345
+ chrome, replace `useBreakpoint('sm')` with `useSizeClass() !== 'compact'` and `useBreakpoint('lg')`
346
+ with `useSizeClass() === 'expanded'`. A page's own layout swap is a container decision, not a
347
+ `useSizeClass()` one — corrected after release; see the `basalt/raw-breakpoint` section above.
156
348
 
157
349
  **Card chrome keys on the card's own width, not the viewport.** `ChartCard`, `StatCard` and `Section`
158
350
  are now `container: basalt-card / inline-size` (`container-type: inline-size` — layout and inline-size
@@ -322,7 +514,7 @@ exactly the case it was restored for. Measured: a 7-entry legend in a 92–150px
322
514
  entries at `maxRows` 3, 6 **and** 99, with five series drawn in colours nothing named.
323
515
 
324
516
  The fit now resolves an ABSENT cap only. When a caller states a number on a `fill` band, the legend
325
- takes the ENTRIES it asked for (`maxRows` counts entries, not rows — see § Unreleased) and
517
+ takes the ENTRIES it asked for (`maxRows` counts entries, not rows — see § 1.30.1) and
326
518
  **`VX.minPlotHeight` stops being a floor** — the plot yields the height instead. Both things that used to trim the number are framework DEFAULTS (the tier's cap,
327
519
  and a floor about the plot); the stated number is the only intent in the room.
328
520
 
@@ -487,7 +679,7 @@ children is invisible to every rule. Grep for `SimpleGrid` before concluding the
487
679
  ### Charts — a responsive `height`, and the legend escape 1.30.0 promised
488
680
 
489
681
  Both of these landed in 1.30.0 with no row here, and both were only partly reachable. **Read this
490
- section against 1.30.1, not 1.30.0** — the § Unreleased entries above are what makes them usable.
682
+ section against 1.30.1, not 1.30.0** — the § 1.30.1 entries above are what makes them usable.
491
683
 
492
684
  - **`height` accepts `{ base, sm?, md?, lg? }`** as well as a plain number, resolved against the
493
685
  chart's MEASURED container width (the same width the tier reads), not the viewport. In 1.30.0 the
@@ -499,7 +691,7 @@ section against 1.30.1, not 1.30.0** — the § Unreleased entries above are wha
499
691
  measured box, so a 380px inspector panel on a 1440px desktop IS "a phone" and its two-row default
500
692
  rolled a six-series legend up to `+4 more` with no way to say no. `margin` and `xLabelRotate`
501
693
  always had that escape; the legend did not. Under `fill` a measured cap still applies on top — see
502
- § Unreleased for what that actually costs, because "can still roll up FURTHER" understated it.
694
+ § 1.30.1 for what that actually costs, because "can still roll up FURTHER" understated it.
503
695
 
504
696
  ### Toolchain — the shipped lefthook preset's `check-theme`, and what it does NOT fix
505
697
 
@@ -48,8 +48,7 @@ single mount (`basalt/hand-rolled-shell` fires on `AppShell.*` parts or a `Burge
48
48
  does not render it). The sub-components are exported for a genuinely divergent layout; prefer the
49
49
  shell, which already wires collapse persistence, the mobile breakpoint and the breadcrumb.
50
50
 
51
- Every extension point is **declared data, not a `ReactNode` slot**, so basalt owns the mobile
52
- projection:
51
+ Extension points are **declared data, not `ReactNode` slots**, so basalt owns the mobile projection:
53
52
 
54
53
  | Prop | Shape | What basalt then owns |
55
54
  | ------------------- | ------------------------------------ | -------------------------------------------------------------------------------------- |
@@ -98,6 +97,8 @@ Depth-tier law (which token for which surface, why static, why the ring is inset
98
97
  clears the iOS zoom threshold via an `!important` floor (`sub-16-input-font` flags a smaller one).
99
98
  - **Charts measure themselves** — pass `height`/`aspectRatio`/`fill` only; never `useElementSize`
100
99
  in a chart file (Mantine is banned there), never raw `@visx/responsive` outside `charts/**`.
100
+ - **A page's layout keys on its container, not the viewport** (`type="container"` grids, `CONTAINER_CLASSES`).
101
+ `useSizeClass()`: shell chrome, overlay form, `ViewTabs only: 'sm-down'` partners. Recipes: `MIGRATING.md`.
101
102
 
102
103
  ## Scroll regions
103
104
 
@@ -105,8 +106,7 @@ Depth-tier law (which token for which surface, why static, why the ring is inset
105
106
  it draws its own bar inside its root, so the bar floats instead of reflowing the column
106
107
  (`AppSidebar`'s nav is the reference: `type="hover"`, `scrollbars="y"`). Raw `overflow: auto` stays
107
108
  correct only where a library owns the node (`BasaltStickToBottom`, `BasaltVirtualList`) and for a
108
- table body (`Table.ScrollContainer type="native"` — a `ScrollArea` viewport would break a sticky
109
- `<thead>`'s positioning context).
109
+ table body (`Table.ScrollContainer type="native"` — a `ScrollArea` viewport breaks a sticky `<thead>`).
110
110
 
111
111
  ## Interaction feedback and motion
112
112