basalt-ui 1.19.0 → 1.20.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 (83) hide show
  1. package/AGENTS.md +15 -1
  2. package/CHANGELOG.md +27 -0
  3. package/MIGRATING.md +228 -0
  4. package/README.md +53 -9
  5. package/agent/rules/basalt-app.md +43 -0
  6. package/agent/rules/basalt-charts.md +26 -14
  7. package/agent/rules/basalt-mantine.md +25 -10
  8. package/agent/rules/basalt-router.md +22 -10
  9. package/agent/rules/basalt-tokens.md +75 -6
  10. package/agent/skills/basalt-app/SKILL.md +42 -6
  11. package/configs/check.yml +11 -4
  12. package/configs/lefthook.yml +21 -2
  13. package/configs/oxlint-plugin.js +515 -25
  14. package/configs/oxlint-plugin.test.ts +383 -10
  15. package/configs/oxlint.json +10 -1
  16. package/dist/agent/stick-to-bottom.js +1 -1
  17. package/dist/agent-chat/thread-detail-panel.js +1 -1
  18. package/dist/agent-chat/thread-message.js +2 -2
  19. package/dist/agent-chat/tool-chip.js +1 -1
  20. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  21. package/dist/charts/kinds/Donut.js +1 -1
  22. package/dist/charts/primitives/ChartLegend.js +2 -2
  23. package/dist/charts/primitives/ChartTooltip.js +1 -1
  24. package/dist/cli/index.d.ts +107 -15
  25. package/dist/cli/index.d.ts.map +1 -1
  26. package/dist/cli/index.js +610 -72
  27. package/dist/cli/toolchain-wiring.test.d.ts +2 -0
  28. package/dist/cli/toolchain-wiring.test.d.ts.map +1 -0
  29. package/dist/dashboard/delta-badge.js +2 -2
  30. package/dist/dashboard/stat-card.js +1 -1
  31. package/dist/data/virtual-list.js +5 -5
  32. package/dist/guard/index.d.ts +56 -2
  33. package/dist/guard/index.d.ts.map +1 -1
  34. package/dist/guard/index.js +326 -59
  35. package/dist/guard/types.d.ts +25 -5
  36. package/dist/guard/types.d.ts.map +1 -1
  37. package/dist/register.d.ts +25 -0
  38. package/dist/register.d.ts.map +1 -1
  39. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  40. package/dist/shell/app-mobile-nav.js +14 -14
  41. package/dist/shell/app-mobile-nav.module.css +44 -11
  42. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -1
  43. package/dist/surfaces.d.ts +1 -1
  44. package/dist/surfaces.js +1 -1
  45. package/dist/tokens/index.d.ts +3 -3
  46. package/dist/tokens/index.d.ts.map +1 -1
  47. package/dist/tokens/index.js +2 -0
  48. package/dist/tokens/palette.d.ts +35 -27
  49. package/dist/tokens/palette.d.ts.map +1 -1
  50. package/dist/tokens/palette.js +24 -18
  51. package/dist/tokens.css +19 -17
  52. package/dist/vite-color-mix.js +2 -2
  53. package/llms.txt +3 -3
  54. package/package.json +4 -3
  55. package/src/agent/stick-to-bottom.tsx +1 -1
  56. package/src/agent-chat/thread-detail-panel.tsx +1 -1
  57. package/src/agent-chat/thread-message.tsx +3 -3
  58. package/src/agent-chat/tool-chip.tsx +1 -1
  59. package/src/charts/kinds/Donut.tsx +2 -1
  60. package/src/charts/primitives/ChartLegend.tsx +2 -2
  61. package/src/charts/primitives/ChartTooltip.tsx +1 -1
  62. package/src/cli/index.ts +1070 -99
  63. package/src/cli/init-sync.test.ts +19 -3
  64. package/src/cli/tokens-css.test.ts +27 -14
  65. package/src/cli/toolchain-wiring.test.ts +694 -0
  66. package/src/dashboard/delta-badge.tsx +3 -3
  67. package/src/dashboard/stat-card.tsx +1 -1
  68. package/src/data/virtual-list.tsx +8 -8
  69. package/src/guard/check-source.test.ts +663 -11
  70. package/src/guard/index.ts +727 -94
  71. package/src/guard/types.ts +29 -4
  72. package/src/register.ts +32 -0
  73. package/src/shell/app-mobile-nav.module.css +44 -11
  74. package/src/shell/app-mobile-nav.module.css.d.ts +1 -1
  75. package/src/shell/app-mobile-nav.test.tsx +34 -0
  76. package/src/shell/app-mobile-nav.tsx +16 -5
  77. package/src/surfaces.ts +1 -1
  78. package/src/theme/density-relations.test.ts +8 -5
  79. package/src/theme/spacing.test.ts +2 -0
  80. package/src/theme-toggle/index.tsx +2 -2
  81. package/src/tokens/index.ts +5 -3
  82. package/src/tokens/palette.ts +24 -18
  83. package/src/vite-color-mix.ts +2 -2
@@ -1,7 +1,7 @@
1
1
  const HEX = /#[0-9a-fA-F]{3,8}\b/g;
