basalt-ui 1.35.0 → 1.36.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 (66) hide show
  1. package/AGENTS.md +21 -21
  2. package/CHANGELOG.md +16 -0
  3. package/MIGRATING.md +85 -14
  4. package/agent/rules/basalt-controls.md +1 -1
  5. package/agent/rules/basalt-mantine.md +1 -1
  6. package/configs/oxlint-plugin.js +51 -4
  7. package/configs/oxlint-plugin.test.ts +58 -15
  8. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  9. package/dist/charts/primitives/ChartFrame.js +2 -1
  10. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  11. package/dist/charts/primitives/ChartLegend.js +2 -1
  12. package/dist/charts/primitives/chart-tier.d.ts +0 -2
  13. package/dist/charts/primitives/chart-tier.d.ts.map +1 -1
  14. package/dist/charts/primitives/chart-tier.js +0 -5
  15. package/dist/common/index.d.ts +1 -0
  16. package/dist/common/index.d.ts.map +1 -1
  17. package/dist/common/index.js +2 -0
  18. package/dist/common/use-media-query.d.ts +7 -0
  19. package/dist/common/use-media-query.d.ts.map +1 -1
  20. package/dist/common/use-media-query.js +4 -0
  21. package/dist/common/vx-families.d.ts +1 -1
  22. package/dist/{forms → controls}/form-layout.d.ts +1 -1
  23. package/dist/controls/form-layout.d.ts.map +1 -0
  24. package/dist/{forms → controls}/form-layout.js +2 -2
  25. package/dist/controls/index.d.ts +1 -0
  26. package/dist/controls/index.d.ts.map +1 -1
  27. package/dist/controls/index.js +10 -0
  28. package/dist/forms/index.d.ts +37 -7
  29. package/dist/forms/index.d.ts.map +1 -1
  30. package/dist/forms/index.js +10 -2
  31. package/dist/guard/index.d.ts +3 -1
  32. package/dist/guard/index.d.ts.map +1 -1
  33. package/dist/guard/index.js +2 -8
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +3 -1
  37. package/dist/provider/index.d.ts +5 -4
  38. package/dist/provider/index.d.ts.map +1 -1
  39. package/dist/shell/use-size-class.d.ts.map +1 -1
  40. package/dist/surfaces.d.ts +4 -4
  41. package/dist/surfaces.d.ts.map +1 -1
  42. package/dist/surfaces.js +11 -3
  43. package/dist/tokens/index.d.ts.map +1 -1
  44. package/dist/tokens/index.js +5 -7
  45. package/llms.txt +5 -5
  46. package/package.json +1 -1
  47. package/src/charts/primitives/ChartFrame.tsx +2 -1
  48. package/src/charts/primitives/ChartLegend.tsx +2 -1
  49. package/src/charts/primitives/chart-tier.tsx +0 -6
  50. package/src/common/index.ts +1 -0
  51. package/src/common/use-media-query.ts +10 -0
  52. package/src/common/vx-families.ts +1 -1
  53. package/src/{forms → controls}/form-layout.tsx +7 -5
  54. package/src/controls/index.ts +16 -0
  55. package/src/forms/index.ts +36 -9
  56. package/src/guard/index.ts +5 -15
  57. package/src/index.ts +1 -0
  58. package/src/provider/index.tsx +5 -4
  59. package/src/shell/use-size-class.ts +6 -4
  60. package/src/surfaces.ts +11 -3
  61. package/src/tokens/index.ts +12 -15
  62. package/dist/forms/form-layout.d.ts.map +0 -1
  63. /package/dist/{forms → controls}/form-layout.module.css +0 -0
  64. /package/dist/{forms → controls}/form-layout.module.css.d.ts +0 -0
  65. /package/src/{forms → controls}/form-layout.module.css +0 -0
  66. /package/src/{forms → controls}/form-layout.module.css.d.ts +0 -0
