basalt-ui 1.31.0 → 1.32.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/MIGRATING.md +101 -17
  3. package/agent/rules/basalt-mantine.md +4 -4
  4. package/configs/oxlint-plugin.js +159 -42
  5. package/configs/oxlint-plugin.test.ts +182 -0
  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 +33 -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.module.css +3 -13
  48. package/dist/guard/index.d.ts.map +1 -1
  49. package/dist/guard/index.js +6 -5
  50. package/dist/notifications/center.d.ts.map +1 -1
  51. package/dist/provider/index.d.ts.map +1 -1
  52. package/dist/provider/index.js +12 -10
  53. package/dist/shell/aside-docking.d.ts +31 -0
  54. package/dist/shell/aside-docking.d.ts.map +1 -0
  55. package/dist/shell/aside-docking.js +19 -0
  56. package/dist/shell/index.d.ts +0 -11
  57. package/dist/shell/index.d.ts.map +1 -1
  58. package/dist/shell/index.js +13 -14
  59. package/dist/shell/use-breakpoint.d.ts +1 -1
  60. package/dist/theme/floating.module.css +1 -0
  61. package/dist/theme-lab/derive-controls.d.ts.map +1 -1
  62. package/llms.txt +2 -2
  63. package/package.json +2 -1
  64. package/src/charts/kinds/BandStrip.tsx +18 -64
  65. package/src/charts/kinds/DualPanel.tsx +4 -24
  66. package/src/charts/kinds/MirroredBars.tsx +12 -66
  67. package/src/charts/kinds/band-chrome.tsx +91 -0
  68. package/src/charts/primitives/CartesianChart.tsx +79 -122
  69. package/src/charts/primitives/ChartCard.tsx +5 -27
  70. package/src/charts/primitives/ChartFrame.tsx +37 -76
  71. package/src/charts/primitives/ChartLegend.tsx +7 -154
  72. package/src/charts/primitives/ChartTooltip.tsx +16 -0
  73. package/src/charts/primitives/HoverOverlay.tsx +52 -21
  74. package/src/charts/primitives/chart-card-context.ts +25 -16
  75. package/src/charts/primitives/chart-frame-layout.ts +44 -56
  76. package/src/charts/primitives/legend-disclosure.tsx +177 -0
  77. package/src/controls/controls.module.css +5 -0
  78. package/src/controls/sync-button.module.css +1 -0
  79. package/src/dashboard/stat-group.module.css +19 -64
  80. package/src/dashboard/widget-grid.module.css +11 -38
  81. package/src/data/data-table-state.ts +218 -0
  82. package/src/data/data-table.tsx +127 -218
  83. package/src/forms/form-layout.module.css +3 -13
  84. package/src/guard/index.ts +21 -16
  85. package/src/notifications/center.tsx +3 -0
  86. package/src/provider/index.tsx +18 -17
  87. package/src/shell/aside-docking.ts +54 -0
  88. package/src/shell/index.tsx +15 -37
  89. package/src/shell/use-breakpoint.ts +2 -2
  90. package/src/theme/floating.module.css +1 -0
  91. package/src/theme-lab/derive-controls.tsx +2 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ # [1.32.0](https://github.com/jkrumm/basalt-ui/compare/v1.31.0...v1.32.0) (2026-09-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * build BasaltDataTable cards only in card mode, guard the routed-prop lookup ([4024e83](https://github.com/jkrumm/basalt-ui/commit/4024e8333c83cb4808507929f47246209e67a00a))
