@animus-ui/system 0.1.11 → 0.1.12

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 (85) hide show
  1. package/README.md +124 -51
  2. package/dist/Animus.d.ts +1 -1
  3. package/dist/Animus.d.ts.map +1 -1
  4. package/dist/AnimusExtended.d.ts +1 -1
  5. package/dist/AnimusExtended.d.ts.map +1 -1
  6. package/dist/SystemBuilder.d.ts +184 -155
  7. package/dist/SystemBuilder.d.ts.map +1 -1
  8. package/dist/appearance/index.d.ts +0 -68
  9. package/dist/appearance/index.d.ts.map +1 -1
  10. package/dist/appearance/index.js +0 -91
  11. package/dist/assert-root-slot-C3Lp9MNJ.js +10 -0
  12. package/dist/asset.d.ts +4 -13
  13. package/dist/asset.d.ts.map +1 -1
  14. package/dist/bootstrap/createAppearanceBootstrap.d.ts +0 -57
  15. package/dist/bootstrap/createAppearanceBootstrap.d.ts.map +1 -1
  16. package/dist/bootstrap/index.d.ts.map +1 -1
  17. package/dist/bootstrap/index.js +4 -58
  18. package/dist/class-resolver.d.ts +2 -4
  19. package/dist/class-resolver.d.ts.map +1 -1
  20. package/dist/class-resolver.js +1 -1
  21. package/dist/compose.d.ts +2 -16
  22. package/dist/compose.d.ts.map +1 -1
  23. package/dist/compose.js +3 -17
  24. package/dist/composeWithContext.d.ts +4 -15
  25. package/dist/composeWithContext.d.ts.map +1 -1
  26. package/dist/composeWithContext.js +5 -16
  27. package/dist/conditions.d.ts +24 -141
  28. package/dist/conditions.d.ts.map +1 -1
  29. package/dist/{createClassResolver-BWyEQ5KZ.js → createClassResolver-EpwTL9Sa.js} +12 -113
  30. package/dist/{createComposedFamily-k5tTujGI.js → createComposedFamily-C4NovMcd.js} +11 -49
  31. package/dist/groups/index.js +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +318 -409
  35. package/dist/keyframes.d.ts +2 -23
  36. package/dist/keyframes.d.ts.map +1 -1
  37. package/dist/runtime/assert-root-slot.d.ts +2 -13
  38. package/dist/runtime/assert-root-slot.d.ts.map +1 -1
  39. package/dist/runtime/createClassResolver.d.ts +0 -8
  40. package/dist/runtime/createClassResolver.d.ts.map +1 -1
  41. package/dist/runtime/createComposedFamily.d.ts +2 -7
  42. package/dist/runtime/createComposedFamily.d.ts.map +1 -1
  43. package/dist/runtime/index.d.ts +0 -16
  44. package/dist/runtime/index.d.ts.map +1 -1
  45. package/dist/runtime/is-dev.d.ts +2 -42
  46. package/dist/runtime/is-dev.d.ts.map +1 -1
  47. package/dist/runtime/resolveClasses.d.ts +8 -33
  48. package/dist/runtime/resolveClasses.d.ts.map +1 -1
  49. package/dist/runtime/witness.d.ts +0 -8
  50. package/dist/runtime/witness.d.ts.map +1 -1
  51. package/dist/runtime-entry.d.ts +1 -6
  52. package/dist/runtime-entry.d.ts.map +1 -1
  53. package/dist/runtime-entry.js +2 -2
  54. package/dist/selectors.d.ts +6 -45
  55. package/dist/selectors.d.ts.map +1 -1
  56. package/dist/{size-CcVEGaen.js → size-C6jqxiQy.js} +7 -34
  57. package/dist/theme/createTheme.d.ts +26 -95
  58. package/dist/theme/createTheme.d.ts.map +1 -1
  59. package/dist/theme/flattenScale.d.ts +4 -10
  60. package/dist/theme/flattenScale.d.ts.map +1 -1
  61. package/dist/theme/resolveReferences.d.ts +7 -35
  62. package/dist/theme/resolveReferences.d.ts.map +1 -1
  63. package/dist/theme/serializeTokens.d.ts +0 -3
  64. package/dist/theme/serializeTokens.d.ts.map +1 -1
  65. package/dist/theme/types.d.ts +4 -26
  66. package/dist/theme/types.d.ts.map +1 -1
  67. package/dist/theme/utils.d.ts +1 -18
  68. package/dist/theme/utils.d.ts.map +1 -1
  69. package/dist/transforms/border.d.ts +1 -4
  70. package/dist/transforms/border.d.ts.map +1 -1
  71. package/dist/transforms/createTransform.d.ts +6 -31
  72. package/dist/transforms/createTransform.d.ts.map +1 -1
  73. package/dist/transforms/grid.d.ts +2 -12
  74. package/dist/transforms/grid.d.ts.map +1 -1
  75. package/dist/transforms/size.d.ts +2 -8
  76. package/dist/transforms/size.d.ts.map +1 -1
  77. package/dist/types/component.d.ts +9 -105
  78. package/dist/types/component.d.ts.map +1 -1
  79. package/dist/types/config.d.ts +12 -141
  80. package/dist/types/config.d.ts.map +1 -1
  81. package/dist/types/theme.d.ts +8 -165
  82. package/dist/types/theme.d.ts.map +1 -1
  83. package/dist/utils/deepMerge.d.ts.map +1 -1
  84. package/package.json +4 -4
  85. package/dist/assert-root-slot-B73sDJ1Q.js +0 -21
package/dist/index.js CHANGED
@@ -1,11 +1,8 @@
1
- import { t as createClassResolver } from "./createClassResolver-BWyEQ5KZ.js";
2
- import { n as createComponent, t as createComposedFamily } from "./createComposedFamily-k5tTujGI.js";
1
+ import { t as createClassResolver } from "./createClassResolver-EpwTL9Sa.js";
2
+ import { n as createComponent, t as createComposedFamily } from "./createComposedFamily-C4NovMcd.js";
3
3
  import { compose } from "./compose.js";
4
- import { a as borderShorthand, c as numericScale, d as createTransform, i as gridItemRatio, l as stringScale, n as size, o as createScale, r as gridItem, s as numericOrStringScale, t as percentageOrAbsolute, u as areTransformsEqual } from "./size-CcVEGaen.js";
4
+ import { a as borderShorthand, c as numericScale, d as createTransform, i as gridItemRatio, l as stringScale, n as size, o as createScale, r as gridItem, s as numericOrStringScale, t as percentageOrAbsolute, u as areTransformsEqual } from "./size-C6jqxiQy.js";
5
5
  //#region src/utils/deepMerge.ts
6
- /**
7
- * Deep merge utility — replaces lodash.merge for variant accumulation.
8
- */
9
6
  const isMergeable = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