package/AGENTS.md CHANGED
@@ -6,27 +6,27 @@ Toolchain: Bun runtime, oxlint + oxfmt, conventional commits (empty scope), `mas
6
6
 
7
7
  ## Subpath ownership
8
8
 
9
- | Subpath | Layer | Purpose |
10
- | --------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
- | `basalt-ui` | mantine-coupled | BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, PageBar, PageAside, useSizeClass, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, WidgetHeader, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, QueryState/LoadingState/ErrorState, SettingsSection/SettingsRow/DangerZone), ConnectivityProvider/useConnectivity/ConnectivityIndicator (auto-mounted, C1: absorbed from the dropped ./connectivity), createBasaltQueryClient/unwrap/BasaltQueryDevtools (C1: absorbed from the dropped ./query), BasaltDevDock (C5: lazy Router/Query devtools + theme lab in one dock) |
12
- | `basalt-ui/charts` | headless | CartesianChart + visx chart primitives, kinds, sparklines, hooks, and token re-exports (Mantine-free) |
13
- | `basalt-ui/tokens` | headless | VX token refs, buildPaletteCss, defineSeries, seriesTokens, groupTokens, alpha (Mantine-free) |
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 |
15
- | `basalt-ui/vite` | build-tooling | basaltViteConfig(opts) — Vite preset for basalt-ui consumer apps; basaltAppPlugin(opts) — PWA head, manifest, and icon metadata derived from the token palette |
16
- | `basalt-ui/guard` | headless | checkSource, GUARD_RULES, Finding types — the headless theme-guard core |
17
- | `basalt-ui/router-tanstack` | headless | TanStack Router bridge: defineNav/navGroup/navTarget (one typed nav definition) + useNav (sections + mobileNav, spread onto BasaltShell) + useBasaltNav (active route) + useRouterBreadcrumbs + createSearchStore (typed URL > localStorage > fallback store over field.enum/multi/range/number/boolean/string; the deprecated createSearchParamStore/createMultiSearchParamStore wrappers were removed in 1.29.0, C1 consolidation) |
18
- | `basalt-ui/forms` | mantine-coupled | Mantine form layer: useBasaltForm + inputProps/fieldKey (two calls — a spread `key` is a React 19 warning; the deprecated `field` alias for `inputProps` was removed in 1.29.0, C1 consolidation), the FormSection/FormRow/FormGroup/FormActions layout (FormRow is law C1’s form row), useFormSubmit (submit state, decoded errors, server fieldErrors, focus-first-error, validateAsync), FormStateProvider disable propagation, useFieldArray, FormErrorSummary, useFormDraft with autosave (Standard Schema) |
19
- | `basalt-ui/notifications` | mantine-coupled | Mantine notifications: notify helpers, typed registry, persisted history, NotificationBell, NotificationCenter |
20
- | `basalt-ui/commands` | mantine-coupled | typed command bus + overlay controller, toSpotlightActions, ShortcutsHelp, BasaltOverlays |
21
- | `basalt-ui/data/table` | mantine-coupled | BasaltDataTable: a sortable data table over TanStack Table, rendered with Mantine (Mantine-rendered) |
22
- | `basalt-ui/data/virtual` | mantine-coupled | BasaltVirtualList: a windowed virtual list over TanStack Virtual, rendered with Mantine (Mantine-rendered) |
23
- | `basalt-ui/agent` | headless | Headless streaming-chat layer: useAgentStream, aiSdkTransport (recommended default) + edenTransport, isResumable/ResumableAgentTransport (stream-resumption seam), PartList, coalesceParts, the ForeignPart/definePartRenderers/narrowAgentPart open part-registry seam, plus the multi-thread createThreadsStore + useAgentThreadRuns + outcome-resolver seam (Mantine-free) |
24
- | `basalt-ui/agent-chat` | mantine-coupled | Mantine-styled thread-chat components over basalt-ui/agent: ThreadWorkspace, ThreadFeed (variant/renderRow), ThreadFeedRow (inline-expanding Slack row, lazily mounted + kept mounted), ThreadOutcomeCard, ThreadDetailPanel, Composer, ThreadTranscript (open part-renderer registry via its renderers/fallbackRenderer props, per-message MessageAffordances, groupConsecutive, and an optional virtualize/height windowing mode whose VirtualizeOptions carry overscan/estimateSize/initialScroll — a virtualized transcript opens scrolled to the newest message unless initialScroll is "start"), threadPartRenderers, ToolChip (Mantine-coupled). motion is required, not optional — ThreadFeed/ThreadDetailPanel import motion/react eagerly, so this subpath fails to resolve without it installed even though peerDependenciesMeta marks it optional (npm has no per-subpath optionality). remend is genuinely optional here — ThreadTranscript reaches it only through the lazy dynamic import() inside content/markdown.tsx, and @tanstack/react-virtual the same way through the lazy import() behind ThreadTranscript virtualize (absent peer degrades to an unwindowed, height-bound pane). |
25
- | `basalt-ui/content` | mantine-coupled | Prose (article/chat typography), CodeBlock (shiki, optional peer), Callout, TableOfContents, ReadingProgress, Markdown (react-markdown + remark-gfm, optional peers; `streaming` is a rendering mode ONLY — `contentTrust` is the independent security input, and any surface rendering agent/model output must pin `contentTrust="untrusted"`, the sole input to the image-origin allowlist; a `fenceRenderers` registry — settledOnly/FenceRenderer/FenceRenderers/FenceRenderContext; `sanitizeSchema`, an additions-only SanitizeSchemaExtension merged over BASALT_SANITIZE_SCHEMA via mergeSanitizeSchema; the remend streaming-repair pass is now a lazy optional peer), MermaidDiagram (beautiful-mermaid, optional peer), mdxComponents/createMdxComponents, ArticleLayout (docs-page frame), ArticleCard/ArticleGrid (overview cards), Article model (sortArticles/filterArticles/formatArticleDate), FilterSet/ViewTabs/MultiSelectFilter re-exported from ./controls (they replaced the controlled ArticleFilterBar), toArticleActions (Spotlight projector, @mantine/spotlight type-only), GuideLink/GuideDrawer (contextual-help drawer) — the content/prose surface |
26
- | `basalt-ui/controls` | mantine-coupled | The control tier (docs/CONTROLS-SPEC.md §3): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter/CompareFilter/SelectFilter/MultiSelectFilter/NumberFilter/SearchFilter/ToggleFilter (each bound to a FieldHandle — no value/onChange/size, law C2/C5; NumberFilter is the field.number lane, a radio list over `options` or a stepper without), ViewTabs, and the action/sync family (ActionGroup, OverflowMenu, SyncButton, BarAction/GlobalAction). THREE surfaces, picked by the home and never by a prop (useFilterSurface): pill in a PageBar row, sheet in the mobile Filters (n) drawer, panel in a PageAside body — where every control renders an inspector/facet ROW instead of a chip (docs/ASIDE-SPEC.md §3). PanelRow is that row primitive (label above, hint, mono readout, an `end` control on the label line) and SliderControl is the one bound control with no pill form at all; the counts/max props on MultiSelectFilter render the Foundry facet list there. Every control owns its own desktop/mobile swap in CSS (C9) and renders size="ctl" internally. Resolves and renders with NO @mantine/dates installed — the custom date picker is injected through RangeFilter.customPicker from ./controls-dates. |
27
- | `basalt-ui/controls-dates` | mantine-coupled | DateRangePicker — the @mantine/dates implementation of RangeFilter's customPicker seam. Its own subpath because @mantine/dates is an optional peer and basaltViteConfig pre-bundles the whole @mantine scope, so a consumer without the peer (linewatch) must never resolve it: nothing under src/controls may import it, statically or lazily (docs/CONTROLS-SPEC.md §3). |
28
- | `basalt-ui/state` | headless | createPersistedState (versioned localStorage) + the store field vocabulary (field.enum/multi/range/number/boolean/string, FieldHandle, lanes) + createLocalStore, the router-free store — Mantine-free state primitives |
29
- | `basalt-ui/format` | headless | money/percent/integer/compact/deltaPct/duration/durationClock/clock/relativeTime/weekday/km/kcal — Intl-backed, Mantine-free, React-free formatting; charts/utils/format.ts re-exports fmtCompact/fmtPercent/fmtCurrency/fmtInt/fmtAxisDate/fmtTooltipDate from here (C5 consolidation, one implementation) |
9
+ | Subpath | Layer | Purpose |
10
+ | --------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | `basalt-ui` | mantine-coupled | BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, PageBar, PageAside, useSizeClass, useCoarsePointer, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, WidgetHeader, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, QueryState/LoadingState/ErrorState, SettingsSection/SettingsRow/DangerZone), ConnectivityProvider/useConnectivity/ConnectivityIndicator (auto-mounted, C1: absorbed from the dropped ./connectivity), createBasaltQueryClient/unwrap/BasaltQueryDevtools (C1: absorbed from the dropped ./query), BasaltDevDock (C5: lazy Router/Query devtools + theme lab in one dock) |
12
+ | `basalt-ui/charts` | headless | CartesianChart + visx chart primitives, kinds, sparklines, hooks, and token re-exports (Mantine-free) |
13
+ | `basalt-ui/tokens` | headless | VX token refs, buildPaletteCss, defineSeries, seriesTokens, groupTokens, alpha (Mantine-free) |
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 |
15
+ | `basalt-ui/vite` | build-tooling | basaltViteConfig(opts) — Vite preset for basalt-ui consumer apps; basaltAppPlugin(opts) — PWA head, manifest, and icon metadata derived from the token palette |
16
+ | `basalt-ui/guard` | headless | checkSource, GUARD_RULES, Finding types — the headless theme-guard core |
17
+ | `basalt-ui/router-tanstack` | headless | TanStack Router bridge: defineNav/navGroup/navTarget (one typed nav definition) + useNav (sections + mobileNav, spread onto BasaltShell) + useBasaltNav (active route) + useRouterBreadcrumbs + createSearchStore (typed URL > localStorage > fallback store over field.enum/multi/range/number/boolean/string; the deprecated createSearchParamStore/createMultiSearchParamStore wrappers were removed in 1.29.0, C1 consolidation) |
18
+ | `basalt-ui/forms` | mantine-coupled | Mantine form layer: useBasaltForm + inputProps/fieldKey (two calls — a spread `key` is a React 19 warning; the deprecated `field` alias for `inputProps` was removed in 1.29.0, C1 consolidation), useFormSubmit (submit state, decoded errors, server fieldErrors, focus-first-error, validateAsync), FormStateProvider disable propagation, useFieldArray, FormErrorSummary, useFormDraft with autosave (Standard Schema). The FormSection/FormRow/FormGroup/FormActions layout moved to ./controls in 1.36.0 and the useForm/schemaResolver re-exports are deprecated (import from @mantine/form); all six stay here until 1.37.0 |
19
+ | `basalt-ui/notifications` | mantine-coupled | Mantine notifications: notify helpers, typed registry, persisted history, NotificationBell, NotificationCenter |
20
+ | `basalt-ui/commands` | mantine-coupled | typed command bus + overlay controller, toSpotlightActions, ShortcutsHelp, BasaltOverlays |
21
+ | `basalt-ui/data/table` | mantine-coupled | BasaltDataTable: a sortable data table over TanStack Table, rendered with Mantine (Mantine-rendered) |
22
+ | `basalt-ui/data/virtual` | mantine-coupled | BasaltVirtualList: a windowed virtual list over TanStack Virtual, rendered with Mantine (Mantine-rendered) |
23
+ | `basalt-ui/agent` | headless | Headless streaming-chat layer: useAgentStream, aiSdkTransport (recommended default) + edenTransport, isResumable/ResumableAgentTransport (stream-resumption seam), PartList, coalesceParts, the ForeignPart/definePartRenderers/narrowAgentPart open part-registry seam, plus the multi-thread createThreadsStore + useAgentThreadRuns + outcome-resolver seam (Mantine-free) |
24
+ | `basalt-ui/agent-chat` | mantine-coupled | Mantine-styled thread-chat components over basalt-ui/agent: ThreadWorkspace, ThreadFeed (variant/renderRow), ThreadFeedRow (inline-expanding Slack row, lazily mounted + kept mounted), ThreadOutcomeCard, ThreadDetailPanel, Composer, ThreadTranscript (open part-renderer registry via its renderers/fallbackRenderer props, per-message MessageAffordances, groupConsecutive, and an optional virtualize/height windowing mode whose VirtualizeOptions carry overscan/estimateSize/initialScroll — a virtualized transcript opens scrolled to the newest message unless initialScroll is "start"), threadPartRenderers, ToolChip (Mantine-coupled). motion is required, not optional — ThreadFeed/ThreadDetailPanel import motion/react eagerly, so this subpath fails to resolve without it installed even though peerDependenciesMeta marks it optional (npm has no per-subpath optionality). remend is genuinely optional here — ThreadTranscript reaches it only through the lazy dynamic import() inside content/markdown.tsx, and @tanstack/react-virtual the same way through the lazy import() behind ThreadTranscript virtualize (absent peer degrades to an unwindowed, height-bound pane). |
25
+ | `basalt-ui/content` | mantine-coupled | Prose (article/chat typography), CodeBlock (shiki, optional peer), Callout, TableOfContents, ReadingProgress, Markdown (react-markdown + remark-gfm, optional peers; `streaming` is a rendering mode ONLY — `contentTrust` is the independent security input, and any surface rendering agent/model output must pin `contentTrust="untrusted"`, the sole input to the image-origin allowlist; a `fenceRenderers` registry — settledOnly/FenceRenderer/FenceRenderers/FenceRenderContext; `sanitizeSchema`, an additions-only SanitizeSchemaExtension merged over BASALT_SANITIZE_SCHEMA via mergeSanitizeSchema; the remend streaming-repair pass is now a lazy optional peer), MermaidDiagram (beautiful-mermaid, optional peer), mdxComponents/createMdxComponents, ArticleLayout (docs-page frame), ArticleCard/ArticleGrid (overview cards), Article model (sortArticles/filterArticles/formatArticleDate), FilterSet/ViewTabs/MultiSelectFilter re-exported from ./controls (they replaced the controlled ArticleFilterBar), toArticleActions (Spotlight projector, @mantine/spotlight type-only), GuideLink/GuideDrawer (contextual-help drawer) — the content/prose surface |
26
+ | `basalt-ui/controls` | mantine-coupled | The control tier (docs/CONTROLS-SPEC.md §3): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter/CompareFilter/SelectFilter/MultiSelectFilter/NumberFilter/SearchFilter/ToggleFilter (each bound to a FieldHandle — no value/onChange/size, law C2/C5; NumberFilter is the field.number lane, a radio list over `options` or a stepper without), ViewTabs, the action/sync family (ActionGroup, OverflowMenu, SyncButton, BarAction/GlobalAction), and the form layout FormSection/FormRow/FormGroup/FormActions (moved here from ./forms in 1.36.0 — FormRow is law C1’s form row and needs no @mantine/form). THREE surfaces, picked by the home and never by a prop (useFilterSurface): pill in a PageBar row, sheet in the mobile Filters (n) drawer, panel in a PageAside body — where every control renders an inspector/facet ROW instead of a chip (docs/ASIDE-SPEC.md §3). PanelRow is that row primitive (label above, hint, mono readout, an `end` control on the label line) and SliderControl is the one bound control with no pill form at all; the counts/max props on MultiSelectFilter render the Foundry facet list there. Every control owns its own desktop/mobile swap in CSS (C9) and renders size="ctl" internally. Resolves and renders with NO @mantine/dates installed — the custom date picker is injected through RangeFilter.customPicker from ./controls-dates. |
27
+ | `basalt-ui/controls-dates` | mantine-coupled | DateRangePicker — the @mantine/dates implementation of RangeFilter's customPicker seam. Its own subpath because @mantine/dates is an optional peer and basaltViteConfig pre-bundles the whole @mantine scope, so a consumer without the peer (linewatch) must never resolve it: nothing under src/controls may import it, statically or lazily (docs/CONTROLS-SPEC.md §3). |
28
+ | `basalt-ui/state` | headless | createPersistedState (versioned localStorage) + the store field vocabulary (field.enum/multi/range/number/boolean/string, FieldHandle, lanes) + createLocalStore, the router-free store — Mantine-free state primitives |
29
+ | `basalt-ui/format` | headless | money/percent/integer/compact/deltaPct/duration/durationClock/clock/relativeTime/weekday/km/kcal — Intl-backed, Mantine-free, React-free formatting; charts/utils/format.ts re-exports fmtCompact/fmtPercent/fmtCurrency/fmtInt/fmtAxisDate/fmtTooltipDate from here (C5 consolidation, one implementation) |
30
30
 
31
31
  ## Hard rules
32
32
 
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [1.36.1](https://github.com/jkrumm/basalt-ui/compare/v1.36.0...v1.36.1) (2026-10-01)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * release an unauthored MIGRATING section as nothing-to-migrate ([f02a5d9](https://github.com/jkrumm/basalt-ui/commit/f02a5d9b9909b0d226470e8ed8bcfb27c7452f01))
7
+
8
+ # [1.36.0](https://github.com/jkrumm/basalt-ui/compare/v1.35.0...v1.36.0) (2026-10-01)
9
+
10
+
11
+ ### Features
12
+
13
+ * export useCoarsePointer from the root barrel ([060764f](https://github.com/jkrumm/basalt-ui/commit/060764fb75169148a0beb3913d2f6d457d2c3cf0))
14
+ * move the form layout to basalt-ui/controls ([18f9eec](https://github.com/jkrumm/basalt-ui/commit/18f9eecfbac4e27c13ace023257f61ce27ab656e))
15
+ * promote unknown-vx-token to error ([0ac5008](https://github.com/jkrumm/basalt-ui/commit/0ac5008d86b1b1f5a4a9dbec82335452837c3d1c))
16
+
1
17
  # [1.35.0](https://github.com/jkrumm/basalt-ui/compare/v1.34.1...v1.35.0) (2026-10-01)
2
18
 
3
19
 
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 | 1.32.0 | 1.32.1 | 1.33.0 | 1.34.0 | 1.34.1 | 1.35.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` (§ 1.31.0) | — | `SIZE_CLASSES`/`CONTAINER_CLASSES` actually exported (§ Fixes) | `CONTAINER_GRID_BREAKPOINTS`; `raw-breakpoint` trusts it (§ 1.33.0) | `CONTAINER_KEYS` (§ 1.34.0) | — | — | — |
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`) | — | `useBreakpoint` removed (§ 1.34.0) | — | — | — |
74
- | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — | `NumberFilter clearable`; `createSearchStore` `resets` (§ State and 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) | C1 + breakpoint pairs → `error`; `unknown-vx-token` `warn` (§ 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 | 1.33.0 | 1.34.0 | 1.34.1 | 1.35.0 | 1.36.0 | 1.36.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) | `CONTAINER_GRID_BREAKPOINTS`; `raw-breakpoint` trusts it (§ 1.33.0) | `CONTAINER_KEYS` (§ 1.34.0) | — | — | — | — | — |
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`) | — | `useBreakpoint` removed (§ 1.34.0) | — | — | `useCoarsePointer()` (§ `useCoarsePointer()`) | — | — |
74
+ | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — | `NumberFilter clearable`; `createSearchStore` `resets` (§ State and controls) | — | — | — | `FormSection`/`FormRow`/`FormGroup`/`FormActions` move here (§ The form layout moves) | — | — |
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) | — | — | — | — | the layout + `useForm`/`schemaResolver` deprecated, removed 1.37.0 (§ The form layout moves) | — | — |
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) | C1 + breakpoint pairs → `error`; `unknown-vx-token` `warn` (§ Guards) | — | — | — | `unknown-vx-token` → `error`; the token factories throw (§ Guards) | — | — |
81
81
 
