@animus-ui/extract 0.1.17 → 0.1.19

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 (35) hide show
  1. package/README.md +2 -2
  2. package/crates/extract-v2/animus-extract-v2.darwin-arm64.node +0 -0
  3. package/crates/extract-v2/animus-extract-v2.linux-arm64-gnu.node +0 -0
  4. package/crates/extract-v2/animus-extract-v2.linux-x64-gnu.node +0 -0
  5. package/crates/extract-v2/index.d.ts +5 -0
  6. package/dist/index.cjs +6 -2
  7. package/dist/index.mjs +2 -2
  8. package/dist/pipeline/asset-placeholders.d.ts +19 -0
  9. package/dist/pipeline/asset-placeholders.d.ts.map +1 -1
  10. package/dist/pipeline/engine-retirement.d.ts.map +1 -1
  11. package/dist/pipeline/index.d.ts +3 -3
  12. package/dist/pipeline/index.d.ts.map +1 -1
  13. package/dist/pipeline/manifest-diagnostics.d.ts +32 -1
  14. package/dist/pipeline/manifest-diagnostics.d.ts.map +1 -1
  15. package/dist/pipeline/manifest-schema.d.ts +5 -2
  16. package/dist/pipeline/manifest-schema.d.ts.map +1 -1
  17. package/dist/pipeline/property-diagnostics.d.ts +17 -0
  18. package/dist/pipeline/property-diagnostics.d.ts.map +1 -0
  19. package/dist/pipeline/property-registrations.d.ts +23 -0
  20. package/dist/pipeline/property-registrations.d.ts.map +1 -0
  21. package/dist/pipeline/run-analysis.d.ts +2 -0
  22. package/dist/pipeline/run-analysis.d.ts.map +1 -1
  23. package/dist/pipeline/system-config.d.ts +6 -0
  24. package/dist/pipeline/system-config.d.ts.map +1 -1
  25. package/dist/{pipeline-D7TdVERQ.mjs → pipeline-BuiItKT-.mjs} +393 -31
  26. package/dist/{pipeline-OMz1HGqU.cjs → pipeline-CD2qmEEn.cjs} +420 -34
  27. package/dist/session/extraction-session.d.ts +3 -5
  28. package/dist/session/extraction-session.d.ts.map +1 -1
  29. package/dist/session/timing.d.ts.map +1 -1
  30. package/dist/session.cjs +20 -20
  31. package/dist/session.mjs +20 -20
  32. package/index-v2.js +1 -1
  33. package/package.json +4 -4
  34. package/dist/pipeline/timing-waterfall.d.ts +0 -6
  35. package/dist/pipeline/timing-waterfall.d.ts.map +0 -1
@@ -1166,7 +1166,7 @@ function createV2EngineApi(deps) {
1166
1166
  }
1167
1167
  //#endregion
1168
1168
  //#region pipeline/engine-retirement.ts
1169
- const RETIRED_ENGINE_MESSAGE = "animus: extraction engine 'v1' was retired (openspec: retire-extract-v1) — v2 is the only engine; remove the engine option / ANIMUS_ENGINE override";
1169
+ const RETIRED_ENGINE_MESSAGE = "animus: the 'v1' extraction engine is no longer supported, and v2 is the only engine. Remove the engine option from your Animus config and unset the ANIMUS_ENGINE environment variable.";
1170
1170
  function assertNoRetiredEngineSelection(engineOption) {
1171
1171
  if (engineOption === "v1" || process.env.ANIMUS_ENGINE === "v1") throw new Error(RETIRED_ENGINE_MESSAGE);
1172
1172
  }
@@ -2023,6 +2023,31 @@ function substituteAssetPlaceholders(css, urlBySpecifier) {
2023
2023
  }
2024
2024
  return out;
2025
2025
  }
2026
+ function findSheetAssetSpecifiers(sheets) {
2027
+ return [.../* @__PURE__ */ new Set([
2028
+ ...findAssetSpecifiers(sheets.variableCss),
2029
+ ...findAssetSpecifiers(sheets.globalCss),
2030
+ ...findAssetSpecifiers(sheets.componentCss)
2031
+ ])];
2032
+ }
2033
+ /** The one substitution step every emitted sheet passes through. */
2034
+ function substituteSheetAssets(sheets, urlBySpecifier) {
2035
+ return {
2036
+ variableCss: substituteAssetPlaceholders(sheets.variableCss, urlBySpecifier),
2037
+ globalCss: substituteAssetPlaceholders(sheets.globalCss, urlBySpecifier),
2038
+ componentCss: substituteAssetPlaceholders(sheets.componentCss, urlBySpecifier)
2039
+ };
2040
+ }
2041
+ const UNSUBSTITUTED_ASSET_CODE = "animus.asset.unsubstituted-placeholder";
2042
+ /** A placeholder in emitted CSS is a broken URL in the browser: it fails a
2043
+ * strict build and warns otherwise. */
2044
+ function reportSurvivingAssetPlaceholders(css, report) {
2045
+ const specifiers = findAssetSpecifiers(css);
2046
+ if (specifiers.length === 0) return;
2047
+ const message = `${report.prefix} asset() placeholders reached emitted CSS unsubstituted: ${specifiers.join(", ")} (${UNSUBSTITUTED_ASSET_CODE})`;
2048
+ if (report.strict) throw new Error(message);
2049
+ report.warn(message);
2050
+ }
2026
2051
  //#endregion