10
7
  function deepMerge(target, source) {
11
8
  const result = { ...target };
@@ -38,17 +35,20 @@ var AnimusExtendedWithAll = class {
38
35
  return new AnimusExtended(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, this.custom, this.compounds);
39
36
  }
40
37
  asElement(component) {
41
- const Component = createComponent(component, "", this._buildComponentConfig());
38
+ const config = this._buildComponentConfig();
39
+ const Component = createComponent(component, "", config);
42
40
  const extendFn = this.extend.bind(this);
43
41
  return Object.assign(Component, { extend: extendFn });
44
42
  }
45
43
  asComponent(AsComponent) {
46
- const Component = createComponent(AsComponent, "", this._buildComponentConfig());
44
+ const config = this._buildComponentConfig();
45
+ const Component = createComponent(AsComponent, "", config);
47
46
  const extendFn = this.extend.bind(this);
48
47
  return Object.assign(Component, { extend: extendFn });
49
48
  }
50
49
  asClass() {
51
- return createClassResolver("", this._buildComponentConfig());
50
+ const config = this._buildComponentConfig();
51
+ return createClassResolver("", config);
52
52
  }
53
53
  _buildComponentConfig() {
54
54
  const variantConfig = {};
@@ -137,17 +137,20 @@ var AnimusWithAll = class {
137
137
  return new AnimusExtended(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, this.custom, this.compounds);
138
138
  }
139
139
  asElement(component) {
140
- const Component = createComponent(component, "", this._buildComponentConfig());
140
+ const config = this._buildComponentConfig();
141
+ const Component = createComponent(component, "", config);
141
142
  const extendFn = this.extend.bind(this);
142
143
  return Object.assign(Component, { extend: extendFn });
143
144
  }
144
145
  asComponent(AsComponent) {
145
- const Component = createComponent(AsComponent, "", this._buildComponentConfig());
146
+ const config = this._buildComponentConfig();
147
+ const Component = createComponent(AsComponent, "", config);
146
148
  const extendFn = this.extend.bind(this);
147
149
  return Object.assign(Component, { extend: extendFn });
148
150
  }
149
151
  asClass() {
150
- return createClassResolver("", this._buildComponentConfig());
152
+ const config = this._buildComponentConfig();
153
+ return createClassResolver("", config);
151
154
  }
152
155
  _buildComponentConfig() {
153
156
  const variantConfig = {};
@@ -231,14 +234,10 @@ var Animus = class extends AnimusWithBase {
231
234
  };
232
235
  //#endregion
233
236
  //#region src/asset.ts
234
- /** The reserved scheme marking an unsubstituted asset reference. */
235
237
  const ASSET_PLACEHOLDER_PREFIX = "animus-asset:";
236
238
  /**
237
- * Reference a package-owned asset (e.g. a font file) by module specifier:
238
- * `asset('@acme/tokens/fonts/inter.woff2')`. Resolution — aliases, exports
239
- * maps, `base`, content hashing — belongs to the host bundler; this function
240
- * only brands the specifier into its placeholder form. A literal URL string
241
- * (`'/fonts/inter.woff2'`) remains the pass-through alternative.
239
+ * Brands the specifier only — aliases, exports maps and hashing belong to the
240
+ * host bundler. A literal URL string stays a valid alternative.
242
241
  */
243
242
  function asset(specifier) {
244
243
  return ASSET_PLACEHOLDER_PREFIX + specifier;
@@ -246,27 +245,8 @@ function asset(specifier) {
246
245
  //#endregion
247
246
  //#region src/conditions.ts
248
247
  /**
249
- * Built-in condition aliases (design D8, increment 06).
250
- *
251
- * The Panda-compatible media-feature set: motion, print, orientation, contrast,
252
- * and OS color-scheme preferences. Every alias is a pure `@media` feature query
253
- * (`kind: 'media'`). Color-mode aliases (`_dark` / `_light`) are DELIBERATELY
254
- * excluded — they are selector-kind (`[data-color-mode]`) surface owned by the
255
- * `system-color-scheme` change, not media features.
256
- *
257
- * ORDER BAND (inc-03 full-pass): built-ins occupy a reserved band BELOW the user
258
- * band — one slot of 10 per alias in table row order, 300–380. The user band
259
- * starts at 500 (`mergeConditions` floors allocation at 490 → first user alias
260
- * is 500), so built-ins and user registrations never interleave on collision.
261
- * This mirrors the selector registry's real layout (built-ins 10–440, users
262
- * 500+).
263
- *
264
- * The static type-level mirror is `BuiltInConditionAlias` in `types/config.ts`
265
- * (added to `KnownUnderscoreKey`'s VALIDATING branch, exactly like
266
- * `BuiltInSelectorAlias`). Built-ins are NOT members of the augmentable
267
- * `Conditions` interface: interface members would make publication permanently
268
- * non-empty, killing inc-04's graceful-degradation contract. The
269
- * `types.test-d.tsx` drift positives keep the two in sync.
248
+ * Media-feature aliases only — color-mode aliases are selector-kind. The
249
+ * reserved order band (300–380) sits below the user band (500+).
270
250
  */
271
251
  const BUILT_IN_CONDITIONS = {
272
252
  _motionReduce: {
@@ -315,38 +295,22 @@ const BUILT_IN_CONDITIONS = {
315
295
  kind: "media"
316
296
  }
317
297
  };
318
- /** The supported at-rule prefixes, longest-first so `@media`/`@container`/
319
- * `@supports` match unambiguously. */
320
298
  const CONDITION_PREFIXES = [
321
299
  ["@media", "media"],
322
300
  ["@container", "container"],
323
301
  ["@supports", "supports"]
324
302
  ];
325
303
  /**
326
- * Infer the condition kind from an at-rule value's prefix (design D3).
327
- * Throws (fail-loud, build time) when the value does not begin with a
328
- * supported at-rule name. The compile-time complement is `addConditions()`'s
329
- * value constraint (`@media`/`@container`/`@supports`-prefixed template
330
- * literals); this runtime throw remains the backstop for untyped callers.
304
+ * Throws on an unsupported prefix — the runtime backstop for untyped
305
+ * callers, complementing `addConditions()`'s value constraint.
331
306
  */
332
307
  function inferConditionKind(value) {
333
308
  for (const [prefix, kind] of CONDITION_PREFIXES) if (value.startsWith(prefix)) return kind;
334
309
  throw new Error(`addConditions: value "${value}" must begin with @media, @container, or @supports`);
335
310
  }
336
311
  /**
337
- * Merge user-provided condition aliases with a base map.
338
- *
339
- * - User aliases override built-ins of the same name, preserving the existing
340
- * order (mirrors `mergeSelectors`).
341
- * - New aliases allocate orders CONTINUING from the highest existing order
342
- * (floored at 490, so the first user alias lands at 500) rather than
343
- * restarting at 500 each call — chained `.addConditions()` calls must not
344
- * collide on order 500.
345
- * - Fails loud when a condition alias name is already reserved by the SELECTOR
346
- * alias registry (`reservedSelectorNames`): the two registries share the `_`
347
- * namespace at the call site and their names MUST be disjoint, else a block
348
- * key would be ambiguous. The error names the offending alias and both
349
- * registries.
312
+ * Overrides keep the existing order; new aliases continue past the highest
313
+ * existing order (floor 490) so chained calls never collide on 500.
350
314
  */
351
315
  function mergeConditions(base, custom, reservedSelectorNames = /* @__PURE__ */ new Set()) {
352
316
  const merged = { ...base };
@@ -370,16 +334,12 @@ function mergeConditions(base, custom, reservedSelectorNames = /* @__PURE__ */ n
370
334
  }
371
335
  return merged;
372
336
  }
373
- /** Get the sorted alias keys for deterministic cascade ordering. */
374
337
  function getSortedConditionKeys(map) {
375
338
  return Object.keys(map).sort((a, b) => map[a].order - map[b].order);
376
339
  }
377
340
  /**
378
- * Serialize the condition map for the extraction pipeline.
379
- * Emits `alias → { value, order, kind }` in registry (cascade) order so the
380
- * JSON is deterministic. Distinct in SHAPE from the selector map (which
381
- * flattens to `alias → selector`) — the object entry is forward-compatible
382
- * with a future multi-valued `values: string[]` property (design D3).
341
+ * Emits `alias → { value, order, kind }` in cascade order so the JSON is
342
+ * deterministic.
383
343
  */
384
344
  function serializeConditionMap(map) {
385
345
  const conditions = {};
@@ -433,12 +393,6 @@ function keyframes(map) {
433
393
  }
434
394
  //#endregion
435
395
  //#region src/selectors.ts
436
- /**
437
- * Built-in selector aliases.
438
- *
439
- * Compound selectors (e.g. `_disabled`) target multiple CSS selectors
440
- * via comma-separation to cover native, ARIA, and data attribute conventions.
441
- */
442
396
  const BUILT_IN_SELECTORS = {
443
397
  _link: {
444
398
  selector: "&:link",
@@ -542,13 +496,8 @@ const BUILT_IN_SELECTORS = {
542
496
  }
543
497
  };
544
498
  /**
545
- * Merge user-provided selectors with built-in defaults.
546
- * User selectors override built-in aliases of the same name.
547
- * New aliases allocate orders CONTINUING from the highest existing order
548
- * (floored at 490, so the first user alias lands at 500) rather than
549
- * restarting at 500 each call (mirrors `mergeConditions`) — aliases from
550
- * successive `.addSelectors()`/`extend()` merges must not collide on
551
- * order 500.
499
+ * Order allocation continues past the highest existing order (floor 490, so
500
+ * the first custom alias is 500); restarting per call would collide.
552
501
  */
553
502
  function mergeSelectors(base, custom) {
554
503
  const merged = { ...base };
@@ -566,15 +515,9 @@ function mergeSelectors(base, custom) {
566
515
  }
567
516
  return merged;
568
517
  }
569
- /** Get the sorted alias keys for deterministic cascade ordering. */
570
518
  function getSortedAliasKeys(map) {
571
519
  return Object.keys(map).sort((a, b) => map[a].order - map[b].order);
572
520
  }
573
- /**
574
- * Serialize the selector map for the extraction pipeline.
575
- * Emits a flat `Record<string, string>` (alias → selector) plus
576
- * the ordered key list for cascade determinism.
577
- */
578
521
  function serializeSelectorMap(map) {
579
522
  const selectors = {};
580
523
  const order = getSortedAliasKeys(map);
@@ -586,6 +529,68 @@ function serializeSelectorMap(map) {
586
529
  }
587
530
  //#endregion
588
531
  //#region src/SystemBuilder.ts
532
+ /**
533
+ * No console output: a warn here would ship in production consumer bundles,
534
+ * and the extraction host shims `console`. The record is the witness.
535
+ */
536
+ function legacyVerbWitness(source, verb, sourceIndex) {
537
+ const record = source.getVocabularyRecord?.();
538
+ if (!record) return null;
539
+ const names = [...record.keyframes.map((entry) => entry.name), ...record.globalStyles.map((entry) => entry.name)];
540
+ if (names.length === 0) return null;
541
+ return {
542
+ code: "animus.vocabulary.legacy-verb",
543
+ verb,
544
+ source: `${verb} source #${sourceIndex}`,
545
+ names
546
+ };
547
+ }
548
+ /**
549
+ * Frozen two levels deep (frame entries + stop bodies) so later mutation of
550
+ * the caller's collection cannot reach a sealed record; deeper values alias.
551
+ */
552
+ function snapshotFrameData(frames) {
553
+ const copy = {};
554
+ for (const [key, entry] of Object.entries(frames)) {
555
+ const stops = {};
556
+ for (const [stop, body] of Object.entries(entry.frames ?? {})) stops[stop] = Object.freeze({ ...body });
557
+ copy[key] = Object.freeze({
558
+ name: entry.name,
559
+ frames: Object.freeze(stops)
560
+ });
561
+ }
562
+ return Object.freeze(copy);
563
+ }
564
+ /**
565
+ * A name collision resolves to the incoming side, and the winner takes the
566
+ * incoming declaration position so record order stays declaration order.
567
+ */
568
+ function mergeVocabularyEntries(existingEntries, existingCollisions, incoming, incomingOrigin) {
569
+ const entries = existingEntries.map((entry) => ({ ...entry }));
570
+ const collisions = [...existingCollisions];
571
+ for (const input of incoming) {
572
+ const existingIndex = entries.findIndex((entry) => entry.name === input.name);
573
+ if (existingIndex !== -1) {
574
+ const loser = entries[existingIndex];
575
+ collisions.push({
576
+ code: "animus.vocabulary.collision",
577
+ name: input.name,
578
+ winner: incomingOrigin,
579
+ loser: loser.origin
580
+ });
581
+ console.warn(`animus: vocabulary "${input.name}" is registered by both ${loser.origin} and ${incomingOrigin} — ${incomingOrigin} wins; rename one entry (animus.vocabulary.collision)`);
582
+ entries.splice(existingIndex, 1);
583
+ }
584
+ entries.push({
585
+ ...input,
586
+ origin: incomingOrigin
587
+ });
588
+ }
589
+ return {
590
+ entries,
591
+ collisions
592
+ };
593
+ }
589
594
  const snapshotTransformBySource = /* @__PURE__ */ new WeakMap();
590
595
  function snapshotTransform(source) {
591
596
  const cached = snapshotTransformBySource.get(source);
@@ -608,12 +613,10 @@ function snapshotTransform(source) {
608
613
  return wrapper;
609
614
  }
610
615
  /**
611
- * The one runtime discriminator for a library bundle: `system.toConfig`
612
- * being callable. A built system instance also has a `.system()` CHAIN
613
- * METHOD, so presence of a `system` key alone cannot discriminate the two
614
- * shapes. Both builders' `from()` use this guard; the QuickJS capture
615
- * script in the Rust system-loader mirrors it by necessity (it cannot
616
- * import TS) and points back here.
616
+ * A built system also carries a `.system()` chain method, so only
617
+ * `system.toConfig` being callable discriminates a bundle from an instance.
618
+ * The QuickJS capture script in the Rust system-loader reimplements the same
619
+ * discriminator — it cannot import this file, so change both together.
617
620
  */
618
621
  function isLibraryBundle(value) {
619
622
  const system = value?.system;
@@ -640,13 +643,6 @@ function arePropDefinitionsEqual(existing, incoming, structuralScale = false) {
640
643
  function orderedMembersEqual(existing, incoming) {
641
644
  return existing.length === incoming.length && existing.every((member, index) => member === incoming[index]);
642
645
  }
643
- /**
644
- * Divergent-prop error naming both definitions AND both origins — used by the
645
- * `extend()` merge (sibling/dual-version conflicts, design D3/G4) and by
646
- * `addGroup`/`addProps` when the colliding entry arrived through `extend()`
647
- * (origin labels "extended source #n" / "builder state"). When no extension
648
- * provenance exists, the pre-existing origin-less messages are kept verbatim.
649
- */
650
646
  function divergentPropError(key, existing, incoming, existingOrigin, incomingOrigin) {
651
647
  return /* @__PURE__ */ new Error(`Prop "${key}" already registered with a different definition. Existing (${existingOrigin}): property="${existing.property}", scale="${String(existing.scale)}". Incoming (${incomingOrigin}): property="${incoming.property}", scale="${String(incoming.scale)}".`);
652
648
  }
@@ -658,7 +654,10 @@ var SystemBuilder = class SystemBuilder {
658
654
  #conditionRegistry;
659
655
  #extendProvenance;
660
656
  #extendCount;
661
- constructor(propRegistry, groupRegistry, selectorRegistry, includesRegistry, conditionRegistry, extendProvenance, extendCount) {
657
+ #vocabularyRegistry;
658
+ #vocabularyCollisions;
659
+ #legacyVerbWitnesses;
660
+ constructor(propRegistry, groupRegistry, selectorRegistry, includesRegistry, conditionRegistry, extendProvenance, extendCount, vocabularyRegistry, vocabularyCollisions, legacyVerbWitnesses) {
662
661
  this.#propRegistry = propRegistry || {};
663
662
  this.#groupRegistry = groupRegistry || {};
664
663
  this.#selectorRegistry = selectorRegistry || { ...BUILT_IN_SELECTORS };
@@ -666,6 +665,9 @@ var SystemBuilder = class SystemBuilder {
666
665
  this.#conditionRegistry = conditionRegistry || { ...BUILT_IN_CONDITIONS };
667
666
  this.#extendProvenance = extendProvenance || /* @__PURE__ */ new Map();
668
667
  this.#extendCount = extendCount || 0;
668
+ this.#vocabularyRegistry = vocabularyRegistry || [];
669
+ this.#vocabularyCollisions = vocabularyCollisions || [];
670
+ this.#legacyVerbWitnesses = legacyVerbWitnesses || [];
669
671
  }
670
672
  #originOf(provenanceKey) {
671
673
  const index = this.#extendProvenance.get(provenanceKey);
@@ -673,7 +675,9 @@ var SystemBuilder = class SystemBuilder {
673
675
  }
674
676
  from(source) {
675
677
  const instance = isLibraryBundle(source) ? source.system : source;
676
- return new SystemBuilder(this.#propRegistry, this.#groupRegistry, this.#selectorRegistry, [...this.#includesRegistry, instance], this.#conditionRegistry, this.#extendProvenance, this.#extendCount);
678
+ const fromCount = this.#legacyVerbWitnesses.filter((entry) => entry.verb === "from").length;
679
+ const witness = legacyVerbWitness(instance, "from", fromCount + 1);
680
+ return new SystemBuilder(this.#propRegistry, this.#groupRegistry, this.#selectorRegistry, [...this.#includesRegistry, instance], this.#conditionRegistry, this.#extendProvenance, this.#extendCount, this.#vocabularyRegistry, this.#vocabularyCollisions, witness ? [...this.#legacyVerbWitnesses, witness] : this.#legacyVerbWitnesses);
677
681
  }
678
682
  extend(source) {
679
683
  const instance = isLibraryBundle(source) ? source.system : source;
@@ -751,57 +755,43 @@ var SystemBuilder = class SystemBuilder {
751
755
  ...this.#conditionRegistry,
752
756
  ...conditionOverrides
753
757
  }, newConditions, new Set(Object.keys(nextSelectors)));
754
- return new SystemBuilder(nextProps, nextGroups, nextSelectors, [...this.#includesRegistry, instance], nextConditions, provenance, sourceIndex);
758
+ const sourceRecord = instance.getVocabularyRecord?.();
759
+ if (!sourceRecord) throw new Error("extend: source system is not sealed (or was built by an older @animus-ui/system) — registered vocabulary travels only on sealed instances. Call seal() on the source bundle and export the sealed instance.");
760
+ let nextVocabulary = this.#vocabularyRegistry;
761
+ let nextVocabularyCollisions = this.#vocabularyCollisions;
762
+ const inheritedEntries = [...sourceRecord.keyframes.map((entry) => ({
763
+ kind: "keyframes",
764
+ name: entry.name,
765
+ frames: entry.frames
766
+ })), ...sourceRecord.globalStyles.map((entry) => ({
767
+ kind: "globalStyles",
768
+ name: entry.name,
769
+ styles: entry.styles,
770
+ ...entry.fontFaces ? { fontFaces: entry.fontFaces } : {}
771
+ }))];
772
+ if (inheritedEntries.length > 0) {
773
+ const merged = mergeVocabularyEntries(this.#vocabularyRegistry, this.#vocabularyCollisions, inheritedEntries, incomingOrigin);
774
+ nextVocabulary = merged.entries;
775
+ nextVocabularyCollisions = merged.collisions;
776
+ }
777
+ return new SystemBuilder(nextProps, nextGroups, nextSelectors, [...this.#includesRegistry, instance], nextConditions, provenance, sourceIndex, nextVocabulary, nextVocabularyCollisions, this.#legacyVerbWitnesses);
755
778
  }
756
779
  /**
757
- * Register custom selector aliases (`_hoverChild`, …) → `&`-relative selector
758
- * strings. Overriding a built-in SELECTOR alias is legal; a name owned by the
759
- * CONDITION registry (built-in or registered earlier in this chain) maps to
760
- * the branded `ReservedByConditionRegistry` instead of its selector string —
761
- * the compile-time complement of the cross-registry throw below. The clash is
762
- * checked in VALUE position so `S` stays a naked inference site; subtracting
763
- * the reserved names from the KEY position is impossible — `Exclude` is a
764
- * silent no-op against a template-literal pattern. `NarrowedAliases` keeps a
765
- * widened `Conds` from swallowing the whole `_` namespace.
766
- *
767
- * It wraps the ACCUMULATION too: a widened registration contributes nothing
768
- * to `Sels` rather than widening it, so the pattern can never reach the
769
- * published `RegistryBrand` — see `addConditions` for the full argument.
780
+ * Registers `_` aliases → `&`-relative selectors. The cross-registry clash
781
+ * is checked in VALUE position: `Exclude` on a `_${string}` key is a no-op.
770
782
  */
771
783
  addSelectors(selectors) {
772
784
  for (const name of Object.keys(selectors)) if (name in this.#conditionRegistry) throw new Error(`addSelectors: "${name}" is already registered as a condition alias; a name resolves through exactly one registry. Pick a distinct name.`);
773
785
  const merged = mergeSelectors(this.#selectorRegistry, selectors);
774
- return new SystemBuilder(this.#propRegistry, this.#groupRegistry, merged, this.#includesRegistry, this.#conditionRegistry, this.#extendProvenance, this.#extendCount);
786
+ return new SystemBuilder(this.#propRegistry, this.#groupRegistry, merged, this.#includesRegistry, this.#conditionRegistry, this.#extendProvenance, this.#extendCount, this.#vocabularyRegistry, this.#vocabularyCollisions, this.#legacyVerbWitnesses);
775
787
  }
776
788
  /**
777
- * Register condition aliases (`_motionReduce`, `_cardSm`, …) → at-rule
778
- * condition strings (`@media …` / `@container …` / `@supports …`).
779
- * Recognized as block keys in style objects; user aliases override built-ins
780
- * of the same name (design D3). Keys are constrained to `_`-prefixed aliases
781
- * and values to `@`-prefixed at-rule strings — a value that does not begin
782
- * with an at-rule name is a compile-time type error (design D9; the runtime
783
- * `inferConditionKind` throw is defense-in-depth). The registered keys are
784
- * accumulated into the phantom `Conds` union and surfaced on `build()`.
785
- *
786
- * A key owned by the SELECTOR registry (built-in or registered earlier in
787
- * this chain) maps to the branded `ReservedBySelectorRegistry` — see
788
- * `addSelectors` for why the clash is checked in value position. Overriding a
789
- * built-in CONDITION alias stays legal (design D3); only the OPPOSITE
790
- * registry is subtracted.
791
- *
792
- * `NarrowedAliases` wraps the ACCUMULATION, not just the gate: a widened
793
- * registration (`Record<`_${string}`, …>`) contributes nothing to `Conds`.
794
- * Accumulating the pattern instead would carry it out through `build()`'s
795
- * `RegistryBrand`, and a consumer publishing `ConditionsOf` into the
796
- * augmentable `Conditions` interface would then have every `_` key type as
797
- * registered — `UnknownConditionAlias` would never fire again, anywhere in
798
- * that project. The gate keeps its own `NarrowedAliases` regardless:
799
- * `extend()`-sourced unions can still arrive widened from a system built
800
- * before this narrowing.
789
+ * Registers `_` aliases → at-rule strings; user aliases override built-ins
790
+ * of the same name. A selector-owned key maps to the branded rejection.
801
791
  */
802
792
  addConditions(conditions) {
803
793
  const merged = mergeConditions(this.#conditionRegistry, conditions, new Set(Object.keys(this.#selectorRegistry)));
804
- return new SystemBuilder(this.#propRegistry, this.#groupRegistry, this.#selectorRegistry, this.#includesRegistry, merged, this.#extendProvenance, this.#extendCount);
794
+ return new SystemBuilder(this.#propRegistry, this.#groupRegistry, this.#selectorRegistry, this.#includesRegistry, merged, this.#extendProvenance, this.#extendCount, this.#vocabularyRegistry, this.#vocabularyCollisions, this.#legacyVerbWitnesses);
805
795
  }
806
796
  addGroup(name, config) {
807
797
  if (name in this.#propRegistry) throw new Error(`Group name "${name}" collides with an existing prop name. Group names and prop names must be disjoint.`);
@@ -818,10 +808,11 @@ var SystemBuilder = class SystemBuilder {
818
808
  ...config
819
809
  };
820
810
  const newGroup = { [name]: Object.keys(config) };
821
- return new SystemBuilder(nextProps, {
811
+ const nextGroups = {
822
812
  ...this.#groupRegistry,
823
813
  ...newGroup
824
- }, this.#selectorRegistry, this.#includesRegistry, this.#conditionRegistry, this.#extendProvenance, this.#extendCount);
814
+ };
815
+ return new SystemBuilder(nextProps, nextGroups, this.#selectorRegistry, this.#includesRegistry, this.#conditionRegistry, this.#extendProvenance, this.#extendCount, this.#vocabularyRegistry, this.#vocabularyCollisions, this.#legacyVerbWitnesses);
825
816
  }
826
817
  addProps(config) {
827
818
  for (const key of Object.keys(config)) if (key in this.#groupRegistry) throw new Error(`Prop name "${key}" collides with an existing group name. Group names and prop names must be disjoint.`);
@@ -833,42 +824,123 @@ var SystemBuilder = class SystemBuilder {
833
824
  throw new Error(`Prop "${key}" already registered with a different definition.`);
834
825
  }
835
826
  }
836
- return new SystemBuilder({
827
+ const nextProps = {
837
828
  ...this.#propRegistry,
838
829
  ...config
839
- }, this.#groupRegistry, this.#selectorRegistry, this.#includesRegistry, this.#conditionRegistry, this.#extendProvenance, this.#extendCount);
830
+ };
831
+ return new SystemBuilder(nextProps, this.#groupRegistry, this.#selectorRegistry, this.#includesRegistry, this.#conditionRegistry, this.#extendProvenance, this.#extendCount, this.#vocabularyRegistry, this.#vocabularyCollisions, this.#legacyVerbWitnesses);
840
832
  }
841
833
  build() {
842
- const animus = new Animus(Object.fromEntries(Object.entries(this.#propRegistry).map(([key, entry]) => [key, { ...entry }])), Object.fromEntries(Object.entries(this.#groupRegistry).map(([key, members]) => [key, [...members]])));
834
+ const propSource = Object.fromEntries(Object.entries(this.#propRegistry).map(([key, entry]) => [key, { ...entry }]));
835
+ const groupSource = Object.fromEntries(Object.entries(this.#groupRegistry).map(([key, members]) => [key, [...members]]));
843
836
  const snapshot = createRegistrySnapshot(this.#propRegistry, this.#groupRegistry, this.#selectorRegistry, this.#conditionRegistry);
844
- const system = Object.assign(animus, { toConfig: () => {
845
- return serializeInstance(snapshot.props, snapshot.groups, snapshot.selectors, snapshot.conditions);
846
- } });
847
- Object.defineProperty(system, "getRegistrySnapshot", {
848
- value: () => snapshot,
849
- enumerable: false
850
- });
837
+ const mintInstance = () => {
838
+ const animus = new Animus(Object.fromEntries(Object.entries(propSource).map(([key, entry]) => [key, { ...entry }])), Object.fromEntries(Object.entries(groupSource).map(([key, members]) => [key, [...members]])));
839
+ const instance = Object.assign(animus, { toConfig: () => {
840
+ return serializeInstance(snapshot.props, snapshot.groups, snapshot.selectors, snapshot.conditions);
841
+ } });
842
+ Object.defineProperty(instance, "getRegistrySnapshot", {
843
+ value: () => snapshot,
844
+ enumerable: false
845
+ });
846
+ return instance;
847
+ };
848
+ const system = mintInstance();
849
+ const legacyVerbWitnessRecord = this.#legacyVerbWitnesses;
851
850
  const createGlobalStyles = ((styles, options) => ({
852
851
  __brand: "GlobalStyleBlock",
853
852
  styles,
854
853
  ...options?.fontFaces?.length ? { fontFaces: [...options.fontFaces] } : {}
855
854
  }));
856
855
  const createKeyframes = ((frames) => keyframes(frames));
857
- return {
858
- system,
859
- createGlobalStyles,
860
- createKeyframes
856
+ const makeBundle = (entries, collisions, localCallCount) => {
857
+ let consumedBy;
858
+ const registerEntries = (label, incoming) => {
859
+ if (consumedBy === "seal") throw new Error(`${label}: this system is already sealed — registration happens between build() and seal().`);
860
+ if (consumedBy === "register") throw new Error(`${label}: this bundle was superseded by a later registration call — registration is linear; chain the calls and seal the final bundle.`);
861
+ const merged = mergeVocabularyEntries(entries, collisions, incoming, `local registration #${localCallCount + 1}`);
862
+ consumedBy = "register";
863
+ return makeBundle(merged.entries, merged.collisions, localCallCount + 1);
864
+ };
865
+ const registerKeyframes = (map) => {
866
+ const incoming = [];
867
+ for (const [name, collection] of Object.entries(map)) {
868
+ if (!collection || collection.__brand !== "Keyframes" || typeof collection.__frames !== "object") throw new TypeError(`registerKeyframes: "${name}" is not a createKeyframes collection — register the factory return value itself.`);
869
+ incoming.push({
870
+ kind: "keyframes",
871
+ name,
872
+ frames: snapshotFrameData(collection.__frames)
873
+ });
874
+ }
875
+ return registerEntries("registerKeyframes", incoming);
876
+ };
877
+ const registerGlobalStyles = (map) => {
878
+ const incoming = [];
879
+ for (const [name, block] of Object.entries(map)) {
880
+ if (!block || block.__brand !== "GlobalStyleBlock" || typeof block.styles !== "object") throw new TypeError(`registerGlobalStyles: "${name}" is not a createGlobalStyles block — register the factory return value itself.`);
881
+ const blockValue = block;
882
+ const styles = Object.freeze(Object.fromEntries(Object.entries(blockValue.styles).map(([selector, body]) => [selector, Object.freeze({ ...body })])));
883
+ incoming.push({
884
+ kind: "globalStyles",
885
+ name,
886
+ styles,
887
+ ...blockValue.fontFaces?.length ? { fontFaces: Object.freeze(blockValue.fontFaces.map((face) => Object.freeze({ ...face }))) } : {}
888
+ });
889
+ }
890
+ return registerEntries("registerGlobalStyles", incoming);
891
+ };
892
+ const seal = () => {
893
+ if (consumedBy === "seal") throw new Error("seal: this system is already sealed — seal() returns exactly one instance per build().");
894
+ if (consumedBy === "register") throw new Error("seal: this bundle was superseded by a later registration call — registration is linear; seal the final bundle.");
895
+ const record = Object.freeze({
896
+ version: 1,
897
+ keyframes: Object.freeze(entries.filter((entry) => entry.kind === "keyframes").map((entry) => Object.freeze({
898
+ name: entry.name,
899
+ frames: entry.frames
900
+ }))),
901
+ globalStyles: Object.freeze(entries.filter((entry) => entry.kind === "globalStyles").map((entry) => Object.freeze({
902
+ name: entry.name,
903
+ styles: entry.styles,
904
+ ...entry.fontFaces ? { fontFaces: entry.fontFaces } : {}
905
+ }))),
906
+ collisions: Object.freeze(collisions.map((entry) => Object.freeze({ ...entry }))),
907
+ legacyVerbs: Object.freeze(legacyVerbWitnessRecord.map((entry) => ({
908
+ ...entry,
909
+ names: entry.names.filter((name) => !entries.some((kept) => kept.name === name))
910
+ })).filter((entry) => entry.names.length > 0).map((entry) => Object.freeze({
911
+ ...entry,
912
+ names: Object.freeze([...entry.names])
913
+ })))
914
+ });
915
+ const sealed = mintInstance();
916
+ Object.defineProperty(sealed, "getVocabularyRecord", {
917
+ value: () => record,
918
+ enumerable: false
919
+ });
920
+ for (const member of ["registerKeyframes", "registerGlobalStyles"]) Object.defineProperty(sealed, member, {
921
+ value: () => {
922
+ throw new Error(`${member}: this system is sealed — registration happens between build() and seal().`);
923
+ },
924
+ enumerable: false
925
+ });
926
+ consumedBy = "seal";
927
+ return sealed;
928
+ };
929
+ return {
930
+ system,
931
+ createGlobalStyles,
932
+ createKeyframes,
933
+ registerKeyframes,
934
+ registerGlobalStyles,
935
+ seal
936
+ };
861
937
  };
938
+ return makeBundle(this.#vocabularyRegistry.map((entry) => ({ ...entry })), [...this.#vocabularyCollisions], 0);
862
939
  }
863
940
  };
864
941
  /**
865
- * Freeze the builder's registries into the build-time snapshot (design D7):
866
- * containers, per-entry objects, and the mutable values nested inside a prop
867
- * (`properties` arrays, object/array scales) are copies, so neither the
868
- * builder's onward chaining nor post-build mutation of the instance's public
869
- * registry fields reaches serialized or merged output. Transform functions
870
- * are cached immutable forwarding wrappers: behavior survives without keeping
871
- * mutable serialization metadata live.
942
+ * Copies and freezes containers, entries, and the mutable values inside a
943
+ * prop, so later mutation reaches neither serialized nor merged output.
872
944
  */
873
945
  function createRegistrySnapshot(propRegistry, groupRegistry, selectorRegistry, conditionRegistry) {
874
946
  const props = {};
@@ -934,33 +1006,24 @@ function serializeInstance(propRegistry, groupRegistry, selectorRegistry, condit
934
1006
  };
935
1007
  }
936
1008
  function createSystem(config) {
937
- return new SystemBuilder(void 0, void 0, void 0, config?.includes ?? []);
1009
+ const includes = config?.includes ?? [];
1010
+ const witnesses = [];
1011
+ includes.forEach((source, index) => {
1012
+ const witness = legacyVerbWitness(source, "includes", index + 1);
1013
+ if (witness) witnesses.push(witness);
1014
+ });
1015
+ return new SystemBuilder(void 0, void 0, void 0, includes, void 0, void 0, void 0, void 0, void 0, witnesses);
938
1016
  }
939
1017
  //#endregion
940
1018
  //#region src/theme/resolveReferences.ts
941
1019
  /**
942
- * Late-binding token-reference resolution over the flattened theme (D4,
943
- * openspec change first-class-extension).
944
- *
945
- * Replaces the old single-pass, insertion-order-dependent rewrite: references
946
- * resolve against the COMPLETE flattened map via depth-first traversal of the
947
- * reference DAG, so declaration order is never observable (G3), references
948
- * inside emitted scales resolve into the emitted variable declarations
949
- * instead of leaking literally into CSS (G2), and a token resolves to the
950
- * same value whether its scale is emitted or inlined (G1 — an emitted target
951
- * substitutes as `var()`, which the cascade late-binds to exactly the value
952
- * the inlined form substitutes directly).
953
- *
954
- * Pure module: consumes the flatten pass's maps, returns fresh maps, mutates
955
- * nothing. Runs in QuickJS — ES built-ins only, no Node/WHATWG APIs.
1020
+ * Runs inside QuickJS: ES built-ins only, no Node or WHATWG APIs.
956
1021
  */
957
- /** Token ref pattern: {scale.key}, {scale.key.sub}, or {path/NN} (opacity). */
958
1022
  const TOKEN_REF_RE$1 = /\{([^}]+)\}/g;
959
- /** DFS node states for cycle detection. */
960
1023
  const UNVISITED = 0;
961
1024
  const RESOLVING = 1;
962
1025
  const RESOLVED = 2;
963
- /** Parse every `{...}` occurrence via matchAll — no shared lastIndex. */
1026
+ /** `matchAll`, not `exec`: a shared global regex would carry `lastIndex`. */
964
1027
  function parseReferences(value) {
965
1028
  const references = [];
966
1029
  for (const match of value.matchAll(TOKEN_REF_RE$1)) {
@@ -978,26 +1041,12 @@ function parseReferences(value) {
978
1041
  return references;
979
1042
  }
980
1043
  /**
981
- * Resolve token references over the complete flattened maps.
982
- *
983
- * - `tokenMap`/`variableMap`/`variables` are `flattenTheme`'s outputs: an
984
- * emitted path's tokenMap entry is its `var()` indirection and its RAW
985
- * authored value is parked in `variables`; a non-emitted path's tokenMap
986
- * entry IS its raw value.
987
- * - Substitution per reference: emitted target → `var(--target)`; non-emitted
988
- * target → its resolved literal; `{path/NN}` keeps the existing
989
- * `color-mix` output shape (`/0` → `transparent`, `/100` → the base).
990
- * - Unresolvable target: warn once per missing path, keep the literal — a
991
- * SUPPORTED pattern (a kit theme may reference tokens its consumer
992
- * provides later). Dangling-reference errors arrive with the explicit
993
- * replacement form (D5, increment 04).
994
- * - Reference cycle: hard error naming the cycle's token paths in traversal
995
- * order, regardless of emission flags (emission-invariant failure, G1).
1044
+ * An unresolvable reference warns and keeps its literal: a kit theme may
1045
+ * reference tokens its consumer supplies later.
996
1046
  */
997
1047
  function resolveReferences(tokenMap, variableMap, variables) {
998
1048
  const paths = Object.keys(tokenMap).sort();
999
1049
  const known = new Set(paths);
1000
- /** The authored (raw) value behind a path — see the contract note above. */
1001
1050
  const rawValueOf = (path) => {
1002
1051
  const varName = variableMap[path];
1003
1052
  if (varName !== void 0 && variables[varName] !== void 0) return variables[varName];
@@ -1013,7 +1062,6 @@ function resolveReferences(tokenMap, variableMap, variables) {
1013
1062
  }
1014
1063
  const state = /* @__PURE__ */ new Map();
1015
1064
  const resolved = /* @__PURE__ */ new Map();
1016
- /** Active DFS trail — the cycle report slices it from the revisited node. */
1017
1065
  const trail = [];
1018
1066
  const warnedMissing = /* @__PURE__ */ new Set();
1019
1067
  const substitute = (reference, match) => {
@@ -1076,7 +1124,6 @@ function resolveReferences(tokenMap, variableMap, variables) {
1076
1124
  }
1077
1125
  //#endregion
1078
1126
  //#region src/theme/utils.ts
1079
- /** Type guard: true for non-null, non-array objects */
1080
1127
  function isObject(value) {
1081
1128
  return value !== null && typeof value === "object" && !Array.isArray(value);
1082
1129
  }
@@ -1092,11 +1139,6 @@ function merge(target, ...sources) {
1092
1139
  }
1093
1140
  return target;
1094
1141
  }
1095
- /**
1096
- * Resolve a dot-path string against a nested object.
1097
- * walkDotPath({ gray: { 50: '#fafafa' } }, 'gray.50') → '#fafafa'
1098
- * The `_` identity key is handled: 'primary' resolves to obj.primary._ if obj.primary is an object with _.
1099
- */
1100
1142
  function walkDotPath(obj, path) {
1101
1143
  const parts = path.split(".");
1102
1144
  let current = obj;
@@ -1107,11 +1149,7 @@ function walkDotPath(obj, path) {
1107
1149
  return current;
1108
1150
  }
1109
1151
  /**
1110
- * Flatten a nested object into a flat Record with dot-path keys.
1111
- * The `_` key is an identity marker — it produces the parent key without suffix.
1112
- * { gray: { 50: '#fafafa' } } → { 'gray.50': '#fafafa' }
1113
- * { primary: { _: 'ember', hover: 'x' } } → { 'primary': 'ember', 'primary.hover': 'x' }
1114
- * CSS variable names use dash-join, computed at the serialization boundary (not here).
1152
+ * A `_` key is an identity marker: it takes the parent path with no suffix.
1115
1153
  */
1116
1154
  function flattenToDotPaths(object, path) {
1117
1155
  const result = {};
@@ -1123,15 +1161,9 @@ function flattenToDotPaths(object, path) {
1123
1161
  }
1124
1162
  return result;
1125
1163
  }
1126
- /**
1127
- * Convert a dot-path key to a dash-join key for CSS variable naming.
1128
- * 'gray.50' → 'gray-50'
1129
- * 'primary.hover' → 'primary-hover'
1130
- */
1131
1164
  function dotToDash(dotPath) {
1132
1165
  return dotPath.replace(/\./g, "-");
1133
1166
  }
1134
- /** Map over object values — matches lodash.mapValues overload signatures */
1135
1167
  //#endregion
1136
1168
  //#region src/theme/createTheme.ts
1137
1169
  const COLOR_FUNCTION_PREFIXES = [
@@ -1146,7 +1178,6 @@ const COLOR_FUNCTION_PREFIXES = [
1146
1178
  "color(",
1147
1179
  "color-mix("
1148
1180
  ];
1149
- /** Validate that a value is a valid CSS <color>. */
1150
1181
  function isValidCSSColor(value) {
1151
1182
  if (typeof value !== "string") return false;
1152
1183
  const v = value.trim();
@@ -1157,10 +1188,6 @@ function isValidCSSColor(value) {
1157
1188
  if (/^[a-zA-Z]+$/.test(v)) return true;
1158
1189
  return false;
1159
1190
  }
1160
- /**
1161
- * Validate that mode aliases reference existing color keys via dot-path traversal.
1162
- * Aliases are dot-path strings like 'gray.50' that must resolve in the nested color structure.
1163
- */
1164
1191
  function validateModeAliases(modeName, aliases, nestedColors, flatColorKeys, prefix) {
1165
1192
  for (const [key, value] of Object.entries(aliases)) {
1166
1193
  const aliasPath = prefix ? `${prefix}.${key}` : key;
@@ -1174,12 +1201,11 @@ function validateModeAliases(modeName, aliases, nestedColors, flatColorKeys, pre
1174
1201
  }
1175
1202
  }
1176
1203
  /**
1177
- * Reserved mode name (D4). "System" is modeled as the ABSENCE of
1178
- * `data-color-mode`, never as a mode: an attribute value `system` would defeat
1179
- * the `:root:not([data-color-mode])` guard while matching no mode block.
1204
+ * The system preference is the ABSENCE of `data-color-mode`; a mode named
1205
+ * `system` would defeat the `:root:not([data-color-mode])` guard.
1180
1206
  */
1181
1207
  const RESERVED_MODE_NAME = "system";
1182
- const RESERVED_THEME_KEYS = new Set([
1208
+ const RESERVED_THEME_KEYS = /* @__PURE__ */ new Set([
1183
1209
  "breakpoints",
1184
1210
  "modes",
1185
1211
  "mode",
@@ -1191,22 +1217,16 @@ const RESERVED_THEME_KEYS = new Set([
1191
1217
  "serialize",
1192
1218
  "varRef"
1193
1219
  ]);
1194
- const COLOR_SCHEME_VALUES = new Set([
1220
+ const COLOR_SCHEME_VALUES = /* @__PURE__ */ new Set([
1195
1221
  "light",
1196
1222
  "dark",
1197
1223
  "normal"
1198
1224
  ]);
1199
- /**
1200
- * The axis → scheme pairs the mapping forces: the mode named for the OS light
1201
- * preference must classify `light`, the dark one `dark`. Single source for
1202
- * BOTH the default fill and the conflict check in
1203
- * {@link resolveColorModeOptions} — one rule, one table.
1204
- */
1225
+ /** OS-preference axis → the `color-scheme` a mode mapped to it must carry. */
1205
1226
  const MAPPING_FORCED_SCHEMES = [["light", "light"], ["dark", "dark"]];
1206
1227
  /**
1207
- * Merge an incoming option object over the one already on the theme, mirroring
1208
- * `merge`'s per-key override. Returns `undefined` when neither side supplied
1209
- * one, so an unconfigured theme never gains the key (byte parity, G4).
1228
+ * `undefined` when neither side supplied one, so an unconfigured theme never
1229
+ * gains the key.
1210
1230
  */
1211
1231
  function mergeOptionObject(existing, incoming) {
1212
1232
  const hasExisting = isObject(existing);
@@ -1217,31 +1237,12 @@ function mergeOptionObject(existing, incoming) {
1217
1237
  ...hasIncoming ? incoming : {}
1218
1238
  };
1219
1239
  }
1220
- /** Reject the reserved mode name (D4) wherever a mode set is declared or merged. */
1221
1240
  function validateReservedModeNames(modeNames) {
1222
1241
  for (const modeName of modeNames) if (modeName === RESERVED_MODE_NAME) throw new Error(`addColorModes: '${RESERVED_MODE_NAME}' is a reserved mode name — the system preference is represented by the absence of the data-color-mode attribute, never by a declared mode. Rename it. Declared modes: ${modeNames.join(", ")}`);
1223
1242
  }
1224
1243
  /**
1225
- * Normalize AND validate the optional system-participation options against the
1226
- * declared modes, returning the normalized classification. One entry point on
1227
- * purpose: the D3-amendment fill (mapping-named modes default to their forced
1228
- * schemes) must always run before totality is checked, so exposing fill and
1229
- * validation separately would make "validate an unfilled map" representable.
1230
- * An explicit entry survives the fill's spread and is still conflict-checked,
1231
- * so a wrong value errors rather than being silently corrected. `undefined`
1232
- * passes through untouched — the classification stays opt-in, and a theme with
1233
- * only `systemPreference` emits no `color-scheme` (byte parity, G4).
1234
- *
1235
- * ALWAYS called with MERGED state — `theme.modes` unions across `addColorModes`
1236
- * calls and `from()` composition, so a per-call view both misses invalidation
1237
- * (a later mode declaration un-totals a carried classification) and invents
1238
- * false rejections (a mapping naming a mode declared by an earlier call).
1239
- * Run at both gates: `addColorModes` (fail fast) and `build()` (authoritative —
1240
- * `from()` composition never passes through `addColorModes`; only build()'s
1241
- * resolved map feeds emission and the manifest).
1242
- *
1243
- * Error tone mirrors `validateModeAliases`: `addColorModes:` prefix plus the
1244
- * available names.
1244
+ * Fills mapping-named modes with their forced schemes, then validates. Takes
1245
+ * the MERGED mode set: a later declaration can un-total a carried map.
1245
1246
  */
1246
1247
  function resolveColorModeOptions(modeNames, systemPreference, suppliedBrowserColorScheme) {
1247
1248
  let browserColorScheme = suppliedBrowserColorScheme;
@@ -1279,12 +1280,8 @@ function resolveColorModeOptions(modeNames, systemPreference, suppliedBrowserCol
1279
1280
  return browserColorScheme;
1280
1281
  }
1281
1282
  /**
1282
- * Validate the D6 `basedOn` mode-base map against the MERGED mode set: every
1283
- * key and every base must name a declared mode, self-bases are rejected, and
1284
- * base chains must terminate (a cycle can never fill coverage). Runs at both
1285
- * gates like the other mode options — `addColorModes` (fail fast) and
1286
- * `build()` (authoritative: extend/from composition merges modes without
1287
- * passing through `addColorModes`).
1283
+ * Validates against the MERGED mode set: composition merges modes without
1284
+ * passing through `addColorModes`, so `build()` runs this again.
1288
1285
  */
1289
1286
  function validateModeBases(modeNames, modeBases) {
1290
1287
  if (!modeBases) return;
@@ -1305,7 +1302,6 @@ function validateModeBases(modeNames, modeBases) {
1305
1302
  }
1306
1303
  }
1307
1304
  }
1308
- /** Validate all color entries, throwing on invalid values. */
1309
1305
  function validateColors(colors) {
1310
1306
  for (const [key, value] of Object.entries(colors)) if (isObject(value)) validateColors(value);
1311
1307
  else if (!isValidCSSColor(value)) throw new Error(`addColors: '${String(value)}' is not a valid CSS <color> value for key '${key}'. Expected hex (#fff), rgb(), hsl(), oklch(), named color, transparent, or currentColor.`);
@@ -1342,12 +1338,8 @@ function copyState(state, nextTheme) {
1342
1338
  return next;
1343
1339
  }
1344
1340
  /**
1345
- * Exact leaf-path flatten for extend() provenance: like `flattenToDotPaths`
1346
- * but WITHOUT the `_` identity-key collapse, so a leaf and a branch can
1347
- * never share a path spelling. A prefix relation between two tracked paths
1348
- * is then a GENUINE structural divergence (one sibling authored a leaf
1349
- * value where the other authored a nested branch) — the review-F1 case that
1350
- * per-leaf value comparison alone cannot see.
1341
+ * Leaf-path flatten WITHOUT the `_` identity collapse, so a leaf and a branch
1342
+ * can never share a spelling and a prefix relation is a real divergence.
1351
1343
  */
1352
1344
  function flattenLeafPathsExact(object, path) {
1353
1345
  const result = {};
@@ -1360,12 +1352,8 @@ function flattenLeafPathsExact(object, path) {
1360
1352
  return result;
1361
1353
  }
1362
1354
  /**
1363
- * Deep copy of plain theme data (records, arrays, primitives). `merge`
1364
- * adopts and MUTATES nested source objects in place, so EVERY builder step
1365
- * copies before folding: a consumed kit's built theme must never be
1366
- * corrupted by composition, and prior builder state must never be shared by
1367
- * reference — a one-level copy lets branching a builder cross-contaminate
1368
- * both branches and lets build() outputs mutate after the fact.
1355
+ * `merge` adopts and MUTATES nested source objects, so every builder step deep
1356
+ * copies first: a consumed kit's theme and prior state must survive intact.
1369
1357
  */
1370
1358
  function deepCopyPlain(value) {
1371
1359
  if (Array.isArray(value)) return value.map(deepCopyPlain);
@@ -1377,7 +1365,6 @@ function deepCopyPlain(value) {
1377
1365
  }
1378
1366
  return value;
1379
1367
  }
1380
- /** Structural equality over plain theme data (records, arrays, primitives). */
1381
1368
  function plainDataEqual(a, b) {
1382
1369
  if (Object.is(a, b)) return true;
1383
1370
  if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((item, index) => plainDataEqual(item, b[index]));
@@ -1390,10 +1377,8 @@ function plainDataEqual(a, b) {
1390
1377
  return false;
1391
1378
  }
1392
1379
  /**
1393
- * Re-seed builder state from a source theme's manifest. This is `from()`'s
1394
- * manifest read, factored out verbatim so `extend()` inherits the SAME
1395
- * emitted-scale/contextual-var/manifest-v2 carry semantics: the manifest is
1396
- * non-enumerable, so the ordinary key-copy loop never sees it (D6/D8).
1380
+ * Re-seeds emitted scales, contextual vars and the manifest-v2 carry from a
1381
+ * source manifest — non-enumerable, so key-copy loops never see it.
1397
1382
  */
1398
1383
  function reseedStateFromManifest(state, manifest, mergeExtensionState = false) {
1399
1384
  if (manifest?.emittedScales) for (const scale of manifest.emittedScales) state.emittedScales.add(scale);
@@ -1406,29 +1391,27 @@ function reseedStateFromManifest(state, manifest, mergeExtensionState = false) {
1406
1391
  }
1407
1392
  if (manifest?.contextualVars) for (const [scale, vars] of Object.entries(manifest.contextualVars)) {
1408
1393
  const existing = mergeExtensionState ? state.contextualVars.get(scale) : void 0;
1409
- state.contextualVars.set(scale, existing ? [...new Set([...existing, ...vars])] : [...vars]);
1410
- }
1411
- if (manifest) if (manifest.manifestVersion === 2) {
1412
- state.carriedManifestV2 = {
1413
- tokenDefinitions: manifest.tokenDefinitions,
1414
- emittedScales: manifest.emittedScales,
1415
- modeAliasDefinitions: manifest.modeAliasDefinitions,
1416
- registrations: manifest.registrations,
1417
- emitterVersion: manifest.emitterVersion,
1418
- contractHash: manifest.contractHash,
1419
- cssFragments: manifest.cssFragments
1420
- };
1421
- if (manifest.registrations) for (const [name, registration] of Object.entries(manifest.registrations)) {
1422
- const existing = state.contextualVarRegistrations.get(name);
1423
- if (mergeExtensionState && existing && (existing.syntax !== registration.syntax || existing.inherits !== registration.inherits || existing.initialValue !== registration.initialValue)) throw new Error(`extend: contextual variable '${name}' has divergent @property registrations across extended themes`);
1424
- state.contextualVarRegistrations.set(name, registration);
1425
- }
1426
- } else state.hasLegacyManifestSource = true;
1394
+ state.contextualVars.set(scale, existing ? [.../* @__PURE__ */ new Set([...existing, ...vars])] : [...vars]);
1395
+ }
1396
+ if (manifest) {
1397
+ if (manifest.manifestVersion === 2) {
1398
+ state.carriedManifestV2 = {
1399
+ tokenDefinitions: manifest.tokenDefinitions,
1400
+ emittedScales: manifest.emittedScales,
1401
+ modeAliasDefinitions: manifest.modeAliasDefinitions,
1402
+ registrations: manifest.registrations,
1403
+ emitterVersion: manifest.emitterVersion,
1404
+ contractHash: manifest.contractHash,
1405
+ cssFragments: manifest.cssFragments
1406
+ };
1407
+ if (manifest.registrations) for (const [name, registration] of Object.entries(manifest.registrations)) {
1408
+ const existing = state.contextualVarRegistrations.get(name);
1409
+ if (mergeExtensionState && existing && (existing.syntax !== registration.syntax || existing.inherits !== registration.inherits || existing.initialValue !== registration.initialValue)) throw new Error(`extend: contextual variable '${name}' has divergent @property registrations across extended themes`);
1410
+ state.contextualVarRegistrations.set(name, registration);
1411
+ }
1412
+ } else state.hasLegacyManifestSource = true;
1413
+ }
1427
1414
  }
1428
- /**
1429
- * ThemeScales — the final phase. Has addScale, extendScale, declareContextualVars, build.
1430
- * Also allows addColors and addColorModes for augmentation.
1431
- */
1432
1415
  var ThemeBuilder = class ThemeBuilder {
1433
1416
  /** @internal */ _state;
1434
1417
  constructor(state) {
@@ -1440,15 +1423,8 @@ var ThemeBuilder = class ThemeBuilder {
1440
1423
  return new ThemeBuilder(copyState(this._state, nextTheme));
1441
1424
  }
1442
1425
  /**
1443
- * FROZEN for the deprecation window (D1/G6): source-WINS precedence over
1444
- * prior builder state, callable at ANY stage — the phantom Stage passes
1445
- * through unchanged. Do not add a stage gate; do not flip the merge
1446
- * direction. Those semantics ship under the new verb only.
1447
- *
1448
- * @deprecated Use `extend(source)` — the single extension verb on both
1449
- * builders: the extended source seeds the base and later local calls win.
1450
- * `from()` keeps these frozen source-wins semantics for at least one minor
1451
- * release after `extend()` ships.
1426
+ * Source-WINS precedence over prior builder state, callable at any stage.
1427
+ * @deprecated Use `extend(source)` — the source seeds the base, local wins.
1452
1428
  */
1453
1429
  from(builtTheme) {
1454
1430
  const source = isLibraryBundle(builtTheme) ? builtTheme.theme ?? builtTheme.tokens ?? {} : builtTheme;
@@ -1463,19 +1439,8 @@ var ThemeBuilder = class ThemeBuilder {
1463
1439
  return next;
1464
1440
  }
1465
1441
  /**
1466
- * Extend this theme from a consumed library (D1/D2): the source's complete
1467
- * configuration — tokens, modes, preferences, registrations, emitted-scale
1468
- * set — seeds the builder as the BASE, and local calls made after
1469
- * `extend()` win on conflict (the mirror of `from()`'s source-wins).
1470
- * Chainable and repeatable, but only before augmentation methods ("inherit
1471
- * first, then extend" — enforced by the phantom builder stage). Accepts a
1472
- * built theme or a library bundle, consuming the bundle's theme half
1473
- * (`theme ?? tokens`, D9) and ignoring the rest.
1474
- *
1475
- * Sibling conflicts fail loud (D3/G4): a leaf path defined divergently by
1476
- * two extended sources throws naming the path and both sources
1477
- * positionally; equal values coalesce silently, and the consumer's own
1478
- * post-extend `add*` calls override silently (NS-4).
1442
+ * Inherits a built theme or library bundle as the BASE; local calls after
1443
+ * `extend()` win, and leaves that two sources define divergently throw.
1479
1444
  */
1480
1445
  extend(source) {
1481
1446
  const themeHalf = isLibraryBundle(source) ? source.theme ?? source.tokens ?? {} : source;
@@ -1586,10 +1551,6 @@ var ThemeBuilder = class ThemeBuilder {
1586
1551
  const nextTheme = merge(deepCopyPlain(this._state.theme), { [key]: updateFn(this._state.theme[key]) });
1587
1552
  return new ThemeBuilder(copyState(this._state, nextTheme));
1588
1553
  }
1589
- /**
1590
- * Finalize the theme build.
1591
- * Flattens nested data at the boundary — produces manifest and serialize().
1592
- */
1593
1554
  build() {
1594
1555
  const theme = deepCopyPlain(this._state.theme);
1595
1556
  const emittedScales = this._state.emittedScales;
@@ -1722,14 +1683,11 @@ var ThemeBuilder = class ThemeBuilder {
1722
1683
  function createTheme() {
1723
1684
  return new ThemeBuilder(createState());
1724
1685
  }
1725
- /** Token ref pattern: {scale.key} or {scale.key.sub} */
1686
+ /** Authored token reference: `{scale.key}` or `{scale.key/opacity}`. */
1726
1687
  const TOKEN_REF_RE = /\{([^}]+)\}/g;
1727
1688
  /**
1728
- * Flatten the nested theme into dot-path keyed token map and CSS variable declarations.
1729
- * This is the ONLY place where flattening happens. Mode VALUE maps are no
1730
- * longer computed here — they resolve AFTER `resolveReferences` (see
1731
- * {@link resolveModeValueMaps}), closing the G2 gap where mode-override
1732
- * declarations bypassed the resolver.
1689
+ * The only flatten pass: nested theme → dot-path token map and variable
1690
+ * declarations. Mode value maps resolve later, after `resolveReferences`.
1733
1691
  */
1734
1692
  function flattenTheme(theme, emittedScales, effectiveModes) {
1735
1693
  const tokenMap = {};
@@ -1776,17 +1734,15 @@ function flattenTheme(theme, emittedScales, effectiveModes) {
1776
1734
  tokenDefinitions
1777
1735
  };
1778
1736
  }
1779
- /** Rebuild a record with lexicographically sorted keys (wire determinism, G3). */
1737
+ /** Sorted-key rebuild — the serialized wire must not expose insertion order. */
1780
1738
  function sortRecordByKey(record) {
1781
1739
  const sorted = {};
1782
1740
  for (const key of Object.keys(record).sort()) sorted[key] = record[key];
1783
1741
  return sorted;
1784
1742
  }
1785
1743
  /**
1786
- * Collect the AUTHORED mode alias graph: mode name → alias dot-path → the
1787
- * authored color dot-path (manifest v2, D6 — never a resolved value).
1788
- * Empty when the theme has no modes or no colors, mirroring the original
1789
- * flatten-pass guard.
1744
+ * The AUTHORED alias graph: mode → alias dot-path → authored color dot-path,
1745
+ * never a resolved value.
1790
1746
  */
1791
1747
  function collectAuthoredModeAliases(theme) {
1792
1748
  const authored = {};
@@ -1803,13 +1759,8 @@ function collectAuthoredModeAliases(theme) {
1803
1759
  return authored;
1804
1760
  }
1805
1761
  /**
1806
- * D6 coverage over the merged mode set: a CONSUMER-declared mode leaving
1807
- * inherited aliases uncovered must name a base (`basedOn`) whose chain
1808
- * covers them — otherwise the build fails listing the uncovered set.
1809
- * Inherited modes are exempt (a kit's own asymmetry is pre-existing
1810
- * behavior and must round-trip byte-identically). Returns the effective
1811
- * alias map per mode (base-chain fills + authored, authored winning) and
1812
- * one aggregated fill report per mode for the build diagnostic.
1762
+ * A CONSUMER-declared mode leaving inherited aliases uncovered must name a
1763
+ * base whose chain covers them; inherited modes are exempt.
1813
1764
  */
1814
1765
  function resolveModeCoverage(authoredModeAliases, modeBases, inheritedModes, inheritedModeAliases) {
1815
1766
  const effectiveModes = {};
@@ -1825,7 +1776,7 @@ function resolveModeCoverage(authoredModeAliases, modeBases, inheritedModes, inh
1825
1776
  const stillUncovered = [];
1826
1777
  for (const alias of uncovered) {
1827
1778
  let cursor = base;
1828
- const seen = new Set([modeName]);
1779
+ const seen = /* @__PURE__ */ new Set([modeName]);
1829
1780
  let resolved;
1830
1781
  while (cursor !== void 0 && !seen.has(cursor)) {
1831
1782
  seen.add(cursor);
@@ -1855,12 +1806,8 @@ function resolveModeCoverage(authoredModeAliases, modeBases, inheritedModes, inh
1855
1806
  };
1856
1807
  }
1857
1808
  /**
1858
- * Resolve the per-mode value maps AFTER reference resolution: every mode
1859
- * declaration carries the RESOLVED value of its target color (emitted →
1860
- * the resolved declaration value; inlined → the resolved literal), never
1861
- * the raw flattened string — the G2 closure for `[data-color-mode]` blocks.
1862
- * Modes iterate in sorted name order and lines in sorted property-name
1863
- * order, so mode declaration/insertion order is never observable (G3).
1809
+ * Per-mode declarations carry the RESOLVED value of their target color, never
1810
+ * the raw flattened string. Modes and lines sort, hiding authoring order.
1864
1811
  */
1865
1812
  function resolveModeValueMaps(effectiveModes, variableMap, variables, tokenMap) {
1866
1813
  const modeVariables = {};
@@ -1893,12 +1840,8 @@ function resolveModeValueMaps(effectiveModes, variableMap, variables, tokenMap)
1893
1840
  };
1894
1841
  }
1895
1842
  /**
1896
- * D5 enforcement: a reference whose target is absent from the merged map
1897
- * AND was dropped by an explicit `addScale({ replace: true })` fails the
1898
- * build — unconditional on usage — naming the referencing token (with its
1899
- * positional origin), the replacement call's scale, and the dropped keys.
1900
- * Targets never defined ANYWHERE stay warn-and-literal (supported kit
1901
- * pattern); a re-added key is simply known again and passes.
1843
+ * A reference to a target dropped by `addScale({ replace: true })` fails the
1844
+ * build. Targets never defined anywhere stay warn-and-literal.
1902
1845
  */
1903
1846
  function assertNoDroppedReferences(tokenDefinitions, flatTokenMap, droppedTokenPaths, extendProvenance) {
1904
1847
  if (droppedTokenPaths.size === 0) return;
@@ -1919,9 +1862,8 @@ function assertNoDroppedReferences(tokenDefinitions, flatTokenMap, droppedTokenP
1919
1862
  }
1920
1863
  if (violations.length > 0) throw new Error(`build: dangling token reference(s) after explicit scale replacement — ${violations.join("; ")}. Dropped keys: ${[...droppedNamed].sort().join(", ")}.`);
1921
1864
  }
1922
- /** A resolved value still carrying a `{…}` reference — never shippable (G2). */
1865
+ /** A resolved value still carrying a `{…}` reference — never shippable. */
1923
1866
  const UNRESOLVED_REF_RE = /\{[^}]+\}/;
1924
- /** Custom-property names referenced through `var(--name)` in a value. */
1925
1867
  const VAR_REF_NAME_RE = /var\(\s*(--[\w-]+)/g;
1926
1868
  function varRefNames(value) {
1927
1869
  const names = [];
@@ -1929,18 +1871,8 @@ function varRefNames(value) {
1929
1871
  return names;
1930
1872
  }
1931
1873
  /**
1932
- * Split root and mode variable maps into shippable declarations and omitted
1933
- * var names (G2): any value still containing `{…}` after resolution — a
1934
- * direct or TRANSITIVE never-defined target — is withheld from emitted CSS.
1935
- *
1936
- * Withholding must follow `var()` indirection too: flattenTheme synthesizes
1937
- * the initial mode's aliases as `var(--target)` BEFORE resolution, so a
1938
- * withheld target would otherwise leave an emitted alias pointing at a
1939
- * declaration that exists nowhere. A declaration is dangling when a var()
1940
- * target of its value was DECLARED in this build but withheld everywhere
1941
- * the declaration can see (:root for root declarations; the same mode
1942
- * block or :root for mode declarations); chains drop to a fixpoint. Var
1943
- * names never declared here (breakpoints, contextual vars) are exempt.
1874
+ * Withholds any declaration whose value still contains `{…}`, then — to a
1875
+ * fixpoint — any whose `var()` target was withheld everywhere it can see.
1944
1876
  */
1945
1877
  function omitUnresolvedDeclarations(variables, modeVariables) {
1946
1878
  const omitted = [];
@@ -1989,11 +1921,8 @@ function omitUnresolvedDeclarations(variables, modeVariables) {
1989
1921
  };
1990
1922
  }
1991
1923
  /**
1992
- * Classify a RAW token value into its authored form (manifest v2, D6). MUST
1993
- * run before `resolveReferences` — resolution rewrites the string, and the
1994
- * authored graph cannot be reconstructed from resolved CSS (D8). Uses
1995
- * `matchAll` so the shared global {@link TOKEN_REF_RE} never carries a stale
1996
- * `lastIndex` between callers.
1924
+ * Classifies a RAW token value into its authored form. `matchAll` keeps the
1925
+ * shared global {@link TOKEN_REF_RE} from carrying a stale `lastIndex`.
1997
1926
  */
1998
1927
  function parseTokenDefinition(rawValue) {
1999
1928
  const references = [];
@@ -2017,15 +1946,12 @@ function parseTokenDefinition(rawValue) {
2017
1946
  }
2018
1947
  /**
2019
1948
  * Version of the CSS emitter that composed a manifest's fragments. Bump when
2020
- * ANY emitted byte changes for the same authored input — composition (D8)
2021
- * regenerates dirty sections "under the pinned emitter version", and this is
2022
- * that pin.
1949
+ * any emitted byte changes for the same authored input.
2023
1950
  */
2024
1951
  const EMITTER_VERSION = 1;
2025
1952
  /**
2026
- * Sorted-key canonical form for hashing: object keys are emitted in sorted
2027
- * order at every depth so the digest is independent of insertion order;
2028
- * arrays keep authored order (reference order is contractual).
1953
+ * Keys sort at every depth so the digest ignores insertion order; arrays keep
1954
+ * authored order, which is contractual.
2029
1955
  */
2030
1956
  function canonicalize(value) {
2031
1957
  if (Array.isArray(value)) return value.map(canonicalize);
@@ -2038,13 +1964,8 @@ function canonicalize(value) {
2038
1964
  return value;
2039
1965
  }
2040
1966
  /**
2041
- * sha256 hex digest. Primary path is `node:crypto` `createHash('sha256')`,
2042
- * reached via `process.getBuiltinModule` so this module gains NO static
2043
- * `node:crypto` import edge: built themes execute inside client bundles today
2044
- * (e2e fixtures inline `createTheme(…).build()` at module init), and a static
2045
- * builtin import is exactly what `./bootstrap` documents as forbidden in app
2046
- * bundles. Non-Node runtimes use the pure fallback below, which produces
2047
- * identical hex for identical input — determinism holds across environments.
1967
+ * Reached through `process.getBuiltinModule` so this module gains no static
1968
+ * `node:crypto` import edge — built themes run inside client bundles.
2048
1969
  */
2049
1970
  function sha256Hex(input) {
2050
1971
  const nodeCrypto = globalThis.process?.getBuiltinModule?.("node:crypto");
@@ -2122,11 +2043,8 @@ function rotr(x, n) {
2122
2043
  return (x >>> n | x << 32 - n) >>> 0;
2123
2044
  }
2124
2045
  /**
2125
- * UTF-8 bytes of `input` without WHATWG `TextEncoder`: the Rust system-loader
2126
- * evaluates the system bundle in QuickJS, which provides ES built-ins only —
2127
- * no Node globals and no WHATWG APIs. Iterating by code point handles
2128
- * surrogate pairs; lone surrogates cannot reach this path because the input
2129
- * is well-formed `JSON.stringify` output (ES2019 escapes them as `\uXXXX`).
2046
+ * UTF-8 bytes without `TextEncoder`: the Rust system-loader evaluates this
2047
+ * bundle in QuickJS, which provides ES built-ins only.
2130
2048
  */
2131
2049
  function utf8Bytes(input) {
2132
2050
  const bytes = [];
@@ -2140,9 +2058,8 @@ function utf8Bytes(input) {
2140
2058
  return new Uint8Array(bytes);
2141
2059
  }
2142
2060
  /**
2143
- * Pure SHA-256 (FIPS 180-4) over the UTF-8 bytes of `input` — the non-Node
2144
- * fallback for {@link sha256Hex}. Not a security surface: the contract hash
2145
- * is a content digest for composition-identity comparison only.
2061
+ * Pure SHA-256 (FIPS 180-4), the non-Node fallback for {@link sha256Hex}. Not
2062
+ * a security surface — the contract hash is a content digest only.
2146
2063
  */
2147
2064
  function sha256HexFallback(input) {
2148
2065
  const bytes = utf8Bytes(input);
@@ -2164,7 +2081,7 @@ function sha256HexFallback(input) {
2164
2081
  528734635,
2165
2082
  1541459225
2166
2083
  ]);
2167
- const w = new Uint32Array(64);
2084
+ const w = /* @__PURE__ */ new Uint32Array(64);
2168
2085
  for (let offset = 0; offset < paddedLength; offset += 64) {
2169
2086
  for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4);
2170
2087
  for (let i = 16; i < 64; i++) {
@@ -2207,20 +2124,13 @@ function sha256HexFallback(input) {
2207
2124
  for (const word of state) hex += word.toString(16).padStart(8, "0");
2208
2125
  return hex;
2209
2126
  }
2210
- /**
2211
- * Stable digest over the canonical authored inputs. Identical authored input
2212
- * ⇒ identical hash, across processes: keys sort at every depth and
2213
- * `JSON.stringify` drops `undefined`-valued members deterministically.
2214
- */
2127
+ /** Identical authored input ⇒ identical hash, across processes. */
2215
2128
  function computeContractHash(input) {
2216
2129
  return sha256Hex(JSON.stringify(canonicalize(input)));
2217
2130
  }
2218
2131
  /**
2219
- * Build `@property` registration rules for registered contextual vars.
2220
- * The emitted custom property is `--${name}` — the same name the Rust resolver
2221
- * maps a bare contextual var value to. Rules emit in declaration order and only
2222
- * for names that are genuinely declared contextual vars. Returns `''` when no
2223
- * registration metadata was supplied (opt-in / byte-identical guarantee).
2132
+ * `@property` rules for registered contextual vars, emitted as `--${name}` —
2133
+ * the name the Rust resolver maps a bare contextual var to. `''` when none.
2224
2134
  */
2225
2135
  function buildPropertyRegistrationCss(contextualVars, registrations) {
2226
2136
  if (registrations.size === 0) return "";
@@ -2235,7 +2145,6 @@ function buildPropertyRegistrationCss(contextualVars, registrations) {
2235
2145
  }
2236
2146
  return blocks.join("\n");
2237
2147
  }
2238
- /** Build CSS variable blocks from flattened data. */
2239
2148
  function buildVariableCss(rootVariables, breakpointVariables, modeVariables, systemEmission = {}) {
2240
2149
  const { initialMode, systemPreference, browserColorScheme } = systemEmission;
2241
2150
  const parts = [];