82
82
  ---
83
83
 
@@ -86,10 +86,81 @@ 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.36.1
90
+
91
+ _Nothing to migrate in this release._
92
+
93
+ ## 1.36.0 — the 1.36.0 minor
94
+
95
+ ### Guards — `unknown-vx-token` is an `error`
96
+
97
+ The last grace entry leaves the ledger, which is now empty. Its grace ran one minor past 1.35.0 so
98
+ the factory throw below stayed out of the shell a11y release. Measured 2026-10-01 on 1.34.x: all
99
+ nine consumers — argo, rb, image-gen, image-share, email-gateway, linewatch, obsidian, rollhook,
100
+ weatherorb — at 0 findings, basalt's own tree and the playground at 0 too. A finding after
101
+ upgrading is a `var(--vx-*)` written since: take the replacement from the finding's remedy (the
102
+ removed-name table in § 1.33.0) or a name from `basalt-ui/tokens.css`, or
103
+ `theme-allow unknown-vx-token — <reason>`. `groupTokens` / `seriesTokens` now **throw** (dev builds)
104
+ on a ref inside a basalt family instead of warning — rename the group (`groupTokens('app', …)`).
105
+
106
+ ### `useSizeClass()` does not flash in a client-rendered app
107
+
108
+ Its JSDoc claimed the first client paint renders `sizeClassHint`. Only SSR and the `hydrateRoot`
109
+ pass do; under `createRoot` the FIRST render already reads `matchMedia` (now pinned by a test). So a
110
+ CSR app's page-level swap partnering shell chrome — a panel standing in for the sidebar at
111
+ `compact` — is `useSizeClass() === 'compact' ? <A /> : <B />`: one mount, no flash, no waiver. A
112
+ `hiddenFrom`/`visibleFrom` pair waived `raw-breakpoint` "so desktop never flashes" can go. No
113
+ `<html data-basalt-size-class>` ships: CSS keyed on a JS-stamped attribute is a second viewport
114
+ seam outside `shell/`, and it would flash under SSR exactly where the hook does not.
115
+
116
+ ### The form layout moves to `basalt-ui/controls`
117
+
118
+ `FormRow`/`FormGroup` need no `@mantine/form`, but importing them from `basalt-ui/forms` evaluates
119
+ a barrel whose `useBasaltForm` value-imports it — so image-gen added `@mantine/form` only to render
120
+ a row, and weatherorb hit the same. Dropping the `useForm`/`schemaResolver` re-export alone would
121
+ not fix that, so the four layout pieces moved to `./controls` (it already owns `FilterSet`,
122
+ `PanelRow`) and resolve with no `@mantine/form` installed. Named consumers: image-gen drops
123
+ `@mantine/form` entirely; argo `explorer-page.tsx` and image-share `routes/public.tsx` move the
124
+ import. `FormRow`/`FormGroup` stay law C1's form-row home from either subpath.
125
+
126
+ | Was (`basalt-ui/forms`) | Now | Removed in |
127
+ | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ---------- |
128
+ | `FormSection`, `FormRow`, `FormGroup`, `FormActions` (+ their `*Props`/`*Slot` types) | the same names from `basalt-ui/controls` | 1.37.0 |
129
+ | `useForm`, `schemaResolver`, `UseFormReturnType`, `UseFormInput` | `@mantine/form` directly (zero consumers imported them from basalt) | 1.37.0 |
130
+
131
+ **Grace:** every old import still resolves in 1.36.x. `basalt/deprecated-export` warns on the six
132
+ values, but the rows are `fix: false` — the autofix rewrites a specifier inside its own import
133
+ statement and cannot change the module — so move the import by hand
134
+ (`import { FormRow } from 'basalt-ui/controls'`). Both subpaths are homes for
135
+ `control-outside-home`/`raw-selection-control` throughout. The `public symbols` budget runs at 401
136
+ during the grace minor; 1.37.0 removes six.
137
+
138
+ ### `useCoarsePointer()` — the pointer class in JS
139
+
140
+ Exported from `basalt-ui` (it was chart-internal). The JS twin of `--vx-hit`'s `(pointer: coarse)`,
141
+ for a branch CSS cannot express — mount a `PinInput` or a digit pad, attach a keydown listener.
142
+ Styling stays `@media (pointer: coarse)` in CSS. Server and hydration snapshot `false`; a
143
+ `createRoot` app reads the real pointer on first render. Named consumer: rb `lock-screen.tsx`,
144
+ whose `useMediaQuery('(pointer: coarse)')` and its `theme-allow raw-breakpoint` can go.
145
+
146
+ ### Design calls closed without new API
147
+
148
+ Each was parked in the consumer loop pending a second named consumer; measured at 1.34.x:
149
+
150
+ | Ask | Verdict | Do instead |
151
+ | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
152
+ | a container class above `wide` | none ships — one live site (a 1368px content-fit bar); the others moved to 800 | § 1.32.1 off-class table: own-name or `basalt-grid` query, no waiver |
153
+ | `ContainerSwap` primitive | none ships — every hand-roll is the 4-line one-query form, and most query a container declared above the swapped pair, which a component would have to own | § 1.32.1 swap recipe |
154
+ | shell-less top-level nav | none ships — one app; `BasaltShell` is the one nav projection | `theme-allow control-outside-home — top-level nav, no shell` |
155
+ | Modal bottom sheet on a fine pointer | keeps keying on the compact SIZE class — the sheet is that class's overlay layout; the pointer axis owns hit size only | nothing — a desktop window under 840px is `compact` and gets the compact overlay |
156
+ | `FormRow` `orientation` / width prop | none ships — one consumer | `style={{ width }}` on the row |
157
+
89
158
  ## 1.35.0