2
- const FUNC = /\b(?:rgba?|hsla?)\(/g;
2
+ const FUNC = /\b(?:rgba?|hsla?)\((?!\s*\$\{)/g;
3
3
  const LOCALSTORAGE_THEME = /localStorage\s*\.\s*getItem\s*\(\s*['"]theme['"]\s*\)/g;
4
- const RAW_FONT_FAMILY_VAR_ESCAPE = /var\(\s*--(?:basalt-font-|mantine-font-family-)/.source;
4
+ const RAW_FONT_FAMILY_VAR_ESCAPE = /var\(\s*--/.source;
5
5
  const RAW_FONT_FAMILY_KEYWORD = /(?:inherit|initial|unset|revert)\b/.source;
6
6
  const RAW_FONT_FAMILY = new RegExp(
7
7
  `\\bfontFamily\\s*[:=]\\s*\\{?(?!['"\`]?\\s*${RAW_FONT_FAMILY_VAR_ESCAPE})(?!['"\`]?\\s*${RAW_FONT_FAMILY_KEYWORD})['"\`][^'"\`]+['"\`]|\\bfont-family\\s*:\\s*(?!['"\`]?\\s*${RAW_FONT_FAMILY_VAR_ESCAPE})(?!['"\`]?\\s*${RAW_FONT_FAMILY_KEYWORD})(?:['"\`][^'"\`]+['"\`]|[A-Za-z][\\w-]*)`,
@@ -9,14 +9,42 @@ const RAW_FONT_FAMILY = new RegExp(
9
9
  );
10
10
  const SURFACE_BORDER = /\bborder(?:Top|Bottom|Left|Right)?\s*:\s*(?!['"`]?[^'"`]*(?:var\(|\$\{))(?!['"`]?(?:none|transparent|inherit|unset|revert)\b)['"`][^'"`]+['"`]/g;
11
11
  const SURFACE_RADIUS = /\bborderRadius\s*:\s*(?:[0-9]+|['"`](?!\s*var\()[^'"`]*[0-9])/g;
12
- const SURFACE_SHADOW = /\bboxShadow\s*:\s*(?!['"`]?[^'"`]*(?:var\(|\$\{))['"`][^'"`]+['"`]/g;
12
+ const SHADOW_DECL = /\b(?:boxShadow|box-shadow)\s*:\s*(['"`])((?:[^'"`\\]|\\.)*)\1/g;
13
+ const SHADOW_KEEPS_CARD_DEPTH = /--vx-shadow-|\bVX\.shadow/;
14
+ const SHADOW_IS_COMPOSED = /var\(|\$\{/;
15
+ const CSS_SURFACE_RADIUS = /(?<![\w-])border-radius\s*:\s*([^;}]+)/g;
16
+ const CSS_SURFACE_SHADOW = /(?<![\w-])box-shadow\s*:\s*([^;}]+)/g;
17
+ const MICRO_RADIUS_CEILING_PX = 3;
18
+ const SHAPE_RADIUS_VALUES = /* @__PURE__ */ new Set(["50%", "9999px", "999px", "100%"]);
19
+ function isAllowedCssRadius(value) {
20
+ const rest = value.replace(/var\([^)]*\)/g, " ").trim();
21
+ if (rest === "") return true;
22
+ return rest.split(/\s+/).every((part) => {
23
+ if (SHAPE_RADIUS_VALUES.has(part)) return true;
24
+ const literal = /^[-+]?(\d+(?:\.\d+)?|\.\d+)(px|rem)?$/.exec(part);
25
+ if (literal === null) return false;
26
+ const px = literal[2] === "rem" ? Number(literal[1]) * ROOT_FONT_SIZE_PX : Number(literal[1]);
27
+ return px <= MICRO_RADIUS_CEILING_PX;
28
+ });
29
+ }
30
+ const INLINE_FONT_SIZE = /(?<![\w-;])font-?[Ss]ize\s*:\s*['"]?(\d+(?:\.\d+)?)(?:px)?['"]?(?=[,;\s}]|$)/g;
13
31
  const CARD_SURFACE_TAG = /<(?:Card|Paper)(?![\w.])(?:=>|[^>])*?>/g;
14
32
  const WITH_BORDER_PROP = /\bwithBorder\b(?!\s*=\s*\{\s*false\s*\})/;
15
33
  const OFF_SYSTEM_SURFACE_VAR = /var\(--mantine-color-(gray|dark)-\d/g;
16
34
  const MANTINE_SHADE_INDEX = /\b(?:color|c|bg|backgroundColor|borderColor)\s*=\s*\{?\s*['"][a-z]+\.\d\b|var\(--mantine-color-(?!gray-|dark-)[a-z]+-\d/g;
17
- const RAW_HTML_TAG = /<(?:div|span|section|header|nav|footer|aside|main|article|ul|ol)\b/;
35
+ const RAW_HTML_TAG_NAME = "<(?:div|span|section|header|nav|footer|aside|main|article|ul|ol)\\b";
36
+ const RAW_HTML_TAG = new RegExp(RAW_HTML_TAG_NAME);
37
+ const RAW_HTML_LAYOUT_TAG = new RegExp(`${RAW_HTML_TAG_NAME}(?:=>|[^>])*?>`, "g");
18
38
  const INLINE_STYLE = /style=\{\{/;
39
+ const STYLE_IDENTIFIER = /style=\{\s*([A-Za-z_$][\w$]*)\s*\}/;
19
40
  const LAYOUT_SURFACE_PROP = /\b(?:display|padding|margin|gap|flex|grid|border|background|width|height)\b/;
41
+ function hoistedObjectBody(codeText, name) {
42
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
43
+ const decl = new RegExp(`\\b(?:const|let|var)\\s+${escaped}\\b[^=;]*=\\s*\\{([^}]*)\\}`).exec(
44
+ codeText
45
+ );
46
+ return decl?.[1] ?? "";
47
+ }
20
48
  const BOX_SIDE = "(?:Top|Bottom|Left|Right|Block|Inline)(?:Start|End)?";
21
49
  const BOX_SIDE_KEBAB = "(?:top|bottom|left|right|block|inline)(?:-(?:start|end))?";
22
50
  const SPACING_PROP = `(?:padding|margin)(?:${BOX_SIDE}|-${BOX_SIDE_KEBAB})?|gap|rowGap|columnGap|row-gap|column-gap`;
@@ -77,9 +105,15 @@ const DEFAULT_GUARD_CONFIG = {
77
105
  exemptRules: {},
78
106
  severity: {}
79
107
  };
80
- const GRACE_PERIOD_KINDS = {};
108
+ const GRACE_PERIOD_KINDS = {
109
+ "theme-allow-unscoped": "introduced in the round-4 guard minor \u2014 promote to error one minor later. Every consumer has bare `theme-allow` comments today (basalt itself has ~20), and landing this as an error would fail every one of their builds on an upgrade they took for the fixes.",
110
+ "surface-shadow-override": "introduced in the round-4 guard minor \u2014 promote to error one minor later. Catches the shape a TOKEN-FLUENT consumer writes, so the code it rejects is code that looks correct and passed every previous release.",
111
+ "css-raw-surface": 'introduced in the round-4 guard minor \u2014 promote to error one minor later. The surface kinds reaching CSS at all is the "existing kind, new file type" case the doctrine names explicitly.',
112
+ "inline-font-size": "introduced in the round-4 guard minor \u2014 promote to error one minor later. The oxlint plugin has enforced the same thing as `basalt/no-raw-font-size` for a while, so a consumer running both sees nothing new; one running check-theme alone gets a minor of runway.",
113
+ "hidden-inline-style": "introduced in the round-4 guard minor \u2014 promote by DELETING the kind and folding its two scans into `raw-html-layout`, which is where they belong once the widening has had its minor."
114
+ };
81
115
  function severityOf(kind, cfg) {
82
- return cfg.severity?.[kind] ?? (kind in GRACE_PERIOD_KINDS ? "warn" : "error");
116
+ return cfg.severity?.[kind] ?? (Object.hasOwn(GRACE_PERIOD_KINDS, kind) ? "warn" : "error");
83
117
  }
84
118
  const FINDING_TEXT_MAX_LENGTH = 100;
85
119
  function textForFinding(sourceLine, fallbackToken) {
@@ -90,18 +124,166 @@ function textForFinding(sourceLine, fallbackToken) {
90
124
  function isChartFile(relPath) {
91
125
  return relPath.includes("/charts/") && !AXIS_WRAPPER_FILE.test(relPath);
92
126
  }
127
+ const EXEMPT_RULE_ALIASES = {
128
+ "hidden-inline-style": "raw-html-layout",
129
+ "surface-shadow-override": "raw-surface",
130
+ "css-raw-surface": "raw-surface"
131
+ };
93
132
  function isRuleExempt(kind, relPath, exemptRules) {
133
+ const alias = EXEMPT_RULE_ALIASES[kind];
134
+ if (alias !== void 0 && isRuleExempt(alias, relPath, exemptRules)) return true;
94
135
  const patterns = exemptRules?.[kind];
95
136
  if (patterns === void 0 || patterns.length === 0) return false;
96
137
  const segments = relPath.split("/");
97
138
  return patterns.some((pattern) => segments.includes(pattern.replace(/\/$/, "")));
98
139
  }
99
- function isSkippedLine(line, cfg) {
100
- return line.includes(cfg.allowComment);
140
+ const PLUGIN_RULE_IDS = /* @__PURE__ */ new Set([
141
+ "no-raw-font-size",
142
+ "raw-size-literal",
143
+ "card-inset",
144
+ "chart-in-raw-surface",
145
+ "hand-rolled-plot",
146
+ "chart-legend-literal",
147
+ "hand-rolled-shell",
148
+ "shadow-basalt-export",
149
+ "raw-scroll-container",
150
+ "visx-boundary",
151
+ "visx-tooltip",
152
+ "token-layer-boundary",
153
+ // These three honour `basalt-agent-allow`, never `theme-allow` — but they are still REAL ids, so
154
+ // a `theme-allow ai-sdk-major` must parse as a (useless) scoped annotation rather than as prose.
155
+ "agent-resume-guard",
156
+ "agent-no-raw-usechat",
157
+ "ai-sdk-major"
158
+ ]);
159
+ const MIN_ALLOW_REASON_LENGTH = 4;
160
+ function parseAllowAnnotation(rest) {
161
+ const rules = [];
162
+ const unknownRules = [];
163
+ let remainder = rest.replace(/^[\s,]+/, "");
164
+ let inIdSlot = true;
165
+ for (; ; ) {
166
+ const token = /^(?:basalt\/)?([a-z][a-z0-9-]*)(?=$|[\s,:—–])/.exec(remainder);
167
+ const id = token?.[1];
168
+ if (token === null || id === void 0) break;
169
+ if (!(Object.hasOwn(GUARD_RULES, id) || PLUGIN_RULE_IDS.has(id))) {
170
+ if (inIdSlot) unknownRules.push(id);
171
+ break;
172
+ }
173
+ rules.push(id);
174
+ const after = remainder.slice(token[0].length);
175
+ remainder = after.replace(/^[\s,]+/, "");
176
+ inIdSlot = /^\s*,/.test(after);
177
+ }
178
+ const reason = remainder.replace(/^(?:—|–|-{1,2}|:)\s*/, "").trim();
179
+ return { rules, unknownRules, hasReason: reason.length >= MIN_ALLOW_REASON_LENGTH };
180
+ }
181
+ const MAX_CSS_CONTINUATION_LINES = 8;
182
+ const COMMENT_OPENER_BEFORE = /(?:\/\/|\/\*|<!--)\s*$/;
183
+ const COMMENT_ONLY_LINE = /^\s*(?:\{\s*\/\*|\/\/|\/\*|\*|<!--)/;
184
+ const ANNOTATION_PREFIX = /(?:^|\/\/|\/\*|<!--|^\s*\*)\s*$/;
185
+ function collectAllowAnnotations(lines, codeLines, allowComment, syntax) {
186
+ const byLine = /* @__PURE__ */ new Map();
187
+ const declared = [];
188
+ const add = (index, annotation) => {
189
+ const list = byLine.get(index) ?? [];
190
+ list.push(annotation);
191
+ byLine.set(index, list);
192
+ };
193
+ for (let i = 0; i < lines.length; i++) {
194
+ const line = lines[i] ?? "";
195
+ const stripped = codeLines[i] ?? "";
196
+ const isCommentOnlyLine = stripped.trim() === "" || COMMENT_ONLY_LINE.test(line);
197
+ let from = 0;
198
+ for (; ; ) {
199
+ const at = line.indexOf(allowComment, from);
200
+ if (at === -1) break;
201
+ from = at + allowComment.length;
202
+ if (stripped[at] !== " " && !COMMENT_OPENER_BEFORE.test(line.slice(0, at))) continue;
203
+ const annotation = parseAllowAnnotation(line.slice(from));
204
+ if (ANNOTATION_PREFIX.test(line.slice(0, at))) declared.push({ line: i + 1, annotation });
205
+ add(i, annotation);
206
+ if (isCommentOnlyLine) {
207
+ add(i + 1, annotation);
208
+ continue;
209
+ }
210
+ if (syntax !== "css") continue;
211
+ for (let back = 1; back <= MAX_CSS_CONTINUATION_LINES; back++) {
212
+ const previous = (codeLines[i - back] ?? "").trim();
213
+ if (previous === "" || /[;{}]$/.test(previous)) break;
214
+ add(i - back, annotation);
215
+ }
216
+ }
217
+ }
218
+ return { waivers: byLine, declared };
219
+ }
220
+ function annotationsCover(annotations, kind) {
221
+ return (annotations ?? []).some(
222
+ (a) => a.rules.length === 0 && a.unknownRules.length === 0 || a.rules.includes(kind)
223
+ );
101
224
  }
102
225
  function guardSyntaxFor(relPath) {
103
- return relPath.endsWith(".css") ? "css" : "ts";
226
+ if (relPath.endsWith(".css")) return "css";
227
+ if (/\.(?:html?|webmanifest|json)$/.test(relPath)) return "markup";
228
+ return "ts";
229
+ }
230
+ const MARKUP_KINDS = /* @__PURE__ */ new Set(["raw-hex", "raw-color-fn", "raw-font-family"]);
231
+ function stripMarkupComments(text) {
232
+ return text.replace(/<!--[\s\S]*?-->/g, (m) => m.replace(/[^\n]/g, " "));
233
+ }
234
+ const GENERATED_HEADER_LINE = "/* @generated basalt-ui \u2014 do not edit; regenerate with the command on the next line */";
235
+ const GENERATED_PROVENANCE_LINE = /^\/\* basalt-ui \d+\.\d+\.\d+[^\s]* — `basalt-ui (?:tokens:css|fonts:css)[^`]*` \*\/$/;
236
+ const GENERATED_HEADER_LINES = 2;
237
+ const GENERATED_COMMENT_LINE = /^\/\*(?:[^*]|\*(?!\/))*\*\/$/;
238
+ const GENERATED_SELECTOR_LINE = /^[^{};]*[,{]$/;
239
+ const GENERATED_DECL_LINE = /^(?:--(?:vx|basalt)-[A-Za-z0-9_-]*\s*:[^{};]*;|\})$/;
240
+ const NO_GENERATED_LINES = /* @__PURE__ */ new Set();
241
+ function braceDelta(line) {
242
+ let delta = 0;
243
+ for (const ch of line) {
244
+ if (ch === "{") delta++;
245
+ else if (ch === "}") delta--;
246
+ }
247
+ return delta;
104
248
  }
249
+ function generatedTokenLines(relPath, lines, allowComment) {
250
+ if (!relPath.endsWith(".css")) return NO_GENERATED_LINES;
251
+ if ((lines[0] ?? "").trim() !== GENERATED_HEADER_LINE) return NO_GENERATED_LINES;
252
+ if (!GENERATED_PROVENANCE_LINE.test((lines[1] ?? "").trim())) return NO_GENERATED_LINES;
253
+ const skippable = /* @__PURE__ */ new Set([1, 2]);
254
+ let depth = 0;
255
+ for (let i = GENERATED_HEADER_LINES; i < lines.length; i++) {
256
+ const line = lines[i] ?? "";
257
+ const trimmed = line.trim();
258
+ const shaped = trimmed === "" || GENERATED_COMMENT_LINE.test(trimmed) || (depth === 0 ? GENERATED_SELECTOR_LINE : GENERATED_DECL_LINE).test(trimmed);
259
+ if (shaped && !line.includes(allowComment)) skippable.add(i + 1);
260
+ depth += braceDelta(line);
261
+ if (depth < 0) return NO_GENERATED_LINES;
262
+ }
263
+ return skippable;
264
+ }
265
+ const TOKENS_ONLY_DISABLED_KINDS = /* @__PURE__ */ new Set([
266
+ "localstorage-theme",
267
+ "off-identity-accent",
268
+ "mantine-shade-index",
269
+ "raw-spacing",
270
+ "raw-radius",
271
+ "card-with-border",
272
+ "off-system-surface-var",
273
+ "raw-html-layout",
274
+ // Same remedy as raw-html-layout, word for word ("use a Mantine layout primitive") — it is that
275
+ // kind's widening, so it has to share its gate here exactly as it shares it in
276
+ // EXEMPT_RULE_ALIASES. Splitting them told a Mantine-free consumer to import Box/Flex.
277
+ "hidden-inline-style",
278
+ "inline-spacing",
279
+ "inline-display",
280
+ "raw-visx-axis",
281
+ "raw-motion-value",
282
+ "unframed-chart",
283
+ "chart-missing-aria-label",
284
+ "raw-form-control",
285
+ "sub-16-input-font"
286
+ ]);
105
287
  const REGEX_PRECEDING_PUNCT = /* @__PURE__ */ new Set([
106
288
  "=",
107
289
  "(",
@@ -419,6 +601,41 @@ const GUARD_RULES = {
419
601
  // object literal — both JSX-shaped, neither appears in CSS text.
420
602
  appliesTo: (relPath) => !relPath.endsWith(".css"),
421
603
  message: "fontSize below 16 on a form control is dead code \u2014 the styles.css iOS floor is `!important` and always wins. Either drop the override, or be honest about 16px in the design."
604
+ },
605
+ "theme-allow-unscoped": {
606
+ kind: "theme-allow-unscoped",
607
+ pattern: /theme-allow/,
608
+ // handled inline (annotation pass); entry keeps the registry complete
609
+ message: "theme-allow without a usable rule id and a reason. Write `theme-allow <rule-id> \u2014 <why>`: the id scopes the exception to that one kind (a bare comment waives EVERY kind on the line, including ones added later), and the reason is what makes it reviewable in a diff. An id that names no rule is a typo, not a blanket waiver \u2014 it suppresses nothing."
610
+ },
611
+ "surface-shadow-override": {
612
+ kind: "surface-shadow-override",
613
+ pattern: SHADOW_DECL,
614
+ // handled inline (value is judged, not just matched)
615
+ enabled: (cfg) => cfg.rawSurface,
616
+ message: "Token-composed boxShadow that REPLACES card depth \u2014 a card whose shadow drops --vx-shadow-card is the one card in the app with no depth. Compose with it (`${VX.shadowCard}, <your ring>`) rather than instead of it."
617
+ },
618
+ "css-raw-surface": {
619
+ kind: "css-raw-surface",
620
+ pattern: CSS_SURFACE_RADIUS,
621
+ // handled inline (value is judged); entry keeps the registry complete
622
+ enabled: (cfg) => cfg.rawSurface,
623
+ // The kebab dialect only exists in CSS text; the camelCase half is `raw-surface`.
624
+ appliesTo: (relPath) => relPath.endsWith(".css"),
625
+ message: "Surface literal in CSS \u2014 use var(--vx-radius-card|--vx-radius-md) for a corner and var(--vx-shadow-card) for depth, the same tokens the TSX side is held to."
626
+ },
627
+ "hidden-inline-style": {
628
+ kind: "hidden-inline-style",
629
+ pattern: RAW_HTML_LAYOUT_TAG,
630
+ // handled inline (full-text tag-scoped scan + hoisted-const lookup)
631
+ enabled: (cfg) => cfg.rawHtmlLayout,
632
+ appliesTo: (relPath) => !isChartFile(relPath) && !relPath.endsWith(".css"),
633
+ message: "Raw HTML element with inline layout/surface styling that the line scan cannot see \u2014 the tag is formatted across lines, or the style object is hoisted to a const. Same violation as raw-html-layout: use a Mantine layout primitive (Box/Flex/Grid/Stack/Group). Its own kind for one minor so the widening lands as a warning; it merges into raw-html-layout at the promotion."
634
+ },
635
+ "inline-font-size": {
636
+ kind: "inline-font-size",
637
+ pattern: INLINE_FONT_SIZE,
638
+ message: "Raw font-size literal \u2014 route through VX.text.* (numbers) / --vx-text-* (CSS), or the Mantine fz token. Same rule the oxlint plugin enforces as basalt/no-raw-font-size; this is the half a check-theme-only CI can see."
422
639
  }
423
640
  };
424
641
  function ruleApplies(kind, relPath) {
@@ -426,6 +643,8 @@ function ruleApplies(kind, relPath) {
426
643
  return appliesTo === void 0 || appliesTo(relPath);
427
644
  }
428
645
  function checkSource(text, relPath, cfg) {
646
+ const lines = text.split("\n");
647
+ const generatedLines = generatedTokenLines(relPath, lines, cfg.allowComment);
429
648
  const findings = [];
430
649
  const forbiddenAccentRe = GUARD_RULES["off-identity-accent"].pattern(cfg);
431
650
  const spacingPropRe = GUARD_RULES["raw-spacing"].pattern(cfg);
@@ -433,101 +652,157 @@ function checkSource(text, relPath, cfg) {
433
652
  GUARD_RULES["raw-radius"].pattern.source,
434
653
  GUARD_RULES["raw-radius"].pattern.flags
435
654
  );
436
- const lines = text.split("\n");
437
655
  const syntax = guardSyntaxFor(relPath);
438
- const codeText = stripComments(text, syntax);
656
+ const codeText = syntax === "markup" ? stripMarkupComments(text) : stripComments(text, syntax);
439
657
  const codeLines = codeText.split("\n");
658
+ const { waivers, declared } = collectAllowAnnotations(lines, codeLines, cfg.allowComment, syntax);
659
+ const isAllowed = (line, kind) => annotationsCover(waivers.get(line - 1), kind);
660
+ const isAllowedInRange = (start, end, kind) => {
661
+ for (let n = start; n <= end; n++) if (isAllowed(n, kind)) return true;
662
+ return false;
663
+ };
664
+ const push = (kind, line, token) => {
665
+ if (isAllowed(line, kind)) return;
666
+ findings.push({ relPath, line, token, kind });
667
+ };
668
+ for (const { line, annotation } of declared) {
669
+ if (annotation.unknownRules.length === 0 && annotation.rules.length > 0 && annotation.hasReason)
670
+ continue;
671
+ const unknown = `unknown rule id '${annotation.unknownRules.join("', '")}'`;
672
+ const missing = annotation.unknownRules.length > 0 ? annotation.rules.length === 0 ? `${unknown} \u2014 waives nothing` : `${unknown} \u2014 not waived` : annotation.rules.length === 0 ? "no rule id" : "no reason";
673
+ findings.push({
674
+ relPath,
675
+ line,
676
+ token: `${cfg.allowComment} (${missing})`,
677
+ kind: "theme-allow-unscoped"
678
+ });
679
+ }
680
+ const rawHtmlLayoutLines = /* @__PURE__ */ new Set();
440
681
  for (let i = 0; i < lines.length; i++) {
441
682
  const line = codeLines[i] ?? "";
442
- if ((lines[i] ?? "").includes(cfg.allowComment)) continue;
443
683
  for (const m of line.matchAll(GUARD_RULES["raw-hex"].pattern)) {
444
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-hex" });
684
+ push("raw-hex", i + 1, m[0]);
445
685
  }
446
686
  for (const m of line.matchAll(GUARD_RULES["raw-color-fn"].pattern)) {
447
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-color-fn" });
687
+ push("raw-color-fn", i + 1, m[0]);
448
688
  }
449
689
  if (ruleApplies("localstorage-theme", relPath)) {
450
690
  for (const m of line.matchAll(GUARD_RULES["localstorage-theme"].pattern)) {
451
- findings.push({ relPath, line: i + 1, token: m[0], kind: "localstorage-theme" });
691
+ push("localstorage-theme", i + 1, m[0]);
452
692
  }
453
693
  }
454
694
  for (const m of line.matchAll(GUARD_RULES["raw-font-family"].pattern)) {
455
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-font-family" });
695
+ push("raw-font-family", i + 1, m[0]);
696
+ }
697
+ for (const m of line.matchAll(GUARD_RULES["inline-font-size"].pattern)) {
698
+ push("inline-font-size", i + 1, m[0]);
456
699
  }
457
700
  if (ruleApplies("off-identity-accent", relPath)) {
458
701
  for (const m of line.matchAll(forbiddenAccentRe)) {
459
- findings.push({ relPath, line: i + 1, token: m[1] ?? "", kind: "off-identity-accent" });
702
+ push("off-identity-accent", i + 1, m[1] ?? "");
460
703
  }
461
704
  }
462
705
  if (ruleApplies("raw-spacing", relPath)) {
463
- for (const m of line.matchAll(spacingPropRe)) {
464
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-spacing" });
465
- }
706
+ for (const m of line.matchAll(spacingPropRe)) push("raw-spacing", i + 1, m[0]);
466
707
  }
467
708
  if (GUARD_RULES["raw-radius"].enabled(cfg) && ruleApplies("raw-radius", relPath)) {
468
- for (const m of line.matchAll(radiusPropRe)) {
469
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-radius" });
470
- }
709
+ for (const m of line.matchAll(radiusPropRe)) push("raw-radius", i + 1, m[0]);
471
710
  }
472
711
  if (GUARD_RULES["raw-surface"].enabled(cfg)) {
473
- for (const m of line.matchAll(SURFACE_BORDER)) {
474
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-surface" });
475
- }
476
- for (const m of line.matchAll(SURFACE_RADIUS)) {
477
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-surface" });
478
- }
479
- for (const m of line.matchAll(SURFACE_SHADOW)) {
480
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-surface" });
712
+ for (const m of line.matchAll(SURFACE_BORDER)) push("raw-surface", i + 1, m[0]);
713
+ for (const m of line.matchAll(SURFACE_RADIUS)) push("raw-surface", i + 1, m[0]);
714
+ if (syntax === "css") {
715
+ for (const m of line.matchAll(CSS_SURFACE_RADIUS)) {
716
+ const value = m[1] ?? "";
717
+ if (!/\d/.test(value) || isAllowedCssRadius(value)) continue;
718
+ push("css-raw-surface", i + 1, m[0].trim());
719
+ }
720
+ for (const m of line.matchAll(CSS_SURFACE_SHADOW)) {
721
+ const value = m[1] ?? "";
722
+ if (SHADOW_IS_COMPOSED.test(value) || /^\s*none\s*$/.test(value)) continue;
723
+ push("css-raw-surface", i + 1, m[0].trim());
724
+ }
725
+ } else {
726
+ for (const m of line.matchAll(SHADOW_DECL)) {
727
+ const value = m[2] ?? "";
728
+ if (!SHADOW_IS_COMPOSED.test(value)) {
729
+ push("raw-surface", i + 1, m[0]);
730
+ continue;
731
+ }
732
+ if (SHADOW_KEEPS_CARD_DEPTH.test(value)) continue;
733
+ push("surface-shadow-override", i + 1, m[0]);
734
+ }
481
735
  }
482
736
  }
483
737
  if (GUARD_RULES["off-system-surface-var"].enabled(cfg)) {
484
738
  for (const m of line.matchAll(GUARD_RULES["off-system-surface-var"].pattern)) {
485
- findings.push({ relPath, line: i + 1, token: m[0], kind: "off-system-surface-var" });
739
+ push("off-system-surface-var", i + 1, m[0]);
486
740
  }
487
741
  }
488
742
  if (GUARD_RULES["mantine-shade-index"].enabled(cfg)) {
489
743
  for (const m of line.matchAll(GUARD_RULES["mantine-shade-index"].pattern)) {
490
- findings.push({ relPath, line: i + 1, token: m[0], kind: "mantine-shade-index" });
744
+ push("mantine-shade-index", i + 1, m[0]);
491
745
  }
492
746
  }
493
747
  if (GUARD_RULES["raw-html-layout"].enabled(cfg) && ruleApplies("raw-html-layout", relPath) && RAW_HTML_TAG.test(line) && INLINE_STYLE.test(line) && LAYOUT_SURFACE_PROP.test(line)) {
494
- findings.push({ relPath, line: i + 1, token: "<raw-html style>", kind: "raw-html-layout" });
748
+ rawHtmlLayoutLines.add(i + 1);
749
+ push("raw-html-layout", i + 1, "<raw-html style>");
495
750
  }
496
751
  if (GUARD_RULES["inline-spacing"].enabled(cfg)) {
497
752
  for (const m of line.matchAll(GUARD_RULES["inline-spacing"].pattern)) {
498
753
  if (syntax === "css" && isSubScaleCssSpacing(line, m.index)) continue;
499
- findings.push({ relPath, line: i + 1, token: m[0], kind: "inline-spacing" });
754
+ push("inline-spacing", i + 1, m[0]);
500
755
  }
501
756
  }
502
757
  if (GUARD_RULES["inline-display"].enabled(cfg) && ruleApplies("inline-display", relPath)) {
503
758
  for (const m of line.matchAll(GUARD_RULES["inline-display"].pattern)) {
504
- findings.push({ relPath, line: i + 1, token: m[0], kind: "inline-display" });
759
+ push("inline-display", i + 1, m[0]);
505
760
  }
506
761
  }
507
762
  if (GUARD_RULES["raw-visx-axis"].enabled(cfg) && GUARD_RULES["raw-visx-axis"].appliesTo(relPath)) {
508
763
  for (const m of line.matchAll(GUARD_RULES["raw-visx-axis"].pattern)) {
509
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-visx-axis" });
764
+ push("raw-visx-axis", i + 1, m[0]);
510
765
  }
511
766
  }
512
767
  if (GUARD_RULES["raw-motion-value"].enabled(cfg) && ruleApplies("raw-motion-value", relPath)) {
513
- for (const m of line.matchAll(MOTION_TRANSITION_NUMERIC)) {
514
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-motion-value" });
515
- }
768
+ for (const m of line.matchAll(MOTION_TRANSITION_NUMERIC))
769
+ push("raw-motion-value", i + 1, m[0]);
516
770
  for (const m of line.matchAll(MOTION_TRANSITION_EASE_ARRAY)) {
517
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-motion-value" });
771
+ push("raw-motion-value", i + 1, m[0]);
518
772
  }
519
773
  }
520
774
  if (GUARD_RULES["raw-form-control"].enabled(cfg) && ruleApplies("raw-form-control", relPath)) {
521
775
  for (const m of line.matchAll(GUARD_RULES["raw-form-control"].pattern)) {
522
- findings.push({ relPath, line: i + 1, token: m[0], kind: "raw-form-control" });
776
+ push("raw-form-control", i + 1, m[0]);
523
777
  }
524
778
  }
525
779
  }
780
+ if (GUARD_RULES["hidden-inline-style"].enabled(cfg) && ruleApplies("hidden-inline-style", relPath)) {
781
+ for (const m of codeText.matchAll(RAW_HTML_LAYOUT_TAG)) {
782
+ const tagText = m[0];
783
+ let styleBody = "";
784
+ if (INLINE_STYLE.test(tagText)) styleBody = tagText;
785
+ else {
786
+ const ident = STYLE_IDENTIFIER.exec(tagText)?.[1];
787
+ if (ident !== void 0) styleBody = hoistedObjectBody(codeText, ident);
788
+ }
789
+ if (styleBody === "" || !LAYOUT_SURFACE_PROP.test(styleBody)) continue;
790
+ const startLine = codeText.slice(0, m.index ?? 0).split("\n").length;
791
+ if (rawHtmlLayoutLines.has(startLine)) continue;
792
+ const endLine = startLine + (tagText.split("\n").length - 1);
793
+ if (isAllowedInRange(startLine, endLine, "hidden-inline-style")) continue;
794
+ findings.push({
795
+ relPath,
796
+ line: startLine,
797
+ token: "<raw-html style>",
798
+ kind: "hidden-inline-style"
799
+ });
800
+ }
801
+ }
526
802
  if (GUARD_RULES["unframed-chart"].enabled(cfg) && ruleApplies("unframed-chart", relPath)) {
527
803
  for (const m of codeText.matchAll(RAW_CHART_LEGEND_ARRAY)) {
528
804
  const lineNo = codeText.slice(0, (m.index ?? 0) + m[0].length).split("\n").length;
529
- if (isSkippedLine(lines[lineNo - 1] ?? "", cfg)) continue;
530
- findings.push({ relPath, line: lineNo, token: "items={[", kind: "unframed-chart" });
805
+ push("unframed-chart", lineNo, "items={[");
531
806
  }
532
807
  }
533
808
  if (GUARD_RULES["chart-missing-aria-label"].enabled(cfg) && ruleApplies("chart-missing-aria-label", relPath)) {
@@ -536,14 +811,7 @@ function checkSource(text, relPath, cfg) {
536
811
  if (HAS_ARIA_LABEL_PROP.test(tagText)) continue;
537
812
  const startLine = codeText.slice(0, m.index ?? 0).split("\n").length;
538
813
  const endLine = startLine + (tagText.split("\n").length - 1);
539
- let skipped = false;
540
- for (let n = startLine; n <= endLine; n++) {
541
- if (isSkippedLine(lines[n - 1] ?? "", cfg)) {
542
- skipped = true;
543
- break;
544
- }
545
- }
546
- if (skipped) continue;
814
+ if (isAllowedInRange(startLine, endLine, "chart-missing-aria-label")) continue;
547
815
  findings.push({
548
816
  relPath,
549
817
  line: startLine,
@@ -557,8 +825,7 @@ function checkSource(text, relPath, cfg) {
557
825
  const withBorder = WITH_BORDER_PROP.exec(m[0]);
558
826
  if (withBorder === null) continue;
559
827
  const lineNo = codeText.slice(0, (m.index ?? 0) + withBorder.index).split("\n").length;
560
- if (isSkippedLine(lines[lineNo - 1] ?? "", cfg)) continue;
561
- findings.push({ relPath, line: lineNo, token: "withBorder", kind: "card-with-border" });
828
+ push("card-with-border", lineNo, "withBorder");
562
829
  }
563
830
  }
564
831
  if (GUARD_RULES["sub-16-input-font"].enabled(cfg) && ruleApplies("sub-16-input-font", relPath)) {
@@ -570,8 +837,7 @@ function checkSource(text, relPath, cfg) {
570
837
  const value = Number.parseFloat(fontSizeMatch[1] ?? "");
571
838
  if (!Number.isFinite(value) || value >= 16) continue;
572
839
  const lineNo = codeText.slice(0, (m.index ?? 0) + tagText.length).split("\n").length;
573
- if (isSkippedLine(lines[lineNo - 1] ?? "", cfg)) continue;
574
- findings.push({ relPath, line: lineNo, token: fontSizeMatch[0], kind: "sub-16-input-font" });
840
+ push("sub-16-input-font", lineNo, fontSizeMatch[0]);
575
841
  }
576
842
  for (const m of codeText.matchAll(STYLES_INPUT_PART)) {
577
843
  const inputBody = m[1] ?? "";
@@ -580,11 +846,10 @@ function checkSource(text, relPath, cfg) {
580
846
  const value = Number.parseFloat(fontSizeMatch[1] ?? "");
581
847
  if (!Number.isFinite(value) || value >= 16) continue;
582
848
  const lineNo = codeText.slice(0, (m.index ?? 0) + m[0].length).split("\n").length;
583
- if (isSkippedLine(lines[lineNo - 1] ?? "", cfg)) continue;
584
- findings.push({ relPath, line: lineNo, token: fontSizeMatch[0], kind: "sub-16-input-font" });
849
+ push("sub-16-input-font", lineNo, fontSizeMatch[0]);
585
850
  }
586
851
  }
587
- return findings.filter((f) => !isRuleExempt(f.kind, f.relPath, cfg.exemptRules)).map((f) => ({
852
+ return findings.filter((f) => !generatedLines.has(f.line)).filter((f) => !isRuleExempt(f.kind, f.relPath, cfg.exemptRules)).filter((f) => syntax !== "markup" || MARKUP_KINDS.has(f.kind)).filter((f) => cfg.profile !== "tokens-only" || !TOKENS_ONLY_DISABLED_KINDS.has(f.kind)).map((f) => ({
588
853
  ...f,
589
854
  text: textForFinding(lines[f.line - 1], f.token),
590
855
  severity: severityOf(f.kind, cfg)
@@ -592,6 +857,8 @@ function checkSource(text, relPath, cfg) {
592
857
  }
593
858
  export {
594
859
  DEFAULT_GUARD_CONFIG,
860
+ GENERATED_HEADER_LINE,
595
861
  GUARD_RULES,
862
+ TOKENS_ONLY_DISABLED_KINDS,
596
863
  checkSource
597
864
  };
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Guard types — Mantine-free, dependency-free (zero imports beyond TS types).
3
3
  *
4
- * GuardKind is the closed set of 20 violation kinds the theme guard can emit.
4
+ * GuardKind is the closed set of 25 violation kinds the theme guard can emit.
5
5
  * Finding is the structured result per violation, replacing the old `Violation` shape.
6
6
  * GuardConfig is the per-run configuration that drives checkSource.
7
7
  */
8
- /** The 20 theme-guard violation kinds. */
9
- export type GuardKind = 'raw-hex' | 'raw-color-fn' | 'localstorage-theme' | 'off-identity-accent' | 'mantine-shade-index' | 'raw-spacing' | 'raw-radius' | 'raw-surface' | 'card-with-border' | 'off-system-surface-var' | 'raw-html-layout' | 'inline-spacing' | 'inline-display' | 'raw-visx-axis' | 'raw-motion-value' | 'unframed-chart' | 'chart-missing-aria-label' | 'raw-form-control' | 'sub-16-input-font' | 'raw-font-family';
8
+ /** The 25 theme-guard violation kinds. */
9
+ export type GuardKind = 'raw-hex' | 'raw-color-fn' | 'localstorage-theme' | 'off-identity-accent' | 'mantine-shade-index' | 'raw-spacing' | 'raw-radius' | 'raw-surface' | 'card-with-border' | 'off-system-surface-var' | 'raw-html-layout' | 'inline-spacing' | 'inline-display' | 'raw-visx-axis' | 'raw-motion-value' | 'unframed-chart' | 'chart-missing-aria-label' | 'raw-form-control' | 'sub-16-input-font' | 'raw-font-family' | 'theme-allow-unscoped' | 'surface-shadow-override' | 'css-raw-surface' | 'inline-font-size' | 'hidden-inline-style';
10
10
  /**
11
11
  * How hard a finding lands. `error` fails the build; `warn` reports and passes.
12
12
  *
@@ -127,10 +127,30 @@ export type GuardConfig = {
127
127
  */
128
128
  readonly sub16InputFont: boolean;
129
129
  /**
130
- * Allow-comment policy: a line containing this substring is skipped entirely.
131
- * Default 'theme-allow'. Pure-comment lines (// * /\*) are always skipped.
130
+ * Allow-comment policy. The token that opens an exception annotation inside a COMMENT, on the
131
+ * finding's own line or the line directly above it (matching the oxlint plugin's placement, and
132
+ * surviving an `oxfmt` reflow that moves the comment off the offending line). Default
133
+ * `'theme-allow'`.
134
+ *
135
+ * The accountable form is `theme-allow <rule-id>[, <rule-id>] — <reason>`: the rule ids scope the
136
+ * exception to those kinds only, and the reason says why. A `theme-allow` missing either half
137
+ * still suppresses (so an upgrade never breaks a build over it) but reports
138
+ * {@link GuardKind} `theme-allow-unscoped` on its own line.
132
139
  */
133
140
  readonly allowComment: string;
141
+ /**
142
+ * Which kinds apply to this consumer. `'mantine'` (default) is the full framework guard.
143
+ *
144
+ * `'tokens-only'` is for a consumer that installs the TOKEN LAYER and nothing else — no
145
+ * `@mantine/core`, often no React (`basalt-ui tokens:css` + `basalt-ui/tokens`). Every kind whose
146
+ * remedy is a Mantine component or prop is disabled there, because "use `TextInput` from
147
+ * `@mantine/core`" is not advice, it is a dependency the consumer deliberately does not have.
148
+ * The color/typography kinds stay on — those are the tokens, which is the whole surface they DO
149
+ * consume. See `TOKENS_ONLY_DISABLED_KINDS` in `./index` for the exact set.
150
+ *
151
+ * Optional so a caller built before this field existed keeps compiling; absent means `'mantine'`.
152
+ */
153
+ readonly profile?: 'mantine' | 'tokens-only';
134
154
  /**
135
155
  * Per-rule, per-path exemptions — complements whole-file `exempt` (BasaltConfig, which skips
136
156
  * ALL rules for a file) and code-level `appliesTo` (hardcoded per-kind path scoping, e.g.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/guard/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,0CAA0C;AAC1C,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,cAAc,GACd,oBAAoB,GACpB,qBAAqB,GACrB,qBAAqB,GACrB,aAAa,GACb,YAAY,GACZ,aAAa,GACb,kBAAkB,GAClB,wBAAwB,GACxB,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,eAAe,GACf,kBAAkB,GAClB,gBAAgB,GAChB,0BAA0B,GAC1B,kBAAkB,GAClB,mBAAmB,GACnB,iBAAiB,CAAA;AAErB;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAA;AAE5C;;;;;;;GAOG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;CACjC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,wGAAwG;IACxG,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5C;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAA;IACnC,iGAAiG;IACjG,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC,2GAA2G;IAC3G,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAA;IACrC,sGAAsG;IACtG,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,4FAA4F;IAC5F,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,yFAAyF;IACzF,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,kGAAkG;IAClG,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAA;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;IAC1D;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAA;CAC7D,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/guard/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,0CAA0C;AAC1C,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,cAAc,GACd,oBAAoB,GACpB,qBAAqB,GACrB,qBAAqB,GACrB,aAAa,GACb,YAAY,GACZ,aAAa,GACb,kBAAkB,GAClB,wBAAwB,GACxB,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,eAAe,GACf,kBAAkB,GAClB,gBAAgB,GAChB,0BAA0B,GAC1B,kBAAkB,GAClB,mBAAmB,GACnB,iBAAiB,GACjB,sBAAsB,GACtB,yBAAyB,GACzB,iBAAiB,GACjB,kBAAkB,GAClB,qBAAqB,CAAA;AAEzB;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAA;AAE5C;;;;;;;GAOG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;CACjC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,wGAAwG;IACxG,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5C;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAA;IACnC,iGAAiG;IACjG,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC,2GAA2G;IAC3G,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAA;IACrC,sGAAsG;IACtG,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,4FAA4F;IAC5F,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,yFAAyF;IACzF,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,kGAAkG;IAClG,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAA;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,GAAG,aAAa,CAAA;IAC5C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;IAC1D;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAA;CAC7D,CAAA"}
@@ -136,4 +136,29 @@ export declare namespace StandardSchemaV1 {
136
136
  type InferInput<T extends StandardSchemaV1> = NonNullable<T['~standard']['types']>['input'];
137
137
  type InferOutput<T extends StandardSchemaV1> = NonNullable<T['~standard']['types']>['output'];
138
138
  }
139
+ declare global {
140
+ /**
141
+ * The app's version string, injected as a build-time constant by basalt's own Vite preset —
142
+ * `basaltViteConfig` defines it from `BUILD_VERSION`, falling back to the app package.json
143
+ * `version` (see `../vite`, the `define` block).
144
+ *
145
+ * Declared HERE rather than beside the define because a consumer's `src/**` never loads
146
+ * `basalt-ui/vite`'s types (that subpath is imported from `vite.config.ts`, which most apps keep
147
+ * in a separate tsconfig project). This file is re-exported from the root barrel, so an app that
148
+ * imports from `'basalt-ui'` at all has it in scope with no `/// <reference>` and no
149
+ * `src/vite-env.d.ts` entry — which is the point: basalt shipped the define and no declaration,
150
+ * so every consumer hand-wrote this block, one of them needing an `oxlint-disable` in a file that
151
+ * existed only because basalt asked for it.
152
+ *
153
+ * A consumer importing ONLY a subpath (`basalt-ui/tokens`, `basalt-ui/charts`) does not load this
154
+ * file and does not get the global. That gap is the same set as the applicability gap: reaching
155
+ * for the root barrel is what `BasaltProvider` requires, and an app that never does is not on
156
+ * basalt's Vite preset either, so there is no `__APP_VERSION__` to declare.
157
+ *
158
+ * Precondition: only defined when your Vite config actually goes through `basaltViteConfig` (or
159
+ * you define it yourself). An app that hand-rolls its Vite config and reads this gets a
160
+ * ReferenceError at runtime — TypeScript is trusting the preset, not proving it.
161
+ */
162
+ const __APP_VERSION__: string;
163
+ }
139
164
  //# sourceMappingURL=register.d.ts.map