basalt-ui 1.20.0 → 1.22.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 (89) hide show
  1. package/AGENTS.md +11 -5
  2. package/CHANGELOG.md +33 -0
  3. package/MIGRATING.md +231 -50
  4. package/README.md +57 -10
  5. package/agent/rules/basalt-app.md +49 -2
  6. package/agent/rules/basalt-charts.md +15 -12
  7. package/agent/rules/basalt-content.md +4 -0
  8. package/agent/rules/basalt-router.md +57 -5
  9. package/agent/rules/basalt-state.md +21 -3
  10. package/agent/rules/basalt-tokens.md +78 -16
  11. package/agent/skills/basalt-app/SKILL.md +5 -1
  12. package/agent/skills/basalt-charts/SKILL.md +5 -3
  13. package/agent/templates/CLAUDE-block.md.tpl +11 -5
  14. package/configs/lefthook.yml +48 -15
  15. package/configs/oxlint-plugin.js +218 -69
  16. package/configs/oxlint-plugin.test.ts +331 -9
  17. package/dist/charts/kinds/Bars.d.ts +1 -1
  18. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  19. package/dist/charts/kinds/Donut.d.ts +1 -1
  20. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  21. package/dist/charts/kinds/DualPanel.d.ts +1 -1
  22. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  23. package/dist/charts/kinds/Heatmap.d.ts +1 -1
  24. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  25. package/dist/charts/kinds/MultiLine.d.ts +1 -1
  26. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  27. package/dist/charts/kinds/StackedArea.d.ts +1 -1
  28. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  29. package/dist/charts/kinds/ZonedLine.d.ts +1 -1
  30. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  31. package/dist/cli/index.d.ts +55 -15
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +521 -71
  34. package/dist/cli/waiver-audit.test.d.ts +2 -0
  35. package/dist/cli/waiver-audit.test.d.ts.map +1 -0
  36. package/dist/guard/index.d.ts +114 -2
  37. package/dist/guard/index.d.ts.map +1 -1
  38. package/dist/guard/index.js +216 -33
  39. package/dist/router-tanstack/multi-search-param-store.d.ts +13 -0
  40. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  41. package/dist/router-tanstack/multi-search-param-store.js +4 -1
  42. package/dist/router-tanstack/search-param-store.d.ts +29 -5
  43. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  44. package/dist/router-tanstack/search-param-store.js +31 -3
  45. package/dist/router-tanstack/search-param-store.test.d.ts +2 -0
  46. package/dist/router-tanstack/search-param-store.test.d.ts.map +1 -0
  47. package/dist/shell/index.d.ts +12 -5
  48. package/dist/shell/index.d.ts.map +1 -1
  49. package/dist/shell/index.js +24 -6
  50. package/dist/tokens/index.d.ts +3 -1
  51. package/dist/tokens/index.d.ts.map +1 -1
  52. package/dist/tokens/index.js +5 -1
  53. package/dist/tokens/palette.d.ts +2 -2
  54. package/dist/tokens/palette.js +2 -2
  55. package/dist/tokens/text-scale.test.d.ts +2 -0
  56. package/dist/tokens/text-scale.test.d.ts.map +1 -0
  57. package/dist/tokens.css +4 -2
  58. package/dist/vite.d.ts +28 -3
  59. package/dist/vite.d.ts.map +1 -1
  60. package/dist/vite.js +75 -26
  61. package/llms.txt +2 -2
  62. package/package.json +1 -1
  63. package/src/charts/kinds/Bars.tsx +1 -1
  64. package/src/charts/kinds/Donut.tsx +1 -1
  65. package/src/charts/kinds/DualPanel.tsx +2 -2
  66. package/src/charts/kinds/Heatmap.tsx +1 -1
  67. package/src/charts/kinds/MultiLine.tsx +1 -1
  68. package/src/charts/kinds/StackedArea.tsx +1 -1
  69. package/src/charts/kinds/ZonedLine.tsx +1 -1
  70. package/src/cli/check-theme.test.ts +35 -0
  71. package/src/cli/help.test.ts +5 -1
  72. package/src/cli/index.ts +978 -133
  73. package/src/cli/init-sync.test.ts +2 -0
  74. package/src/cli/template-ledger.test.ts +1 -1
  75. package/src/cli/toolchain-wiring.test.ts +352 -1
  76. package/src/cli/waiver-audit.test.ts +211 -0
  77. package/src/guard/check-source.test.ts +638 -1
  78. package/src/guard/index.ts +556 -63
  79. package/src/router-tanstack/multi-search-param-store.ts +16 -1
  80. package/src/router-tanstack/search-param-store.test.ts +135 -0
  81. package/src/router-tanstack/search-param-store.ts +90 -8
  82. package/src/shell/app-mobile-nav.tsx +1 -1
  83. package/src/shell/index.test.tsx +52 -0
  84. package/src/shell/index.tsx +64 -13
  85. package/src/tokens/index.ts +24 -1
  86. package/src/tokens/palette.ts +2 -2
  87. package/src/tokens/text-scale.test.ts +51 -0
  88. package/src/vite.test.ts +158 -6
  89. package/src/vite.ts +176 -34