2027
2052
  //#region pipeline/resolve-asset.ts
2028
2053
  /**
@@ -2315,14 +2340,35 @@ function unreadableSourceDiagnostic(relPath, error) {
2315
2340
  }
2316
2341
  /** The collision entry code minted by the system package's merge. */
2317
2342
  const VOCABULARY_COLLISION = "animus.vocabulary.collision";
2343
+ /** A theme `@property` registration browsers would ignore; it is not emitted. */
2344
+ const INVALID_PROPERTY_REGISTRATION = "animus.theme.invalid-property-registration";
2318
2345
  /** Minted when a sealed kit with registered vocabulary is consumed through
2319
2346
  * the deprecated `from()`/`includes:` verbs. */
2320
2347
  const VOCABULARY_LEGACY_VERB = "animus.vocabulary.legacy-verb";
2348
+ /** A `var()` fallback that never applies: the property is registered with an
2349
+ * initial value. */
2350
+ const PROPERTY_FALLBACK_SUPPRESSED = "animus.property.fallback-suppressed";
2351
+ /** The same, where the fallback heads a chain of further `var()` reads. */
2352
+ const PROPERTY_FALLBACK_CHAIN_SUPPRESSED = "animus.property.fallback-chain-suppressed";
2353
+ /** A declared property set in keyframes or transitioned without a typed
2354
+ * registration, so it does not interpolate. */
2355
+ const PROPERTY_UNREGISTERED_ANIMATION = "animus.property.unregistered-animation";
2356
+ /** The same, where `allow-discrete` shows interpolation was intended. */
2357
+ const PROPERTY_DISCRETE_ANIMATION = "animus.property.discrete-animation";
2358
+ /** A custom property whose resolved value is `var()` of itself, which makes
2359
+ * it invalid at computed-value time. */
2360
+ const PROPERTY_SELF_REFERENCE = "animus.property.self-reference";
2321
2361
  const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
2322
2362
  [SELECTOR_UNSUPPORTED_SUBJECT, "error"],
2323
2363
  [UNREADABLE_SOURCE_FILE, "error"],
2324
2364
  [VOCABULARY_COLLISION, "warn"],
2325
- [VOCABULARY_LEGACY_VERB, "warn"]
2365
+ [VOCABULARY_LEGACY_VERB, "warn"],
2366
+ [INVALID_PROPERTY_REGISTRATION, "error"],
2367
+ [PROPERTY_FALLBACK_SUPPRESSED, "info"],
2368
+ [PROPERTY_FALLBACK_CHAIN_SUPPRESSED, "warn"],
2369
+ [PROPERTY_UNREGISTERED_ANIMATION, "info"],
2370
+ [PROPERTY_DISCRETE_ANIMATION, "warn"],
2371
+ [PROPERTY_SELF_REFERENCE, "warn"]
2326
2372
  ]);
2327
2373
  /** An unlisted code is `warn`: a witness kind from a newer system package
2328
2374
  * must not fail the strict build of a host that predates its writer. */
@@ -2363,6 +2409,19 @@ function vocabularyWitnessDiagnostics(vocabularyWitnessesJson) {
2363
2409
  });
2364
2410
  return diagnostics;
2365
2411
  }