90
159
 
91
- _Nothing yet. Author the next section under this heading as `## Unreleased — <title>`; the
92
- release renames it and opens a new one here._
160
+ Nothing to migrate: shell accessibility fixes only — the signed-out sidebar account row is named
161
+ in the collapsed rail, a settings entry can be the current page, a custom sidebar block stays in the
162
+ collapsed rail and the mobile drawer, and `raw-selection-control` judges by host-tag nesting instead
163
+ of a 12-line window. `unknown-vx-token` stayed `warn` here; it promotes in § 1.36.0.
93
164
 
94
165
  ## 1.34.1 — the AST lane needs a binary
95
166
 
@@ -51,7 +51,7 @@ A green lint run is not evidence the advisory laws the generated header above li
51
51
  | `PageBar` | `actions`, `sync`, `filters`, `filtersEnd`, `tabs` | `ctl` | the page's filters, tabs, actions, refresh |
52
52
  | `Section` | `actions`, `tabs` | `ctl` | that section's own controls and its count |
53
53
  | `WidgetHeader` / `ChartCard` / `StatCard` / `BasaltDataTable` / `SettingsSection` | `actions` | `ctl` | that widget's own controls and its count |
54
- | `SettingsRow` · `FormRow` / `FormGroup` (`basalt-ui/forms`) | `control` · children | Mantine `md` | ONE form field, or a labelled cluster |
54
+ | `SettingsRow` · `FormRow` / `FormGroup` (`basalt-ui/controls`) | `control` · children | Mantine `md` | ONE form field, or a labelled cluster |
55
55
 