7
+ * exempt a container Grid only when its breakpoints map resolves ([d6d9bda](https://github.com/jkrumm/basalt-ui/commit/d6d9bdaa7eb693a01166d121fdc5bb95002bb09d))
8
+ * honour maxHeight in BasaltDataTable's card projection ([3b59a2f](https://github.com/jkrumm/basalt-ui/commit/3b59a2f5304969e0dcadfa77b1ff375fd0f1d048))
9
+ * key StatGroup and WidgetGrid on the container-class law ([db6e130](https://github.com/jkrumm/basalt-ui/commit/db6e130d050d562926fce63e7f1a5e3d0aeb45f8))
10
+ * move raw-media-query's promotion to 1.33.0 with its AST twin ([ac2641c](https://github.com/jkrumm/basalt-ui/commit/ac2641c4ac1fcb42b2381758ae9a2b20809ce630))
11
+ * move useBreakpoint's removal to 1.33.0 and correct its replacement ([a2c356b](https://github.com/jkrumm/basalt-ui/commit/a2c356bd1979960b8b9fc26eb8341cc84d3e88ee))
12
+ * narrow the container-grid exemption to the props Mantine routes ([d547022](https://github.com/jkrumm/basalt-ui/commit/d5470226008984fba39849062c0ee0abbac178cc))
13
+ * prune ignored dirs during the doc-drift repo walk instead of after it ([a4647dc](https://github.com/jkrumm/basalt-ui/commit/a4647dcd380f74f7ac73989fa6857d2041d16a23))
14
+ * read only width clauses when judging a shell/ media query ([3f14b7f](https://github.com/jkrumm/basalt-ui/commit/3f14b7f342c64c9f2b7b1b04887930f37f695f01))
15
+ * say legend.maxRows changed unit in its deprecation warning ([29ad3a5](https://github.com/jkrumm/basalt-ui/commit/29ad3a5183648a0edc71250b22a996fa818edfb8))
16
+ * stop warning legend-slot contention on a same-commit owner hand-off ([af18cfa](https://github.com/jkrumm/basalt-ui/commit/af18cfad7c3d9747a7cc6c0a117db34bbb8cd531))
17
+
18
+
19
+ ### Features
20
+
21
+ * give basalt/raw-breakpoint a reachable container-grid answer ([2bf4a22](https://github.com/jkrumm/basalt-ui/commit/2bf4a2256fff9b08adcd6c3716b92993b4fdb40a))
22
+ * project BasaltDataTable rows as cards on the table's own width ([f201b2a](https://github.com/jkrumm/basalt-ui/commit/f201b2ac81726fc47b2adb5660fd42ce2c881b3e))
23
+
1
24
  # [1.31.0](https://github.com/jkrumm/basalt-ui/compare/v1.30.2...v1.31.0) (2026-09-28)
2
25
 
3
26
 
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 | 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) | `useBreakpoint` removal → 1.33.0; `useSizeClass` is shell-only (§ 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) | — | — | — | `raw-breakpoint`: the container-grid answer, `error` at 1.33.0 (§ Unreleased) | — |
81
81
 
82
82
  ---
83
83
 
@@ -86,6 +86,88 @@ 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.0 — `basalt/raw-breakpoint` gets its answer
90
+
91
+ ### The C1 pair (`control-outside-home` / `raw-selection-control`) — `error` at 1.33.0, the last extension
92
+
93
+ Re-measured 2026-09-30: basalt's own tree 0, the fleet ~33 (image-gen 11, rb 7, image-share 6,
94
+ weatherorb 5, email-gateway 4, argo 2). Clear them on 1.32.0 while they still warn — the remedies
95
+ are unchanged (§ 1.31.0 below: a `PageBar` home, `basalt-ui/forms` rows, bound `FieldHandle`s, or a
96
+ reasoned `theme-allow`). 1.33.0 promotes all four ids — this pair plus `raw-breakpoint` and
97
+ `raw-media-query` — together.
98
+
99
+ ### `basalt/raw-breakpoint`
100
+
101
+ 1.31.0 shipped this rule (`warn`) with a message that said "reach for a container query" and named
102
+ no primitive, and this file never mentioned it. It flags four shapes; each has a concrete target
103
+ now. **Severity: `warn`, promotes to `error` at 1.33.0** (moved from 1.32.0 — this is the first
104
+ minor with a documented answer). A `theme-allow raw-breakpoint — <reason>` waives one occurrence.
105
+
106
+ **Its CSS twin, the `raw-media-query` guard kind** (`basalt-ui check-theme`), enforces the same law
107
+ in `.css`: a width `@media` outside `shell/**`, an `@container` name other than `basalt-card` /
108
+ `basalt-form` / `basalt-main` / `basalt-grid` or at a width off a `CONTAINER_CLASSES` boundary, and
109
+ a pointer query other than `coarse`/`fine`. Same `warn`, and it moved with the rule — **`error` at
110
+ 1.33.0** (was 1.32.0). `theme-allow raw-media-query — <reason>` waives one line.
111
+
112
+ **The law.** `useSizeClass()` is for **shell chrome**. A page swapping its OWN layout — a column
113
+ count, a table for a card list, a row for a stack — is a **container** decision, keyed on
114
+ `CONTAINER_CLASSES` (`basalt-ui/tokens`: compact 240 · regular 480 · wide 800), not the viewport.
115
+ The primitive is Mantine 9's own `type="container"` grid. Verified against `@mantine/core` 9.3
116
+ source, which is what the rule's exemption is built on:
117
+
118
+ - `SimpleGrid type="container"` wraps itself in a `container: simple-grid / inline-size` div (no
119
+ ancestor needed) and writes **each key verbatim** into `@container simple-grid (min-width: <key>)`.
120
+ Keys are CSS lengths — `'480px'`, not `480` (unitless is invalid) and not `sm` (never resolved
121
+ through `theme.breakpoints`; emits a dead query). The rule flags `sm`-style keys there.
122
+ - `Grid type="container"` only goes container **when it also gets a `breakpoints` map**; without
123
+ one it silently renders viewport media queries (flagged). The map is typed
124
+ `Record<MantineSize, string>` — all five `xs`–`xl`, no custom names — and `Grid.Col` keys resolve
125
+ through it, so `sm` there means whatever width the map gives it (exempt).
126
+
127
+ | Shape (what it flags) | Replace with |
128
+ | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
129
+ | responsive object on a Mantine prop — `SimpleGrid cols={{ base, sm }}` | `SimpleGrid type="container"` with px keys (below) |
130
+ | `Grid.Col span={{ base, lg }}` | `Grid type="container"` + a `breakpoints` map onto container widths; spans keyed through it |
131
+ | `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 |
132
+ | `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 |
133
+ | `useMediaQuery`/`useMatches`/`useViewportSize` (`@mantine/*`), `window.matchMedia`/`innerWidth` | shell chrome: `useSizeClass()`; a page's layout: one of the above |
134
+
135
+ ```tsx
136
+ // before — viewport breakpoints
137
+ <SimpleGrid cols={{ base: 1, sm: 3 }}>…</SimpleGrid>
138
+ <Grid><Grid.Col span={{ base: 12, lg: 4 }}>…</Grid.Col></Grid>
139
+
140
+ // after — the grid's own width
141
+ import { CONTAINER_CLASSES as C } from 'basalt-ui/tokens'
142
+ <SimpleGrid type="container" cols={{ base: 1, [`${C.regular}px`]: 3 }}>…</SimpleGrid>
143
+ const px = (n: number) => `${n}px`
144
+ const containerBreakpoints = { xs: px(C.compact), sm: px(C.regular), md: px(C.wide), lg: px(C.wide), xl: px(C.wide) }
145
+ <Grid type="container" breakpoints={containerBreakpoints}>
146
+ <Grid.Col span={{ base: 12, md: 4 }}>…</Grid.Col>
147
+ </Grid>
148
+ ```
149
+
150
+ Pick the container key by the grid's own width, not by the viewport key it replaces: a grid inside a
151
+ page with an aside or a sidebar is narrower than the viewport, which is the point.
152
+
153
+ ### `StatGroup` / `WidgetGrid` — `sm` opens at 800px, container name `basalt-grid`
154
+
155
+ Both grids now key on the container-class law basalt ships: the `sm` column count opens at the
156
+ `wide` class (**800px** of the grid's own width, was 768); the full `cols` count still opens at
157
+ 1200px. A grid 768–799px wide now renders its base count (StatGroup 2, WidgetGrid 1). The
158
+ container they declare is renamed `basalt-stat-group`/`basalt-widget-grid` → **`basalt-grid`** — only
159
+ a consumer rule written against the old names needs editing. The `@supports not (container-type)`
160
+ viewport fallbacks (these two and `FormRow`'s) are gone: every browser that parses basalt's
161
+ `color-mix()` also parses `@container`.
162
+
163
+ ### `useBreakpoint` — removal moved to 1.33.0, and the 1.31.0 replacement line was half wrong
164
+
165
+ `useBreakpoint('sm')` → `useSizeClass() !== 'compact'` holds for **shell chrome only**. A page
166
+ swapping its own layout on it (argo's table ↔ card list) is a container decision — see above. Note
167
+ that `useSizeClass()` answers `'compact'` on the first frame (the server snapshot,
168
+ `<BasaltProvider sizeClassHint>`) where `useBreakpoint` returned `undefined`, so an
169
+ `isLg === true` guard written for that `undefined` can go.
170
+
89
171
  ## 1.31.0 — size classes and the pointer tier
90
172
 
91
173
  `theme.breakpoints` is now derived from `SIZE_CLASSES` (compact < 840, medium 840–1199, expanded
@@ -93,7 +175,9 @@ release renames it and opens a new one here._
93
175
  it out. Mapping: `xs` = `sm` = 52.5em (medium opens), `md` = `lg` = `xl` = 75em (expanded opens) — the
94
176
  keys collapse. So `xs` moves 36em → 52.5em, `sm` 48em → 52.5em (an iPad portrait is now bottom-bar chrome), `md`
95
177
  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.
178
+ on those keys: `xs` no longer differs from `sm`, and `md`/`lg`/`xl` now switch at the same width. Outside a
179
+ shell home those props are `basalt/raw-breakpoint` findings — move them to a container grid rather
180
+ than re-keying them (see that section above).
97
181
 
98
182
  | Removed | Replacement |
99
183
  | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -149,10 +233,10 @@ everywhere).
149
233
  `storageKey`. Opt out with a controlled `collapsed` prop; a persisted `false` (the user opened it
150
234
  once) pins it open.
151
235
 
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.
236
+ **On notice — `useBreakpoint` (removal moved to 1.33.0).** `@deprecated`, still works. For shell
237
+ chrome, replace `useBreakpoint('sm')` with `useSizeClass() !== 'compact'` and `useBreakpoint('lg')`
238
+ with `useSizeClass() === 'expanded'`. A page's own layout swap is a container decision, not a
239
+ `useSizeClass()` one — corrected after release; see the `basalt/raw-breakpoint` section above.
156
240
 
157
241
  **Card chrome keys on the card's own width, not the viewport.** `ChartCard`, `StatCard` and `Section`
158
242
  are now `container: basalt-card / inline-size` (`container-type: inline-size` — layout and inline-size
@@ -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 own layout keys on its container, not the viewport** — Mantine's `type="container"`
101
+ grids on `CONTAINER_CLASSES`; `useSizeClass()` is shell chrome only. 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
 
@@ -2708,7 +2708,7 @@ const CONTROL_OUTSIDE_HOME_MESSAGE =
2708
2708
  * `@mantine/form` (a form is the third home and its inputs are not filters), and the owner
2709
2709
  * exemption — a file DEFINING a basalt control cannot be told to use one.
2710
2710
  */
2711
- // Ships: warn (grace → 1.32.0)
2711
+ // Ships: warn (grace → 1.33.0)
2712
2712
  const controlOutsideHome = {
2713
2713
  meta: {
2714
2714
  type: 'suggestion',
@@ -3656,8 +3656,9 @@ export const DEPRECATED_EXPORTS = [
3656
3656
  {
3657
3657
  subpath: 'basalt-ui',
3658
3658
  name: 'useBreakpoint',
3659
- replacement: 'useSizeClass (compact/medium/expanded) or @container for a container concern',
3660
- removeIn: '1.31.0',
3659
+ replacement:
3660
+ 'useSizeClass() for shell chrome; a page’s own layout swap is a container decision — a type="container" SimpleGrid/Grid or an @container rule',
3661
+ removeIn: '1.33.0',
3661
3662
  fix: false,
3662
3663
  },
3663
3664
  ]
@@ -3959,14 +3960,16 @@ const RESPONSIVE_OBJECT_KEYS = new Set(['base', 'xs', 'sm', 'md', 'lg', 'xl'])
3959
3960
  * object that merely happens to have a `sm` key among other, unrelated ones is not this shape, and
3960
3961
  * an empty object is not a breakpoint decision at all.
3961
3962
  */
3962
- function isResponsiveObjectValue(node) {
3963
+ function isResponsiveObjectValue(node, namedOnly = false) {
3963
3964
  if (node === null || node === undefined || node.type !== 'ObjectExpression') return false
3964
3965
  if (node.properties.length === 0) return false
3965
- return node.properties.every((prop) => {
3966
- if (prop.type !== 'Property') return false
3967
- const name = propertyKeyName(prop)
3968
- return typeof name === 'string' && RESPONSIVE_OBJECT_KEYS.has(name)
3969
- })
3966
+ const names = node.properties.map((prop) =>
3967
+ prop.type === 'Property' ? propertyKeyName(prop) : undefined,
3968
+ )
3969
+ if (!names.every((name) => typeof name === 'string' && RESPONSIVE_OBJECT_KEYS.has(name)))
3970
+ return false
3971
+ // Under `type="container"` a lone `base` is the unconditional value, not a breakpoint decision.
3972
+ return !namedOnly || names.some((name) => name !== 'base')
3970
3973
  }
3971
3974
 
3972
3975
  /**
@@ -3977,17 +3980,91 @@ function isResponsiveObjectValue(node) {
3977
3980
  * appears — the top-level attribute value and a nested property value alike. Matching only a nested
3978
3981
  * `ObjectExpression` left the Identifier-in-a-property shape trivially bypassable.
3979
3982
  */
3980
- function containsResponsiveObjectValue(node, consts, seen = new Set()) {
3983
+ function containsResponsiveObjectValue(node, consts, namedOnly = false, seen = new Set()) {
3981
3984
  const resolved =
3982
3985
  node !== null && node !== undefined && node.type === 'Identifier' ? consts.get(node.name) : node
3983
3986
  if (resolved === null || resolved === undefined || resolved.type !== 'ObjectExpression')
3984
3987
  return false
3985
3988
  if (seen.has(resolved)) return false
3986
3989
  seen.add(resolved)
3987
- if (isResponsiveObjectValue(resolved)) return true
3990
+ if (isResponsiveObjectValue(resolved, namedOnly)) return true
3988
3991
  return resolved.properties.some(
3989
- (prop) => prop.type === 'Property' && containsResponsiveObjectValue(prop.value, consts, seen),
3992
+ (prop) =>
3993
+ prop.type === 'Property' &&
3994
+ containsResponsiveObjectValue(prop.value, consts, namedOnly, seen),
3995
+ )
3996
+ }
3997
+
3998
+ /**
3999
+ * The props each Mantine grid routes through `@container` queries under `type="container"` —
4000
+ * confirmed in `@mantine/core` 9.3 source: `SimpleGridContainerVariables` reads `cols`/`spacing`/
4001
+ * `verticalSpacing`, `GridVariables` reads `gap`/`rowGap`/`columnGap`, `GridColVariables` reads
4002
+ * `span`/`offset`/`order`/`align`. Every OTHER responsive prop on the same element (`p`, `mt`,
4003
+ * `mih`, …) is a Box style prop and still compiles to viewport `@media`, so the container exemption
4004
+ * stops at this set. `Grid`'s own `breakpoints` is the container scale itself, not a viewport key.
4005
+ */
4006
+ const CONTAINER_ROUTED_PROPS = {
4007
+ SimpleGrid: new Set(['cols', 'spacing', 'verticalSpacing']),
4008
+ Grid: new Set(['gap', 'rowGap', 'columnGap', 'breakpoints']),
4009
+ 'Grid.Col': new Set(['span', 'offset', 'order', 'align']),
4010
+ }
4011
+
4012
+ /**
4013
+ * How Mantine 9 resolves a responsive object on this element's grid, read from `@mantine/core`
4014
+ * 9.3 source — the sanctioned answer to this rule, so it has to be exempt exactly where it works:
4015
+ *
4016
+ * - `'container'` — a `<Grid type="container" breakpoints={…}>` (or a `Grid.Col` inside one) whose
4017
+ * map statically resolves to a non-empty object. Keys resolve through that map into
4018
+ * `@container mantine-grid` queries, so an `sm` key is whatever width the map gives it: exempt.
4019
+ * Mantine types the map as `Record<MantineSize, string>`, so the recipe's keys ARE `xs`–`xl` —
4020
+ * exempting by key name instead of by owner would flag the sanctioned answer itself.
4021
+ * - `'simple-grid'` — `<SimpleGrid type="container">`. `SimpleGridContainerVariables` writes each
4022
+ * key VERBATIM as `@container simple-grid (min-width: <key>)` and never consults
4023
+ * `theme.breakpoints`: a `'480px'` key works (and is not a responsive-object key, so it never
4024
+ * reaches here), while `sm` emits `(min-width: sm)` — invalid CSS, a dead query. Flagged.
4025
+ * - `'fallback'` — `<Grid type="container">` whose `breakpoints` is absent, `undefined`, `{}` or not
4026
+ * statically resolvable (or a `Grid.Col` in one). `Grid` takes its container branch only on a
4027
+ * truthy map, and an empty one emits no query at all — so none of these is the container law,
4028
+ * and an unreadable map is reported rather than trusted. Flagged.
4029
+ * - `undefined` — not a container grid; the ordinary viewport law applies.
4030
+ *
4031
+ * `type` has to be the string literal `"container"`; a dynamic value is not a decision this rule
4032
+ * can read. `Grid.Col` takes its grid's mode by walking to the nearest enclosing `Grid` element.
4033
+ * `consts` is the rule's same-file name→object map, so `breakpoints={map}` resolves like any value.
4034
+ */
4035
+ function containerGridMode(opening, mantineImports, consts) {
4036
+ let tag = resolveMantineTag(opening.name, mantineImports)
4037
+ let grid = opening
4038
+ if (tag === 'Grid.Col') {
4039
+ grid = undefined
4040
+ let node = opening.parent?.parent
4041
+ for (let depth = 0; node !== null && node !== undefined; depth++, node = node.parent) {
4042
+ if (depth > ANCESTRY_MAX_DEPTH || node.type === 'Program') break
4043
+ if (node.type !== 'JSXElement') continue
4044
+ if (resolveMantineTag(node.openingElement.name, mantineImports) !== 'Grid') continue
4045
+ grid = node.openingElement
4046
+ tag = 'Grid'
4047
+ break
4048
+ }
4049
+ }
4050
+ if (grid === undefined || (tag !== 'Grid' && tag !== 'SimpleGrid')) return undefined
4051
+ const attr = (name) =>
4052
+ grid.attributes.find((a) => a.type === 'JSXAttribute' && a.name?.name === name)
4053
+ const type = attr('type')
4054
+ if (
4055
+ type === undefined ||
4056
+ !isStringLiteral(attrValue(type)) ||
4057
+ attrValue(type).value !== 'container'
3990
4058
  )
4059
+ return undefined
4060
+ if (tag === 'SimpleGrid') return 'simple-grid'
4061
+ const breakpoints = attr('breakpoints')
4062
+ if (breakpoints === undefined) return 'fallback'
4063
+ let map = attrValue(breakpoints)
4064
+ if (map?.type === 'Identifier') map = consts.get(map.name)
4065
+ const nonEmpty =
4066
+ map?.type === 'ObjectExpression' && map.properties.some((prop) => prop.type === 'Property')
4067
+ return nonEmpty ? 'container' : 'fallback'
3991
4068
  }
3992
4069
 
3993
4070
  /**
@@ -4011,27 +4088,44 @@ const SHELL_HOME_NAMES = new Set(['BasaltShell', 'AppSidebar', 'MobileNav', 'App
4011
4088
  */
4012
4089
  const RAW_BREAKPOINT_EXEMPT_FILE = /(?:^|[\\/])(?:use-media-query\.ts|ChartTooltip\.tsx)$/
4013
4090
 
4091
+ /**
4092
+ * The one recipe every raw-breakpoint message points at. A page's own layout keys on its CONTAINER
4093
+ * (`CONTAINER_CLASSES`, `basalt-ui/tokens`: compact 240 / regular 480 / wide 800), never on the
4094
+ * viewport — `useSizeClass()` is the shell's seam, not a page's. Mantine 9's own `type="container"`
4095
+ * grids are the primitive: both self-wrap in an `inline-size` container, so they need no ancestor.
4096
+ * The px literals are `CONTAINER_CLASSES` written out (the plugin cannot import TS source);
4097
+ * `oxlint-plugin.test.ts` asserts every one against it, and the MIGRATING recipe too.
4098
+ */
4099
+ export const RAW_BREAKPOINT_RECIPE =
4100
+ 'A page’s own layout keys on its container: <SimpleGrid type="container" cols={{ base: 1, ' +
4101
+ "'480px': 2, '800px': 3 }}> (px keys = CONTAINER_CLASSES, basalt-ui/tokens), or <Grid " +
4102
+ 'type="container" breakpoints={…}> mapping all five xs–xl onto those px widths, with Grid.Col ' +
4103
+ 'span={{ base: 12, sm: 6 }} keyed through that map; a Flex direction swap becomes that SimpleGrid (equal ' +
4104
+ 'columns), a wrapping Group, or a CSS-module @container basalt-grid rule. useSizeClass() is for ' +
4105
+ 'shell chrome only. Recipes: basalt-ui MIGRATING.md § basalt/raw-breakpoint.'
4106
+
4014
4107
  const RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE =
4015
- 'Responsive-object style prop on a Mantine component — the framework owns the three responsive ' +
4016
- 'axes (size class for shell chrome, container class for a component’s own width, pointer tier), ' +
4017
- 'never a per-component `{ base, sm, … }`. Reach for a container query for this component’s own ' +
4018
- 'width, or useSizeClass() for shell-only chrome. (basalt/raw-breakpoint)'
4108
+ 'Responsive-object prop keyed on theme breakpoints — those are viewport widths. ' +
4109
+ `${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
4110
+
4111
+ const RAW_BREAKPOINT_CONTAINER_KEY_MESSAGE =
4112
+ 'Theme breakpoint name under type="container" — Mantine does not resolve it there: ' +
4113
+ "SimpleGrid uses each key verbatim as a (min-width: …) length, so 'sm' is a dead query, " +
4114
+ 'and Grid only goes container with a breakpoints map (without one it silently falls back to ' +
4115
+ `viewport media queries). ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
4019
4116
 
4020
4117
  const RAW_BREAKPOINT_VISIBLE_HIDDEN_MESSAGE =
4021
- "visibleFrom/hiddenFrom outside a shell home or a control's own C9 swap — this pair is the " +
4022
- "size-class axis's one JSX escape hatch. Anywhere else it is a hand-rolled viewport breakpoint " +
4023
- "the framework cannot see or move. Use a container query for a component's own width instead. " +
4024
- '(basalt/raw-breakpoint)'
4118
+ "visibleFrom/hiddenFrom outside a shell home or a control's own C9 swap — a viewport " +
4119
+ `breakpoint the framework cannot see or move. ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
4025
4120
 
4026
4121
  const RAW_BREAKPOINT_HOOK_MESSAGE =
4027
- "useMediaQuery/useMatches/useViewportSize — a raw viewport read outside the framework's one seam " +
4028
- '(useSizeClass(), shell-only). A component deciding its own layout from a viewport read instead of ' +
4029
- 'a container query is exactly the law this rule exists to catch. (basalt/raw-breakpoint)'
4122
+ "useMediaQuery/useMatches/useViewportSize — a raw viewport read outside the framework's " +
4123
+ 'one seam. Shell chrome reads useSizeClass(); a page swapping its own layout (a table for a card ' +
4124
+ `list, a column count) is a container decision. ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
4030
4125
 
4031
4126
  const RAW_BREAKPOINT_GLOBAL_MESSAGE =
4032
- 'window.matchMedia/window.innerWidth read directly — the same viewport read useSizeClass() already ' +
4033
- "owns, shell-only. Route a component's own layout decision through a container query instead. " +
4034
- '(basalt/raw-breakpoint)'
4127
+ 'window.matchMedia/window.innerWidth read directly — the viewport read useSizeClass() owns ' +
4128
+ `for shell chrome. ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
4035
4129
 
4036
4130
  /** The three viewport hooks the law names, whatever module they are imported from. */
4037
4131
  const RAW_BREAKPOINT_HOOK_NAMES = new Set(['useMediaQuery', 'useMatches', 'useViewportSize'])
@@ -4047,7 +4141,7 @@ const RAW_BREAKPOINT_HOOK_NAMES = new Set(['useMediaQuery', 'useMatches', 'useVi
4047
4141
  * declares a shell home (or a control's own C9 swap) is exempt for all four shapes uniformly — the
4048
4142
  * shell's own responsive chrome is the sanctioned use, whatever shape it takes.
4049
4143
  */
4050
- // Ships: warn (grace → 1.32.0)
4144
+ // Ships: warn (grace → 1.33.0)
4051
4145
  const rawBreakpoint = {
4052
4146
  meta: {
4053
4147
  type: 'suggestion',
@@ -4056,7 +4150,8 @@ const rawBreakpoint = {
4056
4150
  'Disallow a hand-rolled viewport breakpoint — a responsive-object Mantine prop, ' +
4057
4151
  'visibleFrom/hiddenFrom outside a shell home, useMediaQuery/useMatches/useViewportSize, or ' +
4058
4152
  'a raw window.matchMedia/innerWidth read. All four shapes are exempt inside a file that ' +
4059
- 'declares a shell-home component (or a control implementing its own C9 swap).',
4153
+ 'declares a shell-home component (or a control implementing its own C9 swap); a ' +
4154
+ 'responsive object on a Mantine type="container" grid is exempt where Mantine resolves it.',
4060
4155
  },
4061
4156
  schema: [],
4062
4157
  },
@@ -4153,12 +4248,27 @@ const rawBreakpoint = {
4153
4248
  if (definesShellHome || controlOwner.isOwner()) return
4154
4249
  for (const node of responsiveObjectCandidates) {
4155
4250
  const value = unwrapExpressionContainer(node.value)
4156
- if (!containsResponsiveObjectValue(value, responsiveObjectConsts)) continue
4157
4251
  const owner = node.parent
4158
4252
  if (owner === null || owner === undefined || owner.type !== 'JSXOpeningElement') continue
4159
- if (resolveMantineTag(owner.name, mantineImports) === undefined) continue
4253
+ const tag = resolveMantineTag(owner.name, mantineImports)
4254
+ if (tag === undefined) continue
4255
+ // The container law reaches only the props Mantine routes through the grid's container
4256
+ // queries; a `p={{ base, lg }}` on the same element is still a viewport @media.
4257
+ const routed = Object.hasOwn(CONTAINER_ROUTED_PROPS, tag)
4258
+ ? CONTAINER_ROUTED_PROPS[tag]
4259
+ : undefined
4260
+ const mode = routed?.has(node.name.name)
4261
+ ? containerGridMode(owner, mantineImports, responsiveObjectConsts)
4262
+ : undefined
4263
+ if (mode === 'container') continue
4264
+ if (!containsResponsiveObjectValue(value, responsiveObjectConsts, mode !== undefined))
4265
+ continue
4160
4266
  if (hasThemeAllow(context, node, 'raw-breakpoint')) continue
4161
- context.report({ node, message: RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE })
4267
+ const message =
4268
+ mode === undefined
4269
+ ? RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE
4270
+ : RAW_BREAKPOINT_CONTAINER_KEY_MESSAGE
4271
+ context.report({ node, message })
4162
4272
  }
4163
4273
  for (const node of visibleHiddenCandidates) {
4164
4274
  if (hasThemeAllow(context, node, 'raw-breakpoint')) continue
@@ -4202,8 +4312,9 @@ const rawBreakpoint = {
4202
4312
  export const PLUGIN_RULE_GRACE = {
4203
4313
  'control-outside-home': {
4204
4314
  since: '1.26.0',
4205
- promote: '1.32.0',
4315
+ promote: '1.33.0',
4206
4316
  why:
4317
+ "Extended 1.32.0 -> 1.33.0 at the 1.32.0 release, the last extension: basalt's own tree is at 0 (consumer-loop r1 dogfood) and argo is at 2, but RE-MEASURED 2026-09-30 the fleet carries ~33 (image-gen 11, rb 7, image-share 6, weatherorb 5, email-gateway 4, argo 2). The 1.32.0 fleet upgrade clears them while they warn, together with raw-breakpoint, so 1.33.0 promotes all four ids against a measured-clean fleet. " +
4207
4318
  'Extended 1.31.0 -> 1.32.0 at the 1.31.0 release: the basalt tree still carries incumbents (data-table, notifications center, theme-lab) and the argo sweep has not run. ' +
4208
4319
  'the wave-6 control guards (docs/CONTROLS-SPEC.md §6). The one openly HEURISTIC rule of the ' +
4209
4320
  'set — "this control has no home" is a claim about layout intent, so its false-positive load ' +
@@ -4232,17 +4343,23 @@ export const PLUGIN_RULE_GRACE = {
4232
4343
  },
4233
4344
  'raw-breakpoint': {
4234
4345
  since: '1.31.0',
4235
- promote: '1.32.0',
4346
+ promote: '1.33.0',
4236
4347
  why:
4237
- 'new in the wave-11 responsive/touch guards. Catches four ' +
4238
- 'independent shapes at once — a responsive-object Mantine prop, visibleFrom/hiddenFrom outside ' +
4239
- 'the three shell homes, the three raw viewport hooks, and window.matchMedia/innerWidth — none ' +
4240
- 'of which basalt policed before this wave, so every consumer on an earlier minor has a green ' +
4241
- "build with all four shapes already in it. basalt-ui/content's own article-card.tsx (a " +
4242
- "`SimpleGrid cols={{ base: 1, sm: 2, lg }}`) is one incumbent measured in basalt's own tree at " +
4243
- 'ship time — a real number, not zero, so this ships warn rather than error from the start. ' +
4244
- '1.32.0 is when that incumbent and any consumer-side ones are expected to have moved to a ' +
4245
- 'container query or a theme-allow with a stated reason.',
4348
+ 'New in the wave-11 responsive/touch guards. Catches four independent shapes at once — a ' +
4349
+ 'responsive-object Mantine prop, visibleFrom/hiddenFrom outside the three shell homes, the ' +
4350
+ 'three raw viewport hooks, and window.matchMedia/innerWidth — none of which basalt policed ' +
4351
+ 'before this wave, so every consumer on an earlier minor has a green build with all four ' +
4352
+ "shapes already in it. basalt-ui/content's own article-card.tsx (a `SimpleGrid cols={{ base: " +
4353
+ "1, sm: 2, lg }}`) was one incumbent measured in basalt's own tree at ship time — a real " +
4354
+ 'number, not zero, so it shipped warn rather than error. ' +
4355
+ 'Extended 1.32.0 -> 1.33.0: 1.31.0 shipped the rule naming "a container query" but no ' +
4356
+ 'primitive, and MIGRATING never mentioned it (argo: 42 warns, no documented target). 1.32.0 ' +
4357
+ 'is the first minor with a reachable, documented answer (Mantine type="container" grids keyed ' +
4358
+ 'on CONTAINER_CLASSES, exempted here), and promoting in the minor that first makes a rule ' +
4359
+ 'satisfiable hands a consumer zero minors to act on it — the control-outside-home ' +
4360
+ "extension's reasoning. 1.33.0 is when the incumbents are expected to have moved to a " +
4361
+ 'container grid or a theme-allow with a stated reason. Promotes together with its CSS twin, ' +
4362
+ 'the `raw-media-query` guard kind.',
4246
4363
  },
4247
4364
  }
4248
4365