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