@@ -1,25 +1,53 @@
1
1
  import { createPersistedState, readPersistedValue } from "../state.js";
2
+ function warnLinkPinsFallback(input) {
3
+ const { key, param, urlValue, storedValue } = input;
4
+ console.warn(
5
+ `[basalt-ui] createSearchParamStore('${key}'): the URL pinned \`${param}=${urlValue}\` (the fallback) while '${storedValue}' was persisted, and nothing has read this store back. A nav link is almost certainly declaring \`search: { ${param}: '${urlValue}' }\` at module scope, which overrides the stored value on every click \u2014 pass the store's own reader instead: \`search: <store>.linkSearch\`. (dev only)`
6
+ );
7
+ }
2
8
  function createSearchParamStore(opts) {
3
9
  const version = opts.version ?? 1;
4
- const readStored = () => {
10
+ let readerWired = false;
11
+ let warned = false;
12
+ const read = () => {
5
13
  const raw = readPersistedValue(opts.key, version);
6
14
  if (typeof raw !== "string") return null;
7
15
  if (opts.values.includes(raw)) return raw;
8
16
  return null;
9
17
  };
18
+ const readStored = () => {
19
+ readerWired = true;
20
+ return read();
21
+ };
22
+ const linkSearch = () => {
23
+ readerWired = true;
24
+ return { [opts.param]: read() ?? opts.fallback };
25
+ };
10
26
  const validateSearch = (search) => {
11
27
  const raw = search[opts.param];
12
28
  if (typeof raw === "string" && opts.values.includes(raw)) {
29
+ if (!warned && !readerWired && raw === opts.fallback && process.env["NODE_ENV"] !== "production") {
30
+ const stored = read();
31
+ if (stored !== null && stored !== raw) {
32
+ warned = true;
33
+ warnLinkPinsFallback({
34
+ key: opts.key,
35
+ param: opts.param,
36
+ urlValue: raw,
37
+ storedValue: stored
38
+ });
39
+ }
40
+ }
13
41
  return { [opts.param]: raw };
14
42
  }
15
- return { [opts.param]: readStored() ?? opts.fallback };
43
+ return { [opts.param]: read() ?? opts.fallback };
16
44
  };
17
45
  const useStore = createPersistedState({
18
46
  key: opts.key,
19
47
  version,
20
48
  initial: opts.fallback
21
49
  });
22
- return { validateSearch, useStore, readStored };
50
+ return { validateSearch, useStore, readStored, linkSearch };
23
51
  }
24
52
  export {
25
53
  createSearchParamStore
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=search-param-store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-param-store.test.d.ts","sourceRoot":"","sources":["../../src/router-tanstack/search-param-store.test.ts"],"names":[],"mappings":""}
@@ -67,13 +67,20 @@ export type BasaltShellProps = {
67
67
  * `onOpen`, e.g. `() => openSpotlight()` from basalt-ui/commands.
68
68
  */
69
69
  search?: SidebarSearchConfig;
70
- /** localStorage key for the persisted sidebar-collapsed flag. Ignored when `collapsed` is set. */
70
+ /**
71
+ * localStorage key for the persisted sidebar-collapsed flag. Ignored when `collapsed` is set.
72
+ *
73
+ * Persisted through basalt's own `createPersistedState`, so the real storage key is
74
+ * `basalt:<storageKey>` and the value is the versioned `{ v, value }` envelope. A consumer
75
+ * mirroring this state reads it with `readPersistedValue(storageKey, 1)` from `basalt-ui/state`
76
+ * — never with a bare `localStorage.getItem`, which is what the pre-1.20.1 shell forced.
77
+ */
71
78
  storageKey?: string;
72
79
  /**
73
- * Controlled desktop-collapse value. When provided, the shell no longer owns the
74
- * `useLocalStorage`-persisted collapse state — the consumer does (e.g. to drive it from its own
75
- * `Cmd+B` hotkey). Pair with `onCollapsedChange` to receive toggle events; omitting both
76
- * reproduces today's internal, persisted collapse behavior unchanged.
80
+ * Controlled desktop-collapse value. When provided, the shell no longer owns the persisted
81
+ * collapse state — the consumer does (e.g. to drive it from its own `Cmd+B` hotkey), and should
82
+ * own it through `createPersistedState` too. Pair with `onCollapsedChange` to receive toggle
83
+ * events; omitting both reproduces today's internal, persisted collapse behavior unchanged.
77
84
  */
78
85
  collapsed?: boolean;
79
86
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.tsx"],"names":[],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { AppShell } from "@mantine/core";
3
- import { useLocalStorage } from "@mantine/hooks";
4
3
  import { useMemo } from "react";
5
4
  import { AppSidebar } from "./app-sidebar.js";
6
5
  import { MobileNav, accountRowCount } from "./app-mobile-nav.js";
@@ -8,6 +7,7 @@ import { projectMobileNav } from "./mobile-nav-model.js";
8
7
  import { AppBreadcrumbs } from "./app-breadcrumbs.js";
9
8
  import { PageActionsOutlet, PageHeaderProvider } from "./page-header.js";
10
9
  import { useBasaltSpacing } from "../theme/index.js";
10
+ import { createPersistedState } from "../state.js";
11
11
  import headerClasses from "./app-header.module.css";
12
12
  import mobileNavClasses from "./app-mobile-nav.module.css";
13
13
  import { AppSidebar as AppSidebar2 } from "./app-sidebar.js";
@@ -47,6 +47,28 @@ function findActiveWithParent(items, parentLabel, parentAnchor, parentHref) {
47
47
  }
48
48
  return void 0;
49
49
  }
50
+ const COLLAPSE_VERSION = 1;
51
+ const collapseStores = /* @__PURE__ */ new Map();
52
+ function migrateLegacyCollapse(key) {
53
+ try {
54
+ if (window.localStorage.getItem(`basalt:${key}`) !== null) return;
55
+ const legacy = window.localStorage.getItem(key);
56
+ if (legacy !== "true" && legacy !== "false") return;
57
+ window.localStorage.setItem(
58
+ `basalt:${key}`,
59
+ JSON.stringify({ v: COLLAPSE_VERSION, value: legacy === "true" })
60
+ );
61
+ } catch {
62
+ }
63
+ }
64
+ function collapseStore(key) {
65
+ const cached = collapseStores.get(key);
66
+ if (cached) return cached;
67
+ if (typeof window !== "undefined") migrateLegacyCollapse(key);
68
+ const store = createPersistedState({ key, version: COLLAPSE_VERSION, initial: false });
69
+ collapseStores.set(key, store);
70
+ return store;
71
+ }
50
72
  function BasaltShell({
51
73
  brand,
52
74
  sections,
@@ -62,11 +84,7 @@ function BasaltShell({
62
84
  children
63
85
  }) {
64
86
  const { step } = useBasaltSpacing();
65
- const [storedCollapsed, setStoredCollapsed] = useLocalStorage({
66
- key: storageKey,
67
- defaultValue: false,
68
- getInitialValueInEffect: false
69
- });
87
+ const [storedCollapsed, setStoredCollapsed] = collapseStore(storageKey)();
70
88
  const isCollapseControlled = collapsedProp !== void 0;
71
89
  const collapsed = isCollapseControlled ? collapsedProp : storedCollapsed;
72
90
  const toggleCollapse = () => {
@@ -40,6 +40,7 @@ export declare const VX: {
40
40
  readonly legendGap: number;
41
41
  readonly legendFontSize: 13.5;
42
42
  readonly text: {
43
+ readonly nano: 10;
43
44
  readonly micro: 11;
44
45
  readonly xs: 12.5;
45
46
  readonly sm: 13.5;
@@ -49,6 +50,7 @@ export declare const VX: {
49
50
  readonly h2: 21;
50
51
  readonly kpi: 24;
51
52
  readonly h1: 26;
53
+ readonly display: 30;
52
54
  };
53
55
  readonly radiusCard: "var(--vx-radius-card)";
54
56
  readonly radiusCtrl: "var(--vx-radius-ctrl)";
@@ -240,7 +242,7 @@ export type BuildPaletteOpts = {
240
242
  * `'core'` keeps the 9 generic anchors — the stack rhythm, the two control heights, the row
241
243
  * insets — and drops the 99 named for a specific basalt component
242
244
  * (`--vx-space-agent-transcript-inset`, `--vx-space-toc-sub-indent`, …), taking the emitted set
243
- * from 202 variables to 103. Nothing outside basalt's own React components reads those, so a
245
+ * from 204 variables to 105. Nothing outside basalt's own React components reads those, so a
244
246
  * framework-free consumer is carrying them for nothing.
245
247
  *
246
248
  * This is a SPACING filter only: color, radius, shadow, type and status variables are identical
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tokens/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAYH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAMvE,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,WAAW,CAAA;AAOjC,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AACnF,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAM5D,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAO7C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzC,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE5C,wFAAwF;AACxF,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,8FAA8F;AAC9F,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,EAAE,GAAG,MAAM,KAAG,MACoB,CAAA;AAsCrE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyIL,CAAA;AAEV,kFAAkF;AAClF,MAAM,MAAM,WAAW,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE;IACjC,4DAA4D;IAC5D,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,WAAW,CAUd;AAOD;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAExC;AAoXD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS,EACpD,GAAG,EAAE,CAAC,EACN,MAAM,SAAK,GACV;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM;CAAE,CAI5B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAEjE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS,EACnD,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,CAAC,GACL;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM;CAAE,CAE5B;AAED;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,2CAA2C;AAC3C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,+GAA+G;IAC/G,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAClC,kGAAkG;IAClG,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,EAAE,mBAAmB,CAAA;IAC5B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;IACzC;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACrB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,CAAA;AAYD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,GAAE,gBAAqB,EAC3B,IAAI,GAAE,WAAgC,GACrC,MAAM,CAqDR;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AASD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,SAAS,GAAG,MAAM,CAkB1E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,MAAM,CASvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,MAAM,CAGtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tokens/index.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,8 @@ import { deriveRadius } from "./palette.js";
14
14
  import { deriveSpacing } from "./palette.js";
15
15
  const alpha = (token, a) => `color-mix(in srgb, ${token} ${Math.round(a * 100)}%, transparent)`;
16
16
  const TEXT = {
17
+ nano: 10,
18
+ // engraved/graphic label INSIDE a drawn object (a plate face, a dial, a chip) — never UI copy or prose
17
19
  micro: 11,
18
20
  // mono uppercase micro-label (table th, Menu.Label, sidebar/section headers, axis ticks)
19
21
  xs: 12.5,
@@ -30,8 +32,10 @@ const TEXT = {
30
32
  // article-density Prose h2 (docs/CONTENT-SPEC.md §5)
31
33
  kpi: 24,
32
34
  // the StatCard hero numeral (density pass: 31 → 24, tighter/less shouty)
33
- h1: 26
35
+ h1: 26,
34
36
  // article-density Prose h1 (docs/CONTENT-SPEC.md §5)
37
+ display: 30
38
+ // a numeral read at arm's length (a keypad readout, a timer, a kiosk figure) — not a heading
35
39
  };
36
40
  const VX = {
37
41
  // Non-color sizing constants
@@ -112,11 +112,11 @@ export declare const SHADOW: {
112
112
  * ever needs more, raise the DROP. Do not reintroduce an edge; both shapes have been tried.
113
113
  */
114
114
  readonly raised: {
115
- readonly light: "0 1px 2px rgba(28, 25, 23, 0.10)";
115
+ readonly light: "0 1px 2px rgba(28, 25, 23, 0.1)";
116
116
  readonly dark: "0 1px 2px rgba(0, 0, 0, 0.35), inset 0 0 0 1px color-mix(in srgb, #ffffff 8%, transparent)";
117
117
  };
118
118
  readonly overlay: {
119
- readonly light: "0 8px 24px rgba(28, 25, 23, 0.10)";
119
+ readonly light: "0 8px 24px rgba(28, 25, 23, 0.1)";
120
120
  readonly dark: "0 8px 24px rgba(0, 0, 0, 0.5)";
121
121
  };
122
122
  };
@@ -72,7 +72,7 @@ const SHADOW = {
72
72
  * ever needs more, raise the DROP. Do not reintroduce an edge; both shapes have been tried.
73
73
  */
74
74
  raised: {
75
- light: "0 1px 2px rgba(28, 25, 23, 0.10)",
75
+ light: "0 1px 2px rgba(28, 25, 23, 0.1)",
76
76
  dark: "0 1px 2px rgba(0, 0, 0, 0.35), inset 0 0 0 1px color-mix(in srgb, #ffffff 8%, transparent)"
77
77
  },
78
78
  // Floating-layer elevation (menus, popovers, tooltips, modals, drawers) — a REAL drop shadow, a
@@ -80,7 +80,7 @@ const SHADOW = {
80
80
  // Unlike `card`, no embedded ring: floating surfaces carry a real 1px `--vx-surface-border` so
81
81
  // Mantine's rotated-square arrow inherits a proper edge.
82
82
  overlay: {
83
- light: "0 8px 24px rgba(28, 25, 23, 0.10)",
83
+ light: "0 8px 24px rgba(28, 25, 23, 0.1)",
84
84
  dark: "0 8px 24px rgba(0, 0, 0, 0.5)"
85
85
  }
86
86
  };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=text-scale.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-scale.test.d.ts","sourceRoot":"","sources":["../../src/tokens/text-scale.test.ts"],"names":[],"mappings":""}
package/dist/tokens.css CHANGED
@@ -138,6 +138,7 @@
138
138
  --vx-fill-hover-lime: color-mix(in srgb, var(--vx-fill-lime) 88%, #000);
139
139
  --vx-fill-hover-yellow: color-mix(in srgb, var(--vx-fill-yellow) 88%, #000);
140
140
  --vx-fill-hover-orange: color-mix(in srgb, var(--vx-fill-orange) 88%, #000);
141
+ --vx-text-nano: 10px;
141
142
  --vx-text-micro: 11px;
142
143
  --vx-text-xs: 12.5px;
143
144
  --vx-text-sm: 13.5px;
@@ -147,6 +148,7 @@
147
148
  --vx-text-h2: 21px;
148
149
  --vx-text-kpi: 24px;
149
150
  --vx-text-h1: 26px;
151
+ --vx-text-display: 30px;
150
152
  --vx-area-top: 22%;
151
153
  --vx-area-bottom: 1%;
152
154
  --vx-good: color-mix(in srgb, var(--vx-good-solid) 18%, transparent);
@@ -274,8 +276,8 @@ html[data-mantine-color-scheme='light'] {
274
276
  --vx-divider: color-mix(in srgb, #e5e5e5 65%, transparent);
275
277
  --vx-shadow-card: 0 1px 2px rgba(28, 25, 23, 0.05), 0 0 0 1px var(--vx-surface-hairline);
276
278
  --vx-shadow-ctrl: 0 1px 2px rgba(28, 25, 23, 0.12);
277
- --vx-shadow-raised: 0 1px 2px rgba(28, 25, 23, 0.10);
278
- --vx-shadow-overlay: 0 8px 24px rgba(28, 25, 23, 0.10);
279
+ --vx-shadow-raised: 0 1px 2px rgba(28, 25, 23, 0.1);
280
+ --vx-shadow-overlay: 0 8px 24px rgba(28, 25, 23, 0.1);
279
281
  --vx-surface-bg: #f2f2f5;
280
282
  --vx-surface-panel: #fafafd;
281
283
  --vx-surface-panel-hover: #ffffff;
package/dist/vite.d.ts CHANGED
@@ -49,7 +49,21 @@ export type BasaltAppOptions = {
49
49
  light: string;
50
50
  dark: string;
51
51
  };
52
- /** Manifest `background_color` + anti-FOUC paint color. Default: the resolved dark theme color. */
52
+ /**
53
+ * Which scheme the anti-FOUC `<style>` paints before the app boots. Default `'dark'` —
54
+ * `BasaltProvider`'s own default. `'light'` paints the light surface, `'auto'` declares
55
+ * `color-scheme: light dark` and picks the background off `prefers-color-scheme`, and `false`
56
+ * paints the background but declares no `color-scheme` at all (leave native controls to Mantine).
57
+ *
58
+ * Set this to whatever the app passes as `defaultColorScheme` — a mismatch is a visible flash of
59
+ * the wrong surface, not a correctness bug: the boot rule stops applying the moment Mantine
60
+ * writes `data-mantine-color-scheme` on `<html>` (see `BOOT_SCOPE`).
61
+ */
62
+ colorScheme?: 'dark' | 'light' | 'auto' | false;
63
+ /**
64
+ * Manifest `background_color` + anti-FOUC paint color. Default: the theme color matching
65
+ * `colorScheme` (the dark one for `'dark'` / `'auto'` / `false`, the light one for `'light'`).
66
+ */
53
67
  backgroundColor?: string;
54
68
  /** Manifest `display` mode. Default: `'standalone'`. */
55
69
  display?: 'standalone' | 'minimal-ui' | 'fullscreen' | 'browser';
@@ -66,7 +80,10 @@ export type BasaltAppOptions = {
66
80
  scope?: string;
67
81
  /** Manifest `id`. Default: the resolved `startUrl` (default- or explicitly-derived, per above). */
68
82
  id?: string;
69
- /** Icon links + manifest icon paths. `false` skips the head `<link>` icons entirely. */
83
+ /**
84
+ * Icon links + manifest icon paths. `false` skips BOTH — the head `<link>` icons and the
85
+ * manifest's `icons` array — so a manifest is never emitted pointing at files that 404.
86
+ */
70
87
  icons?: false | {
71
88
  dir?: string;
72
89
  };
@@ -104,13 +121,21 @@ export type BasaltAppOptions = {
104
121
  * manifest `theme_color` both need a FLAT color, so the flattening step is the contract this
105
122
  * plugin owes its output regardless of which shape the palette hands it.
106
123
  *
124
+ * Two behaviours worth knowing before adopting it, both of which the plugin owns because the
125
+ * consumer cannot reach them:
126
+ * • It HOISTS the shell's `<meta charset>` ahead of everything it injects, so the encoding
127
+ * declaration stays inside the spec's first 1024 bytes (see `hoistCharset`).
128
+ * • The anti-FOUC `<style>` expires the moment Mantine resolves the real scheme, and `colorScheme`
129
+ * selects which surface it paints until then (see `BOOT_SCOPE` / `buildBootStyle`).
130
+ *
107
131
  * @example
108
132
  * // vite.config.ts
109
133
  * import { basaltAppPlugin, basaltViteConfig } from 'basalt-ui/vite'
110
134
  *
111
135
  * export default {
112
136
  * ...basaltViteConfig({ port: 5173 }),
113
- * plugins: [react(), ...basaltAppPlugin({ name: 'Argo' })],
137
+ * // colorScheme mirrors <BasaltProvider defaultColorScheme> — omit it for the dark default.
138
+ * plugins: [react(), ...basaltAppPlugin({ name: 'Argo', colorScheme: 'auto' })],
114
139
  * }
115
140
  */
116
141
  export declare function basaltAppPlugin(options: BasaltAppOptions): PluginOption[];
@@ -1 +1 @@
1
- {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAA6B,YAAY,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAI/E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AA6CD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,UAAU,CAgFpE;AAgBD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gGAAgG;IAChG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACrD,mGAAmG;IACnG,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wDAAwD;IACxD,OAAO,CAAC,EAAE,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAA;IAChE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mGAAmG;IACnG,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,wFAAwF;IACxF,KAAK,CAAC,EAAE,KAAK,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChC,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAC3B,oFAAoF;IACpF,GAAG,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,SAAS,GAAG,qBAAqB,CAAA;KAAE,CAAA;IACvF,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClD,CAAA;AAuZD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,EAAE,CAMzE"}
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAA6B,YAAY,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAI/E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AA6CD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,UAAU,CAgFpE;AAgBD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gGAAgG;IAChG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACrD;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAA;IAC/C;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wDAAwD;IACxD,OAAO,CAAC,EAAE,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAA;IAChE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mGAAmG;IACnG,EAAE,CAAC,EAAE,MAAM,CAAA;IACX;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChC,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAC3B,oFAAoF;IACpF,GAAG,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,SAAS,GAAG,qBAAqB,CAAA;KAAE,CAAA;IACvF,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClD,CAAA;AA4gBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,EAAE,CAMzE"}
package/dist/vite.js CHANGED
@@ -140,6 +140,29 @@ const VIEWPORT_TAG = {
140
140
  tag: "meta",
141
141
  attrs: { name: "viewport", content: "width=device-width, initial-scale=1.0, viewport-fit=cover" }
142
142
  };
143
+ const BOOT_SCOPE = "html:not([data-mantine-color-scheme])";
144
+ const BOOT_COLOR_SCHEME = { dark: "dark", light: "light", auto: "light dark" };
145
+ function buildBootStyle(input) {
146
+ const { colorScheme, themeColor, backgroundColor, explicitBackground } = input;
147
+ if (colorScheme === false) return `${BOOT_SCOPE}{background-color:${backgroundColor}}`;
148
+ const scheme = BOOT_COLOR_SCHEME[colorScheme];
149
+ if (colorScheme !== "auto" || explicitBackground) {
150
+ return `${BOOT_SCOPE}{background-color:${backgroundColor};color-scheme:${scheme}}`;
151
+ }
152
+ return `${BOOT_SCOPE}{background-color:${themeColor.light};color-scheme:${scheme}}@media(prefers-color-scheme:dark){${BOOT_SCOPE}{background-color:${themeColor.dark}}}`;
153
+ }
154
+ const CHARSET_META_RE = /[ \t]*<meta\s+charset\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>=]+))\s*\/?>[ \t]*\r?\n?/i;
155
+ const HTTP_EQUIV_CHARSET_RE = /<meta\s[^>]*http-equiv\s*=\s*["']?content-type["']?[^>]*>/i;
156
+ const DEFAULT_CHARSET = "UTF-8";
157
+ function hoistCharset(html) {
158
+ if (HTTP_EQUIV_CHARSET_RE.test(html)) return { html, tag: void 0 };
159
+ const match = CHARSET_META_RE.exec(html);
160
+ const charset = match?.[1] ?? match?.[2] ?? match?.[3] ?? DEFAULT_CHARSET;
161
+ return {
162
+ html: match ? html.replace(CHARSET_META_RE, "") : html,
163
+ tag: { tag: "meta", attrs: { charset } }
164
+ };
165
+ }
143
166
  function buildSeoTags(input) {
144
167
  const { name, description, seo } = input;
145
168
  const tags = [];
@@ -183,7 +206,7 @@ function buildStaticTags(input) {
183
206
  options,
184
207
  shortName,
185
208
  themeColor,
186
- backgroundColor,
209
+ bootStyle,
187
210
  iconsEnabled,
188
211
  iconsDir,
189
212
  darkreader,
@@ -210,10 +233,7 @@ function buildStaticTags(input) {
210
233
  }
211
234
  }
212
235
  );
213
- tags.push({
214
- tag: "style",
215
- children: `html{background-color:${backgroundColor};color-scheme:dark}`
216
- });
236
+ tags.push({ tag: "style", children: bootStyle });
217
237
  if (iconsEnabled) {
218
238
  tags.push(
219
239
  {
@@ -274,8 +294,9 @@ function buildManifestJson(input) {
274
294
  scope,
275
295
  id,
276
296
  display,
277
- themeColor,
297
+ schemeColor,
278
298
  backgroundColor,
299
+ iconsEnabled,
279
300
  iconsDir,
280
301
  base
281
302
  } = input;
@@ -287,21 +308,29 @@ function buildManifestJson(input) {
287
308
  start_url: startUrl,
288
309
  scope,
289
310
  display,
290
- theme_color: themeColor.dark,
311
+ // A manifest carries ONE theme color, so it follows the boot scheme rather than the dual
312
+ // `<meta name="theme-color">` pair. Unchanged for the default (`'dark'`) and for `'auto'`.
313
+ theme_color: schemeColor,
291
314
  background_color: backgroundColor,
292
- icons: [
293
- {
294
- src: withBase(base, withIconPath(iconsDir, ICON_FILES.manifest192)),
295
- sizes: "192x192",
296
- type: "image/png"
297
- },
298
- {
299
- src: withBase(base, withIconPath(iconsDir, ICON_FILES.manifest512)),
300
- sizes: "512x512",
301
- type: "image/png",
302
- purpose: "any maskable"
303
- }
304
- ]
315
+ // `icons: false` reaches HERE too, not just the head links. The array was unconditional, so
316
+ // `{ manifest: true, icons: false }` shipped a manifest pointing at two PNGs the app never
317
+ // ships — an installable app with broken icons, and the reason rb went hybrid (plugin for the
318
+ // head, a hand-written manifest for the rest). A manifest with NO icons member is valid.
319
+ ...iconsEnabled ? {
320
+ icons: [
321
+ {
322
+ src: withBase(base, withIconPath(iconsDir, ICON_FILES.manifest192)),
323
+ sizes: "192x192",
324
+ type: "image/png"
325
+ },
326
+ {
327
+ src: withBase(base, withIconPath(iconsDir, ICON_FILES.manifest512)),
328
+ sizes: "512x512",
329
+ type: "image/png",
330
+ purpose: "any maskable"
331
+ }
332
+ ]
333
+ } : {}
305
334
  };
306
335
  return JSON.stringify(manifest, null, 2);
307
336
  }
@@ -313,7 +342,15 @@ function createMainPlugin(options) {
313
342
  const iconsDir = options.icons === false ? void 0 : options.icons?.dir;
314
343
  const manifestEnabled = options.manifest !== false;
315
344
  const themeColor = resolveThemeColors(options.themeColor);
316
- const backgroundColor = options.backgroundColor ?? themeColor.dark;
345
+ const colorScheme = options.colorScheme ?? "dark";
346
+ const schemeColor = colorScheme === "light" ? themeColor.light : themeColor.dark;
347
+ const backgroundColor = options.backgroundColor ?? schemeColor;
348
+ const bootStyle = buildBootStyle({
349
+ colorScheme,
350
+ themeColor,
351
+ backgroundColor,
352
+ explicitBackground: options.backgroundColor !== void 0
353
+ });
317
354
  let manifestJson = "";
318
355
  let manifestHref = "/site.webmanifest";
319
356
  let staticTags = [];
@@ -332,8 +369,9 @@ function createMainPlugin(options) {
332
369
  scope,
333
370
  id,
334
371
  display,
335
- themeColor,
372
+ schemeColor,
336
373
  backgroundColor,
374
+ iconsEnabled,
337
375
  iconsDir,
338
376
  base
339
377
  });
@@ -341,7 +379,7 @@ function createMainPlugin(options) {
341
379
  options,
342
380
  shortName,
343
381
  themeColor,
344
- backgroundColor,
382
+ bootStyle,
345
383
  iconsEnabled,
346
384
  iconsDir,
347
385
  darkreader,
@@ -350,9 +388,20 @@ function createMainPlugin(options) {
350
388
  manifestHref
351
389
  });
352
390
  },
353
- transformIndexHtml(html) {
354
- const tags = hasViewportMeta(html) ? staticTags : [VIEWPORT_TAG, ...staticTags];
355
- return { html, tags };
391
+ transformIndexHtml: {
392
+ // `pre` so this plugin's tags are the FIRST head-prepend contributions Vite collects — that
393
+ // ordering is what puts the hoisted <meta charset> at the very top of <head> rather than
394
+ // behind another plugin's tags. Vite injects all collected tags after every hook has run.
395
+ order: "pre",
396
+ handler(html) {
397
+ const hoisted = hoistCharset(html);
398
+ const tags = [
399
+ ...hoisted.tag ? [hoisted.tag] : [],
400
+ ...hasViewportMeta(html) ? [] : [VIEWPORT_TAG],
401
+ ...staticTags
402
+ ];
403
+ return { html: hoisted.html, tags };
404
+ }
356
405
  },
357
406
  generateBundle() {
358
407
  if (!manifestEnabled) return;
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # basalt-ui v1.20.0
1
+ # basalt-ui v1.22.0
2
2
  # Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
3
3
  #
4
4
  # basalt-ui is an opinionated framework for Mantine-based React apps.
@@ -19,7 +19,7 @@
19
19
  # ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
20
20
 
21
21
  Package: basalt-ui
22
- Version: 1.20.0
22
+ Version: 1.22.0
23
23
 
24
24
  ## basalt-ui
25
25
  Import: import { ... } from 'basalt-ui'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basalt-ui",
3
- "version": "1.20.0",
3
+ "version": "1.22.0",
4
4
  "description": "Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets",
5
5
  "keywords": [
6
6
  "charts",
@@ -125,7 +125,7 @@ export type BarsProps<T> = {
125
125
  /** Legend config forwarded to `CartesianChart`; `false` disables the legend (sparkline escape).
126
126
  * Default `{ placement: 'bottom' }`. */
127
127
  legend?: ChartLegendConfig | false
128
- /** Accessible text alternative, forwarded as `aria-label` (+ `role="img"`). */
128
+ /** Accessible text alternative, forwarded as `aria-label` (+ `role="group"`). */
129
129
  ariaLabel?: string
130
130
  /** Forwarded to `CartesianChart` — see `ChartPending`'s JSDoc for the three-state rationale. */
131
131
  isPending?: boolean
@@ -50,7 +50,7 @@ export type DonutProps<K extends string = SeriesKey> = {
50
50
  centerContent?: ReactNode
51
51
  innerRatio?: number
52
52
  padAngle?: number
53
- /** Accessible text alternative, forwarded to `ChartFrame` as `aria-label` (+ `role="img"`). */
53
+ /** Accessible text alternative, forwarded to `ChartFrame` as `aria-label` (+ `role="group"`). */
54
54
  ariaLabel?: string
55
55
  /** Forwarded to `ChartFrame` — see `ChartPending`'s JSDoc for the three-state rationale. */
56
56
  isPending?: boolean
@@ -94,7 +94,7 @@ export type DualPanelProps<T> = {
94
94
  /** Legend config forwarded to `ChartFrame`; `false` disables the legend (sparkline escape).
95
95
  * Default `{ placement: 'bottom' }`. */
96
96
  legend?: ChartLegendConfig | false
97
- /** Accessible text alternative, forwarded to `ChartFrame` as `aria-label` (+ `role="img"`). */
97
+ /** Accessible text alternative, forwarded to `ChartFrame` as `aria-label` (+ `role="group"`). */
98
98
  ariaLabel?: string
99
99
  /** Forwarded to `ChartFrame` — see `ChartPending`'s JSDoc for the three-state rationale. */
100
100
  isPending?: boolean
@@ -521,7 +521,7 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
521
521
  </>
522
522
  )}
523
523
 
524
- {/* theme-allow basalt/hand-rolled-plot: TWO panes over one x scale is not a single
524
+ {/* theme-allow-file basalt/hand-rolled-plot: TWO panes over one x scale is not a single
525
525
  cartesian plot, so `CartesianChart` (one plot rect, one or two y axes) cannot express
526
526
  it. This file therefore assembles the plot itself — but from the SAME parts every
527
527
  other chart gets: `ChartFrame`, `autoMargin` + `probeAxisLabels`, `useChartCursor`,
@@ -53,7 +53,7 @@ export type HeatmapProps<T> = {
53
53
  renderTooltip?: (cell: HeatmapCell) => ReactNode
54
54
  /** Optional gradient legend strip below the grid (faint → solid color). */
55
55
  legend?: { min: string; max: string }
56
- /** Accessible text alternative, forwarded to `ChartFrame` as `aria-label` (+ `role="img"`). */
56
+ /** Accessible text alternative, forwarded to `ChartFrame` as `aria-label` (+ `role="group"`). */
57
57
  ariaLabel?: string
58
58
  /** Forwarded to `ChartFrame` — see `ChartPending`'s JSDoc for the three-state rationale. */
59
59
  isPending?: boolean
@@ -52,7 +52,7 @@ export type MultiLineProps<T> = {
52
52
  /** Legend config forwarded to `CartesianChart`; `false` disables the legend (sparkline escape).
53
53
  * Default `{ placement: 'bottom' }`. */
54
54
  legend?: ChartLegendConfig | false
55
- /** Accessible text alternative, forwarded to `CartesianChart` as `aria-label` (+ `role="img"`). */
55
+ /** Accessible text alternative, forwarded to `CartesianChart` as `aria-label` (+ `role="group"`). */
56
56
  ariaLabel?: string
57
57
  /** Forwarded to `CartesianChart` — see `ChartPending`'s JSDoc for the three-state rationale. */
58
58
  isPending?: boolean
@@ -33,7 +33,7 @@ export type StackedAreaProps<T> = {
33
33
  /** Legend config forwarded to `CartesianChart`; `false` disables the legend (sparkline escape).
34
34
  * Default `{ placement: 'bottom' }`. */
35
35
  legend?: ChartLegendConfig | false
36
- /** Accessible text alternative, forwarded as `aria-label` (+ `role="img"`). */
36
+ /** Accessible text alternative, forwarded as `aria-label` (+ `role="group"`). */
37
37
  ariaLabel?: string
38
38
  /** Forwarded to `CartesianChart` — see `ChartPending`'s JSDoc for the three-state rationale. */
39
39
  isPending?: boolean
@@ -73,7 +73,7 @@ export type ZonedLineProps<T> = {
73
73
  /** Legend config forwarded to `CartesianChart`; `false` disables the legend (sparkline escape).
74
74
  * Default `{ placement: 'bottom' }`. */
75
75
  legend?: ChartLegendConfig | false
76
- /** Accessible text alternative, forwarded to `CartesianChart` as `aria-label` (+ `role="img"`). */
76
+ /** Accessible text alternative, forwarded to `CartesianChart` as `aria-label` (+ `role="group"`). */
77
77
  ariaLabel?: string
78
78
  /** Forwarded to `CartesianChart` — see `ChartPending`'s JSDoc for the three-state rationale. */
79
79
  isPending?: boolean