2412
+ /** The diagnostics a loaded system's own records carry: vocabulary
2413
+ * witnesses and invalid `@property` registrations. */
2414
+ function systemLoadDiagnostics(system) {
2415
+ const registrations = (system.invalidPropertyRegistrations ?? []).map(({ name, reason }) => ({
2416
+ file: "system",
2417
+ component: name,
2418
+ kind: "warn",
2419
+ message: `@property ${name} was not emitted: ${reason}. Browsers ignore an invalid registration and minifying it fails; fix it where the theme declares ${name} (${INVALID_PROPERTY_REGISTRATION})`,
2420
+ code: INVALID_PROPERTY_REGISTRATION,
2421
+ severity: severityFor(INVALID_PROPERTY_REGISTRATION)
2422
+ }));
2423
+ return [...vocabularyWitnessDiagnostics(system.vocabularyWitnessesJson), ...registrations];
2424
+ }
2366
2425
  /**
2367
2426
  * The single strict-escalation policy point for both extraction plugins:
2368
2427
  * error-severity diagnostics throw together under `strict`, else warn.
@@ -2374,9 +2433,13 @@ function surfaceManifestDiagnostics(manifest, warn, policy = {}) {
2374
2433
  let line = null;
2375
2434
  if (diagnostic.kind === "bail") line = `⚠ ${diagnostic.component} not extracted: ${diagnostic.message}`;
2376
2435
  else if (diagnostic.kind === "skip") line = `⚠ ${diagnostic.component}: skipped ${diagnostic.message}`;
2377
- else if (diagnostic.kind === "warn") line = `⚠ ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`;
2436
+ else if (diagnostic.kind === "warn") line = `${diagnostic.severity === "info" ? "ℹ" : "⚠"} ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`;
2378
2437
  if (line === null) continue;
2379
2438
  if (diagnostic.code && !diagnostic.message.includes(diagnostic.code)) line += ` [${diagnostic.code}]`;
2439
+ if (diagnostic.severity === "info") {
2440
+ policy.info?.(line);
2441
+ continue;
2442
+ }
2380
2443
  if (policy.strict && diagnostic.severity === "error") {
2381
2444
  errors.push(`${diagnostic.code ?? "error"} — ${diagnostic.component}: ${diagnostic.message}`);
2382
2445
  continue;
@@ -2386,6 +2449,315 @@ function surfaceManifestDiagnostics(manifest, warn, policy = {}) {
2386
2449
  if (errors.length > 0) throw new Error(`[animus] strict: ${errors.length} error diagnostic(s):\n${errors.join("\n")}`);
2387
2450
  }
2388
2451
  //#endregion
2452
+ //#region pipeline/property-registrations.ts
2453
+ const PROPERTY_RULE = /@property\s+(--[\w-]+)\s*\{[^}]*\}\n?/g;
2454
+ /** The data types a registration's `syntax` may name. */
2455
+ const SYNTAX_TYPES = /* @__PURE__ */ new Set([
2456
+ "angle",
2457
+ "color",
2458
+ "custom-ident",
2459
+ "image",
2460
+ "integer",
2461
+ "length",
2462
+ "length-percentage",
2463
+ "number",
2464
+ "percentage",
2465
+ "resolution",
2466
+ "string",
2467
+ "time",
2468
+ "transform-function",
2469
+ "transform-list",
2470
+ "url"
2471
+ ]);
2472
+ const SYNTAX_COMPONENT = /^(?:<([a-z-]+)>[+#]?|-?[a-zA-Z_][\w-]*)$/;
2473
+ /** The syntax types whose values can carry a font- or container-relative
2474
+ * unit, which makes a typed initial value depend on context. */
2475
+ const DIMENSION_TYPES = /* @__PURE__ */ new Set([
2476
+ "image",
2477
+ "length",
2478
+ "length-percentage",
2479
+ "transform-function",
2480
+ "transform-list"
2481
+ ]);
2482
+ const RELATIVE_UNIT = /(?:\d|\.)(?:r?em|r?ex|r?cap|r?ch|r?ic|r?lh|cq(?:w|h|i|b|min|max))\b/i;
2483
+ const SUBSTITUTION = /\b(?:var|env|attr)\(/i;
2484
+ /** Quoted strings and `url()` hold text, never units or substitutions. */
2485
+ const LITERAL_TEXT = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|url\([^)]*\)/gi;
2486
+ /**
2487
+ * Removes the `@property` rules browsers would ignore — an unknown syntax
2488
+ * component, a syntax other than `*` without an initial value, an initial
2489
+ * value that depends on context, or one the syntax does not accept.
2490
+ * Minifying a stylesheet that contains such a rule can also fail.
2491
+ */
2492
+ function splitInvalidPropertyRegistrations(css) {
2493
+ const invalid = [];
2494
+ return {
2495
+ css: css.replace(PROPERTY_RULE, (rule, name) => {
2496
+ const reason = registrationFailure(rule);
2497
+ if (reason === null) return rule;
2498
+ invalid.push({
2499
+ name,
2500
+ reason
2501
+ });
2502
+ return "";
2503
+ }),
2504
+ invalid
2505
+ };
2506
+ }
2507
+ /** The registrations in `css`, by custom-property name. */
2508
+ function propertyRegistrations(css) {
2509
+ const registrations = /* @__PURE__ */ new Map();
2510
+ for (const match of css.matchAll(PROPERTY_RULE)) registrations.set(match[1], readRegistration(match[0]));
2511
+ return registrations;
2512
+ }
2513
+ function readRegistration(rule) {
2514
+ return {
2515
+ syntax: /syntax:\s*"([^"]*)"/.exec(rule)?.[1]?.trim() ?? "",
2516
+ initialValue: /initial-value\s*:\s*([^;}]*)/.exec(rule)?.[1]?.trim()
2517
+ };
2518
+ }
2519
+ function registrationFailure(rule) {
2520
+ const { syntax, initialValue } = readRegistration(rule);
2521
+ if (syntax !== "*") {
2522
+ const components = syntax.split("|").map((component) => ({
2523
+ text: component.trim(),
2524
+ match: SYNTAX_COMPONENT.exec(component.trim())
2525
+ }));
2526
+ const unknown = components.find(({ match }) => match === null || match[1] !== void 0 && !SYNTAX_TYPES.has(match[1]));
2527
+ if (unknown !== void 0) return `syntax "${syntax}" has the unknown component "${unknown.text}"`;
2528
+ if (initialValue === void 0) return `syntax "${syntax}" needs an initialValue`;
2529
+ const dimensional = components.some(({ match }) => match?.[1] !== void 0 && DIMENSION_TYPES.has(match[1]));
2530
+ const value = initialValue.replace(LITERAL_TEXT, "");
2531
+ if (SUBSTITUTION.test(value) || dimensional && RELATIVE_UNIT.test(value)) return `initialValue "${initialValue}" depends on context; font- and container-relative units and var(), env() and attr() are not allowed`;
2532
+ }
2533
+ try {
2534
+ transform({
2535
+ filename: "animus-registrations.css",
2536
+ code: Buffer.from(rule)
2537
+ });
2538
+ return null;
2539
+ } catch (error) {
2540
+ const detail = error instanceof Error ? error.message : String(error);
2541
+ return `the CSS parser rejects \`${rule.trim()}\` (${detail})`;
2542
+ }
2543
+ }
2544
+ //#endregion
2545
+ //#region pipeline/property-diagnostics.ts
2546
+ /**
2547
+ * Reports custom properties that will not behave as their CSS suggests: a
2548
+ * property that refers to itself and, once the system emits an `@property`
2549
+ * rule, a fallback an initial value suppresses and an animated property that
2550
+ * cannot interpolate. The CSS itself is never changed.
2551
+ */
2552
+ function checkCustomProperties(input) {
2553
+ const registered = propertyRegistrations(input.system.variableCss);
2554
+ const declared = declaredProperties(input.system.contextualVarsJson);
2555
+ const findings = [];
2556
+ const sheets = [{
2557
+ css: input.componentCss,
2558
+ owner: componentOwner(input.manifest)
2559
+ }, {
2560
+ css: input.globalCss,
2561
+ owner: globalOwner
2562
+ }];
2563
+ for (const { css, owner } of sheets) {
2564
+ const parsed = declarations(css);
2565
+ const discreteRules = new Set(parsed.filter((d) => d.property === "transition-behavior" && /\ballow-discrete\b/.test(d.value)).map((d) => d.rule));
2566
+ for (const declaration of parsed) {
2567
+ const found = (code, property, message) => findings.push({
2568
+ code,
2569
+ property,
2570
+ message,
2571
+ owner: owner(declaration)
2572
+ });
2573
+ if (isSelfReference(declaration)) {
2574
+ found(PROPERTY_SELF_REFERENCE, declaration.property, `${declaration.property} resolves to var(${declaration.property}), a reference to itself, so it is invalid at computed-value time. Give it a value other than its own name`);
2575
+ continue;
2576
+ }
2577
+ if (registered.size === 0) continue;
2578
+ for (const read of varReads(declaration.value)) {
2579
+ const registration = registered.get(read.name);
2580
+ if (read.fallback === void 0 || registration?.initialValue === void 0) continue;
2581
+ const chain = read.fallback.startsWith("var(");
2582
+ found(chain ? PROPERTY_FALLBACK_CHAIN_SUPPRESSED : PROPERTY_FALLBACK_SUPPRESSED, read.name, chain ? `var(${read.name}, ${read.fallback}) never reads its fallback chain: ${read.name} is registered with initial-value ${registration.initialValue}, which applies whenever it is unset. Remove the initial value or reorder the chain` : `var(${read.name}, ${read.fallback}) never uses its fallback: ${read.name} is registered with initial-value ${registration.initialValue}, which applies whenever it is unset`);
2583
+ }
2584
+ for (const animated of animatedProperties(declaration, discreteRules)) {
2585
+ if (!declared.has(animated.name)) continue;
2586
+ const reason = interpolationGap(registered.get(animated.name));
2587
+ if (reason === null) continue;
2588
+ if (animated.discrete) found(PROPERTY_DISCRETE_ANIMATION, animated.name, `${animated.name} is transitioned with allow-discrete but ${reason}, so it flips at the midpoint instead of interpolating. Register it with a typed syntax`);
2589
+ else found(PROPERTY_UNREGISTERED_ANIMATION, animated.name, `${animated.name} is ${animated.how} but ${reason}, so it changes in one step instead of interpolating. Register it with a typed syntax to animate it`);
2590
+ }
2591
+ }
2592
+ }
2593
+ return toDiagnostics(findings);
2594
+ }
2595
+ function declaredProperties(contextualVarsJson) {
2596
+ if (!contextualVarsJson) return /* @__PURE__ */ new Set();
2597
+ const byScale = parseInternalWire(contextualVarsJson, "contextualVarsJson (the theme's contextual variable names)");
2598
+ return new Set(Object.values(byScale).flatMap((names) => names.map((name) => `--${name}`)));
2599
+ }
2600
+ /** Declarations in engine-emitted CSS: one `property: value` per `;`. */
2601
+ function declarations(css) {
2602
+ const found = [];
2603
+ const stack = [];
2604
+ let rules = 0;
2605
+ let segment = 0;
2606
+ let parens = 0;
2607
+ let quote = null;
2608
+ const take = (end) => {
2609
+ const text = css.slice(segment, end);
2610
+ const colon = text.indexOf(":");
2611
+ const current = stack[stack.length - 1];
2612
+ if (current === void 0 || colon <= 0) return;
2613
+ found.push({
2614
+ property: text.slice(0, colon).trim(),
2615
+ value: text.slice(colon + 1).replace(/;$/, "").trim(),
2616
+ start: segment + (text.length - text.trimStart().length),
2617
+ end,
2618
+ rule: current.rule,
2619
+ context: stack.map((entry) => entry.prelude)
2620
+ });
2621
+ };
2622
+ for (let i = 0; i < css.length; i += 1) {
2623
+ const c = css[i];
2624
+ if (quote !== null) {
2625
+ if (c === "\\") i += 1;
2626
+ else if (c === quote) quote = null;
2627
+ continue;
2628
+ }
2629
+ if (c === "\"" || c === "'") quote = c;
2630
+ else if (c === "(") parens += 1;
2631
+ else if (c === ")") parens -= 1;
2632
+ else if (parens === 0 && c === "{") {
2633
+ stack.push({
2634
+ prelude: css.slice(segment, i).trim(),
2635
+ rule: rules
2636
+ });
2637
+ rules += 1;
2638
+ segment = i + 1;
2639
+ } else if (parens === 0 && c === ";") {
2640
+ take(i + 1);
2641
+ segment = i + 1;
2642
+ } else if (parens === 0 && c === "}") {
2643
+ take(i);
2644
+ stack.pop();
2645
+ segment = i + 1;
2646
+ }
2647
+ }
2648
+ return found;
2649
+ }
2650
+ function isSelfReference(declaration) {
2651
+ return declaration.property.startsWith("--") && declaration.value.replace(/\s+/g, "") === `var(${declaration.property})`;
2652
+ }
2653
+ /** Every `var()` read in `value`, nested fallbacks included. */
2654
+ function varReads(value) {
2655
+ const reads = [];
2656
+ for (const match of value.matchAll(/var\(\s*(--[\w-]+)\s*/g)) {
2657
+ const after = (match.index ?? 0) + match[0].length;
2658
+ if (value[after] !== ",") {
2659
+ reads.push({
2660
+ name: match[1],
2661
+ fallback: void 0
2662
+ });
2663
+ continue;
2664
+ }
2665
+ let depth = 1;
2666
+ let close = after + 1;
2667
+ for (; close < value.length && depth > 0; close += 1) if (value[close] === "(") depth += 1;
2668
+ else if (value[close] === ")") depth -= 1;
2669
+ reads.push({
2670
+ name: match[1],
2671
+ fallback: value.slice(after + 1, close - 1).trim()
2672
+ });
2673
+ }
2674
+ return reads;
2675
+ }
2676
+ /** Custom properties this declaration sets in a keyframe or names in a
2677
+ * transition. */
2678
+ function animatedProperties(declaration, discreteRules) {
2679
+ const keyframes = declaration.context.find((prelude) => prelude.startsWith("@keyframes"));
2680
+ if (keyframes !== void 0 && declaration.property.startsWith("--")) return [{
2681
+ name: declaration.property,
2682
+ how: `set in ${keyframes}`,
2683
+ discrete: false
2684
+ }];
2685
+ if (declaration.property !== "transition" && declaration.property !== "transition-property") return [];
2686
+ return topLevelItems(declaration.value).flatMap((item) => {
2687
+ const property = item.trim().split(/\s+/)[0] ?? "";
2688
+ return property.startsWith("--") ? [{
2689
+ name: property,
2690
+ how: "transitioned",
2691
+ discrete: /\ballow-discrete\b/.test(item) || discreteRules.has(declaration.rule)
2692
+ }] : [];
2693
+ });
2694
+ }
2695
+ function topLevelItems(value) {
2696
+ const items = [];
2697
+ let depth = 0;
2698
+ let start = 0;
2699
+ for (let i = 0; i < value.length; i += 1) if (value[i] === "(") depth += 1;
2700
+ else if (value[i] === ")") depth -= 1;
2701
+ else if (value[i] === "," && depth === 0) {
2702
+ items.push(value.slice(start, i));
2703
+ start = i + 1;
2704
+ }
2705
+ items.push(value.slice(start));
2706
+ return items;
2707
+ }
2708
+ /** Why a property cannot interpolate, or `null` when it can. */
2709
+ function interpolationGap(registration) {
2710
+ if (registration === void 0) return "it is not registered";
2711
+ if (registration.syntax === "*") return "it is registered with the universal syntax \"*\"";
2712
+ return null;
2713
+ }
2714
+ function componentOwner(manifest) {
2715
+ const components = Object.values(manifest.components);
2716
+ return (declaration) => {
2717
+ const selector = [...declaration.context].reverse().find((prelude) => !prelude.startsWith("@"));
2718
+ const classes = (selector ?? "").match(/\.-?[_a-zA-Z][\w-]*/g) ?? [];
2719
+ for (const dotted of classes) {
2720
+ const name = dotted.slice(1);
2721
+ const owner = components.find((component) => name === component.class_name || name.startsWith(`${component.class_name}--`) || component.replacement.includes(`"${name}"`));
2722
+ if (owner) return {
2723
+ file: owner.file,
2724
+ component: owner.binding
2725
+ };
2726
+ }
2727
+ return {
2728
+ file: "styles",
2729
+ component: selector ?? "stylesheet"
2730
+ };
2731
+ };
2732
+ }
2733
+ function globalOwner(declaration) {
2734
+ const keyframes = declaration.context.find((prelude) => prelude.startsWith("@keyframes"));
2735
+ const selector = [...declaration.context].reverse().find((prelude) => !prelude.startsWith("@"));
2736
+ return {
2737
+ file: "system",
2738
+ component: keyframes ?? selector ?? "global styles"
2739
+ };
2740
+ }
2741
+ /** One diagnostic per code, property and owner. */
2742
+ function toDiagnostics(findings) {
2743
+ const seen = /* @__PURE__ */ new Set();
2744
+ const diagnostics = [];
2745
+ for (const finding of findings) {
2746
+ const key = `${finding.code}|${finding.property}|${finding.owner.file}|${finding.owner.component}`;
2747
+ if (seen.has(key)) continue;
2748
+ seen.add(key);
2749
+ diagnostics.push({
2750
+ file: finding.owner.file,
2751
+ component: finding.owner.component,
2752
+ kind: "warn",
2753
+ message: `${finding.message} (${finding.code})`,
2754
+ code: finding.code,
2755
+ severity: severityFor(finding.code)
2756
+ });
2757
+ }
2758
+ return diagnostics;
2759
+ }
2760
+ //#endregion
2389
2761
  //#region pipeline/unit-fallback.ts
2390
2762
  /** A digit run after one of these continues a word (`#b1b1b7`, `ss01`,
2391
2763
  * `U+0025`), so it is not a bare number. The extractor predicts this pass
@@ -2482,9 +2854,21 @@ function runProjectAnalysis(engineApi, opts) {
2482
2854
  const extractMs = Math.round(performance.now() - t);
2483
2855
  t = performance.now();
2484
2856
  const manifest = JSON.parse(manifestJson);
2857
+ const propertyDiagnostics = checkCustomProperties({
2858
+ system: opts.system,
2859
+ manifest,
2860
+ componentCss: applyUnitFallback(manifest.css),
2861
+ globalCss: manifest.sheets.global
2862
+ });
2485
2863
  surfaceManifestDiagnostics(manifest, opts.warn, {
2486
2864
  strict: opts.strict,
2487
- prepend: [...collectSelectorAliasDiagnostics(opts.system.selectorAliasesJson), ...opts.extraDiagnostics ?? []]
2865
+ info: opts.info,
2866
+ prepend: [
2867
+ ...collectSelectorAliasDiagnostics(opts.system.selectorAliasesJson),
2868
+ ...systemLoadDiagnostics(opts.system),
2869
+ ...propertyDiagnostics,
2870
+ ...opts.extraDiagnostics ?? []
2871
+ ]
2488
2872
  });
2489
2873
  const parseMs = Math.round(performance.now() - t);
2490
2874
  return {
@@ -2587,7 +2971,8 @@ function loadSystemConfig(engineApi, opts) {
2587
2971
  const config = loadSystemModule(opts.systemPath, opts.rootDir);
2588
2972
  let scalesJson = config.scalesJson;
2589
2973
  let variableMapJson = config.variableMapJson;
2590
- let variableCss = config.variableCss;
2974
+ const registrations = splitInvalidPropertyRegistrations(config.variableCss);
2975
+ let variableCss = registrations.css;
2591
2976
  let contextualVarsJson = config.contextualVarsJson || null;
2592
2977
  let declarationScalesJson = config.declarationScalesJson || null;
2593
2978
  if (opts.prefix) {
@@ -2616,6 +3001,8 @@ function loadSystemConfig(engineApi, opts) {
2616
3001
  sourceThemeManifestsJson: config.sourceThemeManifests || null
2617
3002
  };
2618
3003
  if (declarationScalesJson) system.declarationScalesJson = declarationScalesJson;
3004
+ if (config.propertyRecordsJson) system.propertyRecordsJson = config.propertyRecordsJson;
3005
+ if (registrations.invalid.length > 0) system.invalidPropertyRegistrations = registrations.invalid;
2619
3006
  return system;
2620
3007
  }
2621
3008
  //#endregion
@@ -2699,31 +3086,6 @@ function buildSystemPropsModule(opts) {
2699
3086
  return `export const systemPropMap = ${opts.systemPropMapJson};\nexport const systemPropGroups = ${opts.groupRegistryJson};\nexport const dynamicPropConfig = ${JSON.stringify(dynamicPropConfig)};\nexport const transforms = ${transformsSource(opts.admittedTransforms)};\n` + (opts.typedSystemProps.length > 0 ? `export const typedSystemProps = ${JSON.stringify(opts.typedSystemProps)};\n` : "");
2700
3087
  }
2701
3088
  //#endregion
2702
- //#region pipeline/timing-waterfall.ts
2703
- const RUST_TIMING_PHASES = [
2704
- ["parseAndWalk", "parse+walk"],
2705
- ["importResolution", "imports"],
2706
- ["extensionProvenance", "provenance"],
2707
- ["topologicalSort", "topo-sort"],
2708
- ["chainEvaluation", "chains"],
2709
- ["jsxScanning", "jsx-scan"],
2710
- ["systemPropAggregation", "sys-props"],
2711
- ["usageLedger", "usage"],
2712
- ["reconciliation", "reconcile"],
2713
- ["cssGeneration", "css-gen"],
2714
- ["manifestSerialization", "serialize"]
2715
- ];
2716
- function formatRustTimingWaterfall(timing, opts) {
2717
- const lines = [];
2718
- for (const [key, label] of RUST_TIMING_PHASES) {
2719
- const ms = timing[key] ?? 0;
2720
- const pad = " ".repeat(Math.max(0, opts.labelWidth - label.length));
2721
- const extra = key === "parseAndWalk" ? ` (${timing.fileCount ?? 0} files, ${timing.cacheHits ?? 0} cached)` : "";
2722
- lines.push(`${opts.indent}${label}${pad}${String(ms).padStart(5)}ms${extra}`);
2723
- }
2724
- return lines;
2725
- }
2726
- //#endregion
2727
3089
  //#region pipeline/tsconfig-paths.ts
2728
3090
  /**
2729
3091
  * tsconfig `paths` → alias pairs, for drivers with no live bundler config.
@@ -2990,4 +3352,4 @@ function realpathThroughNearestExistingAncestor(path) {
2990
3352
  }
2991
3353
  }
2992
3354
  //#endregion
2993
- export { DEFAULT_EXCLUDE as $, enforceExternalTokenContracts as A, firstOwners as B, surfaceManifestDiagnostics as C, stripLeadingLayerDeclaration as Ct, postProcessCss as D, vocabularyWitnessDiagnostics as E, createSourceCorpus as F, walkPackageSources as G, resolveAbsolutePathSpecifier as H, collectExternalPackageSources as I, sharesVolumeRoot as J, createSourceIdentity as K, excludeCollectedPackages as L, resolveThroughPathAliases as M, findAssetSpecifiers as N, resolveLightningTargets as O, substituteAssetPlaceholders as P, CORE_OPTION_KEYS as Q, extractSystemFilePackages as R, isUnresolvedParentDrop as S, assembleStylesheet as St, unresolvedParentName as T, __require as Tt, staleDistIncludesMessage as U, isExcludedPackageRelativePath as V, unresolvableIncludesMessage as W, discoverFiles as X, compareDiscoveryOrder as Y, AnimusConfigError as Z, clearEngineCache as _, isEngineTransformExtension as _t, assertNoErrorDiagnostics as a, resolveMode as at, VOCABULARY_COLLISION as b, buildAnalyzeProjectArgs as bt, buildSystemPropsModule as c, assertNoRetiredEngineSelection as ct, loadSystemConfig as d, isAdvisorySourceDiagnostic as dt, DRIVER_NAMESPACES as et, applyPrefix as f, parseFilesJson as ft, buildAnalysisInputs as g, ENGINE_TRANSFORM_EXTENSIONS as gt, stableStringify as h, DEFAULT_EXTENSIONS as ht, snapshotFilePlans as i, createExcludeMatcher as it, resolveAssetFile as j, buildPathAliasesJson as k, buildDynamicPropConfig as l, createV2EngineApi as lt, camelToKebab as m, adaptSvelteSource as mt, diffFilePlans as n, STRUCTURAL_EXCLUDE as nt, readTsconfigAliasPairs as o, resolveVerbosity as ot, serializeStaticCss as p, projectExternalFileOwners as pt, isPathWithinRoot as q, hashReplacementPlans as r, assertKnownOptionKeys as rt, formatRustTimingWaterfall as s, RETIRED_ENGINE_MESSAGE as st, toWatchKeys as t, REPLACEABLE_DEFAULT_EXCLUDE as tt, runStructuralSelfCheck as u, ingestSourceEntries as ut, runProjectAnalysis as v, preprocessMdx as vt, unreadableSourceDiagnostic as w, validateLayerOrder as wt, VOCABULARY_LEGACY_VERB as x, ANIMUS_LAYERS as xt, applyUnitFallback as y, contentHash as yt, findPackageRoot as z };
3355
+ export { compareDiscoveryOrder as $, buildPathAliasesJson as A, collectExternalPackageSources as B, surfaceManifestDiagnostics as C, contentHash as Ct, vocabularyWitnessDiagnostics as D, stripLeadingLayerDeclaration as Dt, unresolvedParentName as E, assembleStylesheet as Et, findSheetAssetSpecifiers as F, isExcludedPackageRelativePath as G, extractSystemFilePackages as H, reportSurvivingAssetPlaceholders as I, unresolvableIncludesMessage as J, resolveAbsolutePathSpecifier as K, substituteAssetPlaceholders as L, resolveAssetFile as M, resolveThroughPathAliases as N, postProcessCss as O, validateLayerOrder as Ot, findAssetSpecifiers as P, sharesVolumeRoot as Q, substituteSheetAssets as R, isUnresolvedParentDrop as S, preprocessMdx as St, unreadableSourceDiagnostic as T, ANIMUS_LAYERS as Tt, findPackageRoot as U, excludeCollectedPackages as V, firstOwners as W, createSourceIdentity as X, walkPackageSources as Y, isPathWithinRoot as Z, runProjectAnalysis as _, projectExternalFileOwners as _t, assertNoErrorDiagnostics as a, REPLACEABLE_DEFAULT_EXCLUDE as at, VOCABULARY_COLLISION as b, ENGINE_TRANSFORM_EXTENSIONS as bt, buildDynamicPropConfig as c, createExcludeMatcher as ct, applyPrefix as d, RETIRED_ENGINE_MESSAGE as dt, discoverFiles as et, serializeStaticCss as f, assertNoRetiredEngineSelection as ft, clearEngineCache as g, parseFilesJson as gt, buildAnalysisInputs as h, isAdvisorySourceDiagnostic as ht, snapshotFilePlans as i, DRIVER_NAMESPACES as it, enforceExternalTokenContracts as j, resolveLightningTargets as k, __require as kt, runStructuralSelfCheck as l, resolveMode as lt, stableStringify as m, ingestSourceEntries as mt, diffFilePlans as n, CORE_OPTION_KEYS as nt, readTsconfigAliasPairs as o, STRUCTURAL_EXCLUDE as ot, camelToKebab as p, createV2EngineApi as pt, staleDistIncludesMessage as q, hashReplacementPlans as r, DEFAULT_EXCLUDE as rt, buildSystemPropsModule as s, assertKnownOptionKeys as st, toWatchKeys as t, AnimusConfigError as tt, loadSystemConfig as u, resolveVerbosity as ut, applyUnitFallback as v, adaptSvelteSource as vt, systemLoadDiagnostics as w, buildAnalyzeProjectArgs as wt, VOCABULARY_LEGACY_VERB as x, isEngineTransformExtension as xt, INVALID_PROPERTY_REGISTRATION as y, DEFAULT_EXTENSIONS as yt, createSourceCorpus as z };