56
56
  - **Inside `BasaltShell`** both `PageBar` rows are portals (header / the band above the scrollport)
57
57
  — where you write `<PageBar>` never moves it. Without a shell both rows render in flow, sticky.
@@ -98,7 +98,7 @@ Depth-tier law (which token for which surface, why static, why the ring is inset
98
98
  - **Charts measure themselves** — pass `height`/`aspectRatio`/`fill` only; never `useElementSize`
99
99
  in a chart file (Mantine is banned there), never raw `@visx/responsive` outside `charts/**`.
100
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
+ `useSizeClass()`: shell chrome (and what stands in for it at `compact`), overlay form, `ViewTabs only: 'sm-down'` partners — flash-free under `createRoot`. Recipes: `MIGRATING.md`.
102
102
 
103
103
  ## Scroll regions
104
104
 
@@ -2048,8 +2048,9 @@ const CONTROL_OWNER_NAMES = new Set([
2048
2048
  * `SettingsRow` is the form row — law C1's third home, not an exception to C1 — so
2049
2049
  * `control-outside-home` treating it as a home is the whole of the enforcement a settings page
2050
2050
  * gets, and the two tier rules deliberately do not reach inside it (see {@link SLOT_ATTRS}).
2051
- * `FormRow`/`FormGroup` (`basalt-ui/forms`, `src/forms/form-layout.tsx`) are the SAME home written
2052
- * for a `<form>` rather than a settings page — a `TextInput` bound to a field is read and typed
2051
+ * `FormRow`/`FormGroup` (`basalt-ui/controls`, `src/controls/form-layout.tsx`; `basalt-ui/forms`
2052
+ * re-exports them until 1.37.0 — the set is NAME-matched, so both subpaths are homes) are the SAME
2053
+ * home written for a `<form>` rather than a settings page — a `TextInput` bound to a field is read and typed
2053
2054
  * into at Mantine's own tier, not a 30px chrome affordance — so they are added here rather than
2054
2055
  * given a second walk.
2055
2056
  *
@@ -3639,7 +3640,10 @@ const queryFnUnwrap = {
3639
3640
  * same edit. **That autofix is the row's contract**: only a DROP-IN rename belongs here. A
3640
3641
  * deprecation whose replacement takes a different shape (`createSearchParamStore` →
3641
3642
  * `createSearchStore` + a `fields` object) would need the fix suppressed before it could be a row,
3642
- * and until then it lives in `MIGRATING.md` alone. A row WITH `prop` is a JSX ATTRIBUTE on the component `name`, imported from `subpath`
3643
+ * and until then it lives in `MIGRATING.md` alone. A MOVE between subpaths (`FormRow` from
3644
+ * `basalt-ui/forms` → `basalt-ui/controls`) is the same case: the fix only rewrites the specifier
3645
+ * inside its own import statement and cannot change its module, so such a row says `fix: false` and
3646
+ * reports without rewriting. A row WITH `prop` is a JSX ATTRIBUTE on the component `name`, imported from `subpath`
3643
3647
  * — no autofix, because the three connectivity props collapse into ONE object prop and a
3644
3648
  * per-attribute rewrite would produce three `connectivity=` attributes on one tag.
3645
3649
  *
@@ -3663,6 +3667,48 @@ export const DEPRECATED_EXPORTS = [
3663
3667
  // exports are no longer reachable from any public barrel in `src/charts`/`src/tokens` — a row
3664
3668
  // here would nudge a rewrite to an import that no longer resolves, i.e. "it still resolves today"
3665
3669
  // would be false.
3670
+ {
3671
+ subpath: 'basalt-ui/forms',
3672
+ name: 'FormSection',
3673
+ replacement: "FormSection from 'basalt-ui/controls'",
3674
+ removeIn: '1.37.0',
3675
+ fix: false,
3676
+ },
3677
+ {
3678
+ subpath: 'basalt-ui/forms',
3679
+ name: 'FormRow',
3680
+ replacement: "FormRow from 'basalt-ui/controls'",
3681
+ removeIn: '1.37.0',
3682
+ fix: false,
3683
+ },
3684
+ {
3685
+ subpath: 'basalt-ui/forms',
3686
+ name: 'FormGroup',
3687
+ replacement: "FormGroup from 'basalt-ui/controls'",
3688
+ removeIn: '1.37.0',
3689
+ fix: false,
3690
+ },
3691
+ {
3692
+ subpath: 'basalt-ui/forms',
3693
+ name: 'FormActions',
3694
+ replacement: "FormActions from 'basalt-ui/controls'",
3695
+ removeIn: '1.37.0',
3696
+ fix: false,
3697
+ },
3698
+ {
3699
+ subpath: 'basalt-ui/forms',
3700
+ name: 'useForm',
3701
+ replacement: "useForm from '@mantine/form'",
3702
+ removeIn: '1.37.0',
3703
+ fix: false,
3704
+ },
3705
+ {
3706
+ subpath: 'basalt-ui/forms',
3707
+ name: 'schemaResolver',
3708
+ replacement: "schemaResolver from '@mantine/form'",
3709
+ removeIn: '1.37.0',
3710
+ fix: false,
3711
+ },
3666
3712
  {
3667
3713
  subpath: 'basalt-ui',
3668
3714
  name: 'useBreakpoint',
@@ -4219,7 +4265,8 @@ const RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE =
4219
4265
  /** rb r2 #5: a `(hover: none)` read was flagged with advice that only covered widths. */
4220
4266
  const RAW_BREAKPOINT_POINTER_HINT =
4221
4267
  'A hover/pointer capability read belongs in CSS: @media (pointer: coarse) in a module (the ' +
4222
- 'query --vx-hit keys on) — (hover: none) is not a sanctioned query.'
4268
+ 'query --vx-hit keys on) — (hover: none) is not a sanctioned query. A JS read (mount one of two ' +
4269
+ 'branches, a listener) is useCoarsePointer() from basalt-ui.'
4223
4270
 
4224
4271
  const RAW_BREAKPOINT_BASE_ONLY_MESSAGE =
4225
4272
  'Responsive-object prop with only a base key — there is no breakpoint in it at all, so drop the ' +
@@ -1923,20 +1923,21 @@ describe('basalt/control-outside-home', () => {
1923
1923
  },
1924
1924
  )
1925
1925
 
1926
- // FormRow/FormGroup (`basalt-ui/forms`) are law C1's third home written for a `<form>` rather
1927
- // than a settings page — the same non-home treatment SettingsRow gets, via the same
1928
- // CONTROL_HOST_TAGS ancestry walk.
1929
- it.each(['FormRow', 'FormGroup'])(
1930
- 'does NOT flag a raw Select nested inside a %s imported from basalt-ui/forms',
1931
- (host) => {
1932
- const { code, rules } = run(
1933
- `import { ${host} } from 'basalt-ui/forms'\n${MANTINE_IMPORT}` +
1934
- `export const C = () => <${host} label="L"><Select data={[]} /></${host}>\n`,
1935
- )
1936
- expect(code).toBe(0)
1937
- expect(rules).not.toContain('control-outside-home')
1938
- },
1939
- )
1926
+ // FormRow/FormGroup (`basalt-ui/controls`, still re-exported from `basalt-ui/forms` until 1.37.0)
1927
+ // are law C1's third home written for a `<form>` rather than a settings page — the same non-home
1928
+ // treatment SettingsRow gets, via the same CONTROL_HOST_TAGS ancestry walk, which matches by NAME
1929
+ // so either subpath is a home.
1930
+ it.each(
1931
+ ['FormRow', 'FormGroup'].flatMap((host) =>
1932
+ ['basalt-ui/controls', 'basalt-ui/forms'].map((subpath) => [host, subpath] as const),
1933
+ ),
1934
+ )('does NOT flag a raw Select nested inside a %s imported from %s', (host, subpath) => {
1935
+ const { rules } = run(
1936
+ `import { ${host} } from '${subpath}'\n${MANTINE_IMPORT}` +
1937
+ `export const C = () => <${host} label="L"><Select data={[]} /></${host}>\n`,
1938
+ )
1939
+ expect(rules).not.toContain('control-outside-home')
1940
+ })
1940
1941
 
1941
1942
  it('still flags a bare Select in a page body with no FormRow/FormGroup around it', () => {
1942
1943
  const { rules } = run(
@@ -2202,7 +2203,7 @@ describe('basalt/bound-control-outside-home', () => {
2202
2203
  // FormRow/FormGroup are the same declared non-home as SettingsRow — shared CONTROL_HOST_TAGS walk.
2203
2204
  it.each(['FormRow', 'FormGroup'])('does NOT flag one inside a %s', (host) => {
2204
2205
  const { code, rules } = run(
2205
- `import { ${host} } from 'basalt-ui/forms'\n${CONTROLS_IMPORT}` +
2206
+ `import { ${host} } from 'basalt-ui/controls'\n${CONTROLS_IMPORT}` +
2206
2207
  `export const C = () => <${host} label="L"><SelectFilter field={f} /></${host}>\n`,
2207
2208
  )
2208
2209
  expect(code).toBe(0)
@@ -3380,6 +3381,47 @@ describe('basalt/deprecated-export', () => {
3380
3381
  })
3381
3382
  })
3382
3383
 
3384
+ // 1.36.0: the form layout moved `basalt-ui/forms` → `basalt-ui/controls`. A move BETWEEN subpaths is
3385
+ // not a drop-in rename — the autofix only rewrites the specifier inside its own import statement —
3386
+ // so these rows carry `fix: false` and the message names the new home.
3387
+ describe('basalt/deprecated-export — the form layout move', () => {
3388
+ it.each(['FormSection', 'FormRow', 'FormGroup', 'FormActions'])(
3389
+ 'reports %s from basalt-ui/forms and names basalt-ui/controls',
3390
+ (name) => {
3391
+ const { rules, output } = run(
3392
+ `import { ${name} } from 'basalt-ui/forms'\nexport const x = ${name}\n`,
3393
+ )
3394
+ expect(rules).toContain('deprecated-export')
3395
+ expect(output).toContain(`'basalt-ui/controls'`)
3396
+ expect(output).toContain('removed in 1.37.0')
3397
+ },
3398
+ )
3399
+
3400
+ it.each(['useForm', 'schemaResolver'])('reports %s from basalt-ui/forms', (name) => {
3401
+ const { rules, output } = run(
3402
+ `import { ${name} } from 'basalt-ui/forms'\nexport const x = ${name}\n`,
3403
+ )
3404
+ expect(rules).toContain('deprecated-export')
3405
+ expect(output).toContain(`'@mantine/form'`)
3406
+ })
3407
+
3408
+ it('does NOT report FormRow imported from basalt-ui/controls', () => {
3409
+ const { rules } = run(
3410
+ `import { FormRow } from 'basalt-ui/controls'\nexport const x = FormRow\n`,
3411
+ )
3412
+ expect(rules).not.toContain('deprecated-export')
3413
+ })
3414
+
3415
+ it('never autofixes a cross-subpath row', () => {
3416
+ const source = `import { FormRow } from 'basalt-ui/forms'\nexport const x = FormRow\n`
3417
+ writeFileSync(resolve(dir, 'form-layout-fix.tsx'), source)
3418
+ Bun.spawnSync([OXLINT_BIN, '-c', '.oxlintrc.json', '--fix', 'form-layout-fix.tsx'], {
3419
+ cwd: dir,
3420
+ })
3421
+ expect(readFileSync(resolve(dir, 'form-layout-fix.tsx'), 'utf8')).toBe(source)
3422
+ })
3423
+ })
3424
+
3383
3425
  // ── forms-field-key ──────────────────────────────────────────────────────────
3384
3426
 
3385
3427
  describe('basalt/forms-field-key', () => {
@@ -3983,6 +4025,7 @@ describe('basalt/raw-breakpoint', () => {
3983
4025
  `export const useTouch = () => useMediaQuery('(hover: none)')\n`,
3984
4026
  )
3985
4027
  expect(output).toContain('@media (pointer: coarse)')
4028
+ expect(output).toContain('useCoarsePointer() from basalt-ui')
3986
4029
  })
3987
4030
 
3988
4031
  // obsidian r2 #7: a test-setup polyfill lives in `tests/`, not in a `.test` file.
@@ -1 +1 @@
1
- {"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import {
7
7
  legendSlotContention,
8
8
  plotBelowFloor
9
9
  } from "../../common/errors.js";
10
+ import { useCoarsePointer } from "../../common/use-media-query.js";
10
11
  import { reportOnce, useValidateProps } from "../../common/validate.js";
11
12
  import { SizeClassHintContext, useSizeClass } from "../../shell/use-size-class.js";
12
13
  import { VX } from "../../tokens/index.js";
@@ -15,7 +16,7 @@ import { deriveLegend } from "../series.js";
15
16
  import { ChartCardContext } from "./chart-card-context.js";
16
17
  import { resolveChartLayout, resolveFrameClass } from "./chart-layout.js";
17
18
  import { resolveFrameHeight, resolveLegendRollup, resolvePlotRect } from "./chart-frame-layout.js";
18
- import { ChartTierProvider, useCoarsePointer, useViewportHeight } from "./chart-tier.js";
19
+ import { ChartTierProvider, useViewportHeight } from "./chart-tier.js";
19
20
  import { ChartLegend } from "./ChartLegend.js";
20
21
  import { ChartEmpty, ChartError, ChartPending, resolveChartState } from "./ChartPending.js";
21
22
  import { legendEntryCap, resolveLegendRollup as resolveLegendRollup2, resolvePlotRect as resolvePlotRect2 } from "./chart-frame-layout.js";
@@ -1 +1 @@
1
- {"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAK5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;0EACsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAsOD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAkB,EAClB,WAAW,EACX,MAAkB,EAClB,QAAQ,EACR,OAAY,EACZ,SAAoB,EACpB,MAAc,EACd,OAAO,EACP,IAAc,EACd,SAAS,EACT,KAAK,GACN,EAAE,WAAW,GAAG;IACf,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C,kCAAkC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACxB,+BA2GA"}
1
+ {"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGrD,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAK5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;0EACsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAsOD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAkB,EAClB,WAAW,EACX,MAAkB,EAClB,QAAQ,EACR,OAAY,EACZ,SAAoB,EACpB,MAAc,EACd,OAAO,EACP,IAAc,EACd,SAAS,EACT,KAAK,GACN,EAAE,WAAW,GAAG;IACf,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C,kCAAkC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACxB,+BA2GA"}