From d2c6a70cf60db61327688e8cb4621f666e58d494 Mon Sep 17 00:00:00 2001 From: codecaaron Date: Thu, 20 Aug 2026 11:32:32 -0400 Subject: [PATCH 1/8] fix(system-loader): resolve `exports` wildcard subpaths Node's way MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `resolve_exports_entry` did an exact-key `exports.get()` and implemented no subpath patterns, so every `"./*"`-style exports map (e.g. `@ark-ui/react` 5.36.2) was unresolvable to the system loader while Node and Vite resolve the same specifier fine. The `rust-system-loader` spec already requires a Node-style resolver; this closes the pattern half of it. Node's semantics, matched: an exact key answers first, then `"./*"` patterns by PATTERN_KEY_COMPARE specificity (longest literal prefix, ties broken by longest suffix), with the matched segment substituted into the resolved target. A subpath that matches nothing still returns `None`, so `resolve_bare_specifier` falls through to `module`/`main` exactly as before and the `.` entry is untouched. Also corrects the `animus.keyframes.external-entry-failed` remediation text. Discovery registers TWO scan entries for a subpath-declared kit — the declared entry and a derived alias for the package ROOT module (deliberate: collections routinely live only there, pinned by collect-external-packages' "a derived root alias scans its root entry for keyframes too"). The old message told the reader collections "must be reachable from the package's definition entry", which is advice a consumer with a framework-free definition entry has already followed and which never silences the root barrel. The message now names the scan set it actually walks; the scan itself is unchanged. `library-authoring.mdx` is restated to match. Tests (red first): - resolve_exports_entry_wildcard_subpath_pattern - resolve_exports_entry_wildcard_longest_prefix_wins - resolve_exports_entry_without_pattern_still_falls_through (fall-through guard) - resolve_bare_specifier_through_wildcard_exports (fixture package.json on disk) - external-keyframes: "the entry-failed message describes the whole scanned-entry set" Verification: vp run verify, verify:hygiene:rust, verify:parity (66/66, register stays []), verify:integration, verify:packed — all exit 0. Refs .scratch/agent-styling-perspective/issues/11-exports-wildcard-resolution.md Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DQuCHBc6HCuzoa77JyBwA4 --- .../extract/crates/system-loader/src/lib.rs | 168 +++++++++++++++++- .../extract/pipeline/external-keyframes.ts | 7 +- .../extract/tests/external-keyframes.test.ts | 33 ++++ .../architecture/library-authoring.mdx | 4 +- 4 files changed, 206 insertions(+), 6 deletions(-) diff --git a/packages/extract/crates/system-loader/src/lib.rs b/packages/extract/crates/system-loader/src/lib.rs index 7422d61e..7ed0ca40 100644 --- a/packages/extract/crates/system-loader/src/lib.rs +++ b/packages/extract/crates/system-loader/src/lib.rs @@ -217,6 +217,11 @@ fn find_package_json(pkg_name: &str, start_dir: &str) -> Result /// Resolve an entry from the exports map. /// Handles both string values and nested condition objects. /// For condition objects, follows the `import` condition, then `default`. +/// +/// Subpath keys are matched Node's way: an exact key first, then the +/// `"./*"` wildcard patterns, whose matched segment is substituted into the +/// resolved target. A subpath that matches nothing returns `None`, so +/// `resolve_bare_specifier` keeps falling through to `module`/`main`. fn resolve_exports_entry(exports: &serde_json::Value, key: &str) -> Option { // Normalize key: `./groups` or `/groups` → look up with `./` prefix let lookup_key = if key == "." { @@ -229,8 +234,56 @@ fn resolve_exports_entry(exports: &serde_json::Value, key: &str) -> Option, + lookup_key: &str, +) -> Option { + let mut best: Option<(&str, &str, &serde_json::Value)> = None; + + for (pattern, value) in exports { + // Exactly one `*`, in a subpath key: anything else is not a pattern. + let Some((prefix, suffix)) = pattern.split_once('*') else { + continue; + }; + if !prefix.starts_with("./") || suffix.contains('*') { + continue; + } + if !lookup_key.starts_with(prefix) || !lookup_key.ends_with(suffix) { + continue; + } + if lookup_key.len() < prefix.len() + suffix.len() { + continue; + } + let more_specific = match best { + None => true, + Some((best_prefix, best_suffix, _)) => { + prefix.len() > best_prefix.len() + || (prefix.len() == best_prefix.len() && suffix.len() > best_suffix.len()) + } + }; + if more_specific { + best = Some((prefix, suffix, value)); + } + } + + let (prefix, suffix, value) = best?; + let matched = &lookup_key[prefix.len()..lookup_key.len() - suffix.len()]; + Some(resolve_condition_value(value)?.replace('*', matched)) } /// Resolve a condition value — could be a string or a nested condition object. @@ -2973,6 +3026,117 @@ export const ds = tokens; ); } + #[test] + fn resolve_exports_entry_wildcard_subpath_pattern() { + // Node's `exports` wildcard form, verbatim from `@ark-ui/react` 5.36.2: + // every component subpath is served by one `"./*"` pattern. Without + // pattern support the whole package is unresolvable to this loader + // while Node and Vite resolve it fine. + let exports: serde_json::Value = serde_json::json!({ + ".": { + "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } + }, + "./factory": { + "import": { "types": "./dist/components/factory.d.ts", "default": "./dist/components/factory.js" } + }, + "./*": { + "import": { "types": "./dist/components/*/index.d.ts", "default": "./dist/components/*/index.js" } + }, + "./package.json": "./package.json" + }); + + assert_eq!( + resolve_exports_entry(&exports, "/field"), + Some("./dist/components/field/index.js".to_string()), + "a `./*` pattern must substitute the matched subpath" + ); + // An exact key still wins over the pattern that would also match it. + assert_eq!( + resolve_exports_entry(&exports, "/factory"), + Some("./dist/components/factory.js".to_string()) + ); + assert_eq!( + resolve_exports_entry(&exports, "."), + Some("./dist/index.js".to_string()) + ); + } + + #[test] + fn resolve_exports_entry_wildcard_longest_prefix_wins() { + // Node's PATTERN_KEY_COMPARE: the pattern with the longest literal + // prefix wins, then the longest suffix. + let exports: serde_json::Value = serde_json::json!({ + "./*": "./dist/*.js", + "./lib/*": "./dist/lib/*.js", + "./lib/*.css": "./dist/lib/*.css" + }); + + assert_eq!( + resolve_exports_entry(&exports, "/thing"), + Some("./dist/thing.js".to_string()) + ); + assert_eq!( + resolve_exports_entry(&exports, "/lib/thing"), + Some("./dist/lib/thing.js".to_string()) + ); + assert_eq!( + resolve_exports_entry(&exports, "/lib/thing.css"), + Some("./dist/lib/thing.css".to_string()) + ); + } + + #[test] + fn resolve_exports_entry_without_pattern_still_falls_through() { + // No matching key and no pattern → `None`, so `resolve_bare_specifier` + // keeps falling through to `module`/`main` exactly as before. + let exports: serde_json::Value = serde_json::json!({ + ".": "./dist/index.js", + "./groups": "./dist/groups/index.js" + }); + assert_eq!(resolve_exports_entry(&exports, "/missing"), None); + } + + #[test] + fn resolve_bare_specifier_through_wildcard_exports() { + // The end-to-end resolver over a fixture package.json carrying a + // `"./*"` exports map: the subpath file on disk must be found. + let dir = scratch_dir("wildcard-exports"); + let pkg = dir.join("node_modules/@fixture/wildcard-kit"); + write_fixture( + &pkg.join("package.json"), + "{\n \"name\": \"@fixture/wildcard-kit\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"module\": \"dist/index.js\",\n \"exports\": {\n \".\": { \"import\": { \"types\": \"./dist/index.d.ts\", \"default\": \"./dist/index.js\" } },\n \"./*\": { \"import\": { \"types\": \"./dist/components/*/index.d.ts\", \"default\": \"./dist/components/*/index.js\" } }\n }\n}\n", + ); + write_fixture(&pkg.join("dist/index.js"), "export const kit = 1;\n"); + write_fixture( + &pkg.join("dist/components/field/index.js"), + "export const Field = 1;\n", + ); + let from_dir = dir.join("src"); + fs::create_dir_all(&from_dir).expect("create importing dir"); + + let resolved = + resolve_bare_specifier("@fixture/wildcard-kit/field", &from_dir.to_string_lossy()); + let root = resolve_bare_specifier("@fixture/wildcard-kit", &from_dir.to_string_lossy()); + let missing = + resolve_bare_specifier("@fixture/wildcard-kit/absent", &from_dir.to_string_lossy()); + let _ = fs::remove_dir_all(&dir); + + let resolved = resolved.expect("a `./*` exports pattern must resolve its subpath"); + assert!( + resolved.ends_with("dist/components/field/index.js"), + "unexpected resolution: {resolved}" + ); + let root = root.expect("the `.` entry must still resolve"); + assert!(root.ends_with("dist/index.js"), "unexpected root: {root}"); + // A pattern that matches but whose substituted target is absent from + // disk stays unresolvable — the resolver never invents a path. + let error = missing.expect_err("an absent pattern target must not resolve"); + assert!( + error.contains("@fixture/wildcard-kit/absent"), + "error must name the specifier: {error}" + ); + } + // Integration tests that require the workspace to be built // are gated behind the file existence check. diff --git a/packages/extract/pipeline/external-keyframes.ts b/packages/extract/pipeline/external-keyframes.ts index 1bab3686..58ac7969 100644 --- a/packages/extract/pipeline/external-keyframes.ts +++ b/packages/extract/pipeline/external-keyframes.ts @@ -95,9 +95,10 @@ export function mergeExternalKeyframes( component: 'keyframes', kind: 'warn', message: - `external package entry failed the keyframes scan — its collections are invisible to extraction: ${String(error)}. ` + - `Keyframe collections must be reachable from the package's definition entry without evaluating framework re-exports: ` + - `export them (directly or as a named re-export) from the definition entry, and avoid \`export *\` of framework packages there ` + + `external package entry failed the keyframes scan — any collections it exports are invisible to extraction: ${String(error)}. ` + + `Each admitted entry is scanned on its own: the package specifier your system entry declares, plus the package root module when that declaration was a subpath. ` + + `Every scan evaluates that entry's whole module graph framework-free, so a root barrel re-exporting framework components can fail here while the definition entry scans clean. ` + + `Export keyframe collections (directly or as a named re-export) from an entry that evaluates framework-free, and avoid \`export *\` of framework packages there ` + `(${KEYFRAMES_EXTERNAL_ENTRY_FAILED})`, code: KEYFRAMES_EXTERNAL_ENTRY_FAILED, severity: 'warn', diff --git a/packages/extract/tests/external-keyframes.test.ts b/packages/extract/tests/external-keyframes.test.ts index 8539e30f..c96b4f97 100644 --- a/packages/extract/tests/external-keyframes.test.ts +++ b/packages/extract/tests/external-keyframes.test.ts @@ -76,6 +76,39 @@ describe('mergeExternalKeyframes', () => { expect(JSON.parse(result.keyframesJson!).kitMotion).toBeDefined(); }); + /** + * `collectExternalPackageSources` registers TWO scan entries for a kit + * declared at a subpath — the declared entry and a derived alias for the + * package ROOT module, because collections routinely live only there + * (pinned by `collect-external-packages.test.ts`, "a derived root alias + * scans its root entry for keyframes too"). The failing entry is therefore + * often the root barrel, which for a React kit necessarily re-exports + * framework components. The message must describe that scan set: telling a + * consumer whose definition entry is already framework-free that + * collections "must be reachable from the package's definition entry" is + * advice they have already followed, and it never silences the barrel. + */ + it('the entry-failed message describes the whole scanned-entry set', () => { + const result = mergeExternalKeyframes( + () => { + throw new Error("could not resolve '@ark-ui/react/field'"); + }, + null, + ['/pkg/kit/src/index.ts'], + '/root' + ); + const [diagnostic] = result.diagnostics; + expect(diagnostic.code).toBe(KEYFRAMES_EXTERNAL_ENTRY_FAILED); + // Both scanned entries are named, so the reader can tell which one failed. + expect(diagnostic.message).toContain('your system entry declares'); + expect(diagnostic.message).toContain('package root module'); + // And the false requirement is gone: a framework-free definition entry + // does not exempt the root barrel from being scanned. + expect(diagnostic.message).not.toContain( + "must be reachable from the package's definition entry" + ); + }); + /** * The scan result is animus's own wire: `scanKeyframesExports` is a NAPI * entry point and its JSON is serialized by the engine, never authored by a diff --git a/packages/showcase/src/content/architecture/library-authoring.mdx b/packages/showcase/src/content/architecture/library-authoring.mdx index 1a9546fe..6c9e80db 100644 --- a/packages/showcase/src/content/architecture/library-authoring.mdx +++ b/packages/showcase/src/content/architecture/library-authoring.mdx @@ -59,7 +59,9 @@ Rules: ### Keep Keyframes on the Definition Entry -External keyframe discovery evaluates a scanned entry in the extraction sandbox and reads only branded `createKeyframes` collections. A component barrel that wildcard-re-exports a framework (`export * from '@ark-ui/react'`) is not an evaluable configuration surface: the sandbox cannot know those export names without running the real framework package. The scan therefore degrades gracefully to the coded `animus.keyframes.external-entry-failed` warning instead of attempting framework evaluation or arbitrary export enumeration. +External keyframe discovery evaluates a scanned entry in the extraction sandbox and reads only branded `createKeyframes` collections. Discovery scans two entries per declared package: the specifier your configured Animus entry declares, and — when that declaration is a subpath such as `@scope/kit/definition` — the package root module as well, because collections routinely live only there. Each is scanned independently, so a root barrel that pulls in a framework component graph can fail its own scan while the definition entry scans clean. + +A framework component graph is not an evaluable configuration surface: the sandbox runs no DOM, no bundler, and no real React. A barrel that wildcard-re-exports a framework (`export * from '@ark-ui/react'`) cannot be enumerated at all without running the real package, and even explicit framework subpath imports reach dist graphs the sandbox is not built to evaluate. The scan therefore degrades gracefully to the coded `animus.keyframes.external-entry-failed` warning, quarantined to the entry that failed, instead of attempting framework evaluation. A package that ships no keyframe collections loses nothing to that warning. Export keyframe collections directly from the definition entry or through an explicit named re-export. An application may likewise re-export them from its configured Animus entry. Configured-entry collections seed extraction and win name collisions; external entry scans only augment that known set. Keep framework `export *` declarations on component entry points, outside the definition graph. From e465f62711d72c0afd48a9af2bddfbfc0a237c79 Mon Sep 17 00:00:00 2001 From: codecaaron Date: Fri, 21 Aug 2026 21:23:04 -0400 Subject: [PATCH 2/8] =?UTF-8?q?feat(system):=20two-phase=20vocabulary=20re?= =?UTF-8?q?gistration=20=E2=80=94=20build()=20=E2=86=92=20registerKeyframe?= =?UTF-8?q?s=20=E2=86=92=20seal()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keyframe collections become declared vocabulary: build() keeps its pinned { system, createGlobalStyles, createKeyframes } shape and additionally opens a LINEAR registration window — each registerKeyframes() returns the bundle carrying the accumulated vocabulary axis (template-literal collision labels; index-signature maps rejected), and seal() closes it, returning the final instance with a frozen, version-marked, declaration-ordered vocabulary record. extend() threads the axis from sealed sources via an optional-phantom inference (no second overload — a sealed-specific overload measured 140-309s TS2589 detonations on consumer shapes), including LibraryBundle/LibraryBundleFor publication. Collisions: compile errors on typed paths; coded record entries (animus.vocabulary.collision) with winner-at-own-position ordering on erased paths. Frame payloads are deep-copied + frozen at registration; prop/group sources and the registry snapshot are captured once at build() so the bundle's two instances can never disagree. Strict extend()-requires-sealed is deferred to the atomic migration increment (openspec Ledger DEF-11) and pinned by an it.fails test. Full inc-02 adversarial review (14 findings) dispositioned in the change journal; openspec change: system-vocabulary-registration (local-only). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DQuCHBc6HCuzoa77JyBwA4 --- packages/system/__tests__/types.test-d.tsx | 109 +++- packages/system/__tests__/vocabulary.test.ts | 245 ++++++++ packages/system/src/SystemBuilder.ts | 621 ++++++++++++++++--- packages/system/src/index.ts | 10 + 4 files changed, 900 insertions(+), 85 deletions(-) create mode 100644 packages/system/__tests__/vocabulary.test.ts diff --git a/packages/system/__tests__/types.test-d.tsx b/packages/system/__tests__/types.test-d.tsx index 6db567b0..00b3855a 100644 --- a/packages/system/__tests__/types.test-d.tsx +++ b/packages/system/__tests__/types.test-d.tsx @@ -20,7 +20,7 @@ import { compose, createSystem, createTheme, createTransform } from '../src'; import { composeWithContext } from '../src/composeWithContext'; import { createGlobalStyles, createKeyframes, ds, tokens } from './test-system'; -import type { LibraryBundle } from '../src'; +import type { LibraryBundle, VocabularyOf } from '../src'; import type { AnyBrandedComponent, SharedConfig, @@ -2052,4 +2052,111 @@ void (); extendedMaybeCallable.slot; } +// ── 18. Vocabulary registration — two-phase terminal type state ────────────── +// (vocabulary-registration §"Vocabulary registration window between two +// terminals" + §"Vocabulary name collisions are compile-time errors on typed +// paths"; system-builder §"Sealing terminal closes vocabulary registration". +// Runtime halves in vocabulary.test.ts.) +{ + const kitBuild = createSystem() + .addGroup('kitSurface', { kitGlow: { property: 'boxShadow' } }) + .build(); + const kitMotion = kitBuild.createKeyframes({ + pulse: { '0%': { opacity: 0 }, '100%': { opacity: 1 } }, + }); + const sealedKit = kitBuild.registerKeyframes({ kitMotion }).seal(); + + // Positive: the sealed instance authors chains and serializes like any + // built instance + void sealedKit.styles({}).system({ kitSurface: true }); + void sealedKit.toConfig(); + void sealedKit.getVocabularyRecord(); + + // Positive: the vocabulary axis is introspectable on the sealed type + type _KitVocab = Assert, 'kitMotion'>>; + + // Positive: an empty vocabulary seals too (cheap no-op for plain kits) + const plainSealed = createSystem().build().seal(); + type _EmptyVocab = Assert, never>>; + + // Negative: duplicate local registration is a compile error at the site, + // with the offending name in the reported type + const dupBundle = createSystem().build(); + const motion = dupBundle.createKeyframes({ spin: { '0%': { opacity: 0 } } }); + void dupBundle + .registerKeyframes({ motion }) + // @ts-expect-error — "motion" is already registered vocabulary + .registerKeyframes({ motion }); + + // Negative: the sealed instance carries no registration surface — + // registration is closed at seal() + // @ts-expect-error — sealed instances have no registerKeyframes member + void sealedKit.registerKeyframes; + + // Negative: a value that is not a factory-shaped collection is rejected + // @ts-expect-error — shape mismatch: not a Keyframes collection + void createSystem().build().registerKeyframes({ bogus: { frames: {} } }); + + // Extending a sealed kit threads its vocabulary into the consumer chain; + // a colliding consumer registration is a compile error (the dist-kit path + // rides the same sealed instance type, which published `.d.ts` preserves) + const consumerBundle = createSystem().extend(sealedKit).build(); + const consumerMotion = consumerBundle.createKeyframes({ + blink: { '0%': { opacity: 1 } }, + }); + // @ts-expect-error — "kitMotion" is inherited vocabulary from the kit + void consumerBundle.registerKeyframes({ kitMotion: consumerMotion }); + // Positive: a fresh name unions the axis + const consumerSealed = consumerBundle + .registerKeyframes({ appMotion: consumerMotion }) + .seal(); + type _MergedVocab = Assert< + IsExact, 'kitMotion' | 'appMotion'> + >; + + // Positive: extending through a bundle literal with a sealed system half + // threads the axis the same way + const viaLiteral = createSystem().extend({ system: sealedKit }).build(); + // @ts-expect-error — "kitMotion" arrives through the bundle's sealed half + void viaLiteral.registerKeyframes({ kitMotion: consumerMotion }); + + // Positive: an ANNOTATED LibraryBundle preserves the vocabulary axis + // (the erasure amendment) — collisions stay compile errors + const publishedVocabBundle: LibraryBundle<'kitMotion'> = { system: sealedKit }; + const viaAnnotated = createSystem().extend(publishedVocabBundle).build(); + // @ts-expect-error — "kitMotion" arrives through the annotated bundle axis + void viaAnnotated.registerKeyframes({ kitMotion: consumerMotion }); + + // Pin (updated erasure contract): a BARE LibraryBundle annotation still + // erases the vocabulary axis — no names admitted; the runtime collision + // witness covers this path + const publishedErased: LibraryBundle = { system: sealedKit }; + const viaErased = createSystem().extend(publishedErased).build(); + const erasedSealed = viaErased + .registerKeyframes({ kitMotion: consumerMotion }) + .seal(); + type _ErasedVocab = Assert< + IsExact, 'kitMotion'> + >; + + // Canary (inc-02 adversarial pass RF-1): the bare UNSEALED spellings the + // consumer fixtures use must stay clean and cheap — the vocabulary-axis + // inference must not detonate `extend().build()` into + // TS2589/TS2859 territory. These compile with SrcVocab defaulting never. + const unsealedKit = createSystem() + .addGroup('kitSurface', { kitGlow: { property: 'boxShadow' } }) + .build().system; + void createSystem().extend(unsealedKit).build(); + void createSystem() + .extend(unsealedKit) + .addConditions({ _cardSm: '@container card (min-width: 200px)' }) + .build(); + + // Negative (inc-02 adversarial pass RF-3): an index-signature map cannot + // prove its names — rejected instead of poisoning the axis to `string` + const widened: Record = { anything: kitMotion }; + // @ts-expect-error — index-signature maps cannot register vocabulary + void createSystem().build().registerKeyframes(widened); +} + void TypeTests; diff --git a/packages/system/__tests__/vocabulary.test.ts b/packages/system/__tests__/vocabulary.test.ts new file mode 100644 index 00000000..3294d32a --- /dev/null +++ b/packages/system/__tests__/vocabulary.test.ts @@ -0,0 +1,245 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { createSystem } from '../src'; +import type { VocabularyRecord } from '../src'; + +const FRAMES_A = { '0%': { opacity: 0 }, '100%': { opacity: 1 } }; +const FRAMES_B = { '0%': { opacity: 1 }, '100%': { opacity: 0 } }; + +function recordOf(sealed: { + getVocabularyRecord?(): VocabularyRecord; +}): VocabularyRecord { + const record = sealed.getVocabularyRecord?.(); + if (!record) { + throw new Error('expected a sealed system to carry a vocabulary record'); + } + return record; +} + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe('vocabulary registration — two-phase terminal (runtime)', () => { + it('register between the terminals, then seal: the record carries the collection under its registered name, declaration-ordered, version-marked', () => { + const bundle = createSystem().build(); + const first = bundle.createKeyframes({ pulse: FRAMES_A }); + const second = bundle.createKeyframes({ fade: FRAMES_B }); + + const sealed = bundle + .registerKeyframes({ first }) + .registerKeyframes({ second }) + .seal(); + + const record = recordOf(sealed); + expect(record.version).toBe(1); + expect(record.keyframes.map((entry) => entry.name)).toEqual([ + 'first', + 'second', + ]); + expect(record.keyframes[0]?.frames).toEqual(first.__frames); + expect(record.collisions).toEqual([]); + }); + + it('the sealed record is frozen INCLUDING the frame payload, which is a registration-time copy — mutating the live collection afterwards changes nothing', () => { + const bundle = createSystem().build(); + const motion = bundle.createKeyframes({ pulse: FRAMES_A }); + const sealed = bundle.registerKeyframes({ motion }).seal(); + + const record = recordOf(sealed); + expect(Object.isFrozen(record)).toBe(true); + expect(Object.isFrozen(record.keyframes)).toBe(true); + expect(Object.isFrozen(record.keyframes[0])).toBe(true); + const entry = record.keyframes[0]!; + expect(entry.frames).not.toBe(motion.__frames); + expect(Object.isFrozen(entry.frames)).toBe(true); + expect(Object.isFrozen(entry.frames.pulse)).toBe(true); + expect(Object.isFrozen(entry.frames.pulse?.frames)).toBe(true); + + ( + motion.__frames.pulse.frames as Record> + )['0%'] = { opacity: 0.5 }; + // Literal expectation — the module const aliases the live collection. + expect(entry.frames.pulse?.frames['0%']).toEqual({ opacity: 0 }); + }); + + it('registration is linear: a superseded bundle rejects further registration and sealing loudly', () => { + const bundle = createSystem().build(); + const motion = bundle.createKeyframes({ pulse: FRAMES_A }); + const next = bundle.registerKeyframes({ motion }); + + expect(() => + (bundle as { registerKeyframes(map: object): unknown }).registerKeyframes( + { motion } + ) + ).toThrow(/superseded|linear/); + expect(() => bundle.seal()).toThrow(/superseded|linear/); + expect(recordOf(next.seal()).keyframes.map((e) => e.name)).toEqual([ + 'motion', + ]); + }); + + it('registration after seal throws naming the sealed state', () => { + const bundle = createSystem().build(); + const motion = bundle.createKeyframes({ pulse: FRAMES_A }); + bundle.seal(); + + expect(() => + (bundle as { registerKeyframes(map: object): unknown }).registerKeyframes( + { motion } + ) + ).toThrow(/sealed/); + }); + + it('a second seal() throws — one sealed instance per bundle', () => { + const bundle = createSystem().build(); + bundle.seal(); + expect(() => bundle.seal()).toThrow(/sealed/); + }); + + it('a non-collection value is rejected at runtime naming the key', () => { + const bundle = createSystem().build(); + expect(() => + (bundle as { registerKeyframes(map: object): unknown }).registerKeyframes( + { bogus: { frames: {} } } + ) + ).toThrow(/bogus/); + }); + + it('the sealed instance serializes from a snapshot the unsealed instance cannot reach: post-seal mutation of public registries affects nothing', () => { + const bundle = createSystem() + .addGroup('space', { m: { property: 'margin' } }) + .build(); + const sealed = bundle.seal(); + const before = sealed.toConfig().propConfig; + + ( + sealed as unknown as { propRegistry: Record } + ).propRegistry.injected = { property: 'color' }; + expect(sealed.toConfig().propConfig).toBe(before); + }); + + it('extending a sealed kit merges its vocabulary ahead of local registrations', () => { + const kitBundle = createSystem().build(); + const kitMotion = kitBundle.createKeyframes({ pulse: FRAMES_A }); + const kit = kitBundle.registerKeyframes({ kitMotion }).seal(); + + const consumerBundle = createSystem().extend(kit).build(); + const appMotion = consumerBundle.createKeyframes({ fade: FRAMES_B }); + const sealed = consumerBundle.registerKeyframes({ appMotion }).seal(); + + expect(recordOf(sealed).keyframes.map((entry) => entry.name)).toEqual([ + 'kitMotion', + 'appMotion', + ]); + }); + + it('a local registration colliding with inherited vocabulary wins in place and records a collision witness', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const kitBundle = createSystem().build(); + const kitMotion = kitBundle.createKeyframes({ pulse: FRAMES_A }); + const kit = kitBundle.registerKeyframes({ motion: kitMotion }).seal(); + + const consumerBundle = createSystem().extend(kit).build(); + const localMotion = consumerBundle.createKeyframes({ fade: FRAMES_B }); + const sealed = ( + consumerBundle as unknown as { + registerKeyframes(map: object): { seal(): unknown }; + } + ) + .registerKeyframes({ motion: localMotion }) + .seal() as Parameters[0]; + + const record = recordOf(sealed); + expect(record.keyframes.map((entry) => entry.name)).toEqual(['motion']); + expect(record.keyframes[0]?.frames).toEqual(localMotion.__frames); + expect(record.collisions).toHaveLength(1); + expect(record.collisions[0]).toMatchObject({ + code: 'animus.vocabulary.collision', + name: 'motion', + }); + expect(record.collisions[0]?.winner).not.toBe(record.collisions[0]?.loser); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0]?.[0]).toContain('motion'); + }); + + it('a collision winner takes its OWN declaration position — record order stays declaration order of the survivors', () => { + vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const kitBundle = createSystem().build(); + const kit = kitBundle + .registerKeyframes({ + a: kitBundle.createKeyframes({ pulse: FRAMES_A }), + b: kitBundle.createKeyframes({ fade: FRAMES_B }), + }) + .seal(); + + const consumerBundle = createSystem().extend(kit).build(); + const c = consumerBundle.createKeyframes({ + spin: { '0%': { opacity: 0.25 } }, + }); + const bOverride = consumerBundle.createKeyframes({ blink: FRAMES_B }); + const sealed = ( + consumerBundle + .registerKeyframes({ c }) as unknown as { + registerKeyframes(map: object): { + seal(): Parameters[0]; + }; + } + ) + .registerKeyframes({ b: bOverride }) + .seal(); + + expect(recordOf(sealed).keyframes.map((entry) => entry.name)).toEqual([ + 'a', + 'c', + 'b', + ]); + }); + + it('two extended kits colliding: the later extension wins and the collision is witnessed', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const kitABundle = createSystem().build(); + const kitA = kitABundle + .registerKeyframes({ + motion: kitABundle.createKeyframes({ pulse: FRAMES_A }), + }) + .seal(); + const kitBBundle = createSystem().build(); + const bMotion = kitBBundle.createKeyframes({ fade: FRAMES_B }); + const kitB = kitBBundle.registerKeyframes({ motion: bMotion }).seal(); + + const sealed = createSystem().extend(kitA).extend(kitB).build().seal(); + + const record = recordOf(sealed); + expect(record.keyframes.map((entry) => entry.name)).toEqual(['motion']); + expect(record.keyframes[0]?.frames).toEqual(bMotion.__frames); + expect(record.collisions).toHaveLength(1); + expect(warn).toHaveBeenCalledTimes(1); + }); + + it('an empty vocabulary seals to an empty, version-marked record', () => { + const sealed = createSystem().build().seal(); + const record = recordOf(sealed); + expect(record.version).toBe(1); + expect(record.keyframes).toEqual([]); + expect(record.globalStyles).toEqual([]); + }); + + // SPEC(vocabulary-registration §"Extending an unsealed instance fails + // loud"): the strict rejection is DEFERRED to the atomic migration + // increment (design Ledger DEF-11) — verify:compile sweeps un-migrated + // fixtures until then. `it.fails` pins the obligation: when the flip + // lands, this test starts passing and MUST be inverted to a plain `it`. + it.fails( + 'extending a built-but-unsealed system instance fails loud (flips at the migration increment — DEF-11)', + () => { + const kit = createSystem() + .addGroup('kitSurface', { kitGlow: { property: 'boxShadow' } }) + .build().system; + expect(() => createSystem().extend(kit)).toThrow(/seal/); + } + ); +}); diff --git a/packages/system/src/SystemBuilder.ts b/packages/system/src/SystemBuilder.ts index 98c52c2d..cd83ad7f 100644 --- a/packages/system/src/SystemBuilder.ts +++ b/packages/system/src/SystemBuilder.ts @@ -128,6 +128,229 @@ export interface RegistrySnapshot { conditions: ConditionAliasMap; } +/** + * The structural shape `registerKeyframes` accepts: any `createKeyframes` + * return value qualifies. The brand stays structural — a hand-rolled object + * carrying it is admitted by design (vocabulary-registration: only shape + * mismatches are rejected at compile time; provenance is not claimed). + */ +export interface RegisterableKeyframes { + readonly __brand: 'Keyframes'; + readonly __frames: object; +} + +/** The per-key frame data a collection carries (`Keyframes['__frames']`). */ +export type KeyframesFrameData = Record< + string, + { readonly name: string; readonly frames: KeyframeFrameMap } +>; + +export interface VocabularyKeyframesEntry { + readonly name: string; + readonly frames: KeyframesFrameData; +} + +export interface VocabularyGlobalStyleEntry { + readonly name: string; + readonly block: GlobalStyleBlock; +} + +export interface VocabularyCollisionEntry { + /** Stable machine code — the record, not the console, is the witness + * channel (the loader's evaluation host shims `console` to a no-op). */ + readonly code: 'animus.vocabulary.collision'; + readonly name: string; + readonly winner: string; + readonly loser: string; +} + +/** + * The declaration-ordered, version-marked registration record a sealed + * system carries (vocabulary-registration). The loader reads collections + * exclusively from here; `collisions` is the merge-point witness for paths + * where no type information flows. + */ +export interface VocabularyRecord { + readonly version: 1; + readonly keyframes: readonly VocabularyKeyframesEntry[]; + readonly globalStyles: readonly VocabularyGlobalStyleEntry[]; + readonly collisions: readonly VocabularyCollisionEntry[]; +} + +/** Internal pending/merged vocabulary state (origin powers witness text). */ +interface VocabularyKeyframesState { + name: string; + frames: KeyframesFrameData; + origin: string; +} + +/** + * Registration-time snapshot of a collection's frame data: copied and frozen + * two levels deep (frame entries + stop bodies), so post-registration + * mutation of the caller's live collection never reaches a sealed record. + * (Blind spot: values nested deeper than a stop body are aliased.) + */ +function snapshotFrameData(frames: KeyframesFrameData): KeyframesFrameData { + const copy: Record = {}; + for (const [key, entry] of Object.entries(frames)) { + const stops: KeyframeFrameMap = {}; + for (const [stop, body] of Object.entries(entry.frames ?? {})) { + stops[stop] = Object.freeze({ ...body }) as KeyframeFrameMap[string]; + } + copy[key] = Object.freeze({ + name: entry.name, + frames: Object.freeze(stops) as KeyframeFrameMap, + }); + } + return Object.freeze(copy) as KeyframesFrameData; +} + +/** + * THE vocabulary merge — one policy, both call sites (`extend()` inheriting + * a sealed source's record, and the bundle's registration window). A name + * collision is resolved to the INCOMING side, witnessed with a coded entry, + * and the winner takes its OWN declaration position: the loser is removed + * and the winner appended, so record order always reads as declaration + * order of the surviving registrations (inherited region first, then + * locals; a later extension's win sits at that extension's position). + */ +function mergeVocabularyKeyframes( + existingEntries: readonly VocabularyKeyframesState[], + existingCollisions: readonly VocabularyCollisionEntry[], + incoming: ReadonlyArray<{ name: string; frames: KeyframesFrameData }>, + incomingOrigin: string +): { + entries: VocabularyKeyframesState[]; + collisions: VocabularyCollisionEntry[]; +} { + const entries = existingEntries.map((entry) => ({ ...entry })); + const collisions = [...existingCollisions]; + for (const { name, frames } of incoming) { + const existingIndex = entries.findIndex((entry) => entry.name === name); + if (existingIndex !== -1) { + const loser = entries[existingIndex]; + collisions.push({ + code: 'animus.vocabulary.collision', + name, + winner: incomingOrigin, + loser: loser.origin, + }); + console.warn( + `animus vocabulary collision: keyframes "${name}" is registered by ` + + `both ${loser.origin} and ${incomingOrigin} — ${incomingOrigin} ` + + 'wins. Rename one collection to silence this.' + ); + entries.splice(existingIndex, 1); + } + entries.push({ name, frames, origin: incomingOrigin }); + } + return { entries, collisions }; +} + +declare const VOCABULARY_COLLISION: unique symbol; + +/** + * Impossible-to-satisfy marker type that surfaces a template-literal label + * at a colliding registration site — the compile error names the offending + * vocabulary name instead of a bare structural mismatch. + */ +export interface VocabularyNameCollision { + readonly [VOCABULARY_COLLISION]: `Vocabulary name "${Name}" is already registered on this system`; +} + +declare const VOCABULARY_INDEX_SIGNATURE: unique symbol; + +/** + * Impossible-to-satisfy marker rejecting index-signature registration maps: + * a `Record`-typed map cannot prove its names, would bypass the + * collision mapping (`Extract` is `never`), and would poison + * the accumulated axis to `string`. Registration maps require literal keys. + */ +export interface VocabularyIndexSignatureRejected { + readonly [VOCABULARY_INDEX_SIGNATURE]: 'registerKeyframes requires literal keys — an index-signature map cannot prove its vocabulary names'; +} + +type LiteralKeyMap = string extends keyof M + ? VocabularyIndexSignatureRejected + : unknown; + +declare const VOCABULARY_BRAND: unique symbol; + +/** + * The final instance the sealing terminal returns: a full system instance + * plus the vocabulary record accessor, with the registered names carried as + * phantom type state (`VocabularyOf` reads them back). `.extend()` threads + * this axis into the consumer's chain so collisions are compile errors on + * every typed path, published `.d.ts` included. + */ +export type SealedSystemInstance< + PropReg extends Record, + GroupReg extends Record, + Conds extends string = never, + Sels extends string = never, + Vocab extends string = never, +> = SystemInstance & { + getVocabularyRecord(): VocabularyRecord; + readonly [VOCABULARY_BRAND]?: Vocab; +}; + +/** Read the registered vocabulary names off a sealed system's type. */ +export type VocabularyOf = S extends { + readonly [VOCABULARY_BRAND]?: infer V; +} + ? Extract + : never; + +/** + * The `build()` return: the pinned `{ system, createGlobalStyles, + * createKeyframes }` members unchanged, plus the registration window — + * `registerKeyframes` accumulates vocabulary (chain the calls: the returned + * bundle carries the widened axis) and `seal()` closes registration, + * returning the final instance `.extend()` consumes. One sealed instance + * per bundle; registering or re-sealing afterwards throws. + */ +export interface SystemBundle< + PropReg extends Record, + GroupReg extends Record, + Conds extends string = never, + Sels extends string = never, + Vocab extends string = never, +> { + system: SystemInstance; + createGlobalStyles: GlobalStylesFactory; + createKeyframes: CreateKeyframesFactory; + /** + * Register keyframe collections between the terminals. Two obligations + * travel together: the registration KEY MUST equal the module-scope named + * export the collection leaves its defining module under (the engine + * resolves `motion.ember` references by export name — a mismatched key + * cannot resolve at reference sites), and the shorthand + * `registerKeyframes({ animations })` spelling keeps the two identical by + * construction. Registration is LINEAR: this call returns the bundle + * carrying the accumulated vocabulary and supersedes the receiver — + * chain the calls and seal the final bundle. Registration retains the + * collections' frame bodies through the system object in consumer + * bundles — declared weight, not a hidden zero. + */ + registerKeyframes>( + map: M & + LiteralKeyMap & { + [K in Extract]: VocabularyNameCollision; + } + ): SystemBundle; + seal(): SealedSystemInstance; +} + +/** + * Derive a kit's publishable bundle type from its sealed system, so the + * vocabulary axis is READ off the instance rather than hand-asserted: + * `const bundle: LibraryBundleFor = { system: ds, theme }`. + * A hand-written `LibraryBundle<'…'>` parameter is author-asserted and + * unchecked; the bare `LibraryBundle` annotation erases the axis entirely + * (the runtime collision witness covers that path). + */ +export type LibraryBundleFor = LibraryBundle>; + const snapshotTransformBySource = new WeakMap(); function snapshotTransform(source: TransformFn): TransformFn { @@ -172,11 +395,20 @@ function snapshotTransform(source: TransformFn): TransformFn { * `createTheme().extend()`; each builder takes its half and ignores the rest. * `tokens` is the pre-D9 name for the theme half — both spellings are * accepted (design D9; removal horizon is DEF-8). + * + * `Vocab` is the vocabulary-axis amendment (vocabulary-registration): the + * annotation still erases the system half's registry generics, but a kit may + * declare its registered vocabulary names (`LibraryBundle<'kitMotion'>`) so + * consumer-side collision typing survives publication. The bare annotation + * (`LibraryBundle`) admits no names — the runtime collision witness covers + * that path. */ -export interface LibraryBundle { +export interface LibraryBundle { system: IncludableSystem; theme?: unknown; tokens?: unknown; + /** Phantom vocabulary axis — never present at runtime. */ + readonly __vocabulary?: Vocab; } /** @@ -315,6 +547,7 @@ export class SystemBuilder< Conds extends string = never, Sels extends string = never, Stage extends SystemBuilderStage = 'inherit', + Vocab extends string = never, > { // Structural anchor for the phantom Stage parameter — without a member // referencing it, 'inherit' and 'extend' builders would be mutually @@ -336,6 +569,12 @@ export class SystemBuilder< // extended source. Distinct from the provenance map's max value: an extend // whose entries all coalesce still consumes an index. #extendCount: number; + // Vocabulary inherited from sealed extended sources, in extension order + // (vocabulary-registration: inherited entries precede local registrations + // in the eventual record). Collisions recorded here are extend-time + // (kit-vs-kit); registration-time collisions accumulate in the bundle. + #vocabularyRegistry: readonly VocabularyKeyframesState[]; + #vocabularyCollisions: readonly VocabularyCollisionEntry[]; constructor( propRegistry?: PropReg, @@ -344,7 +583,9 @@ export class SystemBuilder< includesRegistry?: readonly IncludableSystem[], conditionRegistry?: ConditionAliasMap, extendProvenance?: ReadonlyMap, - extendCount?: number + extendCount?: number, + vocabularyRegistry?: readonly VocabularyKeyframesState[], + vocabularyCollisions?: readonly VocabularyCollisionEntry[] ) { this.#propRegistry = propRegistry || ({} as PropReg); this.#groupRegistry = groupRegistry || ({} as GroupReg); @@ -353,6 +594,8 @@ export class SystemBuilder< this.#conditionRegistry = conditionRegistry || { ...BUILT_IN_CONDITIONS }; this.#extendProvenance = extendProvenance || new Map(); this.#extendCount = extendCount || 0; + this.#vocabularyRegistry = vocabularyRegistry || []; + this.#vocabularyCollisions = vocabularyCollisions || []; } // Origin label for divergence errors: where did the existing entry for @@ -384,7 +627,7 @@ export class SystemBuilder< SrcConds extends string = never, SrcSels extends string = never, >( - this: SystemBuilder, + this: SystemBuilder, source: | SystemInstance | { @@ -397,7 +640,8 @@ export class SystemBuilder< GroupReg & SrcGroups, Conds | SrcConds, Sels | SrcSels, - 'inherit' + 'inherit', + Vocab >; /** * A value annotated as the exported {@link LibraryBundle} interface has @@ -411,24 +655,26 @@ export class SystemBuilder< * membership, no merge) for at least one minor release. */ from( - this: SystemBuilder, - source: LibraryBundle - ): SystemBuilder; + this: SystemBuilder, + source: LibraryBundle + ): SystemBuilder; from( - this: SystemBuilder, + this: SystemBuilder, source: IncludableSystem | { system?: unknown; tokens?: unknown } - ): SystemBuilder { + ): SystemBuilder { const instance = isLibraryBundle(source) ? source.system : (source as IncludableSystem); - return new SystemBuilder( + return new SystemBuilder( this.#propRegistry, this.#groupRegistry, this.#selectorRegistry, [...this.#includesRegistry, instance], this.#conditionRegistry, this.#extendProvenance, - this.#extendCount + this.#extendCount, + this.#vocabularyRegistry, + this.#vocabularyCollisions ); } @@ -453,12 +699,17 @@ export class SystemBuilder< SrcGroups extends Record, SrcConds extends string = never, SrcSels extends string = never, + SrcVocab extends string = never, >( - this: SystemBuilder, + this: SystemBuilder, source: - | SystemInstance + | (SystemInstance & { + readonly [VOCABULARY_BRAND]?: SrcVocab; + }) | { - system: SystemInstance; + system: SystemInstance & { + readonly [VOCABULARY_BRAND]?: SrcVocab; + }; theme?: unknown; tokens?: unknown; } @@ -467,22 +718,24 @@ export class SystemBuilder< GroupReg & SrcGroups, Conds | SrcConds, Sels | SrcSels, - 'inherit' + 'inherit', + Vocab | SrcVocab >; /** * A value annotated as the exported {@link LibraryBundle} interface has * already erased its system half's generics (`system: IncludableSystem`), * so no source types are admitted — the runtime merge is identical, and - * the builder's own type state passes through unchanged. + * the builder's own type state passes through unchanged, widened by the + * bundle's declared vocabulary axis (the erasure amendment). */ + extend( + this: SystemBuilder, + source: LibraryBundle + ): SystemBuilder; extend( - this: SystemBuilder, - source: LibraryBundle - ): SystemBuilder; - extend( - this: SystemBuilder, + this: SystemBuilder, source: IncludableSystem | { system?: unknown; theme?: unknown } - ): SystemBuilder { + ): SystemBuilder { const instance = isLibraryBundle(source) ? source.system : (source as IncludableSystem); @@ -650,7 +903,32 @@ export class SystemBuilder< new Set(Object.keys(nextSelectors)) ); - return new SystemBuilder( + // ── Vocabulary (vocabulary-registration): a SEALED source contributes + // its registration record in declaration order, appended after entries + // from earlier extensions. A name collision between extended sources + // resolves to the later extension — one merge policy for both call + // sites, see `mergeVocabularyKeyframes` — with a coded witness entry; + // on typed paths the collision is a compile error at the consumer's + // registration site. An unsealed source carries no record and + // contributes nothing (the strict sealed-source requirement lands with + // the hard-cut migration increment). + const sourceRecord = ( + instance as { getVocabularyRecord?(): VocabularyRecord } + ).getVocabularyRecord?.(); + let nextVocabulary = this.#vocabularyRegistry; + let nextVocabularyCollisions = this.#vocabularyCollisions; + if (sourceRecord && sourceRecord.keyframes.length > 0) { + const merged = mergeVocabularyKeyframes( + this.#vocabularyRegistry, + this.#vocabularyCollisions, + sourceRecord.keyframes, + incomingOrigin + ); + nextVocabulary = merged.entries; + nextVocabularyCollisions = merged.collisions; + } + + return new SystemBuilder( nextProps as PropReg, nextGroups as GroupReg, nextSelectors, @@ -659,7 +937,9 @@ export class SystemBuilder< [...this.#includesRegistry, instance], nextConditions, provenance, - sourceIndex + sourceIndex, + nextVocabulary, + nextVocabularyCollisions ); } @@ -689,7 +969,8 @@ export class SystemBuilder< GroupReg, Conds, Sels | NarrowedAliases>, - 'extend' + 'extend', + Vocab > { // Cross-registry clash guard, REVERSE direction (inc-11 full-pass F-1.4): // a name already registered as a CONDITION alias must not be re-registered @@ -712,7 +993,8 @@ export class SystemBuilder< GroupReg, Conds, Sels | NarrowedAliases>, - 'extend' + 'extend', + Vocab >( this.#propRegistry, this.#groupRegistry, @@ -720,7 +1002,9 @@ export class SystemBuilder< this.#includesRegistry, this.#conditionRegistry, this.#extendProvenance, - this.#extendCount + this.#extendCount, + this.#vocabularyRegistry, + this.#vocabularyCollisions ); } @@ -761,7 +1045,8 @@ export class SystemBuilder< GroupReg, Conds | NarrowedAliases>, Sels, - 'extend' + 'extend', + Vocab > { const merged = mergeConditions( this.#conditionRegistry, @@ -773,7 +1058,8 @@ export class SystemBuilder< GroupReg, Conds | NarrowedAliases>, Sels, - 'extend' + 'extend', + Vocab >( this.#propRegistry, this.#groupRegistry, @@ -781,7 +1067,9 @@ export class SystemBuilder< this.#includesRegistry, merged, this.#extendProvenance, - this.#extendCount + this.#extendCount, + this.#vocabularyRegistry, + this.#vocabularyCollisions ); } @@ -793,7 +1081,8 @@ export class SystemBuilder< GroupReg & Record, Conds, Sels, - 'extend' + 'extend', + Vocab > { // Collision check: group name must not collide with any registered prop name if (name in this.#propRegistry) { @@ -848,7 +1137,8 @@ export class SystemBuilder< GroupReg & Record, Conds, Sels, - 'extend' + 'extend', + Vocab >( nextProps, nextGroups, @@ -856,7 +1146,9 @@ export class SystemBuilder< this.#includesRegistry, this.#conditionRegistry, this.#extendProvenance, - this.#extendCount + this.#extendCount, + this.#vocabularyRegistry, + this.#vocabularyCollisions ); } @@ -865,7 +1157,7 @@ export class SystemBuilder< Partial, never>>, >( config: Conf - ): SystemBuilder { + ): SystemBuilder { // Collision check: prop names must not collide with any registered group name for (const key of Object.keys(config)) { if (key in this.#groupRegistry) { @@ -904,46 +1196,50 @@ export class SystemBuilder< } const nextProps = { ...this.#propRegistry, ...config }; - return new SystemBuilder( + return new SystemBuilder< + PropReg & Conf, + GroupReg, + Conds, + Sels, + 'extend', + Vocab + >( nextProps, this.#groupRegistry, this.#selectorRegistry, this.#includesRegistry, this.#conditionRegistry, this.#extendProvenance, - this.#extendCount + this.#extendCount, + this.#vocabularyRegistry, + this.#vocabularyCollisions ); } - build(): { - system: SystemInstance; - createGlobalStyles: GlobalStylesFactory; - createKeyframes: CreateKeyframesFactory; - } { - // Copied containers AND entries (review probe P9, both depths): the - // instance's public mutable propRegistry/groupRegistry fields must not - // alias the builder's private state at any level, or mutating a built - // instance (a key, or a field inside an entry) would bake into a LATER - // build()'s snapshot on the same builder. The current build's snapshot - // deep-copies its own view separately below. - const animus = new Animus( - Object.fromEntries( - Object.entries(this.#propRegistry).map(([key, entry]) => [ - key, - { ...entry }, - ]) - ) as PropReg, - Object.fromEntries( - Object.entries(this.#groupRegistry).map(([key, members]) => [ - key, - [...(members as readonly string[])], - ]) - ) as GroupReg - ); - - // Immutable registry snapshot (design D7): toConfig() and extend() both - // read from it, so post-build mutation of the public mutable - // propRegistry/groupRegistry fields affects neither. + build(): SystemBundle { + // Everything both instances read is captured ONCE, here (adversarial + // pass on inc 02: reading builder/caller-mutable state again at seal + // time opened a build→seal divergence window). Copied containers AND + // entries (review probe P9, both depths) — an instance's public mutable + // propRegistry/groupRegistry fields must not alias the builder's + // private state at any level, or mutating a built instance would bake + // into a LATER build()'s snapshot on the same builder. Both minted + // instances serialize from this ONE frozen snapshot (design D7's + // isolation property: newly captured at build, immutable thereafter), + // and each gets its own mutable public copies minted from the captured + // sources, so the pair can never disagree. + const propSource = Object.fromEntries( + Object.entries(this.#propRegistry).map(([key, entry]) => [ + key, + { ...entry }, + ]) + ) as PropReg; + const groupSource = Object.fromEntries( + Object.entries(this.#groupRegistry).map(([key, members]) => [ + key, + [...(members as readonly string[])], + ]) + ) as GroupReg; const snapshot = createRegistrySnapshot( this.#propRegistry, this.#groupRegistry as Record, @@ -951,24 +1247,42 @@ export class SystemBuilder< this.#conditionRegistry ); - const system = Object.assign(animus, { - toConfig: (): SerializedConfig => { - return serializeInstance( - snapshot.props, - snapshot.groups, - snapshot.selectors, - snapshot.conditions - ); - }, - }) as SystemInstance; - - // Non-enumerable next to toConfig: additive on the built instance, so - // the QuickJS capture script's bundle discriminator (keyed on - // `system.toConfig` being callable) is untouched. - Object.defineProperty(system, 'getRegistrySnapshot', { - value: (): RegistrySnapshot => snapshot, - enumerable: false, - }); + const mintInstance = (): SystemInstance => { + const animus = new Animus( + Object.fromEntries( + Object.entries(propSource).map(([key, entry]) => [key, { ...entry }]) + ) as PropReg, + Object.fromEntries( + Object.entries(groupSource).map(([key, members]) => [ + key, + [...(members as readonly string[])], + ]) + ) as GroupReg + ); + + const instance = Object.assign(animus, { + toConfig: (): SerializedConfig => { + return serializeInstance( + snapshot.props, + snapshot.groups, + snapshot.selectors, + snapshot.conditions + ); + }, + }) as SystemInstance; + + // Non-enumerable next to toConfig: additive on the built instance, so + // the QuickJS capture script's bundle discriminator (keyed on + // `system.toConfig` being callable) is untouched. + Object.defineProperty(instance, 'getRegistrySnapshot', { + value: (): RegistrySnapshot => snapshot, + enumerable: false, + }); + + return instance; + }; + + const system = mintInstance(); const createGlobalStyles = (( styles: GlobalStyleMap, @@ -984,7 +1298,146 @@ export class SystemBuilder< const createKeyframes = ((frames: Record) => keyframesImpl(frames)) as CreateKeyframesFactory; - return { system, createGlobalStyles, createKeyframes }; + // ── Registration window (vocabulary-registration): open from this + // build() until seal(), and LINEAR — each registerKeyframes returns a + // FRESH bundle carrying the accumulated state, and the superseded + // bundle rejects further use loudly. Object identity therefore carries + // exactly the state its type claims: an unchained second call on a + // stale bundle is a runtime error, never a silent divergence between + // the type axis and the sealed record. Inherited entries (sealed + // extended sources) seed the record in extension order; local + // registrations append after them, labeled by 1-based call index. + const makeBundle = ( + entries: readonly VocabularyKeyframesState[], + collisions: readonly VocabularyCollisionEntry[], + localCallCount: number + ): SystemBundle => { + let consumedBy: 'register' | 'seal' | undefined; + + const registerKeyframes = ( + map: Record + ): SystemBundle => { + if (consumedBy === 'seal') { + throw new Error( + 'registerKeyframes: this system is already sealed — ' + + 'registration happens between build() and seal().' + ); + } + if (consumedBy === 'register') { + throw new Error( + 'registerKeyframes: this bundle was superseded by a later ' + + 'registration call — registration is linear; chain the calls ' + + 'and seal the final bundle.' + ); + } + const incoming: Array<{ name: string; frames: KeyframesFrameData }> = + []; + for (const [name, collection] of Object.entries(map)) { + if ( + !collection || + (collection as { __brand?: unknown }).__brand !== 'Keyframes' || + typeof (collection as { __frames?: unknown }).__frames !== 'object' + ) { + throw new TypeError( + `registerKeyframes: "${name}" is not a createKeyframes ` + + 'collection — register the factory return value itself.' + ); + } + incoming.push({ + name, + frames: snapshotFrameData( + (collection as { __frames: KeyframesFrameData }).__frames + ), + }); + } + const merged = mergeVocabularyKeyframes( + entries, + collisions, + incoming, + `local registration #${localCallCount + 1}` + ); + consumedBy = 'register'; + return makeBundle(merged.entries, merged.collisions, localCallCount + 1); + }; + + const seal = (): SealedSystemInstance< + PropReg, + GroupReg, + Conds, + Sels, + Vocab + > => { + if (consumedBy === 'seal') { + throw new Error( + 'seal: this system is already sealed — seal() returns exactly ' + + 'one instance per build().' + ); + } + if (consumedBy === 'register') { + throw new Error( + 'seal: this bundle was superseded by a later registration call ' + + '— registration is linear; seal the final bundle.' + ); + } + const record: VocabularyRecord = Object.freeze({ + version: 1 as const, + keyframes: Object.freeze( + entries.map((entry) => + // frames were deep-copied and frozen at registration (or + // arrived frozen from a sealed source's record). + Object.freeze({ name: entry.name, frames: entry.frames }) + ) + ), + globalStyles: Object.freeze([]), + collisions: Object.freeze( + collisions.map((entry) => Object.freeze({ ...entry })) + ), + }); + + const sealed = mintInstance() as SealedSystemInstance< + PropReg, + GroupReg, + Conds, + Sels, + Vocab + >; + // Non-enumerable for the same reason as getRegistrySnapshot: the + // QuickJS capture script's discriminators walk enumerable keys only. + Object.defineProperty(sealed, 'getVocabularyRecord', { + value: (): VocabularyRecord => record, + enumerable: false, + }); + // Runtime-only stub (absent from the sealed TYPE, so typed misuse + // stays a compile error): registration attempted on the sealed + // instance itself names the sealed state instead of a bare + // "not a function". + Object.defineProperty(sealed, 'registerKeyframes', { + value: (): never => { + throw new Error( + 'registerKeyframes: this system is sealed — registration ' + + 'happens between build() and seal().' + ); + }, + enumerable: false, + }); + consumedBy = 'seal'; + return sealed; + }; + + return { + system, + createGlobalStyles, + createKeyframes, + registerKeyframes, + seal, + } as SystemBundle; + }; + + return makeBundle( + this.#vocabularyRegistry.map((entry) => ({ ...entry })), + [...this.#vocabularyCollisions], + 0 + ); } } diff --git a/packages/system/src/index.ts b/packages/system/src/index.ts index 6678308e..dce2ecaa 100644 --- a/packages/system/src/index.ts +++ b/packages/system/src/index.ts @@ -26,11 +26,21 @@ export type { GlobalStyleBlock, GlobalStyleMap, GlobalStylesFactory, + KeyframesFrameData, LibraryBundle, + RegisterableKeyframes, RegistrySnapshot, + SealedSystemInstance, SerializedConfig, SystemBuilderStage, + SystemBundle, SystemInstance, + VocabularyCollisionEntry, + VocabularyGlobalStyleEntry, + VocabularyKeyframesEntry, + VocabularyNameCollision, + VocabularyOf, + VocabularyRecord, } from './SystemBuilder'; export { createSystem, SystemBuilder } from './SystemBuilder'; // Scales From bc0ba9fa1c4ec351acf7c9269724f72f9610bff4 Mon Sep 17 00:00:00 2001 From: codecaaron Date: Fri, 21 Aug 2026 21:30:31 -0400 Subject: [PATCH 3/8] feat(system-loader): sealed vocabulary-record consumption + ambiguity guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit loadSystemModule now reads keyframe collections from a sealed system's registration record (getVocabularyRecord): declaration-ordered into the unchanged { exportName: { keyName: { name, frames } } } wire via Object.fromEntries in-context (no HashMap on the path; byte-identical across fresh loads, test-witnessed), with exported-but-unregistered collections excluded and a wrong version marker failing the load loud. Collision witnesses ride a new vocabulary_collisions SystemConfig/NAPI field (code animus.vocabulary.collision) — the record, not the shimmed console, is the witness channel. Multiple DISTINCT toConfig-bearing exports without an explicit exportName now fail the load naming every candidate (dual-built-theme precedent) instead of first-picking. Staging (deleted with the migration increment): record ABSENCE still falls back to the export scan so un-migrated fixtures keep loading; the fallback pin test is named for that deletion. Route green: system-loader 60/60, clippy, hygiene:rust, unit:rust, canary, parity 66/66 (register []), integration 156, verify:compile. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DQuCHBc6HCuzoa77JyBwA4 --- packages/extract/crates/extract-v2/index.d.ts | 9 + packages/extract/crates/extract-v2/src/lib.rs | 8 + .../extract/crates/system-loader/src/lib.rs | 390 +++++++++++++++++- packages/extract/pipeline/system-config.ts | 8 + 4 files changed, 397 insertions(+), 18 deletions(-) diff --git a/packages/extract/crates/extract-v2/index.d.ts b/packages/extract/crates/extract-v2/index.d.ts index 789d897f..34310e5e 100644 --- a/packages/extract/crates/extract-v2/index.d.ts +++ b/packages/extract/crates/extract-v2/index.d.ts @@ -140,6 +140,15 @@ export interface NapiSystemConfig { transformSources?: string globalStyleBlocks?: string keyframesBlocks?: string + /** + * Vocabulary collision witnesses from the sealed system's registration + * record: JSON array of `{ code, name, winner, loser }` with the stable + * code `animus.vocabulary.collision`. The record is the witness channel + * (the evaluation host shims console); hosts surface these as + * diagnostics. Absent when there are no collisions or the system + * predates the record. + */ + vocabularyCollisions?: string /** * Canonical absolute paths of every module evaluated for the system * (sorted; entry included, runtime stubs excluded). The plugins use this diff --git a/packages/extract/crates/extract-v2/src/lib.rs b/packages/extract/crates/extract-v2/src/lib.rs index b77528aa..051d07f8 100644 --- a/packages/extract/crates/extract-v2/src/lib.rs +++ b/packages/extract/crates/extract-v2/src/lib.rs @@ -75,6 +75,13 @@ pub struct NapiSystemConfig { pub transform_sources: Option, pub global_style_blocks: Option, pub keyframes_blocks: Option, + /// Vocabulary collision witnesses from the sealed system's registration + /// record: JSON array of `{ code, name, winner, loser }` with the stable + /// code `animus.vocabulary.collision`. The record is the witness channel + /// (the evaluation host shims console); hosts surface these as + /// diagnostics. Absent when there are no collisions or the system + /// predates the record. + pub vocabulary_collisions: Option, /// Canonical absolute paths of every module evaluated for the system /// (sorted; entry included, runtime stubs excluded). The plugins use this /// as the geological-reset membership set. @@ -112,6 +119,7 @@ pub fn load_system_module( transform_sources: config.transform_sources, global_style_blocks: config.global_style_blocks, keyframes_blocks: config.keyframes_blocks, + vocabulary_collisions: config.vocabulary_collisions, dependencies: config.dependencies, source_theme_manifests: config.source_theme_manifests, }) diff --git a/packages/extract/crates/system-loader/src/lib.rs b/packages/extract/crates/system-loader/src/lib.rs index 7ed0ca40..2e7beb06 100644 --- a/packages/extract/crates/system-loader/src/lib.rs +++ b/packages/extract/crates/system-loader/src/lib.rs @@ -56,6 +56,14 @@ pub struct SystemConfig { /// collection identity so the extractor can substitute /// `motion.ember`-style member-expression references against it. pub keyframes_blocks: Option, + /// Vocabulary collision witnesses from the sealed system's registration + /// record (vocabulary-registration): JSON array of `{ code, name, + /// winner, loser }` entries with the stable code + /// `animus.vocabulary.collision`. The record — not the evaluation + /// host's console (shimmed to a no-op) — is the witness channel; hosts + /// surface these as diagnostics. `None` when the record carries no + /// collisions or the system predates the record. + pub vocabulary_collisions: Option, /// Canonical absolute paths of every module evaluated for this system — /// the entry plus its transitive graph, excluding runtime stubs (which /// have no path). Sorted. Plugins use this as the geological-reset @@ -1376,19 +1384,55 @@ fn extract_system_config<'js>( namespace: &Object<'js>, export_name: Option<&str>, ) -> Result { - // Find SystemInstance (export with .toConfig()) + // Find SystemInstance (export with .toConfig()). Without an explicit + // export name, MORE THAN ONE distinct system-like export is a load + // error naming every candidate (vocabulary-registration ambiguity + // guard; precedent: the dual-built-theme identity check below) — never + // an enumeration-order first-pick, which would silently load a system + // with no registrations during a migration. let system_obj = if let Some(name) = export_name { namespace .get::<_, Object>(name) .map_err(|e| format!("export '{}' not found or not an object: {}", name, e))? } else { - find_export_with_method(namespace, "toConfig")?.ok_or_else(|| { - let keys = list_export_keys(namespace); - format!( - "no SystemInstance found (no export with .toConfig()). Exports: [{}]", - keys.join(", ") - ) - })? + let candidates = find_exports_with_method(namespace, "toConfig"); + match candidates.len() { + 0 => { + let keys = list_export_keys(namespace); + return Err(format!( + "no SystemInstance found (no export with .toConfig()). Exports: [{}]", + keys.join(", ") + )); + } + 1 => candidates.into_iter().next().map(|(_, obj)| obj).unwrap(), + _ => { + let is_same: Function = ctx + .eval(b"(a, b) => a === b" as &[u8]) + .map_err(|e| format!("system export identity check failed: {}", e))?; + let first = candidates[0].1.clone(); + let mut distinct = false; + for (_, obj) in candidates.iter().skip(1) { + let same: bool = is_same + .call((first.clone(), obj.clone())) + .map_err(|e| format!("system export identity check failed: {}", e))?; + if !same { + distinct = true; + break; + } + } + if distinct { + let keys: Vec<&str> = + candidates.iter().map(|(key, _)| key.as_str()).collect(); + return Err(format!( + "ambiguous system exports: [{}] each carry .toConfig() but are not \ + the same object; the loader selects exactly one system — export a \ + single (sealed) instance or pass an explicit export name", + keys.join(", ") + )); + } + first + } + } }; // Call .toConfig() @@ -1509,11 +1553,24 @@ fn extract_system_config<'js>( .get("contextualVarsJson") .map_err(|e| format!("contextualVarsJson not found: {}", e))?; - // Find GlobalStyleBlock exports + // Find GlobalStyleBlock exports (registration conformance for global + // styles is a later increment — export scan stays their channel here). let global_style_blocks = extract_global_style_blocks(namespace); - // Find Keyframes exports - let keyframes_blocks = extract_keyframes_blocks(namespace); + // Keyframe collections (vocabulary-registration): a sealed system's + // registration record is the ONLY source — an exported-but-unregistered + // collection does not carry. A system WITHOUT the record accessor falls + // back to the export scan so un-migrated systems keep loading; the + // migration increment deletes that fallback and makes record absence + // the loud version-skew error. + let has_record = system_obj + .get::<_, Function>("getVocabularyRecord") + .is_ok(); + let (keyframes_blocks, vocabulary_collisions) = if has_record { + extract_vocabulary_record(ctx, &system_obj)? + } else { + (extract_keyframes_blocks(namespace), None) + }; Ok(SystemConfig { prop_config, @@ -1528,6 +1585,7 @@ fn extract_system_config<'js>( transform_sources, global_style_blocks, keyframes_blocks, + vocabulary_collisions, // Populated by load_system_module from the resolved module graph; // execute_bundle only sees the assembled bundle text. dependencies: Vec::new(), @@ -1537,20 +1595,89 @@ fn extract_system_config<'js>( }) } -/// Find an export that has a given method name. -fn find_export_with_method<'js>( +/// Find every export that has a given method name, with its export key. +fn find_exports_with_method<'js>( namespace: &Object<'js>, method_name: &str, -) -> Result>, String> { - let keys = list_export_keys(namespace); - for key in &keys { +) -> Vec<(String, Object<'js>)> { + let mut found = Vec::new(); + for key in list_export_keys(namespace) { if let Ok(obj) = namespace.get::<_, Object>(key.as_str()) { if obj.get::<_, Function>(method_name).is_ok() { - return Ok(Some(obj)); + found.push((key, obj)); } } } - Ok(None) + found +} + +/// Read the sealed system's vocabulary record (vocabulary-registration). +/// Returns `(keyframes_blocks, vocabulary_collisions)`: the record's +/// declaration-ordered `keyframes` array becomes the unchanged +/// `{ exportName: { keyName: { name, frames } } }` wire (insertion order +/// preserved end to end — `Object.fromEntries` + `JSON.stringify` in the +/// evaluation context, `preserve_order` on the Rust side), and its +/// `collisions` entries carry verbatim as the host-facing witness. An +/// incompatible version marker fails the load loud. +fn extract_vocabulary_record<'js>( + ctx: &rquickjs::Ctx<'js>, + system_obj: &Object<'js>, +) -> Result<(Option, Option), String> { + let script = r#"(() => { + const record = globalThis.__sys_ref.getVocabularyRecord(); + if (!record || typeof record !== 'object') { + return JSON.stringify({ invalid: 'getVocabularyRecord() did not return an object' }); + } + if (record.version !== 1) { + return JSON.stringify({ skew: String(record.version) }); + } + const keyframes = Array.isArray(record.keyframes) ? record.keyframes : []; + const collisions = Array.isArray(record.collisions) ? record.collisions : []; + return JSON.stringify({ + keyframeCount: keyframes.length, + keyframes: Object.fromEntries(keyframes.map((entry) => [entry.name, entry.frames])), + collisions, + }); +})()"#; + let _ = ctx.globals().set("__sys_ref", system_obj.clone()); + let result = ctx.eval::(script.as_bytes()); + let _ = ctx.globals().remove("__sys_ref"); + let json = + result.map_err(|e| format!("getVocabularyRecord() evaluation failed: {}", e))?; + let parsed: serde_json::Value = serde_json::from_str(&json) + .map_err(|e| format!("vocabulary record serialization failed: {}", e))?; + + if let Some(skew) = parsed.get("skew").and_then(|v| v.as_str()) { + return Err(format!( + "system vocabulary record version {} is not supported by this loader \ + (expected 1) — the system was built by a mismatched @animus-ui/system; \ + rebuild against a matching version instead of loading with empty \ + collections", + skew + )); + } + if let Some(invalid) = parsed.get("invalid").and_then(|v| v.as_str()) { + return Err(format!("system vocabulary record invalid: {}", invalid)); + } + + let count = parsed + .get("keyframeCount") + .and_then(|v| v.as_u64()) + .unwrap_or(0); + let keyframes_blocks = if count == 0 { + None + } else { + parsed + .get("keyframes") + .map(|v| serde_json::to_string(v).unwrap_or_default()) + }; + let vocabulary_collisions = match parsed.get("collisions").and_then(|v| v.as_array()) { + Some(list) if !list.is_empty() => { + Some(serde_json::to_string(list).unwrap_or_default()) + } + _ => None, + }; + Ok((keyframes_blocks, vocabulary_collisions)) } /// List all export keys from a module namespace. @@ -2195,6 +2322,233 @@ export const ds = tokens; assert_eq!(result.expect("scan must succeed"), None); } + // ── vocabulary-registration: seam-1 record consumption ────────────────── + + const FIXTURE_THEME: &str = "export const theme = { serialize: () => ({\n\ + scalesJson: '{}', variableMapJson: '{}', variableCss: '',\n\ + contextualVarsJson: '{}' }) };\n"; + + fn sealed_system_fixture(record_literal: &str) -> String { + format!( + "export const ds = {{\n\ + toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}),\n\ + getVocabularyRecord: () => ({record_literal}),\n\ + }};\n\ + {FIXTURE_THEME}" + ) + } + + const TWO_COLLECTION_RECORD: &str = "{\n\ + version: 1,\n\ + keyframes: [\n\ + { name: 'first', frames: { pulse: { name: 'animus-kf-aaa', frames: { from: { opacity: 0 } } } } },\n\ + { name: 'second', frames: { fade: { name: 'animus-kf-bbb', frames: { to: { opacity: 1 } } } } },\n\ + ],\n\ + globalStyles: [],\n\ + collisions: [],\n\ + }"; + + #[test] + fn sealed_record_carries_collections_declaration_ordered() { + // rust-system-loader §"Collections come from the sealed registration + // record": registration order reaches the serialized wire. + let dir = scratch_dir("vocab-record-order"); + let entry = dir.join("entry.ts"); + write_fixture(&entry, &sealed_system_fixture(TWO_COLLECTION_RECORD)); + + let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); + let _ = fs::remove_dir_all(&dir); + + let config = result.expect("sealed system must load"); + let blocks = config + .keyframes_blocks + .expect("registered collections must carry"); + let first_at = blocks.find("\"first\"").expect("first present"); + let second_at = blocks.find("\"second\"").expect("second present"); + assert!( + first_at < second_at, + "registration order must reach the wire: {blocks}" + ); + let parsed: serde_json::Value = serde_json::from_str(&blocks).expect("valid JSON"); + assert_eq!( + parsed["first"]["pulse"]["name"], "animus-kf-aaa", + "wire keeps the {{ exportName: {{ keyName: {{ name, frames }} }} }} shape" + ); + } + + #[test] + fn exported_but_unregistered_collection_does_not_carry() { + // The hard-cut negative: a branded export absent from the record is + // invisible to the loader. + let dir = scratch_dir("vocab-unregistered"); + let entry = dir.join("entry.ts"); + let mut source = sealed_system_fixture(TWO_COLLECTION_RECORD); + source.push_str( + "export const motion = { __brand: 'Keyframes', __frames: { spin: { name: 'animus-kf-ccc', frames: {} } } };\n", + ); + write_fixture(&entry, &source); + + let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); + let _ = fs::remove_dir_all(&dir); + + let config = result.expect("sealed system must load"); + let blocks = config.keyframes_blocks.expect("registered ones carry"); + assert!( + !blocks.contains("motion") && !blocks.contains("animus-kf-ccc"), + "unregistered export must not carry: {blocks}" + ); + } + + #[test] + fn wrong_record_version_fails_the_load() { + // rust-system-loader §"Registration-record version skew fails the + // load" — the half that has no fallback: a PRESENT record with an + // incompatible marker. (Record ABSENCE falls back to the export scan + // until the migration increment deletes the scan.) + let dir = scratch_dir("vocab-version-skew"); + let entry = dir.join("entry.ts"); + write_fixture( + &entry, + &sealed_system_fixture( + "{ version: 99, keyframes: [], globalStyles: [], collisions: [] }", + ), + ); + + let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); + let _ = fs::remove_dir_all(&dir); + + let error = result.expect_err("incompatible record version must fail loud"); + assert!( + error.contains("version") && error.contains("99"), + "error must name the version mismatch: {error}" + ); + } + + #[test] + fn ambiguous_system_like_exports_fail_the_load() { + // rust-system-loader §"Ambiguous system-like exports fail the load": + // two DISTINCT toConfig-bearing exports and no explicit exportName. + let dir = scratch_dir("vocab-ambiguous"); + let entry = dir.join("entry.ts"); + write_fixture( + &entry, + &format!( + "export const ds = {{ toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}) }};\n\ + export const dsTwo = {{ toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}) }};\n\ + {FIXTURE_THEME}" + ), + ); + + let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); + let _ = fs::remove_dir_all(&dir); + + let error = result.expect_err("two distinct system-like exports must fail loud"); + assert!( + error.contains("ds") && error.contains("dsTwo"), + "error must name both exports: {error}" + ); + } + + #[test] + fn aliased_reexport_of_one_system_is_not_ambiguous() { + let dir = scratch_dir("vocab-alias"); + let entry = dir.join("entry.ts"); + write_fixture( + &entry, + &format!( + "export const ds = {{ toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}) }};\n\ + export const dsAlias = ds;\n\ + {FIXTURE_THEME}" + ), + ); + + let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); + let _ = fs::remove_dir_all(&dir); + + result.expect("aliases of ONE instance stay unambiguous"); + } + + #[test] + fn record_wire_is_byte_identical_across_fresh_loads() { + // rust-system-loader §"Collections come from the sealed registration + // record" (determinism scenario): two full loads, two runtimes, + // identical bytes. + let dir = scratch_dir("vocab-determinism"); + let entry = dir.join("entry.ts"); + write_fixture(&entry, &sealed_system_fixture(TWO_COLLECTION_RECORD)); + + let first = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None) + .expect("first load"); + let second = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None) + .expect("second load"); + let _ = fs::remove_dir_all(&dir); + + assert_eq!( + first.keyframes_blocks, second.keyframes_blocks, + "fresh-process loads must serialize identical collection bytes" + ); + assert!(first.keyframes_blocks.is_some()); + } + + #[test] + fn record_collisions_carry_to_the_config() { + // The record, not console, is the witness channel (the evaluation + // host shims console to a no-op). + let dir = scratch_dir("vocab-collisions"); + let entry = dir.join("entry.ts"); + write_fixture( + &entry, + &sealed_system_fixture( + "{ version: 1, keyframes: [], globalStyles: [], collisions: [\n\ + { code: 'animus.vocabulary.collision', name: 'motion',\n\ + winner: 'local registration #1', loser: 'extended source #1' },\n\ + ] }", + ), + ); + + let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); + let _ = fs::remove_dir_all(&dir); + + let config = result.expect("sealed system must load"); + let collisions = config + .vocabulary_collisions + .expect("collision entries must carry"); + assert!( + collisions.contains("animus.vocabulary.collision") + && collisions.contains("motion") + && collisions.contains("extended source #1"), + "collision witness must survive verbatim: {collisions}" + ); + } + + #[test] + fn recordless_system_falls_back_to_export_scan_until_migration() { + // STAGING PIN (design Ledger DEF-11 class; deleted at the migration + // increment): a system without a vocabulary record keeps export-scan + // discovery so un-migrated fixtures stay green. The migration + // increment replaces this with the loud version-skew error — this + // test must be DELETED in the same diff. + let dir = scratch_dir("vocab-fallback"); + let entry = dir.join("entry.ts"); + write_fixture( + &entry, + &format!( + "export const ds = {{ toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}) }};\n\ + export const motion = {{ __brand: 'Keyframes', __frames: {{ spin: {{ name: 'animus-kf-ddd', frames: {{}} }} }} }};\n\ + {FIXTURE_THEME}" + ), + ); + + let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); + let _ = fs::remove_dir_all(&dir); + + let config = result.expect("recordless system must still load"); + let blocks = config + .keyframes_blocks + .expect("legacy export scan still discovers"); + assert!(blocks.contains("animus-kf-ddd")); + } + #[test] fn unresolved_bare_specifier_fails_closed() { let dir = scratch_dir("unresolved"); diff --git a/packages/extract/pipeline/system-config.ts b/packages/extract/pipeline/system-config.ts index 9d36490f..7aaa8188 100644 --- a/packages/extract/pipeline/system-config.ts +++ b/packages/extract/pipeline/system-config.ts @@ -30,6 +30,13 @@ export interface SystemConfig { transformSourcesJson?: string | null; globalStyleBlocksJson: string | null; keyframesJson: string | null; + /** Vocabulary collision witnesses from the sealed system's registration + * record (JSON array of `{ code, name, winner, loser }`, stable code + * `animus.vocabulary.collision`). The record — not the evaluation host's + * console, which is shimmed to a no-op — is the witness channel; hosts + * surface these as diagnostics. Optional so pre-load + * `emptySystemConfig()` defaults need not restate it. */ + vocabularyCollisionsJson?: string | null; /** Canonical absolute paths of every module the loader evaluated for this * system (sorted; entry included, runtime stubs excluded). Plugins use it * as the geological-reset membership set. Optional so pre-load @@ -93,6 +100,7 @@ export function loadSystemConfig( transformSourcesJson: config.transformSources || null, globalStyleBlocksJson: config.globalStyleBlocks || null, keyframesJson: config.keyframesBlocks || null, + vocabularyCollisionsJson: config.vocabularyCollisions || null, dependencies: config.dependencies ?? [], sourceThemeManifestsJson: config.sourceThemeManifests || null, }; From bc65e247d0ce50cf273c7f961082efafa42c3b8e Mon Sep 17 00:00:00 2001 From: codecaaron Date: Fri, 21 Aug 2026 22:04:01 -0400 Subject: [PATCH 4/8] feat(extract): key-identity witness + coded unregistered-keyframe diagnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two engine witnesses at the unit seam: registration-key-equals-export-name proves substitution end to end (rewritten from the string-presence test), and a mismatched key skips the reference AND fires the new stable code animus.keyframes.unregistered-reference (trailing-marker convention lifted by diagnostic_code_from_message; severity warn, documented and pinned — strict escalation belongs to the strict-mode contract). The code is minted ONLY at keyframe-eligible value positions: eligibility threads through the eval recursion (animationName/animation own it; responsive maps and nested selector/at-rule blocks inherit it), so the type-licensed responsive form animationName:{_:ref} is witnessed while color:palette.brand can never carry a keyframes code — both directions test-pinned. Reason text speaks registration (build() → register → seal(); key = export name); scan/reachability language is gone, including the stale no-statics comments. Restored the registry-over-static precedence witness and pinned the pre-existing dead-@keyframes emission on mismatch. Route green: extract-v2 559 lib tests, clippy, hygiene:rust, unit:rust, canary, parity 66/66 (register []), integration 156. Increment 04 of openspec change system-vocabulary-registration (local); delegated implementation + independent adversarial review (11 findings dispositioned in the change journal). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DQuCHBc6HCuzoa77JyBwA4 --- .../crates/extract-v2/src/analyze_css.rs | 10 + .../extract/crates/extract-v2/src/engine.rs | 160 ++++++++++++-- .../extract/crates/extract-v2/src/eval.rs | 208 +++++++++++++++--- 3 files changed, 337 insertions(+), 41 deletions(-) diff --git a/packages/extract/crates/extract-v2/src/analyze_css.rs b/packages/extract/crates/extract-v2/src/analyze_css.rs index 49edc578..a4709eb8 100644 --- a/packages/extract/crates/extract-v2/src/analyze_css.rs +++ b/packages/extract/crates/extract-v2/src/analyze_css.rs @@ -4737,6 +4737,16 @@ mod tests { assert!(!out.css.contains('&'), "{}", out.css); } + #[test] + fn unregistered_keyframe_reference_severity_is_warn() { + // Deliberate warn (the spec's "skipped without aborting analysis"); + // escalation belongs to the strict-mode contract, a separate change. + assert_eq!( + diagnostic_severity_for_code(crate::eval::KEYFRAMES_UNREGISTERED_REFERENCE), + "warn" + ); + } + #[test] fn extension_child_inherits_parent_base_across_files() { let out = analyze( diff --git a/packages/extract/crates/extract-v2/src/engine.rs b/packages/extract/crates/extract-v2/src/engine.rs index 99cd6be9..d3762366 100644 --- a/packages/extract/crates/extract-v2/src/engine.rs +++ b/packages/extract/crates/extract-v2/src/engine.rs @@ -1301,11 +1301,12 @@ export const App = () => ; #[test] fn external_package_keyframes_collection_resolves_via_named_import() { - // A `Keyframes` collection discovered from an external - // package entry (delivered through keyframesJson by the loader scan) - // resolves through a regular named import — the animation-name ref - // and exactly one @keyframes block emit, identical to a consumer-entry - // collection. + // A collection registered inside an external kit's definition graph + // (carried to the consumer through the sealed kit and delivered as a + // keyframesJson record entry) resolves through a regular named + // import — the animation-name ref and exactly one @keyframes block + // emit, identical to a collection registered in the consumer's own + // definition graph. let mut engine = ExtractEngine::new(Some(EngineOptions { keyframes_json: Some( r#"{"kitMotion":{"pulse":{"name":"animus-kf-abc123","frames":{"from":{"opacity":0.4},"to":{"opacity":1}}}}}"# @@ -1467,25 +1468,152 @@ export const App = () => ; assert_eq!(manifest["usageResidue"][0]["kind"], "conditional"); } + /// Messages of every diagnostic carrying the unregistered-keyframe-reference + /// code — the witness channel for a reference the registration record + /// cannot answer. + fn unregistered_keyframe_diagnostics(manifest: &serde_json::Value) -> Vec { + manifest["diagnostics"] + .as_array() + .map(|ds| { + ds.iter() + .filter(|d| d["code"] == crate::eval::KEYFRAMES_UNREGISTERED_REFERENCE) + .map(|d| d["message"].as_str().unwrap_or_default().to_string()) + .collect() + }) + .unwrap_or_default() + } + #[test] - fn keyframes_registry_resolves_member_lookup() { - // v1 Phase 2a/2b: keyframes collections resolve `motion.ember` - // through the statics plumbing. + fn registration_key_equal_to_the_export_name_resolves_member_lookup() { + // Key identity: the record's outer key IS the module-scope export + // name the collection leaves its defining module under, so + // `animations.pulse` substitutes the content-hashed name into the + // consuming component's CSS and nothing is witnessed as missing. let mut engine = ExtractEngine::new(Some(EngineOptions { keyframes_json: Some( - r#"{"motion": {"ember": {"name": "anm-ember", "frames": "0%{}"}}}"#.to_string(), + r#"{"animations":{"pulse":{"name":"animus-kf-abc123","frames":{"from":{"opacity":0.4},"to":{"opacity":1}}}}}"# + .to_string(), ), ..Default::default() })) .unwrap(); - let out = engine - .analyze( - r#"[{"path":"system.ts","source":"export const motion = { ember: 'placeholder' };\n"}, - {"path":"a.tsx","source":"import { motion } from './system';\nexport const C = ds.styles({ animationName: motion.ember }).asElement('div');\nexport const App = () => ;\n"}]"# + let manifest: serde_json::Value = serde_json::from_str( + &engine + .analyze( + serde_json::json!([ + { "path": "system.ts", "source": "export const animations = createKeyframes({ pulse: { from: { opacity: 0.4 }, to: { opacity: 1 } } });\n" }, + { "path": "a.tsx", "source": "import { animations } from './system';\nexport const Pulse = ds.styles({ animationName: animations.pulse }).asElement('span');\nexport const App = () => ;\n" } + ]) .to_string(), - ) - .unwrap(); - assert!(out.contains("anm-ember"), "{out}"); + ) + .unwrap(), + ) + .unwrap(); + + let css = manifest["css"].as_str().unwrap_or(""); + assert!( + css.contains("animation-name:animus-kf-abc123") + || css.contains("animation-name: animus-kf-abc123"), + "{css}" + ); + let global = manifest["sheets"]["global"].as_str().unwrap_or(""); + assert_eq!( + global.matches("@keyframes animus-kf-abc123").count(), + 1, + "{global}" + ); + // Negative control: a resolving reference is not witnessed as missing. + assert!( + unregistered_keyframe_diagnostics(&manifest).is_empty(), + "{manifest}" + ); + } + + #[test] + fn registration_key_mismatched_with_the_export_name_skips_and_is_witnessed() { + // Registered under `motion`, exported as `animations`. The engine + // resolves by EXPORT name, so the reference finds no record entry: + // the property drops AND the coded diagnostic names the binding — + // the mismatch is not a silent miss. + let mut engine = ExtractEngine::new(Some(EngineOptions { + keyframes_json: Some( + r#"{"motion":{"pulse":{"name":"animus-kf-abc123","frames":{"from":{"opacity":0.4},"to":{"opacity":1}}}}}"# + .to_string(), + ), + ..Default::default() + })) + .unwrap(); + let manifest: serde_json::Value = serde_json::from_str( + &engine + .analyze( + serde_json::json!([ + { "path": "system.ts", "source": "export const animations = createKeyframes({ pulse: { from: { opacity: 0.4 }, to: { opacity: 1 } } });\n" }, + { "path": "a.tsx", "source": "import { animations } from './system';\nexport const Pulse = ds.styles({ animationName: animations.pulse }).asElement('span');\nexport const App = () => ;\n" } + ]) + .to_string(), + ) + .unwrap(), + ) + .unwrap(); + + let css = manifest["css"].as_str().unwrap_or(""); + assert!(!css.contains("animation-name"), "{css}"); + let coded = unregistered_keyframe_diagnostics(&manifest); + assert_eq!(coded.len(), 1, "{coded:?}"); + assert!(coded[0].contains("animationName"), "{}", coded[0]); + assert!(coded[0].contains("animations.pulse"), "{}", coded[0]); + // Analysis continues: the component still extracts. + assert!(!manifest["components"].as_object().unwrap().is_empty()); + // Pinned (inc-04 review objection 3, pre-existing behavior): the + // record entry still emits its `@keyframes` block into the global + // sheet even though nothing can reference it — a dead block, not a + // missing one. If this pin starts failing because orphan emission + // was removed, that is an improvement; retire the pin deliberately. + let global_sheet = manifest["sheets"]["global"].as_str().unwrap_or(""); + assert!( + global_sheet.contains("@keyframes animus-kf-abc123"), + "registered-but-unreferenced collections currently emit dead CSS: {global_sheet}" + ); + } + + #[test] + fn record_entry_wins_over_a_same_named_static_export() { + // Precedence witness (inc-04 review objection 8): the registration + // record's entry OVERWRITES a statically-foldable export of the + // same name in the statics map (`static_exports_by_file` inserts + // first, the record second — last write wins). A plain-object + // export `motion` must not shadow the registered collection. + let mut engine = ExtractEngine::new(Some(EngineOptions { + keyframes_json: Some( + r#"{"motion":{"ember":{"name":"animus-kf-abc123","frames":{"from":{"opacity":0.4},"to":{"opacity":1}}}}}"# + .to_string(), + ), + ..Default::default() + })) + .unwrap(); + let manifest: serde_json::Value = serde_json::from_str( + &engine + .analyze( + serde_json::json!([ + { "path": "system.ts", "source": "export const motion = { ember: 'placeholder' };\n" }, + { "path": "a.tsx", "source": "import { motion } from './system';\nexport const Ember = ds.styles({ animationName: motion.ember }).asElement('span');\nexport const App = () => ;\n" } + ]) + .to_string(), + ) + .unwrap(), + ) + .unwrap(); + + let css = manifest["css"].as_str().unwrap_or(""); + assert!( + css.contains("animation-name:animus-kf-abc123") + || css.contains("animation-name: animus-kf-abc123"), + "the record entry must win: {css}" + ); + assert!( + !css.contains("placeholder"), + "the static export must be shadowed by the record: {css}" + ); } #[test] diff --git a/packages/extract/crates/extract-v2/src/eval.rs b/packages/extract/crates/extract-v2/src/eval.rs index 4949281e..bd8bcebf 100644 --- a/packages/extract/crates/extract-v2/src/eval.rs +++ b/packages/extract/crates/extract-v2/src/eval.rs @@ -44,6 +44,19 @@ pub struct SkippedProperty { /// attribute value (nothing to anchor the class to). pub const SELECTOR_UNSUPPORTED_SUBJECT: &str = "animus.selector.unsupported-subject"; +/// Stable diagnostic code for an `object.property` reference whose base +/// binding carries no entry in the system's keyframe registration record. +/// Registration is the only channel that puts a collection in front of the +/// extractor, so a reference the record cannot answer is an authoring +/// mistake with a specific repair — distinct in kind from the generic +/// dynamic-value per-property skip, which has none. +/// +/// Severity: WARN, deliberately (the spec's "skipped without aborting +/// analysis") — `diagnostic_severity_for_code`'s default arm applies; +/// escalation belongs to the strict-mode contract, which is a separate +/// change. Severity-pinned in the analyze_css test module. +pub const KEYFRAMES_UNREGISTERED_REFERENCE: &str = "animus.keyframes.unregistered-reference"; + /// True when a style key looks selector-shaped (`&` present) but carries no /// substitutable subject — every `&` is inside quotes. pub(crate) fn unsupported_selector_key(key: &str) -> bool { @@ -87,6 +100,29 @@ pub fn eval_object_expr( pub fn eval_object_expr_with_statics( obj: &ObjectExpression<'_>, static_values: Option<&FxHashMap>, +) -> Result<(Value, Vec, Vec), BailError> { + eval_object_expr_scoped(obj, static_values, false) +} + +/// The recursive core, carrying keyframe ELIGIBILITY: whether the value +/// position being evaluated sits under an animation-name property +/// (`animationName`/`animation`), directly or through any nested block — +/// responsive maps (`animationName: { _: ref, sm: ref }`), selector and +/// at-rule blocks all inherit the owning property's eligibility. Only +/// eligible positions may carry the `KEYFRAMES_UNREGISTERED_REFERENCE` +/// code (a keyframes-coded diagnostic on `color: palette.brand` would +/// re-create the false-alarm class registration eliminates); the decision +/// is made where the reason is MINTED, never by post-hoc string surgery. +/// Property-name authority note: the type surface widens `KeyframeRef` +/// onto `animationName` only (`PassThroughProp<'animationName'>` in +/// packages/system/src/types/config.ts); `animation` is admitted here +/// because the shorthand can embed a keyframe name, and the kebab twins +/// (`animation-name`) are deliberately absent — quoted-kebab authoring is +/// outside the typed surface and gets the neutral reason. +fn eval_object_expr_scoped( + obj: &ObjectExpression<'_>, + static_values: Option<&FxHashMap>, + keyframes_eligible: bool, ) -> Result<(Value, Vec, Vec), BailError> { let mut map = Map::new(); let mut skipped = Vec::new(); @@ -104,6 +140,8 @@ pub fn eval_object_expr_with_statics( } let key = eval_property_key(&prop.key)?; + let eligible = keyframes_eligible + || matches!(key.as_str(), "animationName" | "animation"); // Selector-shaped keys whose every `&` is quoted have no // substitutable subject: record a coded skip instead of @@ -140,7 +178,7 @@ pub fn eval_object_expr_with_statics( // Handle nested objects directly to propagate inner captures if let Expression::ObjectExpression(inner_obj) = &prop.value { - match eval_object_expr_with_statics(inner_obj, static_values) { + match eval_object_expr_scoped(inner_obj, static_values, eligible) { Ok((value, inner_skips, inner_captured)) => { skipped.extend(inner_skips); // Prefix inner captures with the outer key @@ -162,7 +200,12 @@ pub fn eval_object_expr_with_statics( } // Try to evaluate the value. On failure, skip this property. - match eval_expression_with_statics(&prop.value, &mut skipped, static_values) { + match eval_expression_scoped( + &prop.value, + &mut skipped, + static_values, + eligible, + ) { Ok(value) => { map.insert(key, value); } @@ -209,6 +252,17 @@ pub(crate) fn eval_expression_with_statics( expr: &Expression<'_>, skips: &mut Vec, static_values: Option<&FxHashMap>, +) -> Result { + eval_expression_scoped(expr, skips, static_values, false) +} + +/// The recursive expression core, carrying keyframe eligibility (see +/// `eval_object_expr_scoped`). +fn eval_expression_scoped( + expr: &Expression<'_>, + skips: &mut Vec, + static_values: Option<&FxHashMap>, + keyframes_eligible: bool, ) -> Result { // `as`/`satisfies`/non-null/parens are erased type-level syntax: a wrapped // expression evaluates exactly like its operand (semantic-const-resolution, @@ -260,7 +314,7 @@ pub(crate) fn eval_expression_with_statics( // Note: captures from nested objects are discarded here — this path is // only reached for non-object properties in eval_object_expr (objects are // handled directly). This path remains for eval_array_element contexts. - match eval_object_expr_with_statics(obj, static_values) { + match eval_object_expr_scoped(obj, static_values, keyframes_eligible) { Ok((value, inner_skips, _captures)) => { skips.extend(inner_skips); Ok(value) @@ -326,6 +380,7 @@ pub(crate) fn eval_expression_with_statics( &member.object, member.property.name.as_str(), static_values, + keyframes_eligible, ))) } Expression::ComputedMemberExpression(_) => { @@ -340,16 +395,24 @@ pub(crate) fn eval_expression_with_statics( /// /// A bare "member expression (non-static)" names neither the binding nor the /// contract it failed, so an author reading the skip cannot tell a typo from a -/// keyframes collection the engine never discovered. Everything needed is -/// already at this seam: `static_values` is the same map the engine seeds with -/// the keyframes registry (`engine.rs` injects collections under their -/// imported/exported local binding), so its membership IS the discovery -/// answer. Every reason keeps the `(non-static)` marker so existing skip -/// surfacing is unchanged in kind. +/// keyframe collection that was never registered. Everything needed is already +/// at this seam: `static_values` is the same map the engine seeds from the +/// keyframe registration record (`engine.rs` injects each registered +/// collection under the local binding its export name resolves to), so its +/// membership IS the registration answer. Every reason keeps the +/// `(non-static)` marker so existing skip surfacing is unchanged in kind; the +/// unregistered case additionally carries the stable +/// `KEYFRAMES_UNREGISTERED_REFERENCE` code, which the manifest lifts out of +/// the message into `CssDiagnostic::code` — but ONLY when the value position +/// is keyframe-ELIGIBLE (under an animation-name property, directly or +/// through nested blocks; see `eval_object_expr_scoped`). Ineligible +/// positions get the neutral non-static reason: the code is minted here or +/// not at all, never stripped after the fact. fn member_expression_skip_reason( object: &Expression<'_>, property: &str, static_values: Option<&FxHashMap>, + keyframes_eligible: bool, ) -> String { let Expression::Identifier(ident) = object else { // Nested/computed object — no single binding to name. @@ -358,20 +421,23 @@ fn member_expression_skip_reason( let base = ident.name.as_str(); // Every named reason opens the same way and differs only in what follows. let named = format!("member expression '{base}.{property}' (non-static)"); - // No statics at all (the variant stage and a compound's second argument - // evaluate this way): discovery was never consulted, so keyframes advice - // would be unactionable noise. Report the missing context instead. + // No statics at all: this arm's production callers are array-element + // evaluation and the module-statics collection pass, whose skips never + // surface as manifest diagnostics (the variant stage and the compound + // second argument DO evaluate with statics). Keyframe advice here would + // be unactionable; report the missing context instead. let Some(sv) = static_values else { return format!("{named} — evaluated without extraction-time statics"); }; match sv.get(base) { Some(Value::Object(_)) => format!( - "{named} — '{base}' is a discovered collection with no '{property}' member" + "{named} — '{base}' is a registered collection with no '{property}' member" ), Some(_) => format!("{named} — '{base}' is not an object binding"), - None => format!( - "{named} — '{base}' is not a discovered keyframes collection or extraction-time static binding (collections must be reachable from the system entry)" + None if keyframes_eligible => format!( + "{named} — '{base}' is neither a registered keyframe collection nor an extraction-time static binding; if it is a keyframe collection, register it on the system between build() and seal() under the key '{base}' — the registration key must equal the export name ({KEYFRAMES_UNREGISTERED_REFERENCE})" ), + None => format!("{named} — '{base}' is not an extraction-time static binding"), } } @@ -1265,7 +1331,7 @@ const Component = { gap: GAP };"#; // ── the member-expression skip names its binding ───────────────────────── #[test] - fn member_expression_skip_names_undiscovered_collection_and_contract() { + fn member_expression_skip_codes_an_unregistered_collection_and_names_the_repair() { let sv = FxHashMap::default(); let (_, skips, _) = parse_obj_with_statics("{ animationName: motion.pulse }", Some(&sv)); assert_eq!(skips.len(), 1, "{:?}", skips); @@ -1276,17 +1342,98 @@ const Component = { gap: GAP };"#; ); assert!(reason.contains("non-static"), "{reason}"); assert!( - reason.contains("not a discovered keyframes collection"), + reason.contains("is neither a registered keyframe collection"), "{reason}" ); + // The repair, not the mechanism: register between the terminals under + // the export name. assert!( - reason.contains("reachable from the system entry"), + reason.contains("register it on the system between build() and seal()"), + "{reason}" + ); + assert!( + reason.contains("the registration key must equal the export name"), + "{reason}" + ); + // Trailing marker in the manifest's extractable position, so the + // diagnostic carries a stable code rather than only prose. + assert!( + reason.ends_with(&format!("({KEYFRAMES_UNREGISTERED_REFERENCE})")), + "{reason}" + ); + assert_eq!( + crate::analyze_css::diagnostic_code_from_message(reason).as_deref(), + Some(KEYFRAMES_UNREGISTERED_REFERENCE), "{reason}" ); } #[test] - fn member_expression_skip_names_a_missing_member_of_a_known_collection() { + fn keyframes_code_is_scoped_to_animation_name_properties() { + // vocabulary-registration user story 10: a build using no keyframes + // emits ZERO `animus.keyframes.*` diagnostics — an unknown member + // base under an unrelated property must not carry the keyframes + // code or advice, only the neutral non-static reason. + let sv = FxHashMap::default(); + let (_, skips, _) = parse_obj_with_statics("{ color: palette.brand }", Some(&sv)); + assert_eq!(skips.len(), 1, "{:?}", skips); + let reason = &skips[0].reason; + assert!( + reason.contains("member expression 'palette.brand'"), + "{reason}" + ); + assert!( + reason.contains("'palette' is not an extraction-time static binding"), + "{reason}" + ); + assert!( + !reason.contains("keyframe") && !reason.contains("build() and seal()"), + "keyframes advice must not leak onto unrelated properties: {reason}" + ); + assert_eq!( + crate::analyze_css::diagnostic_code_from_message(reason), + None, + "{reason}" + ); + } + + #[test] + fn keyframes_code_fires_through_the_responsive_form() { + // The type surface licenses `animationName: { _: ref, sm: ref }` + // (ResponsiveProp); eligibility must survive the + // nested-block recursion, not read only the immediate key. + let sv = FxHashMap::default(); + let (_, skips, _) = + parse_obj_with_statics("{ animationName: { _: motion.pulse } }", Some(&sv)); + assert_eq!(skips.len(), 1, "{:?}", skips); + let reason = &skips[0].reason; + assert_eq!( + crate::analyze_css::diagnostic_code_from_message(reason).as_deref(), + Some(KEYFRAMES_UNREGISTERED_REFERENCE), + "{reason}" + ); + } + + #[test] + fn responsive_form_of_an_unrelated_property_stays_uncoded() { + let sv = FxHashMap::default(); + let (_, skips, _) = + parse_obj_with_statics("{ color: { _: palette.brand } }", Some(&sv)); + assert_eq!(skips.len(), 1, "{:?}", skips); + let reason = &skips[0].reason; + assert!( + reason.contains("'palette' is not an extraction-time static binding"), + "{reason}" + ); + assert_eq!( + crate::analyze_css::diagnostic_code_from_message(reason), + None, + "{reason}" + ); + } + + #[test] + fn member_expression_skip_names_a_missing_member_of_a_registered_collection() { let mut sv = FxHashMap::default(); let mut motion = Map::new(); motion.insert("ember".to_string(), Value::String("animus-kf-abc".to_string())); @@ -1300,16 +1447,26 @@ const Component = { gap: GAP };"#; "{reason}" ); assert!( - reason.contains("discovered collection with no 'pulse' member"), + reason.contains("registered collection with no 'pulse' member"), + "{reason}" + ); + // A present-but-incomplete collection is NOT the unregistered case. + assert_eq!( + crate::analyze_css::diagnostic_code_from_message(reason), + None, "{reason}" ); } #[test] fn member_expression_skip_without_statics_reports_missing_context() { - // The variant/second-compound-arg path evaluates with NO statics, so - // discovery was never consulted — keyframes advice there would be - // unactionable. Name the binding and the missing context instead. + // No-statics callers (array-element evaluation and the + // module-statics collection pass — the variant stage and the compound + // second argument DO evaluate with statics) never consulted the + // registration record, and their skips never surface as manifest + // diagnostics — keyframe advice there would be unactionable. Name + // the binding and the missing context instead, and do NOT code it as + // an unregistered reference. let (_, skips) = parse_obj_full("{ animationName: motion.pulse }"); assert_eq!(skips.len(), 1, "{:?}", skips); let reason = &skips[0].reason; @@ -1321,8 +1478,9 @@ const Component = { gap: GAP };"#; reason.contains("evaluated without extraction-time statics"), "{reason}" ); - assert!( - !reason.contains("not a discovered keyframes collection"), + assert_eq!( + crate::analyze_css::diagnostic_code_from_message(reason), + None, "{reason}" ); } From afae219675034f2d5f5ef58b23bf6c78fc96c0d6 Mon Sep 17 00:00:00 2001 From: codecaaron Date: Fri, 21 Aug 2026 23:01:16 -0400 Subject: [PATCH 5/8] =?UTF-8?q?feat!:=20the=20vocabulary=20hard=20cut=20?= =?UTF-8?q?=E2=80=94=20every=20system=20seals;=20the=20keyframes=20scan=20?= =?UTF-8?q?is=20gone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration (one tree state, no midpoint): all nine app fixture systems + showcase + test-ds + both test fixtures register their collections and export the SEALED instance; test-ds's kitMotion relocates into the definition graph (defined/registered in system.ts, root re-export keeps consumer imports and the engine's export-name resolution — the vite-app kit witness survives byte-exact, SET-IDENTICAL pre/post). Deletion (total): the NAPI scan entry point + regenerated bindings, the loader's scan fn + its three unit tests, the external-keyframes merge module + test, discovery's scan-entry population (the root-alias module-resolution redirect STAYS, pin rewritten), both host orchestration copies (vite-plugin context/build-start, extraction session), every plugin scan mock, both scan-era animus.keyframes.* codes. Flips: extend() rejects unsealed sources; the loader refuses recordless systems loud; hosts surface the sealed record's coded witnesses via one shared fail-closed mapper (vocabularyWitnessDiagnostics + unit tests). New: the D4 legacy-verb witness — a sealed kit with registered vocabulary consumed through from()/includes: records animus.vocabulary.legacy-verb (positional source label; seal-time filtering never claims a name that a separate .extend() delivered; no console noise in shipped bundles). Fires on exactly the three deliberate legacy lanes, warn severity, lanes green. Docs swept to the sealed shape (Quick Start, create-system, system-setup, library-authoring, global-styles, selectors, builder-chain, svelte, migration, component-test, README, CLAUDE.md). Oracle fixture refreshed to the relocated facts (340/340). Teaching page rewritten to registration. Verified: lint/compile/types/unit:ts(146f/1773)/unit:rust/clippy/hygiene/ canary/parity(66/66, register [])/integration/packed/workers + all eight owner lanes green; repeated-build keyframes determinism witnessed. Increment 05 of openspec change system-vocabulary-registration (local); two adversarial review rounds (14+14 findings) dispositioned in the change journal. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DQuCHBc6HCuzoa77JyBwA4 --- e2e/next-app/src/ds.ts | 15 +- e2e/next16-app/src/ds.ts | 31 +- e2e/packed-app/src/ds.ts | 12 +- e2e/react-router-app/src/ds.ts | 10 +- e2e/rollup-app/fixtures/ds-standalone.ts | 5 +- e2e/rollup-app/fixtures/error-root/ds.ts | 5 +- e2e/rollup-app/fixtures/watch-root/src/ds.ts | 5 +- e2e/rollup-app/src/ds.ts | 47 +-- e2e/svelte-app/src/ds.ts | 4 +- e2e/vinext-app/src/ds.ts | 10 +- e2e/vite-app/scripts/assert-build.ts | 4 +- e2e/vite-app/src/components/KitPulse.tsx | 15 +- e2e/vite-app/src/ds.ts | 28 +- packages/extract/crates/extract-v2/index.d.ts | 27 +- packages/extract/crates/extract-v2/index.js | 1 - packages/extract/crates/extract-v2/src/lib.rs | 34 +- .../extract/crates/system-loader/src/lib.rs | 329 +++++++----------- .../extract/pipeline/discover-packages.ts | 20 -- packages/extract/pipeline/engine-adapter.ts | 8 +- .../extract/pipeline/external-keyframes.ts | 145 -------- packages/extract/pipeline/index.ts | 9 +- .../extract/pipeline/manifest-diagnostics.ts | 70 ++++ packages/extract/pipeline/system-config.ts | 14 +- .../extract/session/extraction-session.ts | 43 +-- .../tests/collect-external-packages.test.ts | 55 +-- .../extract/tests/external-keyframes.test.ts | 154 -------- packages/extract/tests/test-system.ts | 7 +- .../vocabulary-witness-diagnostics.test.ts | 71 ++++ .../tests/svelte-source-lifecycle.test.ts | 3 - .../tests/watch-external-ingestion.test.ts | 61 ---- .../external-ingestion.test.ts | 9 +- .../tests/webpack-gauntlet/harness.ts | 3 +- .../webpack-gauntlet/real-engine.test.ts | 5 +- packages/oracle/__tests__/e2e-demo.test.ts | 2 +- .../__tests__/fixtures/rollup-app/commit.json | 4 +- .../fixtures/rollup-app/manifest.json | 2 +- .../__tests__/fixtures/rollup-app/styles.css | 2 +- .../oracle/__tests__/host-universe.test.ts | 2 +- packages/showcase/CLAUDE.md | 2 +- .../showcase/src/content/advanced/svelte.mdx | 2 +- .../content/architecture/global-styles.mdx | 9 +- .../architecture/library-authoring.mdx | 32 +- .../src/content/architecture/system-setup.mdx | 67 ++-- .../src/content/authoring/selectors.mdx | 10 +- .../src/content/reference/builder-chain.mdx | 4 +- .../src/content/reference/create-system.mdx | 30 +- packages/showcase/src/content/start.mdx | 7 +- .../src/content/support/component-test.mdx | 5 +- .../src/content/support/migration.mdx | 1 + packages/showcase/src/ds.ts | 30 +- packages/system/README.md | 10 +- packages/system/__tests__/extend.test.ts | 85 +++-- packages/system/__tests__/test-system.ts | 12 +- .../__tests__/transform-identity.test.ts | 15 +- packages/system/__tests__/types.test-d.tsx | 10 +- packages/system/__tests__/vocabulary.test.ts | 208 ++++++++--- packages/system/src/SystemBuilder.ts | 152 +++++++- packages/system/src/index.ts | 1 + packages/system/src/keyframes.ts | 3 +- packages/test-ds/src/index.ts | 27 +- packages/test-ds/src/system.ts | 23 +- packages/vite-plugin/src/build-start.ts | 9 - packages/vite-plugin/src/context.ts | 74 +--- .../vite-plugin/tests/dev-lane/fixture.ts | 5 +- .../external-keyframes-lifecycle.test.ts | 125 ------- .../tests/svelte-source-lifecycle.test.ts | 4 - vite.config.ts | 2 +- 67 files changed, 1011 insertions(+), 1224 deletions(-) delete mode 100644 packages/extract/pipeline/external-keyframes.ts delete mode 100644 packages/extract/tests/external-keyframes.test.ts create mode 100644 packages/extract/tests/vocabulary-witness-diagnostics.test.ts delete mode 100644 packages/vite-plugin/tests/external-keyframes-lifecycle.test.ts diff --git a/e2e/next-app/src/ds.ts b/e2e/next-app/src/ds.ts index 4dd4864d..20b32eee 100644 --- a/e2e/next-app/src/ds.ts +++ b/e2e/next-app/src/ds.ts @@ -202,11 +202,7 @@ declare module '@animus-ui/system' { // (showcase remains on `includes:` pending its deferred migration — // registry row 13; see its ds.ts). Do not migrate this lane until removal // is specced. -export const { - system: ds, - createGlobalStyles, - createKeyframes, -} = createSystem({ +const bundle = createSystem({ includes: [testDs], }) .addGroup('space', space) @@ -216,6 +212,8 @@ export const { .addGroup('positioning', positioning) .build(); +export const { createGlobalStyles, createKeyframes } = bundle; + // ─── Keyframes ────────────────────────────────────────────── export const animations = createKeyframes({ @@ -229,6 +227,13 @@ export const animations = createKeyframes({ }, }); +// Sealed system (vocabulary-registration): `animations` registers under its +// export name. The `includes:` alias above deliberately CANNOT carry the +// kit's registered `kitMotion` — this lane is the legacy-verb witness: the +// sealed record carries the coded `animus.vocabulary.legacy-verb` entry the +// hosts surface as a warning. +export const ds = bundle.registerKeyframes({ animations }).seal(); + // ─── Global Styles ────────────────────────────────────────── export const globalStyles = createGlobalStyles({ diff --git a/e2e/next16-app/src/ds.ts b/e2e/next16-app/src/ds.ts index 21499b4e..d57667c3 100644 --- a/e2e/next16-app/src/ds.ts +++ b/e2e/next16-app/src/ds.ts @@ -163,20 +163,18 @@ declare module '@animus-ui/system' { // ─── System ───────────────────────────────────────────────── -export const { - system: ds, - createGlobalStyles, - createKeyframes, - // extend()-form lane (openspec: first-class-extension, D1): test-ds's - // registries MERGE into this system — the kit alone provides the space/ - // layout/text/surface/positioning groups the components use. The only - // LOCAL registration is the additive, transform-free `shadows` prop set - // (boxShadow/shadow/textShadow — the kit does not register them, and the - // Card/Button styles resolve their `shadows`-scale values through the - // registry). Re-spreading kit groups would coalesce under D12 transform - // equality (name + captured source); this lane stays pure-extend + additive - // as the recommended consumption shape. -} = createSystem().extend(testDs).addProps(shadows).build(); +// extend()-form lane (openspec: first-class-extension, D1): test-ds's +// registries MERGE into this system — the kit alone provides the space/ +// layout/text/surface/positioning groups the components use. The only +// LOCAL registration is the additive, transform-free `shadows` prop set +// (boxShadow/shadow/textShadow — the kit does not register them, and the +// Card/Button styles resolve their `shadows`-scale values through the +// registry). Re-spreading kit groups would coalesce under D12 transform +// equality (name + captured source); this lane stays pure-extend + additive +// as the recommended consumption shape. +const bundle = createSystem().extend(testDs).addProps(shadows).build(); + +export const { createGlobalStyles, createKeyframes } = bundle; // ─── Keyframes ────────────────────────────────────────────── @@ -191,6 +189,11 @@ export const animations = createKeyframes({ }, }); +// Sealed system (vocabulary-registration): `animations` registers under its +// export name; the kit's `kitMotion` arrives through the sealed test-ds +// record via `.extend()`. +export const ds = bundle.registerKeyframes({ animations }).seal(); + // ─── Global Styles ────────────────────────────────────────── export const globalStyles = createGlobalStyles({ diff --git a/e2e/packed-app/src/ds.ts b/e2e/packed-app/src/ds.ts index 07670159..ca55bf72 100644 --- a/e2e/packed-app/src/ds.ts +++ b/e2e/packed-app/src/ds.ts @@ -66,11 +66,7 @@ declare module '@animus-ui/system' { interface Theme extends PackedAppTheme {} } -export const { - system: ds, - createGlobalStyles, - createKeyframes, -} = createSystem() +const bundle = createSystem() .addGroup('space', space) .addGroup('layout', { ...layout, ...flex }) .addGroup('text', typography) @@ -78,6 +74,8 @@ export const { .addGroup('positioning', positioning) .build(); +export const { createGlobalStyles, createKeyframes } = bundle; + export const globalStyles = createGlobalStyles({ '*, *::before, *::after': { boxSizing: 'border-box' }, body: { @@ -98,3 +96,7 @@ export const animations = createKeyframes({ '50%': { transform: 'scale(1.05)' }, }, }); + +// Sealed system (vocabulary-registration): `animations` registers under +// its export name; registration closes at seal(). +export const ds = bundle.registerKeyframes({ animations }).seal(); diff --git a/e2e/react-router-app/src/ds.ts b/e2e/react-router-app/src/ds.ts index 476639ed..35b16a3a 100644 --- a/e2e/react-router-app/src/ds.ts +++ b/e2e/react-router-app/src/ds.ts @@ -79,7 +79,7 @@ declare module '@animus-ui/system' { // vite-app/next16-app/vinext-app use `.extend()` (showcase remains on // `includes:` pending its deferred migration — registry row 13; see its // ds.ts). Do not migrate this lane until removal is specced. -export const { system: ds, createGlobalStyles } = createSystem() +const bundle = createSystem() .from(testDs) .addGroup('space', space) .addGroup('layout', { ...layout, ...flex }) @@ -87,6 +87,14 @@ export const { system: ds, createGlobalStyles } = createSystem() .addGroup('surface', { ...color, ...border }) .build(); +export const { createGlobalStyles } = bundle; + +// Sealed system (vocabulary-registration): the `from()` verb above cannot +// carry the kit's registered `kitMotion` — this lane is the from()-side +// legacy-verb witness (`animus.vocabulary.legacy-verb` on the sealed +// record, surfaced by the host as a warning). +export const ds = bundle.seal(); + export const globalStyles = createGlobalStyles({ '*, *::before, *::after': { boxSizing: 'border-box' }, body: { diff --git a/e2e/rollup-app/fixtures/ds-standalone.ts b/e2e/rollup-app/fixtures/ds-standalone.ts index 869b1afd..ab8942a7 100644 --- a/e2e/rollup-app/fixtures/ds-standalone.ts +++ b/e2e/rollup-app/fixtures/ds-standalone.ts @@ -6,8 +6,9 @@ export const theme = createTheme() .addColors({ gray: { 100: '#f5f5f5' } }) .build(); -export const { system: ds } = createSystem() +export const ds = createSystem() .addGroup('color', { color: { property: 'color', scale: 'colors' }, }) - .build(theme); + .build(theme) + .seal(); diff --git a/e2e/rollup-app/fixtures/error-root/ds.ts b/e2e/rollup-app/fixtures/error-root/ds.ts index abb88829..b36b04b9 100644 --- a/e2e/rollup-app/fixtures/error-root/ds.ts +++ b/e2e/rollup-app/fixtures/error-root/ds.ts @@ -15,11 +15,12 @@ const badGlow = createTransform('badGlow', (value) => ({ boxShadow: String(value), })); -export const { system: ds } = createSystem() +export const ds = createSystem() .addGroup('fx', { glow: { property: 'boxShadow', transform: badGlow, }, }) - .build(theme); + .build(theme) + .seal(); diff --git a/e2e/rollup-app/fixtures/watch-root/src/ds.ts b/e2e/rollup-app/fixtures/watch-root/src/ds.ts index c685b8ed..d4f8190b 100644 --- a/e2e/rollup-app/fixtures/watch-root/src/ds.ts +++ b/e2e/rollup-app/fixtures/watch-root/src/ds.ts @@ -16,11 +16,12 @@ const badGlow = createTransform('badGlow', (value) => ({ boxShadow: String(value), })); -export const { system: ds } = createSystem() +export const ds = createSystem() .addGroup('fx', { glow: { property: 'boxShadow', transform: badGlow, }, }) - .build(theme); + .build(theme) + .seal(); diff --git a/e2e/rollup-app/src/ds.ts b/e2e/rollup-app/src/ds.ts index 7eedd8f2..72218409 100644 --- a/e2e/rollup-app/src/ds.ts +++ b/e2e/rollup-app/src/ds.ts @@ -80,28 +80,24 @@ declare module '@animus-ui/system' { interface Theme extends RollupAppTheme {} } -export const { - system: ds, - createGlobalStyles, - createKeyframes, - // extend()-form witness (openspec: first-class-extension, D1/NS-1): this - // lane consumes test-ds through the single extension verb — a REAL registry - // merge. EVERY group here (space, layout, text, surface, positioning) and - // the kit's condition aliases arrive through `.extend(testDs)` alone; the - // app deliberately re-registers nothing, so the merged config IS the kit's - // registry surface plus the local `_motionReduce` re-assertion below. - // (Re-spreading kit groups locally would coalesce under D12 transform - // equality — name + captured source — but pure extension is the - // recommended consumption shape: the merge already provides them.) - // - // Box.tsx opts into the kit's `positioning` group and App.tsx uses - // `top`/`zIndex`, making the emitted CSS the end-to-end witness that a - // kit-registered prop flows through the MERGED config into extraction - // output (rust-system-loader › "Merged configuration is the extraction - // authority"). The legacy lanes stay deliberate elsewhere: next-app keeps - // the deprecated `includes:` alias, react-router-app keeps the deprecated - // `from()` chain (G6). -} = createSystem() +// extend()-form witness (openspec: first-class-extension, D1/NS-1): this +// lane consumes test-ds through the single extension verb — a REAL registry +// merge. EVERY group here (space, layout, text, surface, positioning) and +// the kit's condition aliases arrive through `.extend(testDs)` alone; the +// app deliberately re-registers nothing, so the merged config IS the kit's +// registry surface plus the local `_motionReduce` re-assertion below. +// (Re-spreading kit groups locally would coalesce under D12 transform +// equality — name + captured source — but pure extension is the +// recommended consumption shape: the merge already provides them.) +// +// Box.tsx opts into the kit's `positioning` group and App.tsx uses +// `top`/`zIndex`, making the emitted CSS the end-to-end witness that a +// kit-registered prop flows through the MERGED config into extraction +// output (rust-system-loader › "Merged configuration is the extraction +// authority"). The legacy lanes stay deliberate elsewhere: next-app keeps +// the deprecated `includes:` alias, react-router-app keeps the deprecated +// `from()` chain (G6). +const bundle = createSystem() .extend(testDs) // Condition alias registry (modern-css-surface inc 03). The kit already // carries `_motionReduce`; this local registration re-asserts it with an @@ -112,6 +108,8 @@ export const { }) .build(); +export const { createGlobalStyles, createKeyframes } = bundle; + export const globalStyles = createGlobalStyles( { '*, *::before, *::after': { boxSizing: 'border-box' }, @@ -151,3 +149,8 @@ export const animations = createKeyframes({ '50%': { transform: 'scale(1.05)' }, }, }); + +// Sealed system (vocabulary-registration): `animations` registers under its +// export name; the kit's `kitMotion` arrives through the sealed test-ds +// record via `.extend()`. +export const ds = bundle.registerKeyframes({ animations }).seal(); diff --git a/e2e/svelte-app/src/ds.ts b/e2e/svelte-app/src/ds.ts index ab25f96e..06e2ef26 100644 --- a/e2e/svelte-app/src/ds.ts +++ b/e2e/svelte-app/src/ds.ts @@ -7,4 +7,6 @@ export const theme = /* @__PURE__ */ (() => }) .build())(); -export const { system: ds } = /* @__PURE__ */ (() => createSystem().build())(); +// Sealed system (vocabulary-registration): a vocabulary-free system seals +// too — `.seal()` is the loader's contract for every consumer. +export const ds = /* @__PURE__ */ (() => createSystem().build().seal())(); diff --git a/e2e/vinext-app/src/ds.ts b/e2e/vinext-app/src/ds.ts index cce1519d..ea004dde 100644 --- a/e2e/vinext-app/src/ds.ts +++ b/e2e/vinext-app/src/ds.ts @@ -64,9 +64,13 @@ declare module '@animus-ui/system' { // `.extend(testDs)` alone. Nothing is re-registered locally — re-spreading // kit groups would coalesce under D12 transform equality (name + captured // source), but pure extension is the recommended consumption shape. -export const { system: ds, createGlobalStyles } = createSystem() - .extend(testDs) - .build(); +const bundle = createSystem().extend(testDs).build(); + +export const { createGlobalStyles } = bundle; + +// Sealed system (vocabulary-registration): no local collections; the kit's +// `kitMotion` arrives through the sealed test-ds record via `.extend()`. +export const ds = bundle.seal(); export const globalStyles = createGlobalStyles({ '*, *::before, *::after': { boxSizing: 'border-box' }, diff --git a/e2e/vite-app/scripts/assert-build.ts b/e2e/vite-app/scripts/assert-build.ts index 8bbabf4e..c56ba537 100644 --- a/e2e/vite-app/scripts/assert-build.ts +++ b/e2e/vite-app/scripts/assert-build.ts @@ -290,8 +290,8 @@ async function main(): Promise { }); // Exactly one @keyframes block per unique frame body: the kit collection - // must emit ONCE — not once via the external-entry scan and again via the - // consumer reference — and no app body may collide. + // must emit ONCE — the sealed record delivers it a single time regardless + // of how many consumers reference it — and no app body may collide. assertKeyframesUniqueBodies(css); // Binding-backed vs inline parity (semantic-const-resolution): KitSized diff --git a/e2e/vite-app/src/components/KitPulse.tsx b/e2e/vite-app/src/components/KitPulse.tsx index 39e7fb17..d8f0ebd4 100644 --- a/e2e/vite-app/src/components/KitPulse.tsx +++ b/e2e/vite-app/src/components/KitPulse.tsx @@ -2,13 +2,14 @@ import { kitMotion } from '@animus-ui/test-ds'; import { ds } from '../ds'; -// External keyframe-collection consumer (rust-extraction-pipeline › "External -// package collection discovered from its entry"): `kitMotion` is created and -// exported by the test-ds package ENTRY module, and this component references -// it through a plain named import — the app's ds.ts does NOT re-export it. The -// extractor's keyframes scan must discover the collection from the external -// entry, resolve `kitMotion.pulse` to its `animus-kf-` name, and emit the -// matching @keyframes block exactly once. Pulse.tsx (app-local +// Sealed-kit keyframe consumer (rust-extraction-pipeline › "Sealed kit +// collection resolves in the consumer"): `kitMotion` is defined and +// registered inside test-ds's definition graph, re-exported from the package +// root, and this component references it through a plain named import — the +// app's ds.ts does NOT re-export it. The collection reaches the app through +// the sealed kit's registration record via `.extend()`; the extractor +// resolves `kitMotion.pulse` by the export name at the defining module and +// emits the matching @keyframes block exactly once. Pulse.tsx (app-local // `animations.pulse` from ds.ts) is the inline sibling in the same sheet; // assertKeyframesUniqueBodies pins that no frame body is ever emitted twice. export const KitPulse = ds diff --git a/e2e/vite-app/src/ds.ts b/e2e/vite-app/src/ds.ts index 85fb789a..ff74ecf3 100644 --- a/e2e/vite-app/src/ds.ts +++ b/e2e/vite-app/src/ds.ts @@ -81,8 +81,18 @@ declare module '@animus-ui/system' { interface Theme extends ViteAppTheme {} } +const bundle = createSystem() + .extend(testDs) + // Condition alias registry (modern-css-surface inc 03). The kit already + // carries `_motionReduce`; this local registration re-asserts it with an + // identical value (post-extend app calls override silently, NS-4) so the + // manifest `conditionAliases` plugin glue keeps a local witness here. + .addConditions({ + _motionReduce: '@media (prefers-reduced-motion: reduce)', + }) + .build(); + export const { - system: ds, createGlobalStyles, createKeyframes, // extend()-form witness (openspec: first-class-extension, D1/NS-1): this @@ -102,16 +112,7 @@ export const { // authority"). The legacy lanes stay deliberate elsewhere: next-app keeps // the deprecated `includes:` alias, react-router-app keeps the deprecated // `from()` chain (G6). -} = createSystem() - .extend(testDs) - // Condition alias registry (modern-css-surface inc 03). The kit already - // carries `_motionReduce`; this local registration re-asserts it with an - // identical value (post-extend app calls override silently, NS-4) so the - // manifest `conditionAliases` plugin glue keeps a local witness here. - .addConditions({ - _motionReduce: '@media (prefers-reduced-motion: reduce)', - }) - .build(); +} = bundle; export const globalStyles = createGlobalStyles( { @@ -152,3 +153,8 @@ export const animations = createKeyframes({ '50%': { transform: 'scale(1.05)' }, }, }); + +// Sealed system (vocabulary-registration): `animations` registers under its +// export name; the kit's `kitMotion` arrives through the sealed test-ds +// record via `.extend()` — no local step, and no export scan anywhere. +export const ds = bundle.registerKeyframes({ animations }).seal(); diff --git a/packages/extract/crates/extract-v2/index.d.ts b/packages/extract/crates/extract-v2/index.d.ts index 34310e5e..db242e22 100644 --- a/packages/extract/crates/extract-v2/index.d.ts +++ b/packages/extract/crates/extract-v2/index.d.ts @@ -141,14 +141,16 @@ export interface NapiSystemConfig { globalStyleBlocks?: string keyframesBlocks?: string /** - * Vocabulary collision witnesses from the sealed system's registration - * record: JSON array of `{ code, name, winner, loser }` with the stable - * code `animus.vocabulary.collision`. The record is the witness channel - * (the evaluation host shims console); hosts surface these as - * diagnostics. Absent when there are no collisions or the system - * predates the record. + * Vocabulary witnesses from the sealed system's registration record: + * one JSON array of coded entries — collisions + * (`animus.vocabulary.collision`) and legacy-verb witnesses + * (`animus.vocabulary.legacy-verb`: registered vocabulary consumed + * through `from()`/`includes:`, which cannot carry it). The record is + * the witness channel (the evaluation host shims console); hosts + * surface each entry as a diagnostic keyed by its `code`. Absent when + * the record carries no witnesses. */ - vocabularyCollisions?: string + vocabularyWitnesses?: string /** * Canonical absolute paths of every module evaluated for the system * (sorted; entry included, runtime stubs excluded). The plugins use this @@ -163,14 +165,3 @@ export interface NapiSystemConfig { */ sourceThemeManifests?: string } - -/** - * Scan one module entry for named `Keyframes` collection exports — the - * keyframes-only carve-out for external package entries. The - * entry evaluates through the same loader pipeline as a system module, but - * nothing except `__brand === 'Keyframes'` exports is read from it; the - * consumer's configured system remains the singular config authority. - * Returns the `{ exportName: { keyName: { name, frames } } }` JSON, or None - * when the entry exports no collections. - */ -export declare function scanKeyframesExports(entryPath: string, rootDir: string): string | null diff --git a/packages/extract/crates/extract-v2/index.js b/packages/extract/crates/extract-v2/index.js index 1d9c4d98..ca96089a 100644 --- a/packages/extract/crates/extract-v2/index.js +++ b/packages/extract/crates/extract-v2/index.js @@ -592,4 +592,3 @@ module.exports.discoverChains = nativeBinding.discoverChains module.exports.engineVersion = nativeBinding.engineVersion module.exports.extractFacts = nativeBinding.extractFacts module.exports.loadSystemModule = nativeBinding.loadSystemModule -module.exports.scanKeyframesExports = nativeBinding.scanKeyframesExports diff --git a/packages/extract/crates/extract-v2/src/lib.rs b/packages/extract/crates/extract-v2/src/lib.rs index 051d07f8..5555c191 100644 --- a/packages/extract/crates/extract-v2/src/lib.rs +++ b/packages/extract/crates/extract-v2/src/lib.rs @@ -75,13 +75,15 @@ pub struct NapiSystemConfig { pub transform_sources: Option, pub global_style_blocks: Option, pub keyframes_blocks: Option, - /// Vocabulary collision witnesses from the sealed system's registration - /// record: JSON array of `{ code, name, winner, loser }` with the stable - /// code `animus.vocabulary.collision`. The record is the witness channel - /// (the evaluation host shims console); hosts surface these as - /// diagnostics. Absent when there are no collisions or the system - /// predates the record. - pub vocabulary_collisions: Option, + /// Vocabulary witnesses from the sealed system's registration record: + /// one JSON array of coded entries — collisions + /// (`animus.vocabulary.collision`) and legacy-verb witnesses + /// (`animus.vocabulary.legacy-verb`: registered vocabulary consumed + /// through `from()`/`includes:`, which cannot carry it). The record is + /// the witness channel (the evaluation host shims console); hosts + /// surface each entry as a diagnostic keyed by its `code`. Absent when + /// the record carries no witnesses. + pub vocabulary_witnesses: Option, /// Canonical absolute paths of every module evaluated for the system /// (sorted; entry included, runtime stubs excluded). The plugins use this /// as the geological-reset membership set. @@ -119,28 +121,12 @@ pub fn load_system_module( transform_sources: config.transform_sources, global_style_blocks: config.global_style_blocks, keyframes_blocks: config.keyframes_blocks, - vocabulary_collisions: config.vocabulary_collisions, + vocabulary_witnesses: config.vocabulary_witnesses, dependencies: config.dependencies, source_theme_manifests: config.source_theme_manifests, }) } -/// Scan one module entry for named `Keyframes` collection exports — the -/// keyframes-only carve-out for external package entries. The -/// entry evaluates through the same loader pipeline as a system module, but -/// nothing except `__brand === 'Keyframes'` exports is read from it; the -/// consumer's configured system remains the singular config authority. -/// Returns the `{ exportName: { keyName: { name, frames } } }` JSON, or None -/// when the entry exports no collections. -#[napi] -pub fn scan_keyframes_exports( - entry_path: String, - root_dir: String, -) -> napi::Result> { - animus_system_loader::scan_keyframes_exports(&entry_path, &root_dir) - .map_err(napi::Error::from_reason) -} - #[derive(Deserialize)] struct InputEntry { path: String, diff --git a/packages/extract/crates/system-loader/src/lib.rs b/packages/extract/crates/system-loader/src/lib.rs index 2e7beb06..85cecaaa 100644 --- a/packages/extract/crates/system-loader/src/lib.rs +++ b/packages/extract/crates/system-loader/src/lib.rs @@ -56,14 +56,17 @@ pub struct SystemConfig { /// collection identity so the extractor can substitute /// `motion.ember`-style member-expression references against it. pub keyframes_blocks: Option, - /// Vocabulary collision witnesses from the sealed system's registration - /// record (vocabulary-registration): JSON array of `{ code, name, - /// winner, loser }` entries with the stable code - /// `animus.vocabulary.collision`. The record — not the evaluation - /// host's console (shimmed to a no-op) — is the witness channel; hosts - /// surface these as diagnostics. `None` when the record carries no - /// collisions or the system predates the record. - pub vocabulary_collisions: Option, + /// Vocabulary witnesses from the sealed system's registration record + /// (vocabulary-registration): one JSON array carrying every coded entry + /// — collision entries (`animus.vocabulary.collision`: `{ code, name, + /// winner, loser }`) and legacy-verb entries + /// (`animus.vocabulary.legacy-verb`: `{ code, verb, names }`, a sealed + /// kit with registered vocabulary consumed through `from()`/`includes:` + /// which cannot carry it). The record — not the evaluation host's + /// console (shimmed to a no-op) — is the witness channel; hosts surface + /// each entry as a diagnostic keyed by its `code`. `None` when the + /// record carries no witnesses. + pub vocabulary_witnesses: Option, /// Canonical absolute paths of every module evaluated for this system — /// the entry plus its transitive graph, excluding runtime stubs (which /// have no path). Sorted. Plugins use this as the geological-reset @@ -1559,18 +1562,24 @@ fn extract_system_config<'js>( // Keyframe collections (vocabulary-registration): a sealed system's // registration record is the ONLY source — an exported-but-unregistered - // collection does not carry. A system WITHOUT the record accessor falls - // back to the export scan so un-migrated systems keep loading; the - // migration increment deletes that fallback and makes record absence - // the loud version-skew error. - let has_record = system_obj + // collection does not carry, and a system WITHOUT the record accessor + // fails the load loud (unsealed, or built by an older + // @animus-ui/system) rather than loading with silently empty + // collections. + if system_obj .get::<_, Function>("getVocabularyRecord") - .is_ok(); - let (keyframes_blocks, vocabulary_collisions) = if has_record { - extract_vocabulary_record(ctx, &system_obj)? - } else { - (extract_keyframes_blocks(namespace), None) - }; + .is_err() + { + return Err( + "system carries no vocabulary registration record — it is unsealed or was \ + built by an older @animus-ui/system. Register collections between build() \ + and seal() and export the sealed instance; loading with silently empty \ + collections is refused" + .to_string(), + ); + } + let (keyframes_blocks, vocabulary_witnesses) = + extract_vocabulary_record(ctx, &system_obj)?; Ok(SystemConfig { prop_config, @@ -1585,7 +1594,7 @@ fn extract_system_config<'js>( transform_sources, global_style_blocks, keyframes_blocks, - vocabulary_collisions, + vocabulary_witnesses, // Populated by load_system_module from the resolved module graph; // execute_bundle only sees the assembled bundle text. dependencies: Vec::new(), @@ -1612,13 +1621,14 @@ fn find_exports_with_method<'js>( } /// Read the sealed system's vocabulary record (vocabulary-registration). -/// Returns `(keyframes_blocks, vocabulary_collisions)`: the record's +/// Returns `(keyframes_blocks, vocabulary_witnesses)`: the record's /// declaration-ordered `keyframes` array becomes the unchanged /// `{ exportName: { keyName: { name, frames } } }` wire (insertion order /// preserved end to end — `Object.fromEntries` + `JSON.stringify` in the /// evaluation context, `preserve_order` on the Rust side), and its -/// `collisions` entries carry verbatim as the host-facing witness. An -/// incompatible version marker fails the load loud. +/// `collisions` + `legacyVerbs` entries carry verbatim as one coded +/// host-facing witness array. An incompatible version marker fails the +/// load loud. fn extract_vocabulary_record<'js>( ctx: &rquickjs::Ctx<'js>, system_obj: &Object<'js>, @@ -1633,10 +1643,11 @@ fn extract_vocabulary_record<'js>( } const keyframes = Array.isArray(record.keyframes) ? record.keyframes : []; const collisions = Array.isArray(record.collisions) ? record.collisions : []; + const legacyVerbs = Array.isArray(record.legacyVerbs) ? record.legacyVerbs : []; return JSON.stringify({ keyframeCount: keyframes.length, keyframes: Object.fromEntries(keyframes.map((entry) => [entry.name, entry.frames])), - collisions, + witnesses: collisions.concat(legacyVerbs), }); })()"#; let _ = ctx.globals().set("__sys_ref", system_obj.clone()); @@ -1671,13 +1682,13 @@ fn extract_vocabulary_record<'js>( .get("keyframes") .map(|v| serde_json::to_string(v).unwrap_or_default()) }; - let vocabulary_collisions = match parsed.get("collisions").and_then(|v| v.as_array()) { + let vocabulary_witnesses = match parsed.get("witnesses").and_then(|v| v.as_array()) { Some(list) if !list.is_empty() => { Some(serde_json::to_string(list).unwrap_or_default()) } _ => None, }; - Ok((keyframes_blocks, vocabulary_collisions)) + Ok((keyframes_blocks, vocabulary_witnesses)) } /// List all export keys from a module namespace. @@ -1728,89 +1739,10 @@ fn extract_global_style_blocks(namespace: &Object<'_>) -> Option { } } -/// Extract Keyframes collection exports (objects with `__brand === 'Keyframes'`). -/// -/// Each collection carries `__frames: { keyName: { name, frames } }` — the raw -/// payload the extractor needs to both emit `@keyframes ` blocks and -/// resolve `motion.ember`-style member-expression references in component -/// styles. The returned JSON preserves this nested shape: `{ exportName: -/// { keyName: { name, frames } } }`, keyed by the collection's export name. -fn extract_keyframes_blocks(namespace: &Object<'_>) -> Option { - let keys = list_export_keys(namespace); - let mut blocks: HashMap = HashMap::new(); - let ctx = namespace.ctx().clone(); - - for key in &keys { - if let Ok(obj) = namespace.get::<_, Object>(key.as_str()) { - if let Ok(brand) = obj.get::<_, String>("__brand") { - if brand == "Keyframes" { - // Serialize the full `__frames` record via JSON.stringify. - // Yields `{ keyName: { name, frames } }` per collection. - let script = - format!("JSON.stringify(globalThis.__ns_ref[\"{}\"].__frames)", key); - let _ = ctx.globals().set("__ns_ref", namespace.clone()); - if let Ok(json_str) = ctx.eval::(script.as_bytes()) { - let _ = ctx.globals().remove("__ns_ref"); - if let Ok(parsed) = serde_json::from_str::(&json_str) { - blocks.insert(key.clone(), parsed); - } - } else { - let _ = ctx.globals().remove("__ns_ref"); - } - } - } - } - } - - if blocks.is_empty() { - None - } else { - Some(serde_json::to_string(&blocks).unwrap_or_default()) - } -} - // --------------------------------------------------------------------------- // 6. Public entry point // --------------------------------------------------------------------------- -/// Scan a module entry for named `Keyframes` collection exports WITHOUT -/// extracting any system configuration. External package entries contribute -/// keyframes only — the consumer's configured system stays the singular -/// authority for themes, scales, selectors, conditions, and props -/// (openspec: external-package-file-discovery carve-out). Same -/// read → strip → resolve → bundle → eval pipeline as a system load; the -/// namespace walk reads nothing but `__brand === 'Keyframes'` exports. -/// Returns the `{ exportName: { keyName: { name, frames } } }` JSON shape. -pub fn scan_keyframes_exports( - entry_path: &str, - root_dir: &str, -) -> Result, String> { - let (specifier_map, source_map, stub_exports) = resolve_all_deps(entry_path, root_dir)?; - - let entry_canon = fs::canonicalize(entry_path) - .map_err(|e| format!("failed to canonicalize '{}': {}", entry_path, e))? - .to_string_lossy() - .to_string(); - - let (bundle, layout) = build_bundle(&specifier_map, &source_map, &stub_exports, &entry_canon)?; - - let runtime = Runtime::new().map_err(|e| format!("rquickjs Runtime::new failed: {}", e))?; - let context = - Context::full(&runtime).map_err(|e| format!("rquickjs Context::full failed: {}", e))?; - - context.with(|ctx| { - ctx.eval::<(), _>(bundle.as_bytes()) - .map_err(|e| describe_eval_failure(&ctx, &layout, &e))?; - - let access_script = format!("__modules['{}']", js_quoted(&entry_canon)); - let namespace: Object = ctx - .eval(access_script.as_bytes()) - .map_err(|e| format!("failed to access entry module exports: {}", e))?; - - Ok(extract_keyframes_blocks(&namespace)) - }) -} - /// Load a system module and return its serialized configuration. /// /// Pipeline: read → OXC strip types → resolve deps → bundle → rquickjs eval → extract config. @@ -2267,61 +2199,6 @@ export const ds = tokens; fs::write(path, contents).expect("write fixture"); } - #[test] - fn scan_keyframes_exports_reads_only_branded_collections() { - let dir = scratch_dir("kf-scan"); - let entry = dir.join("index.ts"); - write_fixture( - &entry, - "export const motion = { __brand: 'Keyframes', __frames: { pulse: { name: 'animus-kf-testhash', frames: { from: { opacity: 0.4 }, to: { opacity: 1 } } } } };\n\ - export const notKeyframes = { __brand: 'Other', __frames: {} };\n\ - export const plain = 42;\n", - ); - - let result = scan_keyframes_exports(&entry.to_string_lossy(), &dir.to_string_lossy()); - let _ = fs::remove_dir_all(&dir); - - let json = result - .expect("scan must succeed") - .expect("a Keyframes export must be discovered"); - let parsed: serde_json::Value = serde_json::from_str(&json).expect("valid JSON"); - let obj = parsed.as_object().unwrap(); - assert_eq!(obj.len(), 1, "{json}"); - assert_eq!( - parsed["motion"]["pulse"]["name"], - serde_json::Value::String("animus-kf-testhash".into()) - ); - assert!(parsed["motion"]["pulse"]["frames"]["from"].is_object()); - } - - #[test] - fn scan_keyframes_exports_degrades_to_error_not_panic() { - let dir = scratch_dir("kf-scan-broken"); - let entry = dir.join("index.ts"); - write_fixture(&entry, "throw new Error('entry refuses to evaluate');\n"); - - let result = scan_keyframes_exports(&entry.to_string_lossy(), &dir.to_string_lossy()); - let _ = fs::remove_dir_all(&dir); - - let error = result.expect_err("a throwing entry must surface as Err"); - assert!( - error.contains("refuses to evaluate") || error.contains("eval"), - "error must describe the evaluation failure: {error}" - ); - } - - #[test] - fn scan_keyframes_exports_none_when_no_collections() { - let dir = scratch_dir("kf-scan-empty"); - let entry = dir.join("index.ts"); - write_fixture(&entry, "export const plain = { value: 1 };\n"); - - let result = scan_keyframes_exports(&entry.to_string_lossy(), &dir.to_string_lossy()); - let _ = fs::remove_dir_all(&dir); - - assert_eq!(result.expect("scan must succeed"), None); - } - // ── vocabulary-registration: seam-1 record consumption ────────────────── const FIXTURE_THEME: &str = "export const theme = { serialize: () => ({\n\ @@ -2433,8 +2310,8 @@ export const ds = tokens; write_fixture( &entry, &format!( - "export const ds = {{ toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}) }};\n\ - export const dsTwo = {{ toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}) }};\n\ + "export const ds = {{ toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}), getVocabularyRecord: () => ({{ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }}) }};\n\ + export const dsTwo = {{ toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}), getVocabularyRecord: () => ({{ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }}) }};\n\ {FIXTURE_THEME}" ), ); @@ -2456,7 +2333,7 @@ export const ds = tokens; write_fixture( &entry, &format!( - "export const ds = {{ toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}) }};\n\ + "export const ds = {{ toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}), getVocabularyRecord: () => ({{ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }}) }};\n\ export const dsAlias = ds;\n\ {FIXTURE_THEME}" ), @@ -2491,10 +2368,11 @@ export const ds = tokens; } #[test] - fn record_collisions_carry_to_the_config() { + fn record_witnesses_carry_to_the_config() { // The record, not console, is the witness channel (the evaluation - // host shims console to a no-op). - let dir = scratch_dir("vocab-collisions"); + // host shims console to a no-op): collision AND legacy-verb entries + // arrive as one coded array. + let dir = scratch_dir("vocab-witnesses"); let entry = dir.join("entry.ts"); write_fixture( &entry, @@ -2502,6 +2380,9 @@ export const ds = tokens; "{ version: 1, keyframes: [], globalStyles: [], collisions: [\n\ { code: 'animus.vocabulary.collision', name: 'motion',\n\ winner: 'local registration #1', loser: 'extended source #1' },\n\ + ], legacyVerbs: [\n\ + { code: 'animus.vocabulary.legacy-verb', verb: 'includes',\n\ + names: ['kitMotion'] },\n\ ] }", ), ); @@ -2510,31 +2391,73 @@ export const ds = tokens; let _ = fs::remove_dir_all(&dir); let config = result.expect("sealed system must load"); - let collisions = config - .vocabulary_collisions - .expect("collision entries must carry"); + let witnesses = config + .vocabulary_witnesses + .expect("witness entries must carry"); assert!( - collisions.contains("animus.vocabulary.collision") - && collisions.contains("motion") - && collisions.contains("extended source #1"), - "collision witness must survive verbatim: {collisions}" + witnesses.contains("animus.vocabulary.collision") + && witnesses.contains("motion") + && witnesses.contains("extended source #1") + && witnesses.contains("animus.vocabulary.legacy-verb") + && witnesses.contains("kitMotion"), + "witness entries must survive verbatim: {witnesses}" + ); + } + + #[test] + fn undeclared_root_barrel_is_never_evaluated() { + // Host-seam deletion witness (vocabulary-registration hard cut): the + // consumer's declared definition graph is the ONLY thing evaluated. + // A kit's root barrel that THROWS at module top level (standing in + // for a framework graph) must never run when the consumer imports + // only the definition subpath — discovery-by-scan is gone and + // nothing else walks package entries. + let dir = scratch_dir("vocab-throwing-barrel"); + let kit = dir.join("node_modules").join("@x").join("kit"); + write_fixture( + &kit.join("package.json"), + r#"{"name":"@x/kit","exports":{".":"./index.js","./definition":"./definition.js"}}"#, + ); + write_fixture( + &kit.join("index.js"), + "throw new Error('root barrel must not be evaluated');\n", + ); + write_fixture( + &kit.join("definition.js"), + "export const kitTokens = { fromKit: true };\n", + ); + let entry = dir.join("entry.ts"); + write_fixture( + &entry, + &format!( + "import {{ kitTokens }} from '@x/kit/definition';\n\ + export const marker = kitTokens;\n\ + {}", + sealed_system_fixture( + "{ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }", + ) + ), ); + + let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); + let _ = fs::remove_dir_all(&dir); + + result.expect("the load must succeed without touching the root barrel"); } #[test] - fn recordless_system_falls_back_to_export_scan_until_migration() { - // STAGING PIN (design Ledger DEF-11 class; deleted at the migration - // increment): a system without a vocabulary record keeps export-scan - // discovery so un-migrated fixtures stay green. The migration - // increment replaces this with the loud version-skew error — this - // test must be DELETED in the same diff. - let dir = scratch_dir("vocab-fallback"); + fn recordless_system_fails_the_load() { + // rust-system-loader §"Registration-record version skew fails the + // load", the absence half (the DEF-11-class hard cut): a system + // without the record accessor is unsealed or built by an older + // @animus-ui/system — refuse to load with silently empty + // collections. + let dir = scratch_dir("vocab-recordless"); let entry = dir.join("entry.ts"); write_fixture( &entry, &format!( "export const ds = {{ toConfig: () => ({{ propConfig: '{{}}', groupRegistry: '{{}}' }}) }};\n\ - export const motion = {{ __brand: 'Keyframes', __frames: {{ spin: {{ name: 'animus-kf-ddd', frames: {{}} }} }} }};\n\ {FIXTURE_THEME}" ), ); @@ -2542,11 +2465,11 @@ export const ds = tokens; let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); let _ = fs::remove_dir_all(&dir); - let config = result.expect("recordless system must still load"); - let blocks = config - .keyframes_blocks - .expect("legacy export scan still discovers"); - assert!(blocks.contains("animus-kf-ddd")); + let error = result.expect_err("a recordless system must fail the load"); + assert!( + error.contains("unsealed") && error.contains("seal()"), + "error must name the sealing requirement: {error}" + ); } #[test] @@ -2585,7 +2508,7 @@ export const ds = tokens; &entry, "import { makeTheme } from './theme';\n\ export const theme = makeTheme();\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); write_fixture( &dir.join("theme.ts"), @@ -2643,7 +2566,7 @@ export const ds = tokens; contextualVarsJson: '{}',\n\ }),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); write_fixture( &dir.join("kit/index.ts"), @@ -2699,12 +2622,12 @@ export const ds = tokens; contextualVarsJson: '{}',\n\ }),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); write_fixture( &dir.join("kit/index.ts"), "export const kit = {\n\ - system: { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) },\n\ + system: { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) },\n\ tokens: {\n\ colors: { externalAccent: '#f0f' },\n\ manifest: { variableMap: { 'colors.externalAccent': '--color-external-accent' } },\n\ @@ -2754,12 +2677,12 @@ export const ds = tokens; contextualVarsJson: '{}',\n\ }),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); write_fixture( &dir.join("kit/index.ts"), "export const kit = {\n\ - system: { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) },\n\ + system: { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) },\n\ theme: {\n\ colors: { externalAccent: '#f0f' },\n\ manifest: { variableMap: { 'colors.externalAccent': '--color-external-accent' } },\n\ @@ -2817,7 +2740,7 @@ export const ds = tokens; contextualVarsJson: '{}',\n\ }),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); @@ -2847,7 +2770,7 @@ export const ds = tokens; contextualVarsJson: '{}',\n\ }),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); @@ -2878,7 +2801,7 @@ export const ds = tokens; }),\n\ };\n\ export const tokens = { color: 'red' };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); @@ -2908,7 +2831,7 @@ export const ds = tokens; contextualVarsJson: '{}',\n\ }),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); @@ -2937,7 +2860,7 @@ export const ds = tokens; contextualVarsJson: '{}',\n\ }),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); @@ -2965,7 +2888,7 @@ export const ds = tokens; addScale: () => ({}),\n\ addColors: () => ({}),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); @@ -3000,7 +2923,7 @@ export const ds = tokens; contextualVarsJson: '{}',\n\ }),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); @@ -3025,7 +2948,7 @@ export const ds = tokens; build: () => ({}),\n\ addScale: () => ({}),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); @@ -3048,7 +2971,7 @@ export const ds = tokens; write_fixture( &entry, "export const theme = { color: 'red' };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); @@ -3080,7 +3003,7 @@ export const ds = tokens; }),\n\ };\n\ export const tokens = theme;\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); @@ -3114,7 +3037,7 @@ export const ds = tokens; contextualVarsJson: '{}',\n\ }),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); diff --git a/packages/extract/pipeline/discover-packages.ts b/packages/extract/pipeline/discover-packages.ts index 4a6a3516..fbd672aa 100644 --- a/packages/extract/pipeline/discover-packages.ts +++ b/packages/extract/pipeline/discover-packages.ts @@ -180,12 +180,6 @@ export interface CollectedExternalPackages { packageMap: Record; /** specifier → absolute src/index.ts path, only for packages with one. */ sourceEntries: Map; - /** specifier → absolute entry to scan for branded `Keyframes` collections — - * one per admitted specifier, whatever the package's shape: the redirected - * source entry when src/ serves it, the resolved (dist) entry otherwise. - * Keyed separately from `sourceEntries` because that map doubles as the - * hosts' module-resolution redirect and stays src-only by contract. */ - keyframesScanEntries: Map; /** Absolute directories for bundler loader allowlisting (src/ or dist entry dir). */ packageDirs: string[]; /** Absolute package dir → EVERY declared specifier that claimed it, in @@ -271,7 +265,6 @@ export async function collectExternalPackageSources(opts: { const pushed = new Set(); const packageMap: Record = {}; const sourceEntries = new Map(); - const keyframesScanEntries = new Map(); const packageDirs: string[] = []; const dirOwnerSets: Record = {}; const dirExtensions: Record = {}; @@ -330,7 +323,6 @@ export async function collectExternalPackageSources(opts: { } else { packageMap[specifier] = relative(rootDir, absEntry); } - keyframesScanEntries.set(specifier, srcEntry ?? absEntry); // A kit declared at a subpath is routinely imported at its package // ROOT by app code (`import { Card } from '@scope/kit'`), and a root @@ -351,10 +343,6 @@ export async function collectExternalPackageSources(opts: { if (rootEntry) { packageMap[packageName] = relative(rootDir, rootEntry); sourceEntries.set(packageName, rootEntry); - // The root module routinely carries the package's `Keyframes` - // exports (a definition subpath usually doesn't re-export them) - // — the alias scans alongside the declared entry. - keyframesScanEntries.set(packageName, rootEntry); } } } @@ -399,7 +387,6 @@ export async function collectExternalPackageSources(opts: { onPackageResolved?.(specifier, outputDir); const relPath = relative(rootDir, absEntry); packageMap[specifier] = relPath; - keyframesScanEntries.set(specifier, absEntry); const outputExtensions = new Set(extensionsSet); outputExtensions.add(extname(absEntry)); @@ -453,7 +440,6 @@ export async function collectExternalPackageSources(opts: { entries, packageMap, sourceEntries, - keyframesScanEntries, packageDirs, dirOwnerSets, dirExtensions, @@ -516,11 +502,6 @@ export function excludeCollectedPackages( if (targetRejected(specifier, absEntry)) continue; sourceEntries.set(specifier, absEntry); } - const keyframesScanEntries = new Map(); - for (const [specifier, absEntry] of collected.keyframesScanEntries) { - if (targetRejected(specifier, absEntry)) continue; - keyframesScanEntries.set(specifier, absEntry); - } const dirOwnerSets: Record = {}; for (const [dir, specs] of Object.entries(collected.dirOwnerSets)) { const kept = specs.filter((s) => !rejectedSpecifiers.has(s)); @@ -544,7 +525,6 @@ export function excludeCollectedPackages( ), packageMap, sourceEntries, - keyframesScanEntries, packageDirs: collected.packageDirs.filter( (dir) => !rejectedDirs.includes(dir) ), diff --git a/packages/extract/pipeline/engine-adapter.ts b/packages/extract/pipeline/engine-adapter.ts index f47628ee..2054e03a 100644 --- a/packages/extract/pipeline/engine-adapter.ts +++ b/packages/extract/pipeline/engine-adapter.ts @@ -48,8 +48,6 @@ export interface EngineApi { // it returns, so the surface stays loose here. // eslint-disable-next-line @typescript-eslint/no-explicit-any loadSystemModule: (...args: unknown[]) => any; - /** Keyframes-only scan of an external package entry. */ - scanKeyframesExports: (entryPath: string, rootDir: string) => string | null; /** Parse-only native fact extraction used to prepare adapted sources. */ extractFacts?: (filesJson: string) => string; analyzeProject: ( @@ -162,7 +160,7 @@ export function createV2EngineApi(deps: V2EngineAdapterDeps): () => EngineApi { if (!isV2()) { // SAFETY: the v1 leg IS the native module — `EngineApi` was derived from // the surface `animus-extract-v2`'s NAPI entry points already export - // (loadSystemModule / scanKeyframesExports / analyzeProject / + // (loadSystemModule / analyzeProject / // transformFile / clearAnalysisCache), which is why the v2 adapter below // can mimic it. The module's own generated `index.d.ts` is authoritative // and `loadNativeEngine` is declared `any`, so this names the surface the @@ -173,10 +171,6 @@ export function createV2EngineApi(deps: V2EngineAdapterDeps): () => EngineApi { return { loadSystemModule: (...args: unknown[]) => native.loadSystemModule(...args), - // Keyframes-only scan of an external package entry; returns the - // collections JSON or null, throws on evaluation failure. - scanKeyframesExports: (entryPath: string, rootDir: string) => - native.scanKeyframesExports(entryPath, rootDir) ?? null, extractFacts: (filesJson) => native.extractFacts(filesJson), analyzeProject: ( filesJsonRaw, diff --git a/packages/extract/pipeline/external-keyframes.ts b/packages/extract/pipeline/external-keyframes.ts deleted file mode 100644 index 58ac7969..00000000 --- a/packages/extract/pipeline/external-keyframes.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { parseInternalWire } from './internal-wire'; - -import type { ManifestDiagnostic } from './manifest-diagnostics'; - -/** Stable codes for external keyframes discovery. */ -export const KEYFRAMES_EXTERNAL_ENTRY_FAILED = - 'animus.keyframes.external-entry-failed'; -export const KEYFRAMES_EXPORT_COLLISION = 'animus.keyframes.export-collision'; - -/** - * The scanned-keyframes wire: export name → that export's collection. The - * system loader's `extract_keyframes_blocks` produces this shape on BOTH sides - * of the merge below — for the consumer system (`SystemConfig.keyframesJson`) - * and for every external entry (`scanKeyframesExports`) — so one declaration - * covers both, and a collection that survives the merge is re-serialized - * unchanged. - */ -interface KeyframesCollections { - [exportName: string]: KeyframesCollection; -} - -/** One branded `Keyframes` export, flattened to its `__frames` record. */ -interface KeyframesCollection { - [keyframeName: string]: KeyframeBlock; -} - -/** One keyframe: the emitted `@keyframes` identity plus its authored steps. */ -interface KeyframeBlock { - /** Content-hashed `@keyframes` name the emitter declares and references. */ - name: string; - frames: KeyframeSteps; -} - -/** Step selector (`from`, `to`, `NN%`) → that step's CSS declarations. */ -interface KeyframeSteps { - [step: string]: KeyframeDeclarations; -} - -/** CSS property → value as authored: raw CSS, a number, or a `{scale.key}` - * token reference the engine resolves at emission. */ -interface KeyframeDeclarations { - [property: string]: string | number; -} - -export interface ExternalKeyframesMerge { - /** Consumer collections merged with every discovered external collection - * (consumer wins on name collisions); `null` when nothing exists. */ - keyframesJson: string | null; - /** Warn-severity diagnostics for failed entries and name collisions — - * surfaced through the shared manifest-diagnostics policy point. */ - diagnostics: ManifestDiagnostic[]; -} - -/** - * Merge `Keyframes` collections discovered from external package entries into - * the consumer system's collections. Keyframes are the sole carve-out from - * the consumer-config singular-authority rule: nothing but branded - * collections is read from an entry, an entry that fails to evaluate - * degrades to a coded diagnostic naming it, and a collection whose export - * name collides keeps the earlier (consumer-first) collection with a coded - * diagnostic instead of silently reordering names. - */ -export function mergeExternalKeyframes( - scan: (entryPath: string, rootDir: string) => string | null, - consumerKeyframesJson: string | null | undefined, - externalEntries: Iterable, - rootDir: string -): ExternalKeyframesMerge { - const merged: KeyframesCollections = {}; - if (consumerKeyframesJson) { - try { - Object.assign(merged, JSON.parse(consumerKeyframesJson)); - } catch { - // Delegation, not a swallow: the consumer payload's owner is the system - // loader that produced it (`SystemConfig.keyframesJson`), and the caller - // hands the same field straight to the engine. Returning the ORIGINAL - // string leaves the malformed bytes to fail at that owner instead of - // substituting a value this merge invented. - return { keyframesJson: consumerKeyframesJson, diagnostics: [] }; - } - } - const diagnostics: ManifestDiagnostic[] = []; - const seenEntries = new Set(); - - for (const entryPath of externalEntries) { - if (seenEntries.has(entryPath)) continue; - seenEntries.add(entryPath); - - let scanned: string | null; - try { - scanned = scan(entryPath, rootDir); - } catch (error) { - diagnostics.push({ - file: entryPath, - component: 'keyframes', - kind: 'warn', - message: - `external package entry failed the keyframes scan — any collections it exports are invisible to extraction: ${String(error)}. ` + - `Each admitted entry is scanned on its own: the package specifier your system entry declares, plus the package root module when that declaration was a subpath. ` + - `Every scan evaluates that entry's whole module graph framework-free, so a root barrel re-exporting framework components can fail here while the definition entry scans clean. ` + - `Export keyframe collections (directly or as a named re-export) from an entry that evaluates framework-free, and avoid \`export *\` of framework packages there ` + - `(${KEYFRAMES_EXTERNAL_ENTRY_FAILED})`, - code: KEYFRAMES_EXTERNAL_ENTRY_FAILED, - severity: 'warn', - }); - continue; - } - if (!scanned) continue; - - // The scan RESULT is animus's own wire — `scanKeyframesExports` is a NAPI - // entry point and the engine serializes it. An entry that fails to - // EVALUATE is an external-package failure and degrades to the coded - // diagnostic above; an entry that evaluates and then yields unparseable - // engine output is an engine bug, and `continue` would drop its - // collections indistinguishably from "this package ships no keyframes". - const collections = parseInternalWire( - scanned, - `keyframes collections scanned from '${entryPath}' ` + - "(the engine's scanKeyframesExports)" - ); - for (const [exportName, collection] of Object.entries(collections)) { - const existing = merged[exportName]; - if (existing !== undefined) { - if (JSON.stringify(existing) !== JSON.stringify(collection)) { - diagnostics.push({ - file: entryPath, - component: exportName, - kind: 'warn', - message: `keyframes collection export '${exportName}' collides with an earlier collection of the same name — the earlier one wins (${KEYFRAMES_EXPORT_COLLISION})`, - code: KEYFRAMES_EXPORT_COLLISION, - severity: 'warn', - }); - } - continue; - } - merged[exportName] = collection; - } - } - - return { - keyframesJson: - Object.keys(merged).length > 0 ? JSON.stringify(merged) : null, - diagnostics, - }; -} diff --git a/packages/extract/pipeline/index.ts b/packages/extract/pipeline/index.ts index 78f8a597..69524896 100644 --- a/packages/extract/pipeline/index.ts +++ b/packages/extract/pipeline/index.ts @@ -26,12 +26,6 @@ export { RETIRED_ENGINE_MESSAGE, } from './engine-retirement'; export { contentHash } from './content-hash'; -export { - KEYFRAMES_EXPORT_COLLISION, - KEYFRAMES_EXTERNAL_ENTRY_FAILED, - mergeExternalKeyframes, -} from './external-keyframes'; -export type { ExternalKeyframesMerge } from './external-keyframes'; export type { AnimusCoreOptions, AnimusMode, @@ -164,6 +158,9 @@ export { isUnresolvedParentDrop, surfaceManifestDiagnostics, unresolvedParentName, + VOCABULARY_COLLISION, + VOCABULARY_LEGACY_VERB, + vocabularyWitnessDiagnostics, } from './manifest-diagnostics'; export type { DefaultExtension, PreprocessMdxResult } from './mdx-preprocessor'; export { diff --git a/packages/extract/pipeline/manifest-diagnostics.ts b/packages/extract/pipeline/manifest-diagnostics.ts index d8eec919..cf214a0d 100644 --- a/packages/extract/pipeline/manifest-diagnostics.ts +++ b/packages/extract/pipeline/manifest-diagnostics.ts @@ -122,6 +122,76 @@ export function collectSelectorAliasDiagnostics( return diagnostics; } +/** Stable code for a vocabulary-record collision witness (mirrors the + * entry code minted by @animus-ui/system's merge). */ +export const VOCABULARY_COLLISION = 'animus.vocabulary.collision'; + +/** Stable code for a legacy-verb carriage refusal (a sealed kit with + * registered vocabulary consumed through `from()`/`includes:`). */ +export const VOCABULARY_LEGACY_VERB = 'animus.vocabulary.legacy-verb'; + +/** + * Map the sealed system's vocabulary witness entries + * (vocabulary-registration: collision + legacy-verb records, carried on the + * registration record because the loader's evaluation host shims `console` + * to a no-op) into coded diagnostics for the shared surfacing policy point. + * ONE mapper for every host — the witness text must not fork per plugin. + */ +export function vocabularyWitnessDiagnostics( + vocabularyWitnessesJson: string | null | undefined +): ManifestDiagnostic[] { + if (!vocabularyWitnessesJson) return []; + const entries = parseInternalWire< + Array<{ + code?: string; + name?: string; + winner?: string; + loser?: string; + verb?: string; + source?: string; + names?: string[]; + }> + >( + vocabularyWitnessesJson, + "vocabularyWitnessesJson (the sealed system's vocabulary witness record)" + ); + const diagnostics: ManifestDiagnostic[] = []; + for (const entry of entries) { + if (entry.code === VOCABULARY_COLLISION) { + diagnostics.push({ + file: 'system', + component: entry.name ?? 'keyframes', + kind: 'warn', + message: `keyframes vocabulary "${entry.name}" is registered by both ${entry.loser} and ${entry.winner} — ${entry.winner} wins; rename one collection (${entry.code})`, + code: entry.code, + severity: 'warn', + }); + } else if (entry.code === VOCABULARY_LEGACY_VERB) { + diagnostics.push({ + file: 'system', + component: 'keyframes', + kind: 'warn', + message: `a sealed system (${entry.source ?? `'${entry.verb}' source`}) with registered vocabulary [${(entry.names ?? []).join(', ')}] was consumed through the deprecated '${entry.verb}' verb, which cannot carry it — those collections do NOT reach this consumer; use createSystem().extend(source) (${entry.code})`, + code: entry.code, + severity: 'warn', + }); + } else { + // Fail closed (arch-fail-closed-diagnostics): a witness entry this + // host does not recognize still surfaces, carrying its own code — a + // newer @animus-ui/system's witness kind must never vanish silently. + diagnostics.push({ + file: 'system', + component: 'vocabulary', + kind: 'warn', + message: `unrecognized vocabulary witness entry ${JSON.stringify(entry)} — a newer @animus-ui/system may have recorded a witness kind this host predates${entry.code ? ` (${entry.code})` : ''}`, + code: entry.code, + severity: 'warn', + }); + } + } + return diagnostics; +} + /** * Surface extraction-manifest diagnostics through a plugin's warn channel. * diff --git a/packages/extract/pipeline/system-config.ts b/packages/extract/pipeline/system-config.ts index 7aaa8188..1f8d02be 100644 --- a/packages/extract/pipeline/system-config.ts +++ b/packages/extract/pipeline/system-config.ts @@ -30,13 +30,13 @@ export interface SystemConfig { transformSourcesJson?: string | null; globalStyleBlocksJson: string | null; keyframesJson: string | null; - /** Vocabulary collision witnesses from the sealed system's registration - * record (JSON array of `{ code, name, winner, loser }`, stable code - * `animus.vocabulary.collision`). The record — not the evaluation host's - * console, which is shimmed to a no-op — is the witness channel; hosts - * surface these as diagnostics. Optional so pre-load + /** Vocabulary witness entries from the sealed system's registration + * record — one JSON array of coded entries (collisions and legacy-verb + * carriage refusals). The record — not the evaluation host's console, + * which is shimmed to a no-op — is the witness channel; hosts surface + * each entry via `vocabularyWitnessDiagnostics`. Optional so pre-load * `emptySystemConfig()` defaults need not restate it. */ - vocabularyCollisionsJson?: string | null; + vocabularyWitnessesJson?: string | null; /** Canonical absolute paths of every module the loader evaluated for this * system (sorted; entry included, runtime stubs excluded). Plugins use it * as the geological-reset membership set. Optional so pre-load @@ -100,7 +100,7 @@ export function loadSystemConfig( transformSourcesJson: config.transformSources || null, globalStyleBlocksJson: config.globalStyleBlocks || null, keyframesJson: config.keyframesBlocks || null, - vocabularyCollisionsJson: config.vocabularyCollisions || null, + vocabularyWitnessesJson: config.vocabularyWitnesses || null, dependencies: config.dependencies ?? [], sourceThemeManifestsJson: config.sourceThemeManifests || null, }; diff --git a/packages/extract/session/extraction-session.ts b/packages/extract/session/extraction-session.ts index c49affe7..3383781c 100644 --- a/packages/extract/session/extraction-session.ts +++ b/packages/extract/session/extraction-session.ts @@ -34,8 +34,8 @@ import { isExcludedPackageRelativePath, isPathWithinRoot, loadSystemConfig, - mergeExternalKeyframes, postProcessCss, + vocabularyWitnessDiagnostics, projectExternalFileOwners, resolveAssetFile, resolveLightningTargets, @@ -323,8 +323,9 @@ export class ExtractionSession { private readonly options: SessionOptions; private readonly staticCssJson: string | null; private system: SystemConfig | null = null; - /** Discovery-time keyframes diagnostics awaiting the shared surfacing pass. */ - private externalKeyframesDiagnostics: ManifestDiagnostic[] = []; + /** Vocabulary witness diagnostics from the sealed system's registration + * record (vocabulary-registration), awaiting the shared surfacing pass. */ + private systemVocabularyDiagnostics: ManifestDiagnostic[] = []; /** Full package-resolution map from the last full pipeline — replayed by * incremental passes (sourceEntries alone omits dist-resolved packages). */ private lastPackageMap: Record = {}; @@ -1026,6 +1027,12 @@ export class ExtractionSession { rootDir, prefix: this.options.prefix, }); + // The sealed record is the witness channel (the loader's evaluation + // host shims console): map its coded entries for the shared surfacing + // policy point. + this.systemVocabularyDiagnostics = vocabularyWitnessDiagnostics( + this.system.vocabularyWitnessesJson + ); // Asset specifiers resolve against the system just loaded — drop the // per-specifier copy memo so a changed reference re-reads and re-hashes. this.assetCopyCache.clear(); @@ -1244,32 +1251,6 @@ export class ExtractionSession { this.externalPackageDirs = admitted.packageDirs; - // Keyframes-only carve-out: external package entries - // contribute their `Keyframes` collections; consumer system authority - // is untouched (vite-plugin parity — see PluginContext.applyExternalKeyframes). - // Scan entries cover EVERY admitted package (src entry or dist entry) — - // deriving from sourceEntries would silently skip dist-only packages. - if (this.system && admitted.keyframesScanEntries.size > 0) { - const api = engineApi(); - const merge = mergeExternalKeyframes( - (entry, root) => api.scanKeyframesExports(entry, root), - this.system.keyframesJson, - admitted.keyframesScanEntries.values(), - this.rootDir! - ); - this.system.keyframesJson = merge.keyframesJson; - // Surfacing stays with the single shared policy point inside - // runProjectAnalysis (this file performs no local surfacing) — - // stash for analyzeAndEmit to carry. - this.externalKeyframesDiagnostics = merge.diagnostics; - } else { - // No admitted scan entries: the freshly-loaded system already carries - // exactly its consumer collections, and diagnostics recorded for - // packages no longer declared must not ride every later analysis - // (vite-plugin parity — applyExternalKeyframes' reset arm). - this.externalKeyframesDiagnostics = []; - } - bt.packageResolve = this.elapsed(t); // Step 5+: hand off to the shared analysis + emit core. Production pass @@ -1351,7 +1332,7 @@ export class ExtractionSession { externalFileOwners: this.externalFileOwners, externalSourceEntries: this.externalSourceEntries, externalPackageDirs: this.externalPackageDirs, - externalKeyframesDiagnostics: this.externalKeyframesDiagnostics, + systemVocabularyDiagnostics: this.systemVocabularyDiagnostics, }; } @@ -1700,7 +1681,7 @@ export class ExtractionSession { ...analysisOptions, warn: (message) => this.warn(message), strict: this.options.strict, - extraDiagnostics: this.externalKeyframesDiagnostics, + extraDiagnostics: this.systemVocabularyDiagnostics, }); // Error-diagnostic escalation (extraction-diagnostics §Error diagnostics diff --git a/packages/extract/tests/collect-external-packages.test.ts b/packages/extract/tests/collect-external-packages.test.ts index 0d3e16ba..a60f6d91 100644 --- a/packages/extract/tests/collect-external-packages.test.ts +++ b/packages/extract/tests/collect-external-packages.test.ts @@ -203,47 +203,13 @@ describe('collectExternalPackageSources', () => { expect(result.packageDirs).toEqual([join(pkg, 'dist')]); }); - test('tracks a keyframes scan entry for every admitted package (src or dist)', async () => { - const root = makeRoot(); - const srcPkg = makePackage(join(root, 'packages', 'ds'), { - 'src/index.ts': 'export const ds = 1;', - }); - const distPkg = makePackage( - join(root, 'node_modules', '@x', 'compiled-ds'), - { 'dist/definition.mjs': 'export const system = 1;' } - ); - const subpathPkg = makePackage(join(root, 'packages', 'subpath'), { - 'src/definition.ts': 'export const system = 1;', - 'main.ts': 'export {};', - }); - - const result = await collect(root, { - '@x/ds': join(srcPkg, 'dist', 'index.mjs'), - '@x/compiled-ds/definition': join(distPkg, 'dist', 'definition.mjs'), - '@x/subpath': join(subpathPkg, 'main.ts'), - }); - - // A src package scans its redirected source entry; a dist-only package - // scans the resolved dist entry — imported `Keyframes` collections are - // reachable either way, never only for src-shipping packages. - expect(result.keyframesScanEntries.get('@x/ds')).toBe( - join(srcPkg, 'src', 'index.ts') - ); - expect(result.keyframesScanEntries.get('@x/compiled-ds/definition')).toBe( - join(distPkg, 'dist', 'definition.mjs') - ); - // src/ exists but cannot serve the entry: the resolved entry itself scans. - expect(result.keyframesScanEntries.get('@x/subpath')).toBe( - join(subpathPkg, 'main.ts') - ); - expect(result.keyframesScanEntries.size).toBe(3); - }); - - test('a derived root alias scans its root entry for keyframes too', async () => { - // A kit declared at a subpath (`@x/kit/definition`) routinely exports - // its `Keyframes` collections from the package ROOT module only; the - // derived root alias must scan alongside the declared entry or those - // collections silently vanish from the merge. + test('a derived root alias keeps its module-resolution redirect', async () => { + // A kit declared at a subpath (`@x/kit/definition`) is routinely + // imported at its package ROOT by app code; the derived root alias + // redirects that import to src so the app never bundles untransformed + // dist chains. The redirect is resolution-only — nothing evaluates the + // root entry (vocabulary-registration ended discovery-by-scan; a kit's + // collections reach consumers through its sealed system's record). const root = makeRoot(); const pkg = makePackage(join(root, 'packages', 'kit'), { 'src/index.ts': 'export const kitMotion = 1;', @@ -254,10 +220,10 @@ describe('collectExternalPackageSources', () => { '@x/kit/definition': join(pkg, 'src', 'definition.ts'), }); - expect(result.keyframesScanEntries.get('@x/kit/definition')).toBe( - join(pkg, 'src', 'definition.ts') + expect(result.packageMap['@x/kit']).toBe( + relative(root, join(pkg, 'src', 'index.ts')) ); - expect(result.keyframesScanEntries.get('@x/kit')).toBe( + expect(result.sourceEntries.get('@x/kit')).toBe( join(pkg, 'src', 'index.ts') ); }); @@ -603,7 +569,6 @@ describe('collectExternalPackageSources', () => { '@x/a': 'packages/a/src/index.ts', }); expect([...admitted.sourceEntries.keys()]).toEqual(['@x/a']); - expect([...admitted.keyframesScanEntries.keys()]).toEqual(['@x/a']); expect(admitted.packageDirs).toEqual([join(kitA, 'src')]); expect(firstOwners(admitted.dirOwnerSets)).toEqual({ [join(kitA, 'src')]: '@x/a', diff --git a/packages/extract/tests/external-keyframes.test.ts b/packages/extract/tests/external-keyframes.test.ts deleted file mode 100644 index c96b4f97..00000000 --- a/packages/extract/tests/external-keyframes.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import { - KEYFRAMES_EXPORT_COLLISION, - KEYFRAMES_EXTERNAL_ENTRY_FAILED, - mergeExternalKeyframes, -} from '../pipeline/external-keyframes'; - -const kitCollection = { - kitMotion: { - pulse: { name: 'animus-kf-kit', frames: { from: { opacity: 0.4 } } }, - }, -}; - -describe('mergeExternalKeyframes', () => { - it('merges external collections into consumer collections', () => { - const consumer = JSON.stringify({ - animations: { spin: { name: 'animus-kf-consumer', frames: {} } }, - }); - const result = mergeExternalKeyframes( - () => JSON.stringify(kitCollection), - consumer, - ['/pkg/kit/src/index.ts'], - '/root' - ); - expect(result.diagnostics).toEqual([]); - const merged = JSON.parse(result.keyframesJson!); - expect(Object.keys(merged).sort()).toEqual(['animations', 'kitMotion']); - }); - - it('consumer wins name collisions with a coded diagnostic', () => { - const consumer = JSON.stringify({ - kitMotion: { pulse: { name: 'animus-kf-consumer', frames: {} } }, - }); - const result = mergeExternalKeyframes( - () => JSON.stringify(kitCollection), - consumer, - ['/pkg/kit/src/index.ts'], - '/root' - ); - expect(result.diagnostics).toHaveLength(1); - expect(result.diagnostics[0].code).toBe(KEYFRAMES_EXPORT_COLLISION); - expect(result.diagnostics[0].severity).toBe('warn'); - const merged = JSON.parse(result.keyframesJson!); - expect(merged.kitMotion.pulse.name).toBe('animus-kf-consumer'); - }); - - it('identical re-exports collide silently (no diagnostic)', () => { - const consumer = JSON.stringify(kitCollection); - const result = mergeExternalKeyframes( - () => JSON.stringify(kitCollection), - consumer, - ['/pkg/kit/src/index.ts'], - '/root' - ); - expect(result.diagnostics).toEqual([]); - }); - - it('a throwing entry degrades to a coded diagnostic and other entries proceed', () => { - const result = mergeExternalKeyframes( - (entry) => { - if (entry.includes('broken')) throw new Error('QuickJS eval failed'); - return JSON.stringify(kitCollection); - }, - null, - ['/pkg/broken/src/index.ts', '/pkg/kit/src/index.ts'], - '/root' - ); - expect(result.diagnostics).toHaveLength(1); - expect(result.diagnostics[0].code).toBe(KEYFRAMES_EXTERNAL_ENTRY_FAILED); - expect(result.diagnostics[0].file).toBe('/pkg/broken/src/index.ts'); - // The teaching half: the message names the remedy, not just the failure. - expect(result.diagnostics[0].message).toContain('definition entry'); - expect(result.diagnostics[0].message).toContain('named re-export'); - expect(result.diagnostics[0].message).toContain('export *'); - expect(JSON.parse(result.keyframesJson!).kitMotion).toBeDefined(); - }); - - /** - * `collectExternalPackageSources` registers TWO scan entries for a kit - * declared at a subpath — the declared entry and a derived alias for the - * package ROOT module, because collections routinely live only there - * (pinned by `collect-external-packages.test.ts`, "a derived root alias - * scans its root entry for keyframes too"). The failing entry is therefore - * often the root barrel, which for a React kit necessarily re-exports - * framework components. The message must describe that scan set: telling a - * consumer whose definition entry is already framework-free that - * collections "must be reachable from the package's definition entry" is - * advice they have already followed, and it never silences the barrel. - */ - it('the entry-failed message describes the whole scanned-entry set', () => { - const result = mergeExternalKeyframes( - () => { - throw new Error("could not resolve '@ark-ui/react/field'"); - }, - null, - ['/pkg/kit/src/index.ts'], - '/root' - ); - const [diagnostic] = result.diagnostics; - expect(diagnostic.code).toBe(KEYFRAMES_EXTERNAL_ENTRY_FAILED); - // Both scanned entries are named, so the reader can tell which one failed. - expect(diagnostic.message).toContain('your system entry declares'); - expect(diagnostic.message).toContain('package root module'); - // And the false requirement is gone: a framework-free definition entry - // does not exempt the root barrel from being scanned. - expect(diagnostic.message).not.toContain( - "must be reachable from the package's definition entry" - ); - }); - - /** - * The scan result is animus's own wire: `scanKeyframesExports` is a NAPI - * entry point and its JSON is serialized by the engine, never authored by a - * package. A `catch { continue }` here dropped the entry's collections - * silently — indistinguishable from "this package ships no keyframes", the - * same success-looking default the ENTRY_FAILED diagnostic exists to avoid. - * An entry that fails to EVALUATE still degrades to that diagnostic (the - * external-package boundary); only unparseable engine output throws. - */ - it('throws when the engine returns unparseable scan JSON', () => { - expect(() => - mergeExternalKeyframes( - () => 'not json', - null, - ['/pkg/kit/src/index.ts'], - '/root' - ) - ).toThrow(/keyframes/); - expect(() => - mergeExternalKeyframes( - () => 'not json', - null, - ['/pkg/kit/src/index.ts'], - '/root' - ) - ).toThrow(/SyntaxError/); - }); - - it('returns null when nothing exists and dedupes repeated entries', () => { - let scans = 0; - const empty = mergeExternalKeyframes( - () => { - scans++; - return null; - }, - null, - ['/pkg/kit/src/index.ts', '/pkg/kit/src/index.ts'], - '/root' - ); - expect(empty.keyframesJson).toBeNull(); - expect(scans).toBe(1); - }); -}); diff --git a/packages/extract/tests/test-system.ts b/packages/extract/tests/test-system.ts index f8ead4a3..2d2fad0b 100644 --- a/packages/extract/tests/test-system.ts +++ b/packages/extract/tests/test-system.ts @@ -127,7 +127,9 @@ declare module '@animus-ui/system' { // ─── System ──────────────────────────────────────────────── -export const { system: ds } = createSystem() +// Sealed (vocabulary-registration): the loader consumes sealed instances +// only; a vocabulary-free fixture seals with an empty record. +export const ds = createSystem() .addGroup('flex', flex) .addGroup('grid', grid) .addGroup('mode', mode) @@ -140,4 +142,5 @@ export const { system: ds } = createSystem() .addGroup('typography', typography) .addGroup('positioning', positioning) .addGroup('transitions', transitions) - .build(); + .build() + .seal(); diff --git a/packages/extract/tests/vocabulary-witness-diagnostics.test.ts b/packages/extract/tests/vocabulary-witness-diagnostics.test.ts new file mode 100644 index 00000000..82177529 --- /dev/null +++ b/packages/extract/tests/vocabulary-witness-diagnostics.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from 'vitest'; + +import { + VOCABULARY_COLLISION, + VOCABULARY_LEGACY_VERB, + vocabularyWitnessDiagnostics, +} from '../pipeline'; + +describe('vocabularyWitnessDiagnostics — the one host mapper for the sealed record witness channel', () => { + it('maps a collision entry to a coded warn naming both sources', () => { + const diagnostics = vocabularyWitnessDiagnostics( + JSON.stringify([ + { + code: VOCABULARY_COLLISION, + name: 'motion', + winner: 'local registration #1', + loser: 'extended source #1', + }, + ]) + ); + expect(diagnostics).toHaveLength(1); + expect(diagnostics[0]).toMatchObject({ + code: VOCABULARY_COLLISION, + severity: 'warn', + component: 'motion', + }); + expect(diagnostics[0]?.message).toContain('extended source #1'); + expect(diagnostics[0]?.message).toContain('local registration #1'); + expect(diagnostics[0]?.message).toContain(VOCABULARY_COLLISION); + }); + + it('maps a legacy-verb entry to a coded warn naming the verb, the source, and the refused names', () => { + const diagnostics = vocabularyWitnessDiagnostics( + JSON.stringify([ + { + code: VOCABULARY_LEGACY_VERB, + verb: 'includes', + source: 'includes source #1', + names: ['kitMotion'], + }, + ]) + ); + expect(diagnostics).toHaveLength(1); + expect(diagnostics[0]).toMatchObject({ + code: VOCABULARY_LEGACY_VERB, + severity: 'warn', + }); + expect(diagnostics[0]?.message).toContain("'includes'"); + expect(diagnostics[0]?.message).toContain('includes source #1'); + expect(diagnostics[0]?.message).toContain('kitMotion'); + }); + + it('fails closed on an unrecognized witness code — the entry surfaces instead of vanishing', () => { + const diagnostics = vocabularyWitnessDiagnostics( + JSON.stringify([{ code: 'animus.vocabulary.future-kind', detail: 1 }]) + ); + expect(diagnostics).toHaveLength(1); + expect(diagnostics[0]).toMatchObject({ + code: 'animus.vocabulary.future-kind', + severity: 'warn', + }); + expect(diagnostics[0]?.message).toContain( + 'unrecognized vocabulary witness' + ); + }); + + it('an absent or empty channel maps to zero diagnostics', () => { + expect(vocabularyWitnessDiagnostics(null)).toEqual([]); + expect(vocabularyWitnessDiagnostics(undefined)).toEqual([]); + }); +}); diff --git a/packages/next-plugin/tests/svelte-source-lifecycle.test.ts b/packages/next-plugin/tests/svelte-source-lifecycle.test.ts index 54218d67..ff5966af 100644 --- a/packages/next-plugin/tests/svelte-source-lifecycle.test.ts +++ b/packages/next-plugin/tests/svelte-source-lifecycle.test.ts @@ -19,7 +19,6 @@ const mocks = vi.hoisted(() => ({ analyzeProject: vi.fn<(...args: AnalyzeProjectArgs) => string>(), transformFile: vi.fn(), clearAnalysisCache: vi.fn(), - scanKeyframesExports: vi.fn(), })); import { ExtractionSession } from '../../extract/session/extraction-session'; @@ -47,7 +46,6 @@ setEngineApiOverride(() => { analyzeProject: mocks.analyzeProject, transformFile: mocks.transformFile, clearAnalysisCache: mocks.clearAnalysisCache, - scanKeyframesExports: mocks.scanKeyframesExports, }; return mocks.extractFactsEnabled ? { ...api, extractFacts: mocks.extractFacts } @@ -233,7 +231,6 @@ beforeEach(() => { mocks.clearAnalysisCache.mockReset().mockImplementation(() => { activeTransformSources.clear(); }); - mocks.scanKeyframesExports.mockReset().mockReturnValue(null); }); afterEach(() => { diff --git a/packages/next-plugin/tests/watch-external-ingestion.test.ts b/packages/next-plugin/tests/watch-external-ingestion.test.ts index 1338ac43..f3306854 100644 --- a/packages/next-plugin/tests/watch-external-ingestion.test.ts +++ b/packages/next-plugin/tests/watch-external-ingestion.test.ts @@ -43,7 +43,6 @@ const mocks = vi.hoisted(() => ({ loadSystemModule: vi.fn(), analyzeProject: vi.fn(), clearAnalysisCache: vi.fn(), - scanKeyframesExports: vi.fn(), })); import { setEngineApiOverride } from '../../extract/session/singleton'; @@ -56,7 +55,6 @@ setEngineApiOverride(() => ({ loadSystemModule: mocks.loadSystemModule, analyzeProject: mocks.analyzeProject, clearAnalysisCache: mocks.clearAnalysisCache, - scanKeyframesExports: mocks.scanKeyframesExports, })); let restoreGlobals: () => void; @@ -134,7 +132,6 @@ beforeEach(() => { mocks.loadSystemModule.mockReset().mockReturnValue({ ...SYSTEM_CONFIG }); mocks.analyzeProject.mockReset().mockReturnValue(MANIFEST); mocks.clearAnalysisCache.mockReset(); - mocks.scanKeyframesExports.mockReset().mockReturnValue(null); }); afterEach(() => { @@ -532,36 +529,6 @@ describe('cross-volume external roots (design D5)', () => { }); describe('external keyframes discovery', () => { - test('a dist-only kit contributes its keyframes collections', async () => { - // Published packages routinely ship dist without src/ — their imported - // `Keyframes` collections must merge into the analysis inputs exactly - // like a src-shipping kit's (vite-plugin parity). - const systemSource = `import { createSystem } from '@animus-ui/system'; -import kit from '../../kits/compiled/dist/index.mjs'; -export const system = createSystem({}).extend(kit); -`; - const ws = createWorkspace(systemSource); - const distKit = join(ws.parent, 'kits', 'compiled'); - mkdirSync(join(distKit, 'dist'), { recursive: true }); - writeFileSync(join(distKit, 'package.json'), '{"name":"@kits/compiled"}'); - writeFileSync(join(distKit, 'dist', 'index.mjs'), 'export default {};\n'); - mocks.scanKeyframesExports - .mockReset() - .mockReturnValue('{"kitKeyframes":{"pulse":{"to":{"opacity":1}}}}'); - - const session = makeSession(ws.app); - await session.runFullPipeline(); - - expect(mocks.scanKeyframesExports).toHaveBeenCalledWith( - join(distKit, 'dist', 'index.mjs'), - ws.app - ); - // keyframesJson is analyzeProject positional arg 13 - // (buildAnalyzeProjectArgs). - const analyzeArgs = mocks.analyzeProject.mock.calls.at(-1)!; - expect(analyzeArgs[13]).toContain('kitKeyframes'); - }); - test('a directory event on a dist-only root keeps its widened-extension files', async () => { // A dist-only kit is collected with a WIDENED extension set (the entry's // own `.mjs`). A directory-granularity event marks its root dirty; the @@ -605,32 +572,4 @@ export const system = createSystem({}).extend(kit); }); expect(lastAnalyzedSource(kitButtonKey)).toBe(BUTTON_V2); }); - - test('undeclaring a package clears its recorded keyframes diagnostics', async () => { - // A scan warning recorded for a declared package must not outlive the - // declaration: once the include is removed and the pipeline reruns with - // an empty scan set, stale diagnostics may not ride later analyses - // (vite-plugin parity — applyExternalKeyframes' reset arm). - const ws = createWorkspace(); - mocks.scanKeyframesExports.mockReset().mockImplementation(() => { - throw new Error('unreadable entry'); - }); - - const session = makeSession(ws.app); - await session.runFullPipeline(); - const recorded = session['externalKeyframesDiagnostics']; - expect(recorded.length).toBeGreaterThan(0); - - // Remove the kit import — the geological rewrite of the system file — - // and run the full pipeline again with nothing to scan. - writeFileSync( - join(ws.app, 'src', 'system.ts'), - `import { createSystem } from '@animus-ui/system'; -export const system = createSystem({}); -` - ); - await session.runFullPipeline(); - - expect(session['externalKeyframesDiagnostics']).toEqual([]); - }); }); diff --git a/packages/next-plugin/tests/webpack-gauntlet/external-ingestion.test.ts b/packages/next-plugin/tests/webpack-gauntlet/external-ingestion.test.ts index c08c57a1..6fc57b7a 100644 --- a/packages/next-plugin/tests/webpack-gauntlet/external-ingestion.test.ts +++ b/packages/next-plugin/tests/webpack-gauntlet/external-ingestion.test.ts @@ -29,7 +29,6 @@ const mocks = vi.hoisted(() => ({ analyzeProject: vi.fn(), clearAnalysisCache: vi.fn(), transformFile: vi.fn(), - scanKeyframesExports: vi.fn(), })); import { setEngineApiOverride } from '../../../extract/session/singleton'; @@ -43,7 +42,6 @@ setEngineApiOverride(() => ({ analyzeProject: mocks.analyzeProject, clearAnalysisCache: mocks.clearAnalysisCache, transformFile: mocks.transformFile, - scanKeyframesExports: mocks.scanKeyframesExports, })); import animusLoader from '../../src/loader'; @@ -77,12 +75,9 @@ afterEach(() => { vi.restoreAllMocks(); }); -/** Suite arming: the shared canned engine plus this suite's - * scanKeyframesExports mock. */ +/** Suite arming: the shared canned engine. */ function armSuiteEngine(): void { - armCannedEngine(mocks, () => { - mocks.scanKeyframesExports.mockReset().mockReturnValue(null); - }); + armCannedEngine(mocks, () => {}); } /** Everything one external-workspace scenario drives its watch session with. */ diff --git a/packages/next-plugin/tests/webpack-gauntlet/harness.ts b/packages/next-plugin/tests/webpack-gauntlet/harness.ts index bca7db81..d02a0c4d 100644 --- a/packages/next-plugin/tests/webpack-gauntlet/harness.ts +++ b/packages/next-plugin/tests/webpack-gauntlet/harness.ts @@ -345,8 +345,7 @@ function parseCannedManifestComponents(serialized: string): ReplacementPlan[] { /** * Arm the canned NAPI engine: system config + canned analyze/transform. - * `extra` runs after the standard arming for suite-specific mocks (e.g. - * scanKeyframesExports). + * `extra` runs after the standard arming for suite-specific mocks. */ export function armCannedEngine( mocks: CannedEngineMocks, diff --git a/packages/next-plugin/tests/webpack-gauntlet/real-engine.test.ts b/packages/next-plugin/tests/webpack-gauntlet/real-engine.test.ts index e89bcae5..36471959 100644 --- a/packages/next-plugin/tests/webpack-gauntlet/real-engine.test.ts +++ b/packages/next-plugin/tests/webpack-gauntlet/real-engine.test.ts @@ -66,9 +66,10 @@ import { color } from '@animus-ui/system/groups'; export { tokens } from './theme'; -export const { system: ds } = createSystem() +export const ds = createSystem() .addGroup('surface', color) - .build(); + .build() + .seal(); `; function buttonSource(withVariant: boolean): string { diff --git a/packages/oracle/__tests__/e2e-demo.test.ts b/packages/oracle/__tests__/e2e-demo.test.ts index 7477ce35..66353c81 100644 --- a/packages/oracle/__tests__/e2e-demo.test.ts +++ b/packages/oracle/__tests__/e2e-demo.test.ts @@ -502,7 +502,7 @@ describe('the invariants that hold across the whole path', () => { it('scopes every answer to the same program revision', () => { expect(host.program.kind).toBe('analysis-artifacts'); expect(host.program.label).toBe( - 'animus-commit:410fa0bb91141167e1cad2d6cd6dd150' + 'animus-commit:b06d23b64b8afc57701072492fe0ec10' ); for (const result of EVERY_RESULT) { expect(result.probeStateId).toMatch(/^[0-9a-f]{16}$/); diff --git a/packages/oracle/__tests__/fixtures/rollup-app/commit.json b/packages/oracle/__tests__/fixtures/rollup-app/commit.json index c6cf9233..0bca8182 100644 --- a/packages/oracle/__tests__/fixtures/rollup-app/commit.json +++ b/packages/oracle/__tests__/fixtures/rollup-app/commit.json @@ -2,13 +2,13 @@ "schema": 1, "payloads": { "styles.css": { - "hash": "bb8d158ecab1494b3899930897e722ad" + "hash": "939c144af6e9f8273e411bcb8b6f5d39" }, "system-props.js": { "hash": "46a029fa4ba91ea026c83821dcd7681b" }, "manifest.json": { - "hash": "410fa0bb91141167e1cad2d6cd6dd150" + "hash": "b06d23b64b8afc57701072492fe0ec10" }, "assets/test-font.f2a09939.woff2": { "hash": "f2a09939afd02913e3adbbb98ae9efe8" diff --git a/packages/oracle/__tests__/fixtures/rollup-app/manifest.json b/packages/oracle/__tests__/fixtures/rollup-app/manifest.json index d4a84ff1..cd4056fb 100644 --- a/packages/oracle/__tests__/fixtures/rollup-app/manifest.json +++ b/packages/oracle/__tests__/fixtures/rollup-app/manifest.json @@ -1 +1 @@ -{"fileFacts":{"../../packages/test-ds/src/components/Alert.tsx":{"path":"../../packages/test-ds/src/components/Alert.tsx","chains":[{"className":"animus-Alert-a385f997","descriptor":{"binding":"Alert","terminal":"asElement","tag":"div","stages":[{"method":"styles","argSpan":[67,200],"secondArgSpan":null},{"method":"variant","argSpan":[213,381],"secondArgSpan":null},{"method":"variant","argSpan":[394,541],"secondArgSpan":null},{"method":"compound","argSpan":[560,598],"secondArgSpan":[604,648]},{"method":"compound","argSpan":[670,710],"secondArgSpan":[716,758]},{"method":"compound","argSpan":[780,821],"secondArgSpan":[827,875]}],"extractable":true,"bailReason":null,"span":[54,899],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"flex","alignItems":"flex-start","p":12,"borderRadius":"4px","fontSize":14,"lineHeight":"1.5"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"variant","defaultVariant":null,"base":null,"variants":{"filled":{"color":"background"},"outline":{"bg":"transparent","borderWidth":"1px","borderStyle":"solid"}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"intent","defaultVariant":null,"base":null,"variants":{"info":{"bg":"primary"},"danger":{"bg":"danger"},"success":{"bg":"secondary"}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"compound","value":{"variant":"outline","intent":"info"},"secondValue":{"borderColor":"primary","color":"primary"},"skipped":[],"captured":[],"evalError":null},{"method":"compound","value":{"variant":"outline","intent":"danger"},"secondValue":{"borderColor":"danger","color":"danger"},"skipped":[],"captured":[],"evalError":null},{"method":"compound","value":{"variant":"outline","intent":"success"},"secondValue":{"borderColor":"secondary","color":"secondary"},"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"Alert","local":"Alert","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/components/Badge.tsx":{"path":"../../packages/test-ds/src/components/Badge.tsx","chains":[{"className":"animus-Badge-99781d29","descriptor":{"binding":"Badge","terminal":"asElement","tag":"span","stages":[{"method":"styles","argSpan":[67,238],"secondArgSpan":null},{"method":"variant","argSpan":[251,400],"secondArgSpan":null},{"method":"states","argSpan":[412,537],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[54,559],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"inline-flex","alignItems":"center","px":8,"py":4,"borderRadius":"9999px","fontSize":12,"fontWeight":"500","lineHeight":"1"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"color","defaultVariant":null,"base":null,"variants":{"neutral":{"bg":"surface","color":"text"},"danger":{"bg":"danger","color":"background"}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"states","value":{"disabled":{"opacity":"0.5","cursor":"not-allowed"},"active":{"outline":"2px solid","outlineColor":"primary"}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"Badge","local":"Badge","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/components/Button.tsx":{"path":"../../packages/test-ds/src/components/Button.tsx","chains":[{"className":"animus-Button-c63b6dcd","descriptor":{"binding":"Button","terminal":"asElement","tag":"button","stages":[{"method":"styles","argSpan":[68,269],"secondArgSpan":null},{"method":"variant","argSpan":[282,496],"secondArgSpan":null},{"method":"system","argSpan":[508,546],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[55,570],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"inline-flex","alignItems":"center","justifyContent":"center","borderRadius":"4px","fontWeight":"600","cursor":"pointer","border":"none","lineHeight":"1"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"variant","defaultVariant":null,"base":null,"variants":{"primary":{"bg":"primary","color":"background"},"secondary":{"bg":"secondary","color":"background"},"ghost":{"bg":"transparent","color":"text"}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"system","value":{"px":true,"py":true,"fontSize":true},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"Button","local":"Button","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/components/Card.tsx":{"path":"../../packages/test-ds/src/components/Card.tsx","chains":[{"className":"animus-Card-9aa7af5d","descriptor":{"binding":"Card","terminal":"asElement","tag":"div","stages":[{"method":"styles","argSpan":[573,2126],"secondArgSpan":null},{"method":"system","argSpan":[2138,2169],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[560,2190],"extendsFrom":null},"stages":[{"method":"styles","value":{"bg":"surface","p":16,"borderRadius":"8px","color":"text","containerType":"inline-size","containerName":"card","@container card (min-width: 400px)":{"p":24,"width":"50cqw"},"@media (prefers-reduced-motion: reduce)":{"transition":"none"},"@supports (display: grid)":{"display":"grid","&:focus-visible":{"outline":"2px solid"},"@container card (min-width: 600px)":{"gap":"2cqi"},"fontSize":{"_":14,"sm":16}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"system","value":{"m":true,"mx":true,"my":true},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"Card","local":"Card","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/components/ContainerCard.tsx":{"path":"../../packages/test-ds/src/components/ContainerCard.tsx","chains":[{"className":"animus-ContainerCardRoot-01c5b011","descriptor":{"binding":"ContainerCardRoot","terminal":"asElement","tag":"article","stages":[{"method":"styles","argSpan":[1089,1435],"secondArgSpan":null},{"method":"variant","argSpan":[1448,1555],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[1076,1580],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"flex","flexDirection":"column","gap":8,"p":16,"borderRadius":"8px","bg":"surface","color":"text","containerType":"inline-size","containerName":"card"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"size","defaultVariant":"md","base":null,"variants":{"md":{},"lg":{"p":24}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null},{"className":"animus-ContainerCardMedia-51db5d07","descriptor":{"binding":"ContainerCardMedia","terminal":"asElement","tag":"div","stages":[{"method":"styles","argSpan":[1623,2194],"secondArgSpan":null},{"method":"variant","argSpan":[2207,2307],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[1610,2328],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"block","width":"100%","minHeight":"64px","borderRadius":"4px","background":"var(--current-bg)","@container card (min-width: 400px)":{"minHeight":"120px","width":"50cqw"}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"size","defaultVariant":"md","base":null,"variants":{"md":{},"lg":{}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null},{"className":"animus-ContainerCardBody-133c6ad9","descriptor":{"binding":"ContainerCardBody","terminal":"asElement","tag":"div","stages":[{"method":"styles","argSpan":[2370,2636],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[2357,2657],"extendsFrom":null},"stages":[{"method":"styles","value":{"fontSize":14,"lineHeight":"1.5","color":"text","@container card (min-width: 400px)":{"fontSize":16}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[{"family_binding":"ContainerCard","root_binding":"ContainerCardRoot","slots":[["Root","ContainerCardRoot"],["Media","ContainerCardMedia"],["Body","ContainerCardBody"]],"shared_keys":["size"],"context":false,"span":[2689,2849],"name":"ContainerCard"}],"imports":[{"local":"compose","imported":"compose","source":"@animus-ui/system"},{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"ContainerCard","local":"ContainerCard","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/components/GroupItem.tsx":{"path":"../../packages/test-ds/src/components/GroupItem.tsx","chains":[{"className":"animus-GroupItem-32b2d32f","descriptor":{"binding":"GroupItem","terminal":"asElement","tag":"span","stages":[{"method":"styles","argSpan":[844,1148],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[831,1170],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"inline-flex","alignItems":"center","px":8,"py":4,"borderRadius":"4px","bg":"surface","color":"text","[data-active=\"true\"] &":{"bg":"primary","color":"background"},"_groupHover":{"opacity":"0.9"},"_dark":{"color":"text.muted"}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"GroupItem","local":"GroupItem","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/definition.ts":{"path":"../../packages/test-ds/src/definition.ts","chains":[],"statics":{},"usage":[],"compose":[],"imports":[],"exports":[{"exported":"system","local":null,"source":"./system","original":"ds"},{"exported":"theme","local":null,"source":"./theme","original":"referenceTokens"}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/dev-types.ts":{"path":"../../packages/test-ds/src/dev-types.ts","chains":[],"statics":{},"usage":[],"compose":[],"imports":[{"local":"referenceTokens","imported":"referenceTokens","source":"./theme"}],"exports":[],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/index.ts":{"path":"../../packages/test-ds/src/index.ts","chains":[],"statics":{"kitMotion":{"pulse":"animus-kf-muo7kp"},"kitSizes":{"sm":{"fontSize":14,"px":8,"py":4},"md":{"fontSize":16,"px":16,"py":8},"lg":{"fontSize":20,"px":24,"py":12}}},"usage":[],"compose":[],"imports":[{"local":"createKeyframes","imported":"createKeyframes","source":"./system"}],"exports":[{"exported":"Alert","local":null,"source":"./components/Alert","original":"Alert"},{"exported":"Badge","local":null,"source":"./components/Badge","original":"Badge"},{"exported":"Button","local":null,"source":"./components/Button","original":"Button"},{"exported":"Card","local":null,"source":"./components/Card","original":"Card"},{"exported":"ContainerCard","local":null,"source":"./components/ContainerCard","original":"ContainerCard"},{"exported":"GroupItem","local":null,"source":"./components/GroupItem","original":"GroupItem"},{"exported":"ds","local":null,"source":"./system","original":"ds"},{"exported":"referenceTokens","local":null,"source":"./theme","original":"referenceTokens"},{"exported":"kitSizes","local":"kitSizes","source":null,"original":null},{"exported":"kitMotion","local":"kitMotion","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/system.ts":{"path":"../../packages/test-ds/src/system.ts","chains":[],"statics":{},"usage":[],"compose":[],"imports":[{"local":"createSystem","imported":"createSystem","source":"@animus-ui/system"},{"local":"border","imported":"border","source":"@animus-ui/system/groups"},{"local":"color","imported":"color","source":"@animus-ui/system/groups"},{"local":"flex","imported":"flex","source":"@animus-ui/system/groups"},{"local":"layout","imported":"layout","source":"@animus-ui/system/groups"},{"local":"positioning","imported":"positioning","source":"@animus-ui/system/groups"},{"local":"space","imported":"space","source":"@animus-ui/system/groups"},{"local":"typography","imported":"typography","source":"@animus-ui/system/groups"}],"exports":[],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/theme.ts":{"path":"../../packages/test-ds/src/theme.ts","chains":[],"statics":{},"usage":[],"compose":[],"imports":[{"local":"createTheme","imported":"createTheme","source":"@animus-ui/system"}],"exports":[{"exported":"referenceTokens","local":"referenceTokens","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/Box.tsx":{"path":"src/Box.tsx","chains":[{"className":"animus-Box-399302cf","descriptor":{"binding":"Box","terminal":"asElement","tag":"div","stages":[{"method":"styles","argSpan":[60,112],"secondArgSpan":null},{"method":"system","argSpan":[124,172],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[47,193],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"flex","position":"relative"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"system","value":{"space":true,"layout":true,"positioning":true},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"./ds"}],"exports":[{"exported":"Box","local":"Box","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/Button.tsx":{"path":"src/Button.tsx","chains":[{"className":"animus-Button-b3718a43","descriptor":{"binding":"Button","terminal":"asElement","tag":"button","stages":[{"method":"styles","argSpan":[63,146],"secondArgSpan":null},{"method":"variant","argSpan":[159,313],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[50,337],"extendsFrom":null},"stages":[{"method":"styles","value":{"padding":"8px","borderRadius":"4px","backgroundColor":"blue.500"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"tone","defaultVariant":null,"base":null,"variants":{"quiet":{"backgroundColor":"gray.700"},"loud":{"backgroundColor":"blue.700","fontWeight":700}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[{"element":{"tag":{"ident":"Button"},"attrs":[{"name":"tone","staticValue":"loud","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"loud"}]}}],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"./ds"}],"exports":[{"exported":"Button","local":"Button","source":null,"original":null},{"exported":"App","local":"App","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/Frame.tsx":{"path":"src/Frame.tsx","chains":[],"statics":{},"usage":[{"element":{"tag":{"ident":"section"},"attrs":[{"name":"className","staticValue":"frame","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"frame"}]}}],"compose":[],"imports":[{"local":"ReactNode","imported":"ReactNode","source":"react"}],"exports":[{"exported":"Frame","local":"Frame","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/Group.tsx":{"path":"src/Group.tsx","chains":[],"statics":{},"usage":[{"element":{"tag":{"ident":"div"},"attrs":[]}},{"element":{"tag":{"ident":"div"},"attrs":[{"name":"className","staticValue":"group","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"group"},{"name":"data-active","staticValue":"true","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"true"}]}},{"element":{"tag":{"ident":"GroupItem"},"attrs":[]}},{"element":{"tag":{"ident":"div"},"attrs":[{"name":"data-active","staticValue":"false","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"false"}]}},{"element":{"tag":{"ident":"GroupItem"},"attrs":[]}},{"element":{"tag":{"ident":"Frame"},"attrs":[]}},{"element":{"tag":{"ident":"GroupItem"},"attrs":[]}},{"element":{"tag":{"ident":"div"},"attrs":[{"name":"data-active","staticValue":null,"dynamic":true,"dynamicKind":"conditional","dynamicSpan":{"start":1616,"end":1641},"skip":false,"variantClass":"__dynamic__"}]}},{"element":{"tag":{"ident":"GroupItem"},"attrs":[]}}],"compose":[],"imports":[{"local":"GroupItem","imported":"GroupItem","source":"@animus-ui/test-ds"},{"local":"Frame","imported":"Frame","source":"./Frame"}],"exports":[{"exported":"GroupDemo","local":"GroupDemo","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/ds.ts":{"path":"src/ds.ts","chains":[],"statics":{"animations":{"fadeIn":"animus-kf-1x7guim","pulse":"animus-kf-1yqv0zl"}},"usage":[],"compose":[],"imports":[{"local":"asset","imported":"asset","source":"@animus-ui/system"},{"local":"createSystem","imported":"createSystem","source":"@animus-ui/system"},{"local":"createTheme","imported":"createTheme","source":"@animus-ui/system"},{"local":"testDs","imported":"system","source":"@animus-ui/test-ds/definition"}],"exports":[{"exported":"theme","local":"theme","source":null,"original":null},{"exported":"globalStyles","local":"globalStyles","source":null,"original":null},{"exported":"animations","local":"animations","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/entry.tsx":{"path":"src/entry.tsx","chains":[],"statics":{},"usage":[{"element":{"tag":{"ident":"div"},"attrs":[]}},{"element":{"tag":{"ident":"ButtonApp"},"attrs":[]}},{"element":{"tag":{"ident":"Badge"},"attrs":[{"name":"color","staticValue":"danger","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"danger"}]}}],"compose":[],"imports":[{"local":"Badge","imported":"Badge","source":"@animus-ui/test-ds"},{"local":"ButtonApp","imported":"App","source":"./Button"},{"local":"Button","imported":"Button","source":"./Button"}],"exports":[{"exported":"Badge","local":"Badge","source":null,"original":null},{"exported":"Button","local":"Button","source":null,"original":null},{"exported":"App","local":"App","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]}},"crossFile":{"componentNames":["Alert","Badge","Box","Button","Card","ContainerCardBody","ContainerCardMedia","ContainerCardRoot","GroupItem"],"classResolvers":[],"memberBindings":{"ContainerCard.Body":"ContainerCardBody","ContainerCard.Media":"ContainerCardMedia","ContainerCard.Root":"ContainerCardRoot"},"renderedComponents":["Badge","Button","ContainerCardBody","ContainerCardMedia","ContainerCardRoot","GroupItem"],"variantOptions":{"Alert":{"intent":["danger","info","success"],"variant":["filled","outline"]},"Badge":{"color":["danger","neutral"]},"Button":{"tone":["loud","quiet"],"variant":["ghost","primary","secondary"]},"ContainerCardMedia":{"size":["lg","md"]},"ContainerCardRoot":{"size":["lg","md"]}},"stateNames":{"Badge":["active","disabled"]}},"parseCount":17,"usageResidue":[],"css":"@layer anm-global, anm-base, anm-variants, anm-compounds, anm-states, anm-system, anm-custom;\n\n@layer anm-base {\n .animus-Alert-a385f997 {\n padding: 0.75rem;\n display: flex;\n align-items: flex-start;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n }\n .animus-Badge-99781d29 {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 500;\n line-height: 1;\n }\n .animus-Card-9aa7af5d {\n padding: 1rem;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n border-radius: 8px;\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n @container card (min-width: 400px) {\n .animus-Card-9aa7af5d {\n padding: 1.5rem;\n width: 50cqw;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .animus-Card-9aa7af5d {\n transition: none;\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d {\n display: grid;\n font-size: 0.875rem;\n }\n }\n @supports (display: grid) {\n @media (min-width: 640px) {\n .animus-Card-9aa7af5d {\n font-size: 1rem;\n }\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d:focus-visible {\n outline: 2px solid;\n }\n }\n @supports (display: grid) {\n @container card (min-width: 600px) {\n .animus-Card-9aa7af5d {\n gap: 2cqi;\n }\n }\n }\n .animus-ContainerCardBody-133c6ad9 {\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--color-text);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardBody-133c6ad9 {\n font-size: 1rem;\n }\n }\n .animus-ContainerCardMedia-51db5d07 {\n display: block;\n width: 100%;\n min-height: 64px;\n border-radius: 4px;\n background: var(--current-bg);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardMedia-51db5d07 {\n min-height: 120px;\n width: 50cqw;\n }\n }\n .animus-ContainerCardRoot-01c5b011 {\n gap: 0.5rem;\n padding: 1rem;\n display: flex;\n flex-direction: column;\n border-radius: 8px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n .animus-GroupItem-32b2d32f {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 4px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n [data-active=\"true\"] .animus-GroupItem-32b2d32f {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n color: var(--color-background);\n }\n .group:hover .animus-GroupItem-32b2d32f {\n opacity: 0.9;\n }\n [data-color-mode=\"dark\"] .animus-GroupItem-32b2d32f {\n color: var(--color-text-muted);\n }\n .animus-Box-399302cf {\n display: flex;\n position: relative;\n }\n .animus-Button-c63b6dcd {\n border: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n font-weight: 600;\n line-height: 1;\n cursor: pointer;\n }\n .animus-Button-b3718a43 {\n border-radius: 4px;\n padding: 8px;\n background-color: var(--color-blue-500);\n }\n}\n\n@layer anm-variants {\n @layer standalone, composed;\n @layer standalone {\n\n .animus-Alert-a385f997--variant-filled {\n color: var(--color-background);\n }\n .animus-Alert-a385f997--variant-outline {\n border-width: 1px;\n border-style: solid;\n background-color: transparent;\n --current-bg: transparent;\n }\n .animus-Alert-a385f997--intent-info {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n }\n .animus-Alert-a385f997--intent-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n }\n .animus-Alert-a385f997--intent-success {\n background-color: var(--color-secondary);\n --current-bg: var(--color-secondary);\n }\n .animus-Badge-99781d29--color-neutral {\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n .animus-Badge-99781d29--color-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n color: var(--color-background);\n }\n .animus-ContainerCardRoot-01c5b011--size-lg {\n padding: 1.5rem;\n }\n .animus-Button-c63b6dcd--variant-primary {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n color: var(--color-background);\n }\n .animus-Button-c63b6dcd--variant-secondary {\n background-color: var(--color-secondary);\n --current-bg: var(--color-secondary);\n color: var(--color-background);\n }\n .animus-Button-c63b6dcd--variant-ghost {\n background-color: transparent;\n --current-bg: transparent;\n color: var(--color-text);\n }\n .animus-Button-b3718a43--tone-quiet {\n background-color: var(--color-gray-700);\n }\n .animus-Button-b3718a43--tone-loud {\n font-weight: 700;\n background-color: var(--color-blue-700);\n }\n }\n @layer composed {\n }\n}\n\n@layer anm-compounds {\n .animus-Alert-a385f997--compound-0 {\n border-color: var(--color-primary);\n color: var(--color-primary);\n }\n .animus-Alert-a385f997--compound-1 {\n border-color: var(--color-danger);\n color: var(--color-danger);\n }\n .animus-Alert-a385f997--compound-2 {\n border-color: var(--color-secondary);\n color: var(--color-secondary);\n }\n}\n\n@layer anm-states {\n .animus-Badge-99781d29--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .animus-Badge-99781d29--active {\n outline: 2px solid;\n outline-color: var(--color-primary);\n }\n}\n\n@layer anm-system {\n .animus-dyn-flex {\n flex: var(--animus-flex);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-sm {\n flex: var(--animus-flex-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-md {\n flex: var(--animus-flex-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-lg {\n flex: var(--animus-flex-lg);\n }\n }\n .animus-dyn-m {\n margin: var(--animus-m);\n }\n @media (min-width: 640px) {\n .animus-dyn-m-sm {\n margin: var(--animus-m-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-m-md {\n margin: var(--animus-m-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-m-lg {\n margin: var(--animus-m-lg);\n }\n }\n .animus-dyn-p {\n padding: var(--animus-p);\n }\n @media (min-width: 640px) {\n .animus-dyn-p-sm {\n padding: var(--animus-p-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-p-md {\n padding: var(--animus-p-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-p-lg {\n padding: var(--animus-p-lg);\n }\n }\n .animus-dyn-gap {\n gap: var(--animus-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-gap-sm {\n gap: var(--animus-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-gap-md {\n gap: var(--animus-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-gap-lg {\n gap: var(--animus-gap-lg);\n }\n }\n .animus-dyn-area {\n grid-area: var(--animus-area);\n }\n .animus-dyn-grid-area {\n grid-area: var(--animus-grid-area);\n }\n @media (min-width: 640px) {\n .animus-dyn-area-sm {\n grid-area: var(--animus-area-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-area-sm {\n grid-area: var(--animus-grid-area-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-area-md {\n grid-area: var(--animus-area-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-area-md {\n grid-area: var(--animus-grid-area-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-area-lg {\n grid-area: var(--animus-area-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-area-lg {\n grid-area: var(--animus-grid-area-lg);\n }\n }\n .animus-dyn-grid-column {\n grid-column: var(--animus-grid-column);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-sm {\n grid-column: var(--animus-grid-column-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-md {\n grid-column: var(--animus-grid-column-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-lg {\n grid-column: var(--animus-grid-column-lg);\n }\n }\n .animus-dyn-grid-row {\n grid-row: var(--animus-grid-row);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-sm {\n grid-row: var(--animus-grid-row-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-md {\n grid-row: var(--animus-grid-row-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-lg {\n grid-row: var(--animus-grid-row-lg);\n }\n }\n .animus-dyn-overflow {\n overflow: var(--animus-overflow);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-sm {\n overflow: var(--animus-overflow-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-md {\n overflow: var(--animus-overflow-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-lg {\n overflow: var(--animus-overflow-lg);\n }\n }\n .animus-dyn-align-content {\n align-content: var(--animus-align-content);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-content-sm {\n align-content: var(--animus-align-content-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-content-md {\n align-content: var(--animus-align-content-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-content-lg {\n align-content: var(--animus-align-content-lg);\n }\n }\n .animus-dyn-align-items {\n align-items: var(--animus-align-items);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-items-sm {\n align-items: var(--animus-align-items-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-items-md {\n align-items: var(--animus-align-items-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-items-lg {\n align-items: var(--animus-align-items-lg);\n }\n }\n .animus-dyn-align-self {\n align-self: var(--animus-align-self);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-self-sm {\n align-self: var(--animus-align-self-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-self-md {\n align-self: var(--animus-align-self-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-self-lg {\n align-self: var(--animus-align-self-lg);\n }\n }\n .animus-dyn-bottom {\n bottom: var(--animus-bottom);\n }\n @media (min-width: 640px) {\n .animus-dyn-bottom-sm {\n bottom: var(--animus-bottom-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-bottom-md {\n bottom: var(--animus-bottom-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-bottom-lg {\n bottom: var(--animus-bottom-lg);\n }\n }\n .animus-dyn-column-gap {\n column-gap: var(--animus-column-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-column-gap-sm {\n column-gap: var(--animus-column-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-column-gap-md {\n column-gap: var(--animus-column-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-column-gap-lg {\n column-gap: var(--animus-column-gap-lg);\n }\n }\n .animus-dyn-display {\n display: var(--animus-display);\n }\n @media (min-width: 640px) {\n .animus-dyn-display-sm {\n display: var(--animus-display-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-display-md {\n display: var(--animus-display-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-display-lg {\n display: var(--animus-display-lg);\n }\n }\n .animus-dyn-flex-basis {\n flex-basis: var(--animus-flex-basis);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-basis-sm {\n flex-basis: var(--animus-flex-basis-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-basis-md {\n flex-basis: var(--animus-flex-basis-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-basis-lg {\n flex-basis: var(--animus-flex-basis-lg);\n }\n }\n .animus-dyn-flex-dir {\n flex-direction: var(--animus-flex-dir);\n }\n .animus-dyn-flex-direction {\n flex-direction: var(--animus-flex-direction);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-dir-sm {\n flex-direction: var(--animus-flex-dir-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-direction-sm {\n flex-direction: var(--animus-flex-direction-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-dir-md {\n flex-direction: var(--animus-flex-dir-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-direction-md {\n flex-direction: var(--animus-flex-direction-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-dir-lg {\n flex-direction: var(--animus-flex-dir-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-direction-lg {\n flex-direction: var(--animus-flex-direction-lg);\n }\n }\n .animus-dyn-flex-grow {\n flex-grow: var(--animus-flex-grow);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-grow-sm {\n flex-grow: var(--animus-flex-grow-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-grow-md {\n flex-grow: var(--animus-flex-grow-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-grow-lg {\n flex-grow: var(--animus-flex-grow-lg);\n }\n }\n .animus-dyn-flex-shrink {\n flex-shrink: var(--animus-flex-shrink);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-shrink-sm {\n flex-shrink: var(--animus-flex-shrink-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-shrink-md {\n flex-shrink: var(--animus-flex-shrink-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-shrink-lg {\n flex-shrink: var(--animus-flex-shrink-lg);\n }\n }\n .animus-dyn-flex-wrap {\n flex-wrap: var(--animus-flex-wrap);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-wrap-sm {\n flex-wrap: var(--animus-flex-wrap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-wrap-md {\n flex-wrap: var(--animus-flex-wrap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-wrap-lg {\n flex-wrap: var(--animus-flex-wrap-lg);\n }\n }\n .animus-dyn-font-size {\n font-size: var(--animus-font-size);\n }\n @media (min-width: 640px) {\n .animus-dyn-font-size-sm {\n font-size: var(--animus-font-size-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-font-size-md {\n font-size: var(--animus-font-size-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-font-size-lg {\n font-size: var(--animus-font-size-lg);\n }\n }\n .animus-dyn-grid-column-end {\n grid-column-end: var(--animus-grid-column-end);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-end-sm {\n grid-column-end: var(--animus-grid-column-end-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-end-md {\n grid-column-end: var(--animus-grid-column-end-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-end-lg {\n grid-column-end: var(--animus-grid-column-end-lg);\n }\n }\n .animus-dyn-grid-column-start {\n grid-column-start: var(--animus-grid-column-start);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-start-sm {\n grid-column-start: var(--animus-grid-column-start-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-start-md {\n grid-column-start: var(--animus-grid-column-start-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-start-lg {\n grid-column-start: var(--animus-grid-column-start-lg);\n }\n }\n .animus-dyn-grid-row-end {\n grid-row-end: var(--animus-grid-row-end);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-end-sm {\n grid-row-end: var(--animus-grid-row-end-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-end-md {\n grid-row-end: var(--animus-grid-row-end-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-end-lg {\n grid-row-end: var(--animus-grid-row-end-lg);\n }\n }\n .animus-dyn-grid-row-start {\n grid-row-start: var(--animus-grid-row-start);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-start-sm {\n grid-row-start: var(--animus-grid-row-start-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-start-md {\n grid-row-start: var(--animus-grid-row-start-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-start-lg {\n grid-row-start: var(--animus-grid-row-start-lg);\n }\n }\n .animus-dyn-h {\n height: var(--animus-h);\n }\n .animus-dyn-height {\n height: var(--animus-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-h-sm {\n height: var(--animus-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-height-sm {\n height: var(--animus-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-h-md {\n height: var(--animus-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-height-md {\n height: var(--animus-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-h-lg {\n height: var(--animus-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-height-lg {\n height: var(--animus-height-lg);\n }\n }\n .animus-dyn-justify-content {\n justify-content: var(--animus-justify-content);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-content-sm {\n justify-content: var(--animus-justify-content-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-content-md {\n justify-content: var(--animus-justify-content-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-content-lg {\n justify-content: var(--animus-justify-content-lg);\n }\n }\n .animus-dyn-justify-items {\n justify-items: var(--animus-justify-items);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-items-sm {\n justify-items: var(--animus-justify-items-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-items-md {\n justify-items: var(--animus-justify-items-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-items-lg {\n justify-items: var(--animus-justify-items-lg);\n }\n }\n .animus-dyn-justify-self {\n justify-self: var(--animus-justify-self);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-self-sm {\n justify-self: var(--animus-justify-self-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-self-md {\n justify-self: var(--animus-justify-self-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-self-lg {\n justify-self: var(--animus-justify-self-lg);\n }\n }\n .animus-dyn-left {\n left: var(--animus-left);\n }\n @media (min-width: 640px) {\n .animus-dyn-left-sm {\n left: var(--animus-left-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-left-md {\n left: var(--animus-left-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-left-lg {\n left: var(--animus-left-lg);\n }\n }\n .animus-dyn-mb {\n margin-bottom: var(--animus-mb);\n }\n @media (min-width: 640px) {\n .animus-dyn-mb-sm {\n margin-bottom: var(--animus-mb-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mb-md {\n margin-bottom: var(--animus-mb-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mb-lg {\n margin-bottom: var(--animus-mb-lg);\n }\n }\n .animus-dyn-ml {\n margin-left: var(--animus-ml);\n }\n .animus-dyn-mx {\n margin-left: var(--animus-mx);\n margin-right: var(--animus-mx);\n }\n @media (min-width: 640px) {\n .animus-dyn-ml-sm {\n margin-left: var(--animus-ml-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-mx-sm {\n margin-left: var(--animus-mx-sm);\n margin-right: var(--animus-mx-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-ml-md {\n margin-left: var(--animus-ml-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mx-md {\n margin-left: var(--animus-mx-md);\n margin-right: var(--animus-mx-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-ml-lg {\n margin-left: var(--animus-ml-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mx-lg {\n margin-left: var(--animus-mx-lg);\n margin-right: var(--animus-mx-lg);\n }\n }\n .animus-dyn-mr {\n margin-right: var(--animus-mr);\n }\n @media (min-width: 640px) {\n .animus-dyn-mr-sm {\n margin-right: var(--animus-mr-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mr-md {\n margin-right: var(--animus-mr-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mr-lg {\n margin-right: var(--animus-mr-lg);\n }\n }\n .animus-dyn-mt {\n margin-top: var(--animus-mt);\n }\n .animus-dyn-my {\n margin-top: var(--animus-my);\n margin-bottom: var(--animus-my);\n }\n @media (min-width: 640px) {\n .animus-dyn-mt-sm {\n margin-top: var(--animus-mt-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-my-sm {\n margin-top: var(--animus-my-sm);\n margin-bottom: var(--animus-my-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mt-md {\n margin-top: var(--animus-mt-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-my-md {\n margin-top: var(--animus-my-md);\n margin-bottom: var(--animus-my-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mt-lg {\n margin-top: var(--animus-mt-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-my-lg {\n margin-top: var(--animus-my-lg);\n margin-bottom: var(--animus-my-lg);\n }\n }\n .animus-dyn-max-h {\n max-height: var(--animus-max-h);\n }\n .animus-dyn-max-height {\n max-height: var(--animus-max-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-max-h-sm {\n max-height: var(--animus-max-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-max-height-sm {\n max-height: var(--animus-max-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-h-md {\n max-height: var(--animus-max-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-height-md {\n max-height: var(--animus-max-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-h-lg {\n max-height: var(--animus-max-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-height-lg {\n max-height: var(--animus-max-height-lg);\n }\n }\n .animus-dyn-max-w {\n max-width: var(--animus-max-w);\n }\n .animus-dyn-max-width {\n max-width: var(--animus-max-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-max-w-sm {\n max-width: var(--animus-max-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-max-width-sm {\n max-width: var(--animus-max-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-w-md {\n max-width: var(--animus-max-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-width-md {\n max-width: var(--animus-max-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-w-lg {\n max-width: var(--animus-max-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-width-lg {\n max-width: var(--animus-max-width-lg);\n }\n }\n .animus-dyn-min-h {\n min-height: var(--animus-min-h);\n }\n .animus-dyn-min-height {\n min-height: var(--animus-min-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-min-h-sm {\n min-height: var(--animus-min-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-min-height-sm {\n min-height: var(--animus-min-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-h-md {\n min-height: var(--animus-min-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-height-md {\n min-height: var(--animus-min-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-h-lg {\n min-height: var(--animus-min-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-height-lg {\n min-height: var(--animus-min-height-lg);\n }\n }\n .animus-dyn-min-w {\n min-width: var(--animus-min-w);\n }\n .animus-dyn-min-width {\n min-width: var(--animus-min-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-min-w-sm {\n min-width: var(--animus-min-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-min-width-sm {\n min-width: var(--animus-min-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-w-md {\n min-width: var(--animus-min-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-width-md {\n min-width: var(--animus-min-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-w-lg {\n min-width: var(--animus-min-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-width-lg {\n min-width: var(--animus-min-width-lg);\n }\n }\n .animus-dyn-opacity {\n opacity: var(--animus-opacity);\n }\n @media (min-width: 640px) {\n .animus-dyn-opacity-sm {\n opacity: var(--animus-opacity-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-opacity-md {\n opacity: var(--animus-opacity-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-opacity-lg {\n opacity: var(--animus-opacity-lg);\n }\n }\n .animus-dyn-order {\n order: var(--animus-order);\n }\n @media (min-width: 640px) {\n .animus-dyn-order-sm {\n order: var(--animus-order-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-order-md {\n order: var(--animus-order-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-order-lg {\n order: var(--animus-order-lg);\n }\n }\n .animus-dyn-overflow-x {\n overflow-x: var(--animus-overflow-x);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-x-sm {\n overflow-x: var(--animus-overflow-x-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-x-md {\n overflow-x: var(--animus-overflow-x-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-x-lg {\n overflow-x: var(--animus-overflow-x-lg);\n }\n }\n .animus-dyn-overflow-y {\n overflow-y: var(--animus-overflow-y);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-y-sm {\n overflow-y: var(--animus-overflow-y-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-y-md {\n overflow-y: var(--animus-overflow-y-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-y-lg {\n overflow-y: var(--animus-overflow-y-lg);\n }\n }\n .animus-dyn-pb {\n padding-bottom: var(--animus-pb);\n }\n @media (min-width: 640px) {\n .animus-dyn-pb-sm {\n padding-bottom: var(--animus-pb-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pb-md {\n padding-bottom: var(--animus-pb-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pb-lg {\n padding-bottom: var(--animus-pb-lg);\n }\n }\n .animus-dyn-pl {\n padding-left: var(--animus-pl);\n }\n .animus-dyn-px {\n padding-left: var(--animus-px);\n padding-right: var(--animus-px);\n }\n @media (min-width: 640px) {\n .animus-dyn-pl-sm {\n padding-left: var(--animus-pl-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-px-sm {\n padding-left: var(--animus-px-sm);\n padding-right: var(--animus-px-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pl-md {\n padding-left: var(--animus-pl-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-px-md {\n padding-left: var(--animus-px-md);\n padding-right: var(--animus-px-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pl-lg {\n padding-left: var(--animus-pl-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-px-lg {\n padding-left: var(--animus-px-lg);\n padding-right: var(--animus-px-lg);\n }\n }\n .animus-dyn-pr {\n padding-right: var(--animus-pr);\n }\n @media (min-width: 640px) {\n .animus-dyn-pr-sm {\n padding-right: var(--animus-pr-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pr-md {\n padding-right: var(--animus-pr-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pr-lg {\n padding-right: var(--animus-pr-lg);\n }\n }\n .animus-dyn-pt {\n padding-top: var(--animus-pt);\n }\n .animus-dyn-py {\n padding-top: var(--animus-py);\n padding-bottom: var(--animus-py);\n }\n @media (min-width: 640px) {\n .animus-dyn-pt-sm {\n padding-top: var(--animus-pt-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-py-sm {\n padding-top: var(--animus-py-sm);\n padding-bottom: var(--animus-py-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pt-md {\n padding-top: var(--animus-pt-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-py-md {\n padding-top: var(--animus-py-md);\n padding-bottom: var(--animus-py-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pt-lg {\n padding-top: var(--animus-pt-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-py-lg {\n padding-top: var(--animus-py-lg);\n padding-bottom: var(--animus-py-lg);\n }\n }\n .animus-dyn-pos {\n position: var(--animus-pos);\n }\n .animus-dyn-position {\n position: var(--animus-position);\n }\n @media (min-width: 640px) {\n .animus-dyn-pos-sm {\n position: var(--animus-pos-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-position-sm {\n position: var(--animus-position-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pos-md {\n position: var(--animus-pos-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-position-md {\n position: var(--animus-position-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pos-lg {\n position: var(--animus-pos-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-position-lg {\n position: var(--animus-position-lg);\n }\n }\n .animus-dyn-right {\n right: var(--animus-right);\n }\n @media (min-width: 640px) {\n .animus-dyn-right-sm {\n right: var(--animus-right-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-right-md {\n right: var(--animus-right-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-right-lg {\n right: var(--animus-right-lg);\n }\n }\n .animus-dyn-row-gap {\n row-gap: var(--animus-row-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-row-gap-sm {\n row-gap: var(--animus-row-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-row-gap-md {\n row-gap: var(--animus-row-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-row-gap-lg {\n row-gap: var(--animus-row-gap-lg);\n }\n }\n .animus-dyn-inset {\n top: var(--animus-inset);\n right: var(--animus-inset);\n bottom: var(--animus-inset);\n left: var(--animus-inset);\n }\n .animus-dyn-top {\n top: var(--animus-top);\n }\n @media (min-width: 640px) {\n .animus-dyn-inset-sm {\n top: var(--animus-inset-sm);\n right: var(--animus-inset-sm);\n bottom: var(--animus-inset-sm);\n left: var(--animus-inset-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-top-sm {\n top: var(--animus-top-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-inset-md {\n top: var(--animus-inset-md);\n right: var(--animus-inset-md);\n bottom: var(--animus-inset-md);\n left: var(--animus-inset-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-top-md {\n top: var(--animus-top-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-inset-lg {\n top: var(--animus-inset-lg);\n right: var(--animus-inset-lg);\n bottom: var(--animus-inset-lg);\n left: var(--animus-inset-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-top-lg {\n top: var(--animus-top-lg);\n }\n }\n .animus-dyn-vertical-align {\n vertical-align: var(--animus-vertical-align);\n }\n @media (min-width: 640px) {\n .animus-dyn-vertical-align-sm {\n vertical-align: var(--animus-vertical-align-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-vertical-align-md {\n vertical-align: var(--animus-vertical-align-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-vertical-align-lg {\n vertical-align: var(--animus-vertical-align-lg);\n }\n }\n .animus-dyn-size {\n width: var(--animus-size);\n height: var(--animus-size);\n }\n .animus-dyn-w {\n width: var(--animus-w);\n }\n .animus-dyn-width {\n width: var(--animus-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-size-sm {\n width: var(--animus-size-sm);\n height: var(--animus-size-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-w-sm {\n width: var(--animus-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-width-sm {\n width: var(--animus-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-size-md {\n width: var(--animus-size-md);\n height: var(--animus-size-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-w-md {\n width: var(--animus-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-width-md {\n width: var(--animus-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-size-lg {\n width: var(--animus-size-lg);\n height: var(--animus-size-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-w-lg {\n width: var(--animus-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-width-lg {\n width: var(--animus-width-lg);\n }\n }\n .animus-dyn-z-index {\n z-index: var(--animus-z-index);\n }\n @media (min-width: 640px) {\n .animus-dyn-z-index-sm {\n z-index: var(--animus-z-index-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-z-index-md {\n z-index: var(--animus-z-index-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-z-index-lg {\n z-index: var(--animus-z-index-lg);\n }\n }\n}\n\n","sheets":{"declaration":"@layer anm-global, anm-base, anm-variants, anm-compounds, anm-states, anm-system, anm-custom;\n","global":"@layer anm-global {\n@font-face { font-family: AnimusTestFont; src: url('animus-asset:@animus-ui/test-ds/assets/test-font.woff2') format('woff2'); font-display: swap; }\n\n*, *::before, *::after {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n background-color: var(--color-background);\n --current-bg: var(--color-background);\n color: var(--color-text);\n font-family: system-ui, sans-serif;\n}\n@keyframes animus-kf-1x7guim {\n 0% {\n opacity: 0;\n background-color: var(--color-background);\n --current-bg: var(--color-background);\n }\n 100% {\n opacity: 1;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n }\n}\n\n@keyframes animus-kf-1yqv0zl {\n 0%, 100% {\n transform: scale(1);\n }\n 50% {\n transform: scale(1.05);\n }\n}\n\n@keyframes animus-kf-muo7kp {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.6;\n }\n}\n}\n","base":"@layer anm-base {\n .animus-Alert-a385f997 {\n padding: 0.75rem;\n display: flex;\n align-items: flex-start;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n }\n .animus-Badge-99781d29 {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 500;\n line-height: 1;\n }\n .animus-Card-9aa7af5d {\n padding: 1rem;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n border-radius: 8px;\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n @container card (min-width: 400px) {\n .animus-Card-9aa7af5d {\n padding: 1.5rem;\n width: 50cqw;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .animus-Card-9aa7af5d {\n transition: none;\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d {\n display: grid;\n font-size: 0.875rem;\n }\n }\n @supports (display: grid) {\n @media (min-width: 640px) {\n .animus-Card-9aa7af5d {\n font-size: 1rem;\n }\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d:focus-visible {\n outline: 2px solid;\n }\n }\n @supports (display: grid) {\n @container card (min-width: 600px) {\n .animus-Card-9aa7af5d {\n gap: 2cqi;\n }\n }\n }\n .animus-ContainerCardBody-133c6ad9 {\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--color-text);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardBody-133c6ad9 {\n font-size: 1rem;\n }\n }\n .animus-ContainerCardMedia-51db5d07 {\n display: block;\n width: 100%;\n min-height: 64px;\n border-radius: 4px;\n background: var(--current-bg);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardMedia-51db5d07 {\n min-height: 120px;\n width: 50cqw;\n }\n }\n .animus-ContainerCardRoot-01c5b011 {\n gap: 0.5rem;\n padding: 1rem;\n display: flex;\n flex-direction: column;\n border-radius: 8px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n .animus-GroupItem-32b2d32f {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 4px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n [data-active=\"true\"] .animus-GroupItem-32b2d32f {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n color: var(--color-background);\n }\n .group:hover .animus-GroupItem-32b2d32f {\n opacity: 0.9;\n }\n [data-color-mode=\"dark\"] .animus-GroupItem-32b2d32f {\n color: var(--color-text-muted);\n }\n .animus-Box-399302cf {\n display: flex;\n position: relative;\n }\n .animus-Button-c63b6dcd {\n border: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n font-weight: 600;\n line-height: 1;\n cursor: pointer;\n }\n .animus-Button-b3718a43 {\n border-radius: 4px;\n padding: 8px;\n background-color: var(--color-blue-500);\n }\n}\n","variants":"@layer anm-variants {\n @layer standalone, composed;\n @layer standalone {\n\n .animus-Alert-a385f997--variant-filled {\n color: var(--color-background);\n }\n .animus-Alert-a385f997--variant-outline {\n border-width: 1px;\n border-style: solid;\n background-color: transparent;\n --current-bg: transparent;\n }\n .animus-Alert-a385f997--intent-info {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n }\n .animus-Alert-a385f997--intent-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n }\n .animus-Alert-a385f997--intent-success {\n background-color: var(--color-secondary);\n --current-bg: var(--color-secondary);\n }\n .animus-Badge-99781d29--color-neutral {\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n .animus-Badge-99781d29--color-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n color: var(--color-background);\n }\n .animus-ContainerCardRoot-01c5b011--size-lg {\n padding: 1.5rem;\n }\n .animus-Button-c63b6dcd--variant-primary {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n color: var(--color-background);\n }\n .animus-Button-c63b6dcd--variant-secondary {\n background-color: var(--color-secondary);\n --current-bg: var(--color-secondary);\n color: var(--color-background);\n }\n .animus-Button-c63b6dcd--variant-ghost {\n background-color: transparent;\n --current-bg: transparent;\n color: var(--color-text);\n }\n .animus-Button-b3718a43--tone-quiet {\n background-color: var(--color-gray-700);\n }\n .animus-Button-b3718a43--tone-loud {\n font-weight: 700;\n background-color: var(--color-blue-700);\n }\n }\n @layer composed {\n }\n}\n","compounds":"@layer anm-compounds {\n .animus-Alert-a385f997--compound-0 {\n border-color: var(--color-primary);\n color: var(--color-primary);\n }\n .animus-Alert-a385f997--compound-1 {\n border-color: var(--color-danger);\n color: var(--color-danger);\n }\n .animus-Alert-a385f997--compound-2 {\n border-color: var(--color-secondary);\n color: var(--color-secondary);\n }\n}\n","states":"@layer anm-states {\n .animus-Badge-99781d29--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .animus-Badge-99781d29--active {\n outline: 2px solid;\n outline-color: var(--color-primary);\n }\n}\n","system":"@layer anm-system {\n .animus-dyn-flex {\n flex: var(--animus-flex);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-sm {\n flex: var(--animus-flex-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-md {\n flex: var(--animus-flex-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-lg {\n flex: var(--animus-flex-lg);\n }\n }\n .animus-dyn-m {\n margin: var(--animus-m);\n }\n @media (min-width: 640px) {\n .animus-dyn-m-sm {\n margin: var(--animus-m-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-m-md {\n margin: var(--animus-m-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-m-lg {\n margin: var(--animus-m-lg);\n }\n }\n .animus-dyn-p {\n padding: var(--animus-p);\n }\n @media (min-width: 640px) {\n .animus-dyn-p-sm {\n padding: var(--animus-p-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-p-md {\n padding: var(--animus-p-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-p-lg {\n padding: var(--animus-p-lg);\n }\n }\n .animus-dyn-gap {\n gap: var(--animus-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-gap-sm {\n gap: var(--animus-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-gap-md {\n gap: var(--animus-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-gap-lg {\n gap: var(--animus-gap-lg);\n }\n }\n .animus-dyn-area {\n grid-area: var(--animus-area);\n }\n .animus-dyn-grid-area {\n grid-area: var(--animus-grid-area);\n }\n @media (min-width: 640px) {\n .animus-dyn-area-sm {\n grid-area: var(--animus-area-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-area-sm {\n grid-area: var(--animus-grid-area-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-area-md {\n grid-area: var(--animus-area-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-area-md {\n grid-area: var(--animus-grid-area-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-area-lg {\n grid-area: var(--animus-area-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-area-lg {\n grid-area: var(--animus-grid-area-lg);\n }\n }\n .animus-dyn-grid-column {\n grid-column: var(--animus-grid-column);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-sm {\n grid-column: var(--animus-grid-column-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-md {\n grid-column: var(--animus-grid-column-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-lg {\n grid-column: var(--animus-grid-column-lg);\n }\n }\n .animus-dyn-grid-row {\n grid-row: var(--animus-grid-row);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-sm {\n grid-row: var(--animus-grid-row-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-md {\n grid-row: var(--animus-grid-row-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-lg {\n grid-row: var(--animus-grid-row-lg);\n }\n }\n .animus-dyn-overflow {\n overflow: var(--animus-overflow);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-sm {\n overflow: var(--animus-overflow-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-md {\n overflow: var(--animus-overflow-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-lg {\n overflow: var(--animus-overflow-lg);\n }\n }\n .animus-dyn-align-content {\n align-content: var(--animus-align-content);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-content-sm {\n align-content: var(--animus-align-content-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-content-md {\n align-content: var(--animus-align-content-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-content-lg {\n align-content: var(--animus-align-content-lg);\n }\n }\n .animus-dyn-align-items {\n align-items: var(--animus-align-items);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-items-sm {\n align-items: var(--animus-align-items-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-items-md {\n align-items: var(--animus-align-items-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-items-lg {\n align-items: var(--animus-align-items-lg);\n }\n }\n .animus-dyn-align-self {\n align-self: var(--animus-align-self);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-self-sm {\n align-self: var(--animus-align-self-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-self-md {\n align-self: var(--animus-align-self-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-self-lg {\n align-self: var(--animus-align-self-lg);\n }\n }\n .animus-dyn-bottom {\n bottom: var(--animus-bottom);\n }\n @media (min-width: 640px) {\n .animus-dyn-bottom-sm {\n bottom: var(--animus-bottom-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-bottom-md {\n bottom: var(--animus-bottom-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-bottom-lg {\n bottom: var(--animus-bottom-lg);\n }\n }\n .animus-dyn-column-gap {\n column-gap: var(--animus-column-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-column-gap-sm {\n column-gap: var(--animus-column-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-column-gap-md {\n column-gap: var(--animus-column-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-column-gap-lg {\n column-gap: var(--animus-column-gap-lg);\n }\n }\n .animus-dyn-display {\n display: var(--animus-display);\n }\n @media (min-width: 640px) {\n .animus-dyn-display-sm {\n display: var(--animus-display-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-display-md {\n display: var(--animus-display-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-display-lg {\n display: var(--animus-display-lg);\n }\n }\n .animus-dyn-flex-basis {\n flex-basis: var(--animus-flex-basis);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-basis-sm {\n flex-basis: var(--animus-flex-basis-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-basis-md {\n flex-basis: var(--animus-flex-basis-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-basis-lg {\n flex-basis: var(--animus-flex-basis-lg);\n }\n }\n .animus-dyn-flex-dir {\n flex-direction: var(--animus-flex-dir);\n }\n .animus-dyn-flex-direction {\n flex-direction: var(--animus-flex-direction);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-dir-sm {\n flex-direction: var(--animus-flex-dir-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-direction-sm {\n flex-direction: var(--animus-flex-direction-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-dir-md {\n flex-direction: var(--animus-flex-dir-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-direction-md {\n flex-direction: var(--animus-flex-direction-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-dir-lg {\n flex-direction: var(--animus-flex-dir-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-direction-lg {\n flex-direction: var(--animus-flex-direction-lg);\n }\n }\n .animus-dyn-flex-grow {\n flex-grow: var(--animus-flex-grow);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-grow-sm {\n flex-grow: var(--animus-flex-grow-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-grow-md {\n flex-grow: var(--animus-flex-grow-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-grow-lg {\n flex-grow: var(--animus-flex-grow-lg);\n }\n }\n .animus-dyn-flex-shrink {\n flex-shrink: var(--animus-flex-shrink);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-shrink-sm {\n flex-shrink: var(--animus-flex-shrink-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-shrink-md {\n flex-shrink: var(--animus-flex-shrink-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-shrink-lg {\n flex-shrink: var(--animus-flex-shrink-lg);\n }\n }\n .animus-dyn-flex-wrap {\n flex-wrap: var(--animus-flex-wrap);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-wrap-sm {\n flex-wrap: var(--animus-flex-wrap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-wrap-md {\n flex-wrap: var(--animus-flex-wrap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-wrap-lg {\n flex-wrap: var(--animus-flex-wrap-lg);\n }\n }\n .animus-dyn-font-size {\n font-size: var(--animus-font-size);\n }\n @media (min-width: 640px) {\n .animus-dyn-font-size-sm {\n font-size: var(--animus-font-size-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-font-size-md {\n font-size: var(--animus-font-size-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-font-size-lg {\n font-size: var(--animus-font-size-lg);\n }\n }\n .animus-dyn-grid-column-end {\n grid-column-end: var(--animus-grid-column-end);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-end-sm {\n grid-column-end: var(--animus-grid-column-end-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-end-md {\n grid-column-end: var(--animus-grid-column-end-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-end-lg {\n grid-column-end: var(--animus-grid-column-end-lg);\n }\n }\n .animus-dyn-grid-column-start {\n grid-column-start: var(--animus-grid-column-start);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-start-sm {\n grid-column-start: var(--animus-grid-column-start-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-start-md {\n grid-column-start: var(--animus-grid-column-start-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-start-lg {\n grid-column-start: var(--animus-grid-column-start-lg);\n }\n }\n .animus-dyn-grid-row-end {\n grid-row-end: var(--animus-grid-row-end);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-end-sm {\n grid-row-end: var(--animus-grid-row-end-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-end-md {\n grid-row-end: var(--animus-grid-row-end-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-end-lg {\n grid-row-end: var(--animus-grid-row-end-lg);\n }\n }\n .animus-dyn-grid-row-start {\n grid-row-start: var(--animus-grid-row-start);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-start-sm {\n grid-row-start: var(--animus-grid-row-start-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-start-md {\n grid-row-start: var(--animus-grid-row-start-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-start-lg {\n grid-row-start: var(--animus-grid-row-start-lg);\n }\n }\n .animus-dyn-h {\n height: var(--animus-h);\n }\n .animus-dyn-height {\n height: var(--animus-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-h-sm {\n height: var(--animus-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-height-sm {\n height: var(--animus-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-h-md {\n height: var(--animus-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-height-md {\n height: var(--animus-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-h-lg {\n height: var(--animus-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-height-lg {\n height: var(--animus-height-lg);\n }\n }\n .animus-dyn-justify-content {\n justify-content: var(--animus-justify-content);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-content-sm {\n justify-content: var(--animus-justify-content-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-content-md {\n justify-content: var(--animus-justify-content-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-content-lg {\n justify-content: var(--animus-justify-content-lg);\n }\n }\n .animus-dyn-justify-items {\n justify-items: var(--animus-justify-items);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-items-sm {\n justify-items: var(--animus-justify-items-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-items-md {\n justify-items: var(--animus-justify-items-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-items-lg {\n justify-items: var(--animus-justify-items-lg);\n }\n }\n .animus-dyn-justify-self {\n justify-self: var(--animus-justify-self);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-self-sm {\n justify-self: var(--animus-justify-self-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-self-md {\n justify-self: var(--animus-justify-self-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-self-lg {\n justify-self: var(--animus-justify-self-lg);\n }\n }\n .animus-dyn-left {\n left: var(--animus-left);\n }\n @media (min-width: 640px) {\n .animus-dyn-left-sm {\n left: var(--animus-left-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-left-md {\n left: var(--animus-left-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-left-lg {\n left: var(--animus-left-lg);\n }\n }\n .animus-dyn-mb {\n margin-bottom: var(--animus-mb);\n }\n @media (min-width: 640px) {\n .animus-dyn-mb-sm {\n margin-bottom: var(--animus-mb-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mb-md {\n margin-bottom: var(--animus-mb-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mb-lg {\n margin-bottom: var(--animus-mb-lg);\n }\n }\n .animus-dyn-ml {\n margin-left: var(--animus-ml);\n }\n .animus-dyn-mx {\n margin-left: var(--animus-mx);\n margin-right: var(--animus-mx);\n }\n @media (min-width: 640px) {\n .animus-dyn-ml-sm {\n margin-left: var(--animus-ml-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-mx-sm {\n margin-left: var(--animus-mx-sm);\n margin-right: var(--animus-mx-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-ml-md {\n margin-left: var(--animus-ml-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mx-md {\n margin-left: var(--animus-mx-md);\n margin-right: var(--animus-mx-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-ml-lg {\n margin-left: var(--animus-ml-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mx-lg {\n margin-left: var(--animus-mx-lg);\n margin-right: var(--animus-mx-lg);\n }\n }\n .animus-dyn-mr {\n margin-right: var(--animus-mr);\n }\n @media (min-width: 640px) {\n .animus-dyn-mr-sm {\n margin-right: var(--animus-mr-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mr-md {\n margin-right: var(--animus-mr-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mr-lg {\n margin-right: var(--animus-mr-lg);\n }\n }\n .animus-dyn-mt {\n margin-top: var(--animus-mt);\n }\n .animus-dyn-my {\n margin-top: var(--animus-my);\n margin-bottom: var(--animus-my);\n }\n @media (min-width: 640px) {\n .animus-dyn-mt-sm {\n margin-top: var(--animus-mt-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-my-sm {\n margin-top: var(--animus-my-sm);\n margin-bottom: var(--animus-my-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mt-md {\n margin-top: var(--animus-mt-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-my-md {\n margin-top: var(--animus-my-md);\n margin-bottom: var(--animus-my-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mt-lg {\n margin-top: var(--animus-mt-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-my-lg {\n margin-top: var(--animus-my-lg);\n margin-bottom: var(--animus-my-lg);\n }\n }\n .animus-dyn-max-h {\n max-height: var(--animus-max-h);\n }\n .animus-dyn-max-height {\n max-height: var(--animus-max-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-max-h-sm {\n max-height: var(--animus-max-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-max-height-sm {\n max-height: var(--animus-max-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-h-md {\n max-height: var(--animus-max-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-height-md {\n max-height: var(--animus-max-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-h-lg {\n max-height: var(--animus-max-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-height-lg {\n max-height: var(--animus-max-height-lg);\n }\n }\n .animus-dyn-max-w {\n max-width: var(--animus-max-w);\n }\n .animus-dyn-max-width {\n max-width: var(--animus-max-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-max-w-sm {\n max-width: var(--animus-max-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-max-width-sm {\n max-width: var(--animus-max-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-w-md {\n max-width: var(--animus-max-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-width-md {\n max-width: var(--animus-max-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-w-lg {\n max-width: var(--animus-max-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-width-lg {\n max-width: var(--animus-max-width-lg);\n }\n }\n .animus-dyn-min-h {\n min-height: var(--animus-min-h);\n }\n .animus-dyn-min-height {\n min-height: var(--animus-min-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-min-h-sm {\n min-height: var(--animus-min-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-min-height-sm {\n min-height: var(--animus-min-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-h-md {\n min-height: var(--animus-min-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-height-md {\n min-height: var(--animus-min-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-h-lg {\n min-height: var(--animus-min-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-height-lg {\n min-height: var(--animus-min-height-lg);\n }\n }\n .animus-dyn-min-w {\n min-width: var(--animus-min-w);\n }\n .animus-dyn-min-width {\n min-width: var(--animus-min-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-min-w-sm {\n min-width: var(--animus-min-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-min-width-sm {\n min-width: var(--animus-min-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-w-md {\n min-width: var(--animus-min-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-width-md {\n min-width: var(--animus-min-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-w-lg {\n min-width: var(--animus-min-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-width-lg {\n min-width: var(--animus-min-width-lg);\n }\n }\n .animus-dyn-opacity {\n opacity: var(--animus-opacity);\n }\n @media (min-width: 640px) {\n .animus-dyn-opacity-sm {\n opacity: var(--animus-opacity-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-opacity-md {\n opacity: var(--animus-opacity-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-opacity-lg {\n opacity: var(--animus-opacity-lg);\n }\n }\n .animus-dyn-order {\n order: var(--animus-order);\n }\n @media (min-width: 640px) {\n .animus-dyn-order-sm {\n order: var(--animus-order-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-order-md {\n order: var(--animus-order-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-order-lg {\n order: var(--animus-order-lg);\n }\n }\n .animus-dyn-overflow-x {\n overflow-x: var(--animus-overflow-x);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-x-sm {\n overflow-x: var(--animus-overflow-x-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-x-md {\n overflow-x: var(--animus-overflow-x-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-x-lg {\n overflow-x: var(--animus-overflow-x-lg);\n }\n }\n .animus-dyn-overflow-y {\n overflow-y: var(--animus-overflow-y);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-y-sm {\n overflow-y: var(--animus-overflow-y-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-y-md {\n overflow-y: var(--animus-overflow-y-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-y-lg {\n overflow-y: var(--animus-overflow-y-lg);\n }\n }\n .animus-dyn-pb {\n padding-bottom: var(--animus-pb);\n }\n @media (min-width: 640px) {\n .animus-dyn-pb-sm {\n padding-bottom: var(--animus-pb-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pb-md {\n padding-bottom: var(--animus-pb-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pb-lg {\n padding-bottom: var(--animus-pb-lg);\n }\n }\n .animus-dyn-pl {\n padding-left: var(--animus-pl);\n }\n .animus-dyn-px {\n padding-left: var(--animus-px);\n padding-right: var(--animus-px);\n }\n @media (min-width: 640px) {\n .animus-dyn-pl-sm {\n padding-left: var(--animus-pl-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-px-sm {\n padding-left: var(--animus-px-sm);\n padding-right: var(--animus-px-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pl-md {\n padding-left: var(--animus-pl-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-px-md {\n padding-left: var(--animus-px-md);\n padding-right: var(--animus-px-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pl-lg {\n padding-left: var(--animus-pl-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-px-lg {\n padding-left: var(--animus-px-lg);\n padding-right: var(--animus-px-lg);\n }\n }\n .animus-dyn-pr {\n padding-right: var(--animus-pr);\n }\n @media (min-width: 640px) {\n .animus-dyn-pr-sm {\n padding-right: var(--animus-pr-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pr-md {\n padding-right: var(--animus-pr-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pr-lg {\n padding-right: var(--animus-pr-lg);\n }\n }\n .animus-dyn-pt {\n padding-top: var(--animus-pt);\n }\n .animus-dyn-py {\n padding-top: var(--animus-py);\n padding-bottom: var(--animus-py);\n }\n @media (min-width: 640px) {\n .animus-dyn-pt-sm {\n padding-top: var(--animus-pt-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-py-sm {\n padding-top: var(--animus-py-sm);\n padding-bottom: var(--animus-py-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pt-md {\n padding-top: var(--animus-pt-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-py-md {\n padding-top: var(--animus-py-md);\n padding-bottom: var(--animus-py-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pt-lg {\n padding-top: var(--animus-pt-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-py-lg {\n padding-top: var(--animus-py-lg);\n padding-bottom: var(--animus-py-lg);\n }\n }\n .animus-dyn-pos {\n position: var(--animus-pos);\n }\n .animus-dyn-position {\n position: var(--animus-position);\n }\n @media (min-width: 640px) {\n .animus-dyn-pos-sm {\n position: var(--animus-pos-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-position-sm {\n position: var(--animus-position-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pos-md {\n position: var(--animus-pos-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-position-md {\n position: var(--animus-position-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pos-lg {\n position: var(--animus-pos-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-position-lg {\n position: var(--animus-position-lg);\n }\n }\n .animus-dyn-right {\n right: var(--animus-right);\n }\n @media (min-width: 640px) {\n .animus-dyn-right-sm {\n right: var(--animus-right-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-right-md {\n right: var(--animus-right-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-right-lg {\n right: var(--animus-right-lg);\n }\n }\n .animus-dyn-row-gap {\n row-gap: var(--animus-row-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-row-gap-sm {\n row-gap: var(--animus-row-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-row-gap-md {\n row-gap: var(--animus-row-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-row-gap-lg {\n row-gap: var(--animus-row-gap-lg);\n }\n }\n .animus-dyn-inset {\n top: var(--animus-inset);\n right: var(--animus-inset);\n bottom: var(--animus-inset);\n left: var(--animus-inset);\n }\n .animus-dyn-top {\n top: var(--animus-top);\n }\n @media (min-width: 640px) {\n .animus-dyn-inset-sm {\n top: var(--animus-inset-sm);\n right: var(--animus-inset-sm);\n bottom: var(--animus-inset-sm);\n left: var(--animus-inset-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-top-sm {\n top: var(--animus-top-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-inset-md {\n top: var(--animus-inset-md);\n right: var(--animus-inset-md);\n bottom: var(--animus-inset-md);\n left: var(--animus-inset-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-top-md {\n top: var(--animus-top-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-inset-lg {\n top: var(--animus-inset-lg);\n right: var(--animus-inset-lg);\n bottom: var(--animus-inset-lg);\n left: var(--animus-inset-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-top-lg {\n top: var(--animus-top-lg);\n }\n }\n .animus-dyn-vertical-align {\n vertical-align: var(--animus-vertical-align);\n }\n @media (min-width: 640px) {\n .animus-dyn-vertical-align-sm {\n vertical-align: var(--animus-vertical-align-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-vertical-align-md {\n vertical-align: var(--animus-vertical-align-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-vertical-align-lg {\n vertical-align: var(--animus-vertical-align-lg);\n }\n }\n .animus-dyn-size {\n width: var(--animus-size);\n height: var(--animus-size);\n }\n .animus-dyn-w {\n width: var(--animus-w);\n }\n .animus-dyn-width {\n width: var(--animus-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-size-sm {\n width: var(--animus-size-sm);\n height: var(--animus-size-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-w-sm {\n width: var(--animus-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-width-sm {\n width: var(--animus-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-size-md {\n width: var(--animus-size-md);\n height: var(--animus-size-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-w-md {\n width: var(--animus-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-width-md {\n width: var(--animus-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-size-lg {\n width: var(--animus-size-lg);\n height: var(--animus-size-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-w-lg {\n width: var(--animus-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-width-lg {\n width: var(--animus-width-lg);\n }\n }\n .animus-dyn-z-index {\n z-index: var(--animus-z-index);\n }\n @media (min-width: 640px) {\n .animus-dyn-z-index-sm {\n z-index: var(--animus-z-index-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-z-index-md {\n z-index: var(--animus-z-index-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-z-index-lg {\n z-index: var(--animus-z-index-lg);\n }\n }\n}\n","custom":""},"diagnostics":[{"file":"../../packages/test-ds/src/components/Alert.tsx","component":"Alert","kind":"external-token-candidate","message":"'space.0.75rem' in 'padding' did not resolve against the consumer theme","token":"space.0.75rem"},{"file":"../../packages/test-ds/src/components/Alert.tsx","component":"Alert","kind":"external-token-candidate","message":"'radii.4px' in 'border-radius' did not resolve against the consumer theme","token":"radii.4px"},{"file":"../../packages/test-ds/src/components/Alert.tsx","component":"Alert","kind":"external-token-candidate","message":"'fontSizes.0.875rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.0.875rem"},{"file":"../../packages/test-ds/src/components/Alert.tsx","component":"Alert","kind":"external-token-candidate","message":"'lineHeights.1.5' in 'line-height' did not resolve against the consumer theme","token":"lineHeights.1.5"},{"file":"../../packages/test-ds/src/components/Alert.tsx","component":"Alert","kind":"external-token-candidate","message":"'borderWidths.1px' in 'border-width' did not resolve against the consumer theme","token":"borderWidths.1px"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'space.0.5rem' in 'padding-left' did not resolve against the consumer theme","token":"space.0.5rem"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'space.0.5rem' in 'padding-right' did not resolve against the consumer theme","token":"space.0.5rem"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'space.0.25rem' in 'padding-top' did not resolve against the consumer theme","token":"space.0.25rem"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'space.0.25rem' in 'padding-bottom' did not resolve against the consumer theme","token":"space.0.25rem"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'radii.9999px' in 'border-radius' did not resolve against the consumer theme","token":"radii.9999px"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'fontSizes.0.75rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.0.75rem"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'fontWeights.500' in 'font-weight' did not resolve against the consumer theme","token":"fontWeights.500"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'lineHeights.1' in 'line-height' did not resolve against the consumer theme","token":"lineHeights.1"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'opacities.0.5' in 'opacity' did not resolve against the consumer theme","token":"opacities.0.5"},{"file":"../../packages/test-ds/src/components/Button.tsx","component":"Button","kind":"external-token-candidate","message":"'radii.4px' in 'border-radius' did not resolve against the consumer theme","token":"radii.4px"},{"file":"../../packages/test-ds/src/components/Button.tsx","component":"Button","kind":"external-token-candidate","message":"'fontWeights.600' in 'font-weight' did not resolve against the consumer theme","token":"fontWeights.600"},{"file":"../../packages/test-ds/src/components/Button.tsx","component":"Button","kind":"external-token-candidate","message":"'lineHeights.1' in 'line-height' did not resolve against the consumer theme","token":"lineHeights.1"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'space.1rem' in 'padding' did not resolve against the consumer theme","token":"space.1rem"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'radii.8px' in 'border-radius' did not resolve against the consumer theme","token":"radii.8px"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'space.1.5rem' in 'padding' did not resolve against the consumer theme","token":"space.1.5rem"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'fontSizes.0.875rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.0.875rem"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'fontSizes.1rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.1rem"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'space.2cqi' in 'gap' did not resolve against the consumer theme","token":"space.2cqi"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardBody","kind":"external-token-candidate","message":"'fontSizes.0.875rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.0.875rem"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardBody","kind":"external-token-candidate","message":"'lineHeights.1.5' in 'line-height' did not resolve against the consumer theme","token":"lineHeights.1.5"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardBody","kind":"external-token-candidate","message":"'fontSizes.1rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.1rem"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardMedia","kind":"external-token-candidate","message":"'radii.4px' in 'border-radius' did not resolve against the consumer theme","token":"radii.4px"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardRoot","kind":"external-token-candidate","message":"'space.0.5rem' in 'gap' did not resolve against the consumer theme","token":"space.0.5rem"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardRoot","kind":"external-token-candidate","message":"'space.1rem' in 'padding' did not resolve against the consumer theme","token":"space.1rem"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardRoot","kind":"external-token-candidate","message":"'radii.8px' in 'border-radius' did not resolve against the consumer theme","token":"radii.8px"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardRoot","kind":"external-token-candidate","message":"'space.1.5rem' in 'padding' did not resolve against the consumer theme","token":"space.1.5rem"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'space.0.5rem' in 'padding-left' did not resolve against the consumer theme","token":"space.0.5rem"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'space.0.5rem' in 'padding-right' did not resolve against the consumer theme","token":"space.0.5rem"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'space.0.25rem' in 'padding-top' did not resolve against the consumer theme","token":"space.0.25rem"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'space.0.25rem' in 'padding-bottom' did not resolve against the consumer theme","token":"space.0.25rem"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'radii.4px' in 'border-radius' did not resolve against the consumer theme","token":"radii.4px"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'opacities.0.9' in 'opacity' did not resolve against the consumer theme","token":"opacities.0.9"}],"report":{"components_total":10,"components_extracted":10,"components_eliminated":0,"variants_total":16,"variants_used":16,"variants_eliminated":0,"states_total":2,"states_used":2,"states_eliminated":0,"components_forced":0,"variants_forced":0,"states_forced":0,"eliminated_details":[]},"system_prop_map":{},"dynamic_props":{"alignContent":{"varName":"--animus-align-content","slotClass":"animus-dyn-align-content","property":"alignContent","transformName":null,"transformFnSource":null,"scaleValues":{}},"alignItems":{"varName":"--animus-align-items","slotClass":"animus-dyn-align-items","property":"alignItems","transformName":null,"transformFnSource":null,"scaleValues":{}},"alignSelf":{"varName":"--animus-align-self","slotClass":"animus-dyn-align-self","property":"alignSelf","transformName":null,"transformFnSource":null,"scaleValues":{}},"area":{"varName":"--animus-area","slotClass":"animus-dyn-area","property":"gridArea","transformName":null,"transformFnSource":null,"scaleValues":{}},"bottom":{"varName":"--animus-bottom","slotClass":"animus-dyn-bottom","property":"bottom","transformName":"size","transformFnSource":null,"scaleValues":{}},"columnGap":{"varName":"--animus-column-gap","slotClass":"animus-dyn-column-gap","property":"columnGap","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"display":{"varName":"--animus-display","slotClass":"animus-dyn-display","property":"display","transformName":null,"transformFnSource":null,"scaleValues":{}},"flex":{"varName":"--animus-flex","slotClass":"animus-dyn-flex","property":"flex","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexBasis":{"varName":"--animus-flex-basis","slotClass":"animus-dyn-flex-basis","property":"flexBasis","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexDir":{"varName":"--animus-flex-dir","slotClass":"animus-dyn-flex-dir","property":"flexDirection","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexDirection":{"varName":"--animus-flex-direction","slotClass":"animus-dyn-flex-direction","property":"flexDirection","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexGrow":{"varName":"--animus-flex-grow","slotClass":"animus-dyn-flex-grow","property":"flexGrow","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexShrink":{"varName":"--animus-flex-shrink","slotClass":"animus-dyn-flex-shrink","property":"flexShrink","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexWrap":{"varName":"--animus-flex-wrap","slotClass":"animus-dyn-flex-wrap","property":"flexWrap","transformName":null,"transformFnSource":null,"scaleValues":{}},"fontSize":{"varName":"--animus-font-size","slotClass":"animus-dyn-font-size","property":"fontSize","transformName":null,"transformFnSource":null,"scaleValues":{"12":"0.75rem","14":"0.875rem","16":"1rem","20":"1.25rem","24":"1.5rem"}},"gap":{"varName":"--animus-gap","slotClass":"animus-dyn-gap","property":"gap","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"gridArea":{"varName":"--animus-grid-area","slotClass":"animus-dyn-grid-area","property":"gridArea","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridColumn":{"varName":"--animus-grid-column","slotClass":"animus-dyn-grid-column","property":"gridColumn","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridColumnEnd":{"varName":"--animus-grid-column-end","slotClass":"animus-dyn-grid-column-end","property":"gridColumnEnd","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridColumnStart":{"varName":"--animus-grid-column-start","slotClass":"animus-dyn-grid-column-start","property":"gridColumnStart","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridRow":{"varName":"--animus-grid-row","slotClass":"animus-dyn-grid-row","property":"gridRow","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridRowEnd":{"varName":"--animus-grid-row-end","slotClass":"animus-dyn-grid-row-end","property":"gridRowEnd","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridRowStart":{"varName":"--animus-grid-row-start","slotClass":"animus-dyn-grid-row-start","property":"gridRowStart","transformName":null,"transformFnSource":null,"scaleValues":{}},"h":{"varName":"--animus-h","slotClass":"animus-dyn-h","property":"height","transformName":"size","transformFnSource":null,"scaleValues":{}},"height":{"varName":"--animus-height","slotClass":"animus-dyn-height","property":"height","transformName":"size","transformFnSource":null,"scaleValues":{}},"inset":{"varName":"--animus-inset","slotClass":"animus-dyn-inset","property":"inset","properties":["top","right","bottom","left"],"transformName":"size","transformFnSource":null,"scaleValues":{}},"justifyContent":{"varName":"--animus-justify-content","slotClass":"animus-dyn-justify-content","property":"justifyContent","transformName":null,"transformFnSource":null,"scaleValues":{}},"justifyItems":{"varName":"--animus-justify-items","slotClass":"animus-dyn-justify-items","property":"justifyItems","transformName":null,"transformFnSource":null,"scaleValues":{}},"justifySelf":{"varName":"--animus-justify-self","slotClass":"animus-dyn-justify-self","property":"justifySelf","transformName":null,"transformFnSource":null,"scaleValues":{}},"left":{"varName":"--animus-left","slotClass":"animus-dyn-left","property":"left","transformName":"size","transformFnSource":null,"scaleValues":{}},"m":{"varName":"--animus-m","slotClass":"animus-dyn-m","property":"margin","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"maxH":{"varName":"--animus-max-h","slotClass":"animus-dyn-max-h","property":"maxHeight","transformName":"size","transformFnSource":null,"scaleValues":{}},"maxHeight":{"varName":"--animus-max-height","slotClass":"animus-dyn-max-height","property":"maxHeight","transformName":"size","transformFnSource":null,"scaleValues":{}},"maxW":{"varName":"--animus-max-w","slotClass":"animus-dyn-max-w","property":"maxWidth","transformName":"size","transformFnSource":null,"scaleValues":{}},"maxWidth":{"varName":"--animus-max-width","slotClass":"animus-dyn-max-width","property":"maxWidth","transformName":"size","transformFnSource":null,"scaleValues":{}},"mb":{"varName":"--animus-mb","slotClass":"animus-dyn-mb","property":"marginBottom","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"minH":{"varName":"--animus-min-h","slotClass":"animus-dyn-min-h","property":"minHeight","transformName":"size","transformFnSource":null,"scaleValues":{}},"minHeight":{"varName":"--animus-min-height","slotClass":"animus-dyn-min-height","property":"minHeight","transformName":"size","transformFnSource":null,"scaleValues":{}},"minW":{"varName":"--animus-min-w","slotClass":"animus-dyn-min-w","property":"minWidth","transformName":"size","transformFnSource":null,"scaleValues":{}},"minWidth":{"varName":"--animus-min-width","slotClass":"animus-dyn-min-width","property":"minWidth","transformName":"size","transformFnSource":null,"scaleValues":{}},"ml":{"varName":"--animus-ml","slotClass":"animus-dyn-ml","property":"marginLeft","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"mr":{"varName":"--animus-mr","slotClass":"animus-dyn-mr","property":"marginRight","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"mt":{"varName":"--animus-mt","slotClass":"animus-dyn-mt","property":"marginTop","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"mx":{"varName":"--animus-mx","slotClass":"animus-dyn-mx","property":"margin","properties":["marginLeft","marginRight"],"transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"my":{"varName":"--animus-my","slotClass":"animus-dyn-my","property":"margin","properties":["marginTop","marginBottom"],"transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"opacity":{"varName":"--animus-opacity","slotClass":"animus-dyn-opacity","property":"opacity","transformName":null,"transformFnSource":null,"scaleValues":{}},"order":{"varName":"--animus-order","slotClass":"animus-dyn-order","property":"order","transformName":null,"transformFnSource":null,"scaleValues":{}},"overflow":{"varName":"--animus-overflow","slotClass":"animus-dyn-overflow","property":"overflow","transformName":null,"transformFnSource":null,"scaleValues":{}},"overflowX":{"varName":"--animus-overflow-x","slotClass":"animus-dyn-overflow-x","property":"overflowX","transformName":null,"transformFnSource":null,"scaleValues":{}},"overflowY":{"varName":"--animus-overflow-y","slotClass":"animus-dyn-overflow-y","property":"overflowY","transformName":null,"transformFnSource":null,"scaleValues":{}},"p":{"varName":"--animus-p","slotClass":"animus-dyn-p","property":"padding","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"pb":{"varName":"--animus-pb","slotClass":"animus-dyn-pb","property":"paddingBottom","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"pl":{"varName":"--animus-pl","slotClass":"animus-dyn-pl","property":"paddingLeft","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"pos":{"varName":"--animus-pos","slotClass":"animus-dyn-pos","property":"position","transformName":null,"transformFnSource":null,"scaleValues":{}},"position":{"varName":"--animus-position","slotClass":"animus-dyn-position","property":"position","transformName":null,"transformFnSource":null,"scaleValues":{}},"pr":{"varName":"--animus-pr","slotClass":"animus-dyn-pr","property":"paddingRight","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"pt":{"varName":"--animus-pt","slotClass":"animus-dyn-pt","property":"paddingTop","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"px":{"varName":"--animus-px","slotClass":"animus-dyn-px","property":"padding","properties":["paddingLeft","paddingRight"],"transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"py":{"varName":"--animus-py","slotClass":"animus-dyn-py","property":"padding","properties":["paddingTop","paddingBottom"],"transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"right":{"varName":"--animus-right","slotClass":"animus-dyn-right","property":"right","transformName":"size","transformFnSource":null,"scaleValues":{}},"rowGap":{"varName":"--animus-row-gap","slotClass":"animus-dyn-row-gap","property":"rowGap","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"size":{"varName":"--animus-size","slotClass":"animus-dyn-size","property":"width","properties":["width","height"],"transformName":"size","transformFnSource":null,"scaleValues":{}},"top":{"varName":"--animus-top","slotClass":"animus-dyn-top","property":"top","transformName":"size","transformFnSource":null,"scaleValues":{}},"verticalAlign":{"varName":"--animus-vertical-align","slotClass":"animus-dyn-vertical-align","property":"verticalAlign","transformName":null,"transformFnSource":null,"scaleValues":{}},"w":{"varName":"--animus-w","slotClass":"animus-dyn-w","property":"width","transformName":"size","transformFnSource":null,"scaleValues":{}},"width":{"varName":"--animus-width","slotClass":"animus-dyn-width","property":"width","transformName":"size","transformFnSource":null,"scaleValues":{}},"zIndex":{"varName":"--animus-z-index","slotClass":"animus-dyn-z-index","property":"zIndex","transformName":null,"transformFnSource":null,"scaleValues":{}}},"component_fragments":{"../../packages/test-ds/src/components/Alert.tsx::Alert":{"base":" .animus-Alert-a385f997 {\n padding: 0.75rem;\n display: flex;\n align-items: flex-start;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n }\n","variants":" .animus-Alert-a385f997--variant-filled {\n color: var(--color-background);\n }\n .animus-Alert-a385f997--variant-outline {\n border-width: 1px;\n border-style: solid;\n background-color: transparent;\n --current-bg: transparent;\n }\n .animus-Alert-a385f997--intent-info {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n }\n .animus-Alert-a385f997--intent-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n }\n .animus-Alert-a385f997--intent-success {\n background-color: var(--color-secondary);\n --current-bg: var(--color-secondary);\n }\n","compounds":" .animus-Alert-a385f997--compound-0 {\n border-color: var(--color-primary);\n color: var(--color-primary);\n }\n .animus-Alert-a385f997--compound-1 {\n border-color: var(--color-danger);\n color: var(--color-danger);\n }\n .animus-Alert-a385f997--compound-2 {\n border-color: var(--color-secondary);\n color: var(--color-secondary);\n }\n"},"../../packages/test-ds/src/components/Badge.tsx::Badge":{"base":" .animus-Badge-99781d29 {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 500;\n line-height: 1;\n }\n","variants":" .animus-Badge-99781d29--color-neutral {\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n .animus-Badge-99781d29--color-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n color: var(--color-background);\n }\n","states":" .animus-Badge-99781d29--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .animus-Badge-99781d29--active {\n outline: 2px solid;\n outline-color: var(--color-primary);\n }\n"},"../../packages/test-ds/src/components/Card.tsx::Card":{"base":" .animus-Card-9aa7af5d {\n padding: 1rem;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n border-radius: 8px;\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n @container card (min-width: 400px) {\n .animus-Card-9aa7af5d {\n padding: 1.5rem;\n width: 50cqw;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .animus-Card-9aa7af5d {\n transition: none;\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d {\n display: grid;\n font-size: 0.875rem;\n }\n }\n @supports (display: grid) {\n @media (min-width: 640px) {\n .animus-Card-9aa7af5d {\n font-size: 1rem;\n }\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d:focus-visible {\n outline: 2px solid;\n }\n }\n @supports (display: grid) {\n @container card (min-width: 600px) {\n .animus-Card-9aa7af5d {\n gap: 2cqi;\n }\n }\n }\n"},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardBody":{"base":" .animus-ContainerCardBody-133c6ad9 {\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--color-text);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardBody-133c6ad9 {\n font-size: 1rem;\n }\n }\n"},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardMedia":{"base":" .animus-ContainerCardMedia-51db5d07 {\n display: block;\n width: 100%;\n min-height: 64px;\n border-radius: 4px;\n background: var(--current-bg);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardMedia-51db5d07 {\n min-height: 120px;\n width: 50cqw;\n }\n }\n"},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardRoot":{"base":" .animus-ContainerCardRoot-01c5b011 {\n gap: 0.5rem;\n padding: 1rem;\n display: flex;\n flex-direction: column;\n border-radius: 8px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n","variants":" .animus-ContainerCardRoot-01c5b011--size-lg {\n padding: 1.5rem;\n }\n"},"../../packages/test-ds/src/components/GroupItem.tsx::GroupItem":{"base":" .animus-GroupItem-32b2d32f {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 4px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n [data-active=\"true\"] .animus-GroupItem-32b2d32f {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n color: var(--color-background);\n }\n .group:hover .animus-GroupItem-32b2d32f {\n opacity: 0.9;\n }\n [data-color-mode=\"dark\"] .animus-GroupItem-32b2d32f {\n color: var(--color-text-muted);\n }\n"},"src/Box.tsx::Box":{"base":" .animus-Box-399302cf {\n display: flex;\n position: relative;\n }\n"},"src/Button.tsx::Button":{"base":" .animus-Button-b3718a43 {\n border-radius: 4px;\n padding: 8px;\n background-color: var(--color-blue-500);\n }\n","variants":" .animus-Button-b3718a43--tone-quiet {\n background-color: var(--color-gray-700);\n }\n .animus-Button-b3718a43--tone-loud {\n font-weight: 700;\n background-color: var(--color-blue-700);\n }\n"}},"reverse_provenance":{},"components":{"../../packages/test-ds/src/components/Alert.tsx::Alert":{"file":"../../packages/test-ds/src/components/Alert.tsx","binding":"Alert","class_name":"animus-Alert-a385f997","extends_from":null,"terminal":"asElement","tag":"div","replacement":"createComponent('div', 'animus-Alert-a385f997', {\"variants\":{\"variant\":{\"options\":[\"filled\",\"outline\"]},\"intent\":{\"options\":[\"info\",\"danger\",\"success\"]}},\"compounds\":[{\"conditions\":{\"intent\":\"info\",\"variant\":\"outline\"},\"className\":\"animus-Alert-a385f997--compound-0\"},{\"conditions\":{\"intent\":\"danger\",\"variant\":\"outline\"},\"className\":\"animus-Alert-a385f997--compound-1\"},{\"conditions\":{\"intent\":\"success\",\"variant\":\"outline\"},\"className\":\"animus-Alert-a385f997--compound-2\"}]})","system_prop_names":[]},"../../packages/test-ds/src/components/Badge.tsx::Badge":{"file":"../../packages/test-ds/src/components/Badge.tsx","binding":"Badge","class_name":"animus-Badge-99781d29","extends_from":null,"terminal":"asElement","tag":"span","replacement":"createComponent('span', 'animus-Badge-99781d29', {\"variants\":{\"color\":{\"options\":[\"neutral\",\"danger\"]}},\"states\":[\"disabled\",\"active\"]})","system_prop_names":[]},"../../packages/test-ds/src/components/Button.tsx::Button":{"file":"../../packages/test-ds/src/components/Button.tsx","binding":"Button","class_name":"animus-Button-c63b6dcd","extends_from":null,"terminal":"asElement","tag":"button","replacement":"createComponent('button', 'animus-Button-c63b6dcd', {\"variants\":{\"variant\":{\"options\":[\"primary\",\"secondary\",\"ghost\"]}},\"systemPropNames\":[\"fontSize\",\"px\",\"py\"]}, systemPropMap, dynamicPropConfig)","system_prop_names":["fontSize","px","py"]},"../../packages/test-ds/src/components/Card.tsx::Card":{"file":"../../packages/test-ds/src/components/Card.tsx","binding":"Card","class_name":"animus-Card-9aa7af5d","extends_from":null,"terminal":"asElement","tag":"div","replacement":"createComponent('div', 'animus-Card-9aa7af5d', {\"systemPropNames\":[\"m\",\"mx\",\"my\"]}, systemPropMap, dynamicPropConfig)","system_prop_names":["m","mx","my"]},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardBody":{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","binding":"ContainerCardBody","class_name":"animus-ContainerCardBody-133c6ad9","extends_from":null,"terminal":"asElement","tag":"div","replacement":"createComponent('div', 'animus-ContainerCardBody-133c6ad9', {})","system_prop_names":[]},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardMedia":{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","binding":"ContainerCardMedia","class_name":"animus-ContainerCardMedia-51db5d07","extends_from":null,"terminal":"asElement","tag":"div","replacement":"createComponent('div', 'animus-ContainerCardMedia-51db5d07', {\"variants\":{\"size\":{\"options\":[\"md\",\"lg\"],\"default\":\"md\"}}})","system_prop_names":[]},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardRoot":{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","binding":"ContainerCardRoot","class_name":"animus-ContainerCardRoot-01c5b011","extends_from":null,"terminal":"asElement","tag":"article","replacement":"createComponent('article', 'animus-ContainerCardRoot-01c5b011', {\"variants\":{\"size\":{\"options\":[\"md\",\"lg\"],\"default\":\"md\"}}})","system_prop_names":[]},"../../packages/test-ds/src/components/GroupItem.tsx::GroupItem":{"file":"../../packages/test-ds/src/components/GroupItem.tsx","binding":"GroupItem","class_name":"animus-GroupItem-32b2d32f","extends_from":null,"terminal":"asElement","tag":"span","replacement":"createComponent('span', 'animus-GroupItem-32b2d32f', {})","system_prop_names":[]},"src/Box.tsx::Box":{"file":"src/Box.tsx","binding":"Box","class_name":"animus-Box-399302cf","extends_from":null,"terminal":"asElement","tag":"div","replacement":"createComponent('div', 'animus-Box-399302cf', {\"systemPropNames\":[].concat(systemPropGroups.layout,systemPropGroups.positioning,systemPropGroups.space)}, systemPropMap, dynamicPropConfig)","system_prop_names":["alignContent","alignItems","alignSelf","area","bottom","columnGap","display","flex","flexBasis","flexDir","flexDirection","flexGrow","flexShrink","flexWrap","gap","gridArea","gridColumn","gridColumnEnd","gridColumnStart","gridRow","gridRowEnd","gridRowStart","h","height","inset","justifyContent","justifyItems","justifySelf","left","m","maxH","maxHeight","maxW","maxWidth","mb","minH","minHeight","minW","minWidth","ml","mr","mt","mx","my","opacity","order","overflow","overflowX","overflowY","p","pb","pl","pos","position","pr","pt","px","py","right","rowGap","size","top","verticalAlign","w","width","zIndex"]},"src/Button.tsx::Button":{"file":"src/Button.tsx","binding":"Button","class_name":"animus-Button-b3718a43","extends_from":null,"terminal":"asElement","tag":"button","replacement":"createComponent('button', 'animus-Button-b3718a43', {\"variants\":{\"tone\":{\"options\":[\"quiet\",\"loud\"]}}})","system_prop_names":[]}},"files":{"../../packages/test-ds/src/components/Alert.tsx":["../../packages/test-ds/src/components/Alert.tsx::Alert"],"../../packages/test-ds/src/components/Badge.tsx":["../../packages/test-ds/src/components/Badge.tsx::Badge"],"../../packages/test-ds/src/components/Button.tsx":["../../packages/test-ds/src/components/Button.tsx::Button"],"../../packages/test-ds/src/components/Card.tsx":["../../packages/test-ds/src/components/Card.tsx::Card"],"../../packages/test-ds/src/components/ContainerCard.tsx":["../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardBody","../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardMedia","../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardRoot"],"../../packages/test-ds/src/components/GroupItem.tsx":["../../packages/test-ds/src/components/GroupItem.tsx::GroupItem"],"src/Box.tsx":["src/Box.tsx::Box"],"src/Button.tsx":["src/Button.tsx::Button"]},"timing":{"parseCount":17}} \ No newline at end of file +{"fileFacts":{"../../packages/test-ds/src/components/Alert.tsx":{"path":"../../packages/test-ds/src/components/Alert.tsx","chains":[{"className":"animus-Alert-a385f997","descriptor":{"binding":"Alert","terminal":"asElement","tag":"div","stages":[{"method":"styles","argSpan":[67,200],"secondArgSpan":null},{"method":"variant","argSpan":[213,381],"secondArgSpan":null},{"method":"variant","argSpan":[394,541],"secondArgSpan":null},{"method":"compound","argSpan":[560,598],"secondArgSpan":[604,648]},{"method":"compound","argSpan":[670,710],"secondArgSpan":[716,758]},{"method":"compound","argSpan":[780,821],"secondArgSpan":[827,875]}],"extractable":true,"bailReason":null,"span":[54,899],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"flex","alignItems":"flex-start","p":12,"borderRadius":"4px","fontSize":14,"lineHeight":"1.5"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"variant","defaultVariant":null,"base":null,"variants":{"filled":{"color":"background"},"outline":{"bg":"transparent","borderWidth":"1px","borderStyle":"solid"}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"intent","defaultVariant":null,"base":null,"variants":{"info":{"bg":"primary"},"danger":{"bg":"danger"},"success":{"bg":"secondary"}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"compound","value":{"variant":"outline","intent":"info"},"secondValue":{"borderColor":"primary","color":"primary"},"skipped":[],"captured":[],"evalError":null},{"method":"compound","value":{"variant":"outline","intent":"danger"},"secondValue":{"borderColor":"danger","color":"danger"},"skipped":[],"captured":[],"evalError":null},{"method":"compound","value":{"variant":"outline","intent":"success"},"secondValue":{"borderColor":"secondary","color":"secondary"},"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"Alert","local":"Alert","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/components/Badge.tsx":{"path":"../../packages/test-ds/src/components/Badge.tsx","chains":[{"className":"animus-Badge-99781d29","descriptor":{"binding":"Badge","terminal":"asElement","tag":"span","stages":[{"method":"styles","argSpan":[67,238],"secondArgSpan":null},{"method":"variant","argSpan":[251,400],"secondArgSpan":null},{"method":"states","argSpan":[412,537],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[54,559],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"inline-flex","alignItems":"center","px":8,"py":4,"borderRadius":"9999px","fontSize":12,"fontWeight":"500","lineHeight":"1"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"color","defaultVariant":null,"base":null,"variants":{"neutral":{"bg":"surface","color":"text"},"danger":{"bg":"danger","color":"background"}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"states","value":{"disabled":{"opacity":"0.5","cursor":"not-allowed"},"active":{"outline":"2px solid","outlineColor":"primary"}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"Badge","local":"Badge","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/components/Button.tsx":{"path":"../../packages/test-ds/src/components/Button.tsx","chains":[{"className":"animus-Button-c63b6dcd","descriptor":{"binding":"Button","terminal":"asElement","tag":"button","stages":[{"method":"styles","argSpan":[68,269],"secondArgSpan":null},{"method":"variant","argSpan":[282,496],"secondArgSpan":null},{"method":"system","argSpan":[508,546],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[55,570],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"inline-flex","alignItems":"center","justifyContent":"center","borderRadius":"4px","fontWeight":"600","cursor":"pointer","border":"none","lineHeight":"1"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"variant","defaultVariant":null,"base":null,"variants":{"primary":{"bg":"primary","color":"background"},"secondary":{"bg":"secondary","color":"background"},"ghost":{"bg":"transparent","color":"text"}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"system","value":{"px":true,"py":true,"fontSize":true},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"Button","local":"Button","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/components/Card.tsx":{"path":"../../packages/test-ds/src/components/Card.tsx","chains":[{"className":"animus-Card-9aa7af5d","descriptor":{"binding":"Card","terminal":"asElement","tag":"div","stages":[{"method":"styles","argSpan":[573,2126],"secondArgSpan":null},{"method":"system","argSpan":[2138,2169],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[560,2190],"extendsFrom":null},"stages":[{"method":"styles","value":{"bg":"surface","p":16,"borderRadius":"8px","color":"text","containerType":"inline-size","containerName":"card","@container card (min-width: 400px)":{"p":24,"width":"50cqw"},"@media (prefers-reduced-motion: reduce)":{"transition":"none"},"@supports (display: grid)":{"display":"grid","&:focus-visible":{"outline":"2px solid"},"@container card (min-width: 600px)":{"gap":"2cqi"},"fontSize":{"_":14,"sm":16}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"system","value":{"m":true,"mx":true,"my":true},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"Card","local":"Card","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/components/ContainerCard.tsx":{"path":"../../packages/test-ds/src/components/ContainerCard.tsx","chains":[{"className":"animus-ContainerCardRoot-01c5b011","descriptor":{"binding":"ContainerCardRoot","terminal":"asElement","tag":"article","stages":[{"method":"styles","argSpan":[1089,1435],"secondArgSpan":null},{"method":"variant","argSpan":[1448,1555],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[1076,1580],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"flex","flexDirection":"column","gap":8,"p":16,"borderRadius":"8px","bg":"surface","color":"text","containerType":"inline-size","containerName":"card"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"size","defaultVariant":"md","base":null,"variants":{"md":{},"lg":{"p":24}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null},{"className":"animus-ContainerCardMedia-51db5d07","descriptor":{"binding":"ContainerCardMedia","terminal":"asElement","tag":"div","stages":[{"method":"styles","argSpan":[1623,2194],"secondArgSpan":null},{"method":"variant","argSpan":[2207,2307],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[1610,2328],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"block","width":"100%","minHeight":"64px","borderRadius":"4px","background":"var(--current-bg)","@container card (min-width: 400px)":{"minHeight":"120px","width":"50cqw"}},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"size","defaultVariant":"md","base":null,"variants":{"md":{},"lg":{}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null},{"className":"animus-ContainerCardBody-133c6ad9","descriptor":{"binding":"ContainerCardBody","terminal":"asElement","tag":"div","stages":[{"method":"styles","argSpan":[2370,2636],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[2357,2657],"extendsFrom":null},"stages":[{"method":"styles","value":{"fontSize":14,"lineHeight":"1.5","color":"text","@container card (min-width: 400px)":{"fontSize":16}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[{"family_binding":"ContainerCard","root_binding":"ContainerCardRoot","slots":[["Root","ContainerCardRoot"],["Media","ContainerCardMedia"],["Body","ContainerCardBody"]],"shared_keys":["size"],"context":false,"span":[2689,2849],"name":"ContainerCard"}],"imports":[{"local":"compose","imported":"compose","source":"@animus-ui/system"},{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"ContainerCard","local":"ContainerCard","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/components/GroupItem.tsx":{"path":"../../packages/test-ds/src/components/GroupItem.tsx","chains":[{"className":"animus-GroupItem-32b2d32f","descriptor":{"binding":"GroupItem","terminal":"asElement","tag":"span","stages":[{"method":"styles","argSpan":[844,1148],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[831,1170],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"inline-flex","alignItems":"center","px":8,"py":4,"borderRadius":"4px","bg":"surface","color":"text","[data-active=\"true\"] &":{"bg":"primary","color":"background"},"_groupHover":{"opacity":"0.9"},"_dark":{"color":"text.muted"}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"../system"}],"exports":[{"exported":"GroupItem","local":"GroupItem","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/definition.ts":{"path":"../../packages/test-ds/src/definition.ts","chains":[],"statics":{},"usage":[],"compose":[],"imports":[],"exports":[{"exported":"system","local":null,"source":"./system","original":"ds"},{"exported":"theme","local":null,"source":"./theme","original":"referenceTokens"}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/dev-types.ts":{"path":"../../packages/test-ds/src/dev-types.ts","chains":[],"statics":{},"usage":[],"compose":[],"imports":[{"local":"referenceTokens","imported":"referenceTokens","source":"./theme"}],"exports":[],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/index.ts":{"path":"../../packages/test-ds/src/index.ts","chains":[],"statics":{"kitSizes":{"sm":{"fontSize":14,"px":8,"py":4},"md":{"fontSize":16,"px":16,"py":8},"lg":{"fontSize":20,"px":24,"py":12}}},"usage":[],"compose":[],"imports":[],"exports":[{"exported":"Alert","local":null,"source":"./components/Alert","original":"Alert"},{"exported":"Badge","local":null,"source":"./components/Badge","original":"Badge"},{"exported":"Button","local":null,"source":"./components/Button","original":"Button"},{"exported":"Card","local":null,"source":"./components/Card","original":"Card"},{"exported":"ContainerCard","local":null,"source":"./components/ContainerCard","original":"ContainerCard"},{"exported":"GroupItem","local":null,"source":"./components/GroupItem","original":"GroupItem"},{"exported":"ds","local":null,"source":"./system","original":"ds"},{"exported":"referenceTokens","local":null,"source":"./theme","original":"referenceTokens"},{"exported":"kitSizes","local":"kitSizes","source":null,"original":null},{"exported":"kitMotion","local":null,"source":"./system","original":"kitMotion"}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/system.ts":{"path":"../../packages/test-ds/src/system.ts","chains":[],"statics":{"kitMotion":{"pulse":"animus-kf-muo7kp"}},"usage":[],"compose":[],"imports":[{"local":"createSystem","imported":"createSystem","source":"@animus-ui/system"},{"local":"border","imported":"border","source":"@animus-ui/system/groups"},{"local":"color","imported":"color","source":"@animus-ui/system/groups"},{"local":"flex","imported":"flex","source":"@animus-ui/system/groups"},{"local":"layout","imported":"layout","source":"@animus-ui/system/groups"},{"local":"positioning","imported":"positioning","source":"@animus-ui/system/groups"},{"local":"space","imported":"space","source":"@animus-ui/system/groups"},{"local":"typography","imported":"typography","source":"@animus-ui/system/groups"}],"exports":[{"exported":"kitMotion","local":"kitMotion","source":null,"original":null},{"exported":"ds","local":"ds","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"../../packages/test-ds/src/theme.ts":{"path":"../../packages/test-ds/src/theme.ts","chains":[],"statics":{},"usage":[],"compose":[],"imports":[{"local":"createTheme","imported":"createTheme","source":"@animus-ui/system"}],"exports":[{"exported":"referenceTokens","local":"referenceTokens","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/Box.tsx":{"path":"src/Box.tsx","chains":[{"className":"animus-Box-399302cf","descriptor":{"binding":"Box","terminal":"asElement","tag":"div","stages":[{"method":"styles","argSpan":[60,112],"secondArgSpan":null},{"method":"system","argSpan":[124,172],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[47,193],"extendsFrom":null},"stages":[{"method":"styles","value":{"display":"flex","position":"relative"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"system","value":{"space":true,"layout":true,"positioning":true},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"./ds"}],"exports":[{"exported":"Box","local":"Box","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/Button.tsx":{"path":"src/Button.tsx","chains":[{"className":"animus-Button-b3718a43","descriptor":{"binding":"Button","terminal":"asElement","tag":"button","stages":[{"method":"styles","argSpan":[63,146],"secondArgSpan":null},{"method":"variant","argSpan":[159,313],"secondArgSpan":null}],"extractable":true,"bailReason":null,"span":[50,337],"extendsFrom":null},"stages":[{"method":"styles","value":{"padding":"8px","borderRadius":"4px","backgroundColor":"blue.500"},"secondValue":null,"skipped":[],"captured":[],"evalError":null},{"method":"variant","value":{"prop":"tone","defaultVariant":null,"base":null,"variants":{"quiet":{"backgroundColor":"gray.700"},"loud":{"backgroundColor":"blue.700","fontWeight":700}}},"secondValue":null,"skipped":[],"captured":[],"evalError":null}],"fatalError":null}],"statics":{},"usage":[{"element":{"tag":{"ident":"Button"},"attrs":[{"name":"tone","staticValue":"loud","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"loud"}]}}],"compose":[],"imports":[{"local":"ds","imported":"ds","source":"./ds"}],"exports":[{"exported":"Button","local":"Button","source":null,"original":null},{"exported":"App","local":"App","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/Frame.tsx":{"path":"src/Frame.tsx","chains":[],"statics":{},"usage":[{"element":{"tag":{"ident":"section"},"attrs":[{"name":"className","staticValue":"frame","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"frame"}]}}],"compose":[],"imports":[{"local":"ReactNode","imported":"ReactNode","source":"react"}],"exports":[{"exported":"Frame","local":"Frame","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/Group.tsx":{"path":"src/Group.tsx","chains":[],"statics":{},"usage":[{"element":{"tag":{"ident":"div"},"attrs":[]}},{"element":{"tag":{"ident":"div"},"attrs":[{"name":"className","staticValue":"group","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"group"},{"name":"data-active","staticValue":"true","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"true"}]}},{"element":{"tag":{"ident":"GroupItem"},"attrs":[]}},{"element":{"tag":{"ident":"div"},"attrs":[{"name":"data-active","staticValue":"false","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"false"}]}},{"element":{"tag":{"ident":"GroupItem"},"attrs":[]}},{"element":{"tag":{"ident":"Frame"},"attrs":[]}},{"element":{"tag":{"ident":"GroupItem"},"attrs":[]}},{"element":{"tag":{"ident":"div"},"attrs":[{"name":"data-active","staticValue":null,"dynamic":true,"dynamicKind":"conditional","dynamicSpan":{"start":1616,"end":1641},"skip":false,"variantClass":"__dynamic__"}]}},{"element":{"tag":{"ident":"GroupItem"},"attrs":[]}}],"compose":[],"imports":[{"local":"GroupItem","imported":"GroupItem","source":"@animus-ui/test-ds"},{"local":"Frame","imported":"Frame","source":"./Frame"}],"exports":[{"exported":"GroupDemo","local":"GroupDemo","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/ds.ts":{"path":"src/ds.ts","chains":[],"statics":{"animations":{"fadeIn":"animus-kf-1x7guim","pulse":"animus-kf-1yqv0zl"}},"usage":[],"compose":[],"imports":[{"local":"asset","imported":"asset","source":"@animus-ui/system"},{"local":"createSystem","imported":"createSystem","source":"@animus-ui/system"},{"local":"createTheme","imported":"createTheme","source":"@animus-ui/system"},{"local":"testDs","imported":"system","source":"@animus-ui/test-ds/definition"}],"exports":[{"exported":"theme","local":"theme","source":null,"original":null},{"exported":"globalStyles","local":"globalStyles","source":null,"original":null},{"exported":"animations","local":"animations","source":null,"original":null},{"exported":"ds","local":"ds","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]},"src/entry.tsx":{"path":"src/entry.tsx","chains":[],"statics":{},"usage":[{"element":{"tag":{"ident":"div"},"attrs":[]}},{"element":{"tag":{"ident":"ButtonApp"},"attrs":[]}},{"element":{"tag":{"ident":"Badge"},"attrs":[{"name":"color","staticValue":"danger","dynamic":false,"dynamicKind":null,"dynamicSpan":null,"skip":false,"variantClass":"danger"}]}}],"compose":[],"imports":[{"local":"Badge","imported":"Badge","source":"@animus-ui/test-ds"},{"local":"ButtonApp","imported":"App","source":"./Button"},{"local":"Button","imported":"Button","source":"./Button"}],"exports":[{"exported":"Badge","local":"Badge","source":null,"original":null},{"exported":"Button","local":"Button","source":null,"original":null},{"exported":"App","local":"App","source":null,"original":null}],"transforms":[],"parseDiagnostics":[]}},"crossFile":{"componentNames":["Alert","Badge","Box","Button","Card","ContainerCardBody","ContainerCardMedia","ContainerCardRoot","GroupItem"],"classResolvers":[],"memberBindings":{"ContainerCard.Body":"ContainerCardBody","ContainerCard.Media":"ContainerCardMedia","ContainerCard.Root":"ContainerCardRoot"},"renderedComponents":["Badge","Button","ContainerCardBody","ContainerCardMedia","ContainerCardRoot","GroupItem"],"variantOptions":{"Alert":{"intent":["danger","info","success"],"variant":["filled","outline"]},"Badge":{"color":["danger","neutral"]},"Button":{"tone":["loud","quiet"],"variant":["ghost","primary","secondary"]},"ContainerCardMedia":{"size":["lg","md"]},"ContainerCardRoot":{"size":["lg","md"]}},"stateNames":{"Badge":["active","disabled"]}},"parseCount":17,"usageResidue":[],"css":"@layer anm-global, anm-base, anm-variants, anm-compounds, anm-states, anm-system, anm-custom;\n\n@layer anm-base {\n .animus-Alert-a385f997 {\n padding: 0.75rem;\n display: flex;\n align-items: flex-start;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n }\n .animus-Badge-99781d29 {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 500;\n line-height: 1;\n }\n .animus-Card-9aa7af5d {\n padding: 1rem;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n border-radius: 8px;\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n @container card (min-width: 400px) {\n .animus-Card-9aa7af5d {\n padding: 1.5rem;\n width: 50cqw;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .animus-Card-9aa7af5d {\n transition: none;\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d {\n display: grid;\n font-size: 0.875rem;\n }\n }\n @supports (display: grid) {\n @media (min-width: 640px) {\n .animus-Card-9aa7af5d {\n font-size: 1rem;\n }\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d:focus-visible {\n outline: 2px solid;\n }\n }\n @supports (display: grid) {\n @container card (min-width: 600px) {\n .animus-Card-9aa7af5d {\n gap: 2cqi;\n }\n }\n }\n .animus-ContainerCardBody-133c6ad9 {\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--color-text);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardBody-133c6ad9 {\n font-size: 1rem;\n }\n }\n .animus-ContainerCardMedia-51db5d07 {\n display: block;\n width: 100%;\n min-height: 64px;\n border-radius: 4px;\n background: var(--current-bg);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardMedia-51db5d07 {\n min-height: 120px;\n width: 50cqw;\n }\n }\n .animus-ContainerCardRoot-01c5b011 {\n gap: 0.5rem;\n padding: 1rem;\n display: flex;\n flex-direction: column;\n border-radius: 8px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n .animus-GroupItem-32b2d32f {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 4px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n [data-active=\"true\"] .animus-GroupItem-32b2d32f {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n color: var(--color-background);\n }\n .group:hover .animus-GroupItem-32b2d32f {\n opacity: 0.9;\n }\n [data-color-mode=\"dark\"] .animus-GroupItem-32b2d32f {\n color: var(--color-text-muted);\n }\n .animus-Box-399302cf {\n display: flex;\n position: relative;\n }\n .animus-Button-c63b6dcd {\n border: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n font-weight: 600;\n line-height: 1;\n cursor: pointer;\n }\n .animus-Button-b3718a43 {\n border-radius: 4px;\n padding: 8px;\n background-color: var(--color-blue-500);\n }\n}\n\n@layer anm-variants {\n @layer standalone, composed;\n @layer standalone {\n\n .animus-Alert-a385f997--variant-filled {\n color: var(--color-background);\n }\n .animus-Alert-a385f997--variant-outline {\n border-width: 1px;\n border-style: solid;\n background-color: transparent;\n --current-bg: transparent;\n }\n .animus-Alert-a385f997--intent-info {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n }\n .animus-Alert-a385f997--intent-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n }\n .animus-Alert-a385f997--intent-success {\n background-color: var(--color-secondary);\n --current-bg: var(--color-secondary);\n }\n .animus-Badge-99781d29--color-neutral {\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n .animus-Badge-99781d29--color-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n color: var(--color-background);\n }\n .animus-ContainerCardRoot-01c5b011--size-lg {\n padding: 1.5rem;\n }\n .animus-Button-c63b6dcd--variant-primary {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n color: var(--color-background);\n }\n .animus-Button-c63b6dcd--variant-secondary {\n background-color: var(--color-secondary);\n --current-bg: var(--color-secondary);\n color: var(--color-background);\n }\n .animus-Button-c63b6dcd--variant-ghost {\n background-color: transparent;\n --current-bg: transparent;\n color: var(--color-text);\n }\n .animus-Button-b3718a43--tone-quiet {\n background-color: var(--color-gray-700);\n }\n .animus-Button-b3718a43--tone-loud {\n font-weight: 700;\n background-color: var(--color-blue-700);\n }\n }\n @layer composed {\n }\n}\n\n@layer anm-compounds {\n .animus-Alert-a385f997--compound-0 {\n border-color: var(--color-primary);\n color: var(--color-primary);\n }\n .animus-Alert-a385f997--compound-1 {\n border-color: var(--color-danger);\n color: var(--color-danger);\n }\n .animus-Alert-a385f997--compound-2 {\n border-color: var(--color-secondary);\n color: var(--color-secondary);\n }\n}\n\n@layer anm-states {\n .animus-Badge-99781d29--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .animus-Badge-99781d29--active {\n outline: 2px solid;\n outline-color: var(--color-primary);\n }\n}\n\n@layer anm-system {\n .animus-dyn-flex {\n flex: var(--animus-flex);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-sm {\n flex: var(--animus-flex-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-md {\n flex: var(--animus-flex-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-lg {\n flex: var(--animus-flex-lg);\n }\n }\n .animus-dyn-m {\n margin: var(--animus-m);\n }\n @media (min-width: 640px) {\n .animus-dyn-m-sm {\n margin: var(--animus-m-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-m-md {\n margin: var(--animus-m-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-m-lg {\n margin: var(--animus-m-lg);\n }\n }\n .animus-dyn-p {\n padding: var(--animus-p);\n }\n @media (min-width: 640px) {\n .animus-dyn-p-sm {\n padding: var(--animus-p-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-p-md {\n padding: var(--animus-p-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-p-lg {\n padding: var(--animus-p-lg);\n }\n }\n .animus-dyn-gap {\n gap: var(--animus-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-gap-sm {\n gap: var(--animus-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-gap-md {\n gap: var(--animus-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-gap-lg {\n gap: var(--animus-gap-lg);\n }\n }\n .animus-dyn-area {\n grid-area: var(--animus-area);\n }\n .animus-dyn-grid-area {\n grid-area: var(--animus-grid-area);\n }\n @media (min-width: 640px) {\n .animus-dyn-area-sm {\n grid-area: var(--animus-area-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-area-sm {\n grid-area: var(--animus-grid-area-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-area-md {\n grid-area: var(--animus-area-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-area-md {\n grid-area: var(--animus-grid-area-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-area-lg {\n grid-area: var(--animus-area-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-area-lg {\n grid-area: var(--animus-grid-area-lg);\n }\n }\n .animus-dyn-grid-column {\n grid-column: var(--animus-grid-column);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-sm {\n grid-column: var(--animus-grid-column-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-md {\n grid-column: var(--animus-grid-column-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-lg {\n grid-column: var(--animus-grid-column-lg);\n }\n }\n .animus-dyn-grid-row {\n grid-row: var(--animus-grid-row);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-sm {\n grid-row: var(--animus-grid-row-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-md {\n grid-row: var(--animus-grid-row-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-lg {\n grid-row: var(--animus-grid-row-lg);\n }\n }\n .animus-dyn-overflow {\n overflow: var(--animus-overflow);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-sm {\n overflow: var(--animus-overflow-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-md {\n overflow: var(--animus-overflow-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-lg {\n overflow: var(--animus-overflow-lg);\n }\n }\n .animus-dyn-align-content {\n align-content: var(--animus-align-content);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-content-sm {\n align-content: var(--animus-align-content-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-content-md {\n align-content: var(--animus-align-content-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-content-lg {\n align-content: var(--animus-align-content-lg);\n }\n }\n .animus-dyn-align-items {\n align-items: var(--animus-align-items);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-items-sm {\n align-items: var(--animus-align-items-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-items-md {\n align-items: var(--animus-align-items-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-items-lg {\n align-items: var(--animus-align-items-lg);\n }\n }\n .animus-dyn-align-self {\n align-self: var(--animus-align-self);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-self-sm {\n align-self: var(--animus-align-self-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-self-md {\n align-self: var(--animus-align-self-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-self-lg {\n align-self: var(--animus-align-self-lg);\n }\n }\n .animus-dyn-bottom {\n bottom: var(--animus-bottom);\n }\n @media (min-width: 640px) {\n .animus-dyn-bottom-sm {\n bottom: var(--animus-bottom-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-bottom-md {\n bottom: var(--animus-bottom-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-bottom-lg {\n bottom: var(--animus-bottom-lg);\n }\n }\n .animus-dyn-column-gap {\n column-gap: var(--animus-column-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-column-gap-sm {\n column-gap: var(--animus-column-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-column-gap-md {\n column-gap: var(--animus-column-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-column-gap-lg {\n column-gap: var(--animus-column-gap-lg);\n }\n }\n .animus-dyn-display {\n display: var(--animus-display);\n }\n @media (min-width: 640px) {\n .animus-dyn-display-sm {\n display: var(--animus-display-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-display-md {\n display: var(--animus-display-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-display-lg {\n display: var(--animus-display-lg);\n }\n }\n .animus-dyn-flex-basis {\n flex-basis: var(--animus-flex-basis);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-basis-sm {\n flex-basis: var(--animus-flex-basis-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-basis-md {\n flex-basis: var(--animus-flex-basis-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-basis-lg {\n flex-basis: var(--animus-flex-basis-lg);\n }\n }\n .animus-dyn-flex-dir {\n flex-direction: var(--animus-flex-dir);\n }\n .animus-dyn-flex-direction {\n flex-direction: var(--animus-flex-direction);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-dir-sm {\n flex-direction: var(--animus-flex-dir-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-direction-sm {\n flex-direction: var(--animus-flex-direction-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-dir-md {\n flex-direction: var(--animus-flex-dir-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-direction-md {\n flex-direction: var(--animus-flex-direction-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-dir-lg {\n flex-direction: var(--animus-flex-dir-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-direction-lg {\n flex-direction: var(--animus-flex-direction-lg);\n }\n }\n .animus-dyn-flex-grow {\n flex-grow: var(--animus-flex-grow);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-grow-sm {\n flex-grow: var(--animus-flex-grow-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-grow-md {\n flex-grow: var(--animus-flex-grow-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-grow-lg {\n flex-grow: var(--animus-flex-grow-lg);\n }\n }\n .animus-dyn-flex-shrink {\n flex-shrink: var(--animus-flex-shrink);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-shrink-sm {\n flex-shrink: var(--animus-flex-shrink-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-shrink-md {\n flex-shrink: var(--animus-flex-shrink-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-shrink-lg {\n flex-shrink: var(--animus-flex-shrink-lg);\n }\n }\n .animus-dyn-flex-wrap {\n flex-wrap: var(--animus-flex-wrap);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-wrap-sm {\n flex-wrap: var(--animus-flex-wrap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-wrap-md {\n flex-wrap: var(--animus-flex-wrap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-wrap-lg {\n flex-wrap: var(--animus-flex-wrap-lg);\n }\n }\n .animus-dyn-font-size {\n font-size: var(--animus-font-size);\n }\n @media (min-width: 640px) {\n .animus-dyn-font-size-sm {\n font-size: var(--animus-font-size-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-font-size-md {\n font-size: var(--animus-font-size-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-font-size-lg {\n font-size: var(--animus-font-size-lg);\n }\n }\n .animus-dyn-grid-column-end {\n grid-column-end: var(--animus-grid-column-end);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-end-sm {\n grid-column-end: var(--animus-grid-column-end-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-end-md {\n grid-column-end: var(--animus-grid-column-end-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-end-lg {\n grid-column-end: var(--animus-grid-column-end-lg);\n }\n }\n .animus-dyn-grid-column-start {\n grid-column-start: var(--animus-grid-column-start);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-start-sm {\n grid-column-start: var(--animus-grid-column-start-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-start-md {\n grid-column-start: var(--animus-grid-column-start-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-start-lg {\n grid-column-start: var(--animus-grid-column-start-lg);\n }\n }\n .animus-dyn-grid-row-end {\n grid-row-end: var(--animus-grid-row-end);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-end-sm {\n grid-row-end: var(--animus-grid-row-end-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-end-md {\n grid-row-end: var(--animus-grid-row-end-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-end-lg {\n grid-row-end: var(--animus-grid-row-end-lg);\n }\n }\n .animus-dyn-grid-row-start {\n grid-row-start: var(--animus-grid-row-start);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-start-sm {\n grid-row-start: var(--animus-grid-row-start-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-start-md {\n grid-row-start: var(--animus-grid-row-start-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-start-lg {\n grid-row-start: var(--animus-grid-row-start-lg);\n }\n }\n .animus-dyn-h {\n height: var(--animus-h);\n }\n .animus-dyn-height {\n height: var(--animus-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-h-sm {\n height: var(--animus-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-height-sm {\n height: var(--animus-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-h-md {\n height: var(--animus-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-height-md {\n height: var(--animus-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-h-lg {\n height: var(--animus-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-height-lg {\n height: var(--animus-height-lg);\n }\n }\n .animus-dyn-justify-content {\n justify-content: var(--animus-justify-content);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-content-sm {\n justify-content: var(--animus-justify-content-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-content-md {\n justify-content: var(--animus-justify-content-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-content-lg {\n justify-content: var(--animus-justify-content-lg);\n }\n }\n .animus-dyn-justify-items {\n justify-items: var(--animus-justify-items);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-items-sm {\n justify-items: var(--animus-justify-items-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-items-md {\n justify-items: var(--animus-justify-items-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-items-lg {\n justify-items: var(--animus-justify-items-lg);\n }\n }\n .animus-dyn-justify-self {\n justify-self: var(--animus-justify-self);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-self-sm {\n justify-self: var(--animus-justify-self-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-self-md {\n justify-self: var(--animus-justify-self-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-self-lg {\n justify-self: var(--animus-justify-self-lg);\n }\n }\n .animus-dyn-left {\n left: var(--animus-left);\n }\n @media (min-width: 640px) {\n .animus-dyn-left-sm {\n left: var(--animus-left-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-left-md {\n left: var(--animus-left-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-left-lg {\n left: var(--animus-left-lg);\n }\n }\n .animus-dyn-mb {\n margin-bottom: var(--animus-mb);\n }\n @media (min-width: 640px) {\n .animus-dyn-mb-sm {\n margin-bottom: var(--animus-mb-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mb-md {\n margin-bottom: var(--animus-mb-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mb-lg {\n margin-bottom: var(--animus-mb-lg);\n }\n }\n .animus-dyn-ml {\n margin-left: var(--animus-ml);\n }\n .animus-dyn-mx {\n margin-left: var(--animus-mx);\n margin-right: var(--animus-mx);\n }\n @media (min-width: 640px) {\n .animus-dyn-ml-sm {\n margin-left: var(--animus-ml-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-mx-sm {\n margin-left: var(--animus-mx-sm);\n margin-right: var(--animus-mx-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-ml-md {\n margin-left: var(--animus-ml-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mx-md {\n margin-left: var(--animus-mx-md);\n margin-right: var(--animus-mx-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-ml-lg {\n margin-left: var(--animus-ml-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mx-lg {\n margin-left: var(--animus-mx-lg);\n margin-right: var(--animus-mx-lg);\n }\n }\n .animus-dyn-mr {\n margin-right: var(--animus-mr);\n }\n @media (min-width: 640px) {\n .animus-dyn-mr-sm {\n margin-right: var(--animus-mr-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mr-md {\n margin-right: var(--animus-mr-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mr-lg {\n margin-right: var(--animus-mr-lg);\n }\n }\n .animus-dyn-mt {\n margin-top: var(--animus-mt);\n }\n .animus-dyn-my {\n margin-top: var(--animus-my);\n margin-bottom: var(--animus-my);\n }\n @media (min-width: 640px) {\n .animus-dyn-mt-sm {\n margin-top: var(--animus-mt-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-my-sm {\n margin-top: var(--animus-my-sm);\n margin-bottom: var(--animus-my-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mt-md {\n margin-top: var(--animus-mt-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-my-md {\n margin-top: var(--animus-my-md);\n margin-bottom: var(--animus-my-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mt-lg {\n margin-top: var(--animus-mt-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-my-lg {\n margin-top: var(--animus-my-lg);\n margin-bottom: var(--animus-my-lg);\n }\n }\n .animus-dyn-max-h {\n max-height: var(--animus-max-h);\n }\n .animus-dyn-max-height {\n max-height: var(--animus-max-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-max-h-sm {\n max-height: var(--animus-max-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-max-height-sm {\n max-height: var(--animus-max-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-h-md {\n max-height: var(--animus-max-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-height-md {\n max-height: var(--animus-max-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-h-lg {\n max-height: var(--animus-max-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-height-lg {\n max-height: var(--animus-max-height-lg);\n }\n }\n .animus-dyn-max-w {\n max-width: var(--animus-max-w);\n }\n .animus-dyn-max-width {\n max-width: var(--animus-max-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-max-w-sm {\n max-width: var(--animus-max-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-max-width-sm {\n max-width: var(--animus-max-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-w-md {\n max-width: var(--animus-max-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-width-md {\n max-width: var(--animus-max-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-w-lg {\n max-width: var(--animus-max-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-width-lg {\n max-width: var(--animus-max-width-lg);\n }\n }\n .animus-dyn-min-h {\n min-height: var(--animus-min-h);\n }\n .animus-dyn-min-height {\n min-height: var(--animus-min-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-min-h-sm {\n min-height: var(--animus-min-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-min-height-sm {\n min-height: var(--animus-min-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-h-md {\n min-height: var(--animus-min-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-height-md {\n min-height: var(--animus-min-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-h-lg {\n min-height: var(--animus-min-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-height-lg {\n min-height: var(--animus-min-height-lg);\n }\n }\n .animus-dyn-min-w {\n min-width: var(--animus-min-w);\n }\n .animus-dyn-min-width {\n min-width: var(--animus-min-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-min-w-sm {\n min-width: var(--animus-min-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-min-width-sm {\n min-width: var(--animus-min-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-w-md {\n min-width: var(--animus-min-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-width-md {\n min-width: var(--animus-min-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-w-lg {\n min-width: var(--animus-min-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-width-lg {\n min-width: var(--animus-min-width-lg);\n }\n }\n .animus-dyn-opacity {\n opacity: var(--animus-opacity);\n }\n @media (min-width: 640px) {\n .animus-dyn-opacity-sm {\n opacity: var(--animus-opacity-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-opacity-md {\n opacity: var(--animus-opacity-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-opacity-lg {\n opacity: var(--animus-opacity-lg);\n }\n }\n .animus-dyn-order {\n order: var(--animus-order);\n }\n @media (min-width: 640px) {\n .animus-dyn-order-sm {\n order: var(--animus-order-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-order-md {\n order: var(--animus-order-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-order-lg {\n order: var(--animus-order-lg);\n }\n }\n .animus-dyn-overflow-x {\n overflow-x: var(--animus-overflow-x);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-x-sm {\n overflow-x: var(--animus-overflow-x-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-x-md {\n overflow-x: var(--animus-overflow-x-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-x-lg {\n overflow-x: var(--animus-overflow-x-lg);\n }\n }\n .animus-dyn-overflow-y {\n overflow-y: var(--animus-overflow-y);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-y-sm {\n overflow-y: var(--animus-overflow-y-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-y-md {\n overflow-y: var(--animus-overflow-y-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-y-lg {\n overflow-y: var(--animus-overflow-y-lg);\n }\n }\n .animus-dyn-pb {\n padding-bottom: var(--animus-pb);\n }\n @media (min-width: 640px) {\n .animus-dyn-pb-sm {\n padding-bottom: var(--animus-pb-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pb-md {\n padding-bottom: var(--animus-pb-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pb-lg {\n padding-bottom: var(--animus-pb-lg);\n }\n }\n .animus-dyn-pl {\n padding-left: var(--animus-pl);\n }\n .animus-dyn-px {\n padding-left: var(--animus-px);\n padding-right: var(--animus-px);\n }\n @media (min-width: 640px) {\n .animus-dyn-pl-sm {\n padding-left: var(--animus-pl-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-px-sm {\n padding-left: var(--animus-px-sm);\n padding-right: var(--animus-px-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pl-md {\n padding-left: var(--animus-pl-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-px-md {\n padding-left: var(--animus-px-md);\n padding-right: var(--animus-px-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pl-lg {\n padding-left: var(--animus-pl-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-px-lg {\n padding-left: var(--animus-px-lg);\n padding-right: var(--animus-px-lg);\n }\n }\n .animus-dyn-pr {\n padding-right: var(--animus-pr);\n }\n @media (min-width: 640px) {\n .animus-dyn-pr-sm {\n padding-right: var(--animus-pr-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pr-md {\n padding-right: var(--animus-pr-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pr-lg {\n padding-right: var(--animus-pr-lg);\n }\n }\n .animus-dyn-pt {\n padding-top: var(--animus-pt);\n }\n .animus-dyn-py {\n padding-top: var(--animus-py);\n padding-bottom: var(--animus-py);\n }\n @media (min-width: 640px) {\n .animus-dyn-pt-sm {\n padding-top: var(--animus-pt-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-py-sm {\n padding-top: var(--animus-py-sm);\n padding-bottom: var(--animus-py-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pt-md {\n padding-top: var(--animus-pt-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-py-md {\n padding-top: var(--animus-py-md);\n padding-bottom: var(--animus-py-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pt-lg {\n padding-top: var(--animus-pt-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-py-lg {\n padding-top: var(--animus-py-lg);\n padding-bottom: var(--animus-py-lg);\n }\n }\n .animus-dyn-pos {\n position: var(--animus-pos);\n }\n .animus-dyn-position {\n position: var(--animus-position);\n }\n @media (min-width: 640px) {\n .animus-dyn-pos-sm {\n position: var(--animus-pos-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-position-sm {\n position: var(--animus-position-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pos-md {\n position: var(--animus-pos-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-position-md {\n position: var(--animus-position-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pos-lg {\n position: var(--animus-pos-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-position-lg {\n position: var(--animus-position-lg);\n }\n }\n .animus-dyn-right {\n right: var(--animus-right);\n }\n @media (min-width: 640px) {\n .animus-dyn-right-sm {\n right: var(--animus-right-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-right-md {\n right: var(--animus-right-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-right-lg {\n right: var(--animus-right-lg);\n }\n }\n .animus-dyn-row-gap {\n row-gap: var(--animus-row-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-row-gap-sm {\n row-gap: var(--animus-row-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-row-gap-md {\n row-gap: var(--animus-row-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-row-gap-lg {\n row-gap: var(--animus-row-gap-lg);\n }\n }\n .animus-dyn-inset {\n top: var(--animus-inset);\n right: var(--animus-inset);\n bottom: var(--animus-inset);\n left: var(--animus-inset);\n }\n .animus-dyn-top {\n top: var(--animus-top);\n }\n @media (min-width: 640px) {\n .animus-dyn-inset-sm {\n top: var(--animus-inset-sm);\n right: var(--animus-inset-sm);\n bottom: var(--animus-inset-sm);\n left: var(--animus-inset-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-top-sm {\n top: var(--animus-top-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-inset-md {\n top: var(--animus-inset-md);\n right: var(--animus-inset-md);\n bottom: var(--animus-inset-md);\n left: var(--animus-inset-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-top-md {\n top: var(--animus-top-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-inset-lg {\n top: var(--animus-inset-lg);\n right: var(--animus-inset-lg);\n bottom: var(--animus-inset-lg);\n left: var(--animus-inset-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-top-lg {\n top: var(--animus-top-lg);\n }\n }\n .animus-dyn-vertical-align {\n vertical-align: var(--animus-vertical-align);\n }\n @media (min-width: 640px) {\n .animus-dyn-vertical-align-sm {\n vertical-align: var(--animus-vertical-align-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-vertical-align-md {\n vertical-align: var(--animus-vertical-align-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-vertical-align-lg {\n vertical-align: var(--animus-vertical-align-lg);\n }\n }\n .animus-dyn-size {\n width: var(--animus-size);\n height: var(--animus-size);\n }\n .animus-dyn-w {\n width: var(--animus-w);\n }\n .animus-dyn-width {\n width: var(--animus-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-size-sm {\n width: var(--animus-size-sm);\n height: var(--animus-size-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-w-sm {\n width: var(--animus-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-width-sm {\n width: var(--animus-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-size-md {\n width: var(--animus-size-md);\n height: var(--animus-size-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-w-md {\n width: var(--animus-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-width-md {\n width: var(--animus-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-size-lg {\n width: var(--animus-size-lg);\n height: var(--animus-size-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-w-lg {\n width: var(--animus-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-width-lg {\n width: var(--animus-width-lg);\n }\n }\n .animus-dyn-z-index {\n z-index: var(--animus-z-index);\n }\n @media (min-width: 640px) {\n .animus-dyn-z-index-sm {\n z-index: var(--animus-z-index-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-z-index-md {\n z-index: var(--animus-z-index-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-z-index-lg {\n z-index: var(--animus-z-index-lg);\n }\n }\n}\n\n","sheets":{"declaration":"@layer anm-global, anm-base, anm-variants, anm-compounds, anm-states, anm-system, anm-custom;\n","global":"@layer anm-global {\n@font-face { font-family: AnimusTestFont; src: url('animus-asset:@animus-ui/test-ds/assets/test-font.woff2') format('woff2'); font-display: swap; }\n\n*, *::before, *::after {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n background-color: var(--color-background);\n --current-bg: var(--color-background);\n color: var(--color-text);\n font-family: system-ui, sans-serif;\n}\n@keyframes animus-kf-muo7kp {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.6;\n }\n}\n\n@keyframes animus-kf-1x7guim {\n 0% {\n opacity: 0;\n background-color: var(--color-background);\n --current-bg: var(--color-background);\n }\n 100% {\n opacity: 1;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n }\n}\n\n@keyframes animus-kf-1yqv0zl {\n 0%, 100% {\n transform: scale(1);\n }\n 50% {\n transform: scale(1.05);\n }\n}\n}\n","base":"@layer anm-base {\n .animus-Alert-a385f997 {\n padding: 0.75rem;\n display: flex;\n align-items: flex-start;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n }\n .animus-Badge-99781d29 {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 500;\n line-height: 1;\n }\n .animus-Card-9aa7af5d {\n padding: 1rem;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n border-radius: 8px;\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n @container card (min-width: 400px) {\n .animus-Card-9aa7af5d {\n padding: 1.5rem;\n width: 50cqw;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .animus-Card-9aa7af5d {\n transition: none;\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d {\n display: grid;\n font-size: 0.875rem;\n }\n }\n @supports (display: grid) {\n @media (min-width: 640px) {\n .animus-Card-9aa7af5d {\n font-size: 1rem;\n }\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d:focus-visible {\n outline: 2px solid;\n }\n }\n @supports (display: grid) {\n @container card (min-width: 600px) {\n .animus-Card-9aa7af5d {\n gap: 2cqi;\n }\n }\n }\n .animus-ContainerCardBody-133c6ad9 {\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--color-text);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardBody-133c6ad9 {\n font-size: 1rem;\n }\n }\n .animus-ContainerCardMedia-51db5d07 {\n display: block;\n width: 100%;\n min-height: 64px;\n border-radius: 4px;\n background: var(--current-bg);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardMedia-51db5d07 {\n min-height: 120px;\n width: 50cqw;\n }\n }\n .animus-ContainerCardRoot-01c5b011 {\n gap: 0.5rem;\n padding: 1rem;\n display: flex;\n flex-direction: column;\n border-radius: 8px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n .animus-GroupItem-32b2d32f {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 4px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n [data-active=\"true\"] .animus-GroupItem-32b2d32f {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n color: var(--color-background);\n }\n .group:hover .animus-GroupItem-32b2d32f {\n opacity: 0.9;\n }\n [data-color-mode=\"dark\"] .animus-GroupItem-32b2d32f {\n color: var(--color-text-muted);\n }\n .animus-Box-399302cf {\n display: flex;\n position: relative;\n }\n .animus-Button-c63b6dcd {\n border: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n font-weight: 600;\n line-height: 1;\n cursor: pointer;\n }\n .animus-Button-b3718a43 {\n border-radius: 4px;\n padding: 8px;\n background-color: var(--color-blue-500);\n }\n}\n","variants":"@layer anm-variants {\n @layer standalone, composed;\n @layer standalone {\n\n .animus-Alert-a385f997--variant-filled {\n color: var(--color-background);\n }\n .animus-Alert-a385f997--variant-outline {\n border-width: 1px;\n border-style: solid;\n background-color: transparent;\n --current-bg: transparent;\n }\n .animus-Alert-a385f997--intent-info {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n }\n .animus-Alert-a385f997--intent-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n }\n .animus-Alert-a385f997--intent-success {\n background-color: var(--color-secondary);\n --current-bg: var(--color-secondary);\n }\n .animus-Badge-99781d29--color-neutral {\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n .animus-Badge-99781d29--color-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n color: var(--color-background);\n }\n .animus-ContainerCardRoot-01c5b011--size-lg {\n padding: 1.5rem;\n }\n .animus-Button-c63b6dcd--variant-primary {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n color: var(--color-background);\n }\n .animus-Button-c63b6dcd--variant-secondary {\n background-color: var(--color-secondary);\n --current-bg: var(--color-secondary);\n color: var(--color-background);\n }\n .animus-Button-c63b6dcd--variant-ghost {\n background-color: transparent;\n --current-bg: transparent;\n color: var(--color-text);\n }\n .animus-Button-b3718a43--tone-quiet {\n background-color: var(--color-gray-700);\n }\n .animus-Button-b3718a43--tone-loud {\n font-weight: 700;\n background-color: var(--color-blue-700);\n }\n }\n @layer composed {\n }\n}\n","compounds":"@layer anm-compounds {\n .animus-Alert-a385f997--compound-0 {\n border-color: var(--color-primary);\n color: var(--color-primary);\n }\n .animus-Alert-a385f997--compound-1 {\n border-color: var(--color-danger);\n color: var(--color-danger);\n }\n .animus-Alert-a385f997--compound-2 {\n border-color: var(--color-secondary);\n color: var(--color-secondary);\n }\n}\n","states":"@layer anm-states {\n .animus-Badge-99781d29--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .animus-Badge-99781d29--active {\n outline: 2px solid;\n outline-color: var(--color-primary);\n }\n}\n","system":"@layer anm-system {\n .animus-dyn-flex {\n flex: var(--animus-flex);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-sm {\n flex: var(--animus-flex-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-md {\n flex: var(--animus-flex-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-lg {\n flex: var(--animus-flex-lg);\n }\n }\n .animus-dyn-m {\n margin: var(--animus-m);\n }\n @media (min-width: 640px) {\n .animus-dyn-m-sm {\n margin: var(--animus-m-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-m-md {\n margin: var(--animus-m-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-m-lg {\n margin: var(--animus-m-lg);\n }\n }\n .animus-dyn-p {\n padding: var(--animus-p);\n }\n @media (min-width: 640px) {\n .animus-dyn-p-sm {\n padding: var(--animus-p-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-p-md {\n padding: var(--animus-p-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-p-lg {\n padding: var(--animus-p-lg);\n }\n }\n .animus-dyn-gap {\n gap: var(--animus-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-gap-sm {\n gap: var(--animus-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-gap-md {\n gap: var(--animus-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-gap-lg {\n gap: var(--animus-gap-lg);\n }\n }\n .animus-dyn-area {\n grid-area: var(--animus-area);\n }\n .animus-dyn-grid-area {\n grid-area: var(--animus-grid-area);\n }\n @media (min-width: 640px) {\n .animus-dyn-area-sm {\n grid-area: var(--animus-area-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-area-sm {\n grid-area: var(--animus-grid-area-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-area-md {\n grid-area: var(--animus-area-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-area-md {\n grid-area: var(--animus-grid-area-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-area-lg {\n grid-area: var(--animus-area-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-area-lg {\n grid-area: var(--animus-grid-area-lg);\n }\n }\n .animus-dyn-grid-column {\n grid-column: var(--animus-grid-column);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-sm {\n grid-column: var(--animus-grid-column-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-md {\n grid-column: var(--animus-grid-column-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-lg {\n grid-column: var(--animus-grid-column-lg);\n }\n }\n .animus-dyn-grid-row {\n grid-row: var(--animus-grid-row);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-sm {\n grid-row: var(--animus-grid-row-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-md {\n grid-row: var(--animus-grid-row-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-lg {\n grid-row: var(--animus-grid-row-lg);\n }\n }\n .animus-dyn-overflow {\n overflow: var(--animus-overflow);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-sm {\n overflow: var(--animus-overflow-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-md {\n overflow: var(--animus-overflow-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-lg {\n overflow: var(--animus-overflow-lg);\n }\n }\n .animus-dyn-align-content {\n align-content: var(--animus-align-content);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-content-sm {\n align-content: var(--animus-align-content-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-content-md {\n align-content: var(--animus-align-content-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-content-lg {\n align-content: var(--animus-align-content-lg);\n }\n }\n .animus-dyn-align-items {\n align-items: var(--animus-align-items);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-items-sm {\n align-items: var(--animus-align-items-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-items-md {\n align-items: var(--animus-align-items-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-items-lg {\n align-items: var(--animus-align-items-lg);\n }\n }\n .animus-dyn-align-self {\n align-self: var(--animus-align-self);\n }\n @media (min-width: 640px) {\n .animus-dyn-align-self-sm {\n align-self: var(--animus-align-self-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-align-self-md {\n align-self: var(--animus-align-self-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-align-self-lg {\n align-self: var(--animus-align-self-lg);\n }\n }\n .animus-dyn-bottom {\n bottom: var(--animus-bottom);\n }\n @media (min-width: 640px) {\n .animus-dyn-bottom-sm {\n bottom: var(--animus-bottom-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-bottom-md {\n bottom: var(--animus-bottom-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-bottom-lg {\n bottom: var(--animus-bottom-lg);\n }\n }\n .animus-dyn-column-gap {\n column-gap: var(--animus-column-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-column-gap-sm {\n column-gap: var(--animus-column-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-column-gap-md {\n column-gap: var(--animus-column-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-column-gap-lg {\n column-gap: var(--animus-column-gap-lg);\n }\n }\n .animus-dyn-display {\n display: var(--animus-display);\n }\n @media (min-width: 640px) {\n .animus-dyn-display-sm {\n display: var(--animus-display-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-display-md {\n display: var(--animus-display-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-display-lg {\n display: var(--animus-display-lg);\n }\n }\n .animus-dyn-flex-basis {\n flex-basis: var(--animus-flex-basis);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-basis-sm {\n flex-basis: var(--animus-flex-basis-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-basis-md {\n flex-basis: var(--animus-flex-basis-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-basis-lg {\n flex-basis: var(--animus-flex-basis-lg);\n }\n }\n .animus-dyn-flex-dir {\n flex-direction: var(--animus-flex-dir);\n }\n .animus-dyn-flex-direction {\n flex-direction: var(--animus-flex-direction);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-dir-sm {\n flex-direction: var(--animus-flex-dir-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-direction-sm {\n flex-direction: var(--animus-flex-direction-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-dir-md {\n flex-direction: var(--animus-flex-dir-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-direction-md {\n flex-direction: var(--animus-flex-direction-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-dir-lg {\n flex-direction: var(--animus-flex-dir-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-direction-lg {\n flex-direction: var(--animus-flex-direction-lg);\n }\n }\n .animus-dyn-flex-grow {\n flex-grow: var(--animus-flex-grow);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-grow-sm {\n flex-grow: var(--animus-flex-grow-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-grow-md {\n flex-grow: var(--animus-flex-grow-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-grow-lg {\n flex-grow: var(--animus-flex-grow-lg);\n }\n }\n .animus-dyn-flex-shrink {\n flex-shrink: var(--animus-flex-shrink);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-shrink-sm {\n flex-shrink: var(--animus-flex-shrink-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-shrink-md {\n flex-shrink: var(--animus-flex-shrink-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-shrink-lg {\n flex-shrink: var(--animus-flex-shrink-lg);\n }\n }\n .animus-dyn-flex-wrap {\n flex-wrap: var(--animus-flex-wrap);\n }\n @media (min-width: 640px) {\n .animus-dyn-flex-wrap-sm {\n flex-wrap: var(--animus-flex-wrap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-flex-wrap-md {\n flex-wrap: var(--animus-flex-wrap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-flex-wrap-lg {\n flex-wrap: var(--animus-flex-wrap-lg);\n }\n }\n .animus-dyn-font-size {\n font-size: var(--animus-font-size);\n }\n @media (min-width: 640px) {\n .animus-dyn-font-size-sm {\n font-size: var(--animus-font-size-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-font-size-md {\n font-size: var(--animus-font-size-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-font-size-lg {\n font-size: var(--animus-font-size-lg);\n }\n }\n .animus-dyn-grid-column-end {\n grid-column-end: var(--animus-grid-column-end);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-end-sm {\n grid-column-end: var(--animus-grid-column-end-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-end-md {\n grid-column-end: var(--animus-grid-column-end-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-end-lg {\n grid-column-end: var(--animus-grid-column-end-lg);\n }\n }\n .animus-dyn-grid-column-start {\n grid-column-start: var(--animus-grid-column-start);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-column-start-sm {\n grid-column-start: var(--animus-grid-column-start-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-column-start-md {\n grid-column-start: var(--animus-grid-column-start-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-column-start-lg {\n grid-column-start: var(--animus-grid-column-start-lg);\n }\n }\n .animus-dyn-grid-row-end {\n grid-row-end: var(--animus-grid-row-end);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-end-sm {\n grid-row-end: var(--animus-grid-row-end-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-end-md {\n grid-row-end: var(--animus-grid-row-end-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-end-lg {\n grid-row-end: var(--animus-grid-row-end-lg);\n }\n }\n .animus-dyn-grid-row-start {\n grid-row-start: var(--animus-grid-row-start);\n }\n @media (min-width: 640px) {\n .animus-dyn-grid-row-start-sm {\n grid-row-start: var(--animus-grid-row-start-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-grid-row-start-md {\n grid-row-start: var(--animus-grid-row-start-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-grid-row-start-lg {\n grid-row-start: var(--animus-grid-row-start-lg);\n }\n }\n .animus-dyn-h {\n height: var(--animus-h);\n }\n .animus-dyn-height {\n height: var(--animus-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-h-sm {\n height: var(--animus-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-height-sm {\n height: var(--animus-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-h-md {\n height: var(--animus-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-height-md {\n height: var(--animus-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-h-lg {\n height: var(--animus-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-height-lg {\n height: var(--animus-height-lg);\n }\n }\n .animus-dyn-justify-content {\n justify-content: var(--animus-justify-content);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-content-sm {\n justify-content: var(--animus-justify-content-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-content-md {\n justify-content: var(--animus-justify-content-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-content-lg {\n justify-content: var(--animus-justify-content-lg);\n }\n }\n .animus-dyn-justify-items {\n justify-items: var(--animus-justify-items);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-items-sm {\n justify-items: var(--animus-justify-items-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-items-md {\n justify-items: var(--animus-justify-items-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-items-lg {\n justify-items: var(--animus-justify-items-lg);\n }\n }\n .animus-dyn-justify-self {\n justify-self: var(--animus-justify-self);\n }\n @media (min-width: 640px) {\n .animus-dyn-justify-self-sm {\n justify-self: var(--animus-justify-self-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-justify-self-md {\n justify-self: var(--animus-justify-self-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-justify-self-lg {\n justify-self: var(--animus-justify-self-lg);\n }\n }\n .animus-dyn-left {\n left: var(--animus-left);\n }\n @media (min-width: 640px) {\n .animus-dyn-left-sm {\n left: var(--animus-left-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-left-md {\n left: var(--animus-left-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-left-lg {\n left: var(--animus-left-lg);\n }\n }\n .animus-dyn-mb {\n margin-bottom: var(--animus-mb);\n }\n @media (min-width: 640px) {\n .animus-dyn-mb-sm {\n margin-bottom: var(--animus-mb-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mb-md {\n margin-bottom: var(--animus-mb-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mb-lg {\n margin-bottom: var(--animus-mb-lg);\n }\n }\n .animus-dyn-ml {\n margin-left: var(--animus-ml);\n }\n .animus-dyn-mx {\n margin-left: var(--animus-mx);\n margin-right: var(--animus-mx);\n }\n @media (min-width: 640px) {\n .animus-dyn-ml-sm {\n margin-left: var(--animus-ml-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-mx-sm {\n margin-left: var(--animus-mx-sm);\n margin-right: var(--animus-mx-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-ml-md {\n margin-left: var(--animus-ml-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mx-md {\n margin-left: var(--animus-mx-md);\n margin-right: var(--animus-mx-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-ml-lg {\n margin-left: var(--animus-ml-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mx-lg {\n margin-left: var(--animus-mx-lg);\n margin-right: var(--animus-mx-lg);\n }\n }\n .animus-dyn-mr {\n margin-right: var(--animus-mr);\n }\n @media (min-width: 640px) {\n .animus-dyn-mr-sm {\n margin-right: var(--animus-mr-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mr-md {\n margin-right: var(--animus-mr-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mr-lg {\n margin-right: var(--animus-mr-lg);\n }\n }\n .animus-dyn-mt {\n margin-top: var(--animus-mt);\n }\n .animus-dyn-my {\n margin-top: var(--animus-my);\n margin-bottom: var(--animus-my);\n }\n @media (min-width: 640px) {\n .animus-dyn-mt-sm {\n margin-top: var(--animus-mt-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-my-sm {\n margin-top: var(--animus-my-sm);\n margin-bottom: var(--animus-my-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-mt-md {\n margin-top: var(--animus-mt-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-my-md {\n margin-top: var(--animus-my-md);\n margin-bottom: var(--animus-my-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-mt-lg {\n margin-top: var(--animus-mt-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-my-lg {\n margin-top: var(--animus-my-lg);\n margin-bottom: var(--animus-my-lg);\n }\n }\n .animus-dyn-max-h {\n max-height: var(--animus-max-h);\n }\n .animus-dyn-max-height {\n max-height: var(--animus-max-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-max-h-sm {\n max-height: var(--animus-max-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-max-height-sm {\n max-height: var(--animus-max-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-h-md {\n max-height: var(--animus-max-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-height-md {\n max-height: var(--animus-max-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-h-lg {\n max-height: var(--animus-max-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-height-lg {\n max-height: var(--animus-max-height-lg);\n }\n }\n .animus-dyn-max-w {\n max-width: var(--animus-max-w);\n }\n .animus-dyn-max-width {\n max-width: var(--animus-max-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-max-w-sm {\n max-width: var(--animus-max-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-max-width-sm {\n max-width: var(--animus-max-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-w-md {\n max-width: var(--animus-max-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-max-width-md {\n max-width: var(--animus-max-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-w-lg {\n max-width: var(--animus-max-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-max-width-lg {\n max-width: var(--animus-max-width-lg);\n }\n }\n .animus-dyn-min-h {\n min-height: var(--animus-min-h);\n }\n .animus-dyn-min-height {\n min-height: var(--animus-min-height);\n }\n @media (min-width: 640px) {\n .animus-dyn-min-h-sm {\n min-height: var(--animus-min-h-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-min-height-sm {\n min-height: var(--animus-min-height-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-h-md {\n min-height: var(--animus-min-h-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-height-md {\n min-height: var(--animus-min-height-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-h-lg {\n min-height: var(--animus-min-h-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-height-lg {\n min-height: var(--animus-min-height-lg);\n }\n }\n .animus-dyn-min-w {\n min-width: var(--animus-min-w);\n }\n .animus-dyn-min-width {\n min-width: var(--animus-min-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-min-w-sm {\n min-width: var(--animus-min-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-min-width-sm {\n min-width: var(--animus-min-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-w-md {\n min-width: var(--animus-min-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-min-width-md {\n min-width: var(--animus-min-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-w-lg {\n min-width: var(--animus-min-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-min-width-lg {\n min-width: var(--animus-min-width-lg);\n }\n }\n .animus-dyn-opacity {\n opacity: var(--animus-opacity);\n }\n @media (min-width: 640px) {\n .animus-dyn-opacity-sm {\n opacity: var(--animus-opacity-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-opacity-md {\n opacity: var(--animus-opacity-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-opacity-lg {\n opacity: var(--animus-opacity-lg);\n }\n }\n .animus-dyn-order {\n order: var(--animus-order);\n }\n @media (min-width: 640px) {\n .animus-dyn-order-sm {\n order: var(--animus-order-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-order-md {\n order: var(--animus-order-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-order-lg {\n order: var(--animus-order-lg);\n }\n }\n .animus-dyn-overflow-x {\n overflow-x: var(--animus-overflow-x);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-x-sm {\n overflow-x: var(--animus-overflow-x-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-x-md {\n overflow-x: var(--animus-overflow-x-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-x-lg {\n overflow-x: var(--animus-overflow-x-lg);\n }\n }\n .animus-dyn-overflow-y {\n overflow-y: var(--animus-overflow-y);\n }\n @media (min-width: 640px) {\n .animus-dyn-overflow-y-sm {\n overflow-y: var(--animus-overflow-y-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-overflow-y-md {\n overflow-y: var(--animus-overflow-y-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-overflow-y-lg {\n overflow-y: var(--animus-overflow-y-lg);\n }\n }\n .animus-dyn-pb {\n padding-bottom: var(--animus-pb);\n }\n @media (min-width: 640px) {\n .animus-dyn-pb-sm {\n padding-bottom: var(--animus-pb-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pb-md {\n padding-bottom: var(--animus-pb-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pb-lg {\n padding-bottom: var(--animus-pb-lg);\n }\n }\n .animus-dyn-pl {\n padding-left: var(--animus-pl);\n }\n .animus-dyn-px {\n padding-left: var(--animus-px);\n padding-right: var(--animus-px);\n }\n @media (min-width: 640px) {\n .animus-dyn-pl-sm {\n padding-left: var(--animus-pl-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-px-sm {\n padding-left: var(--animus-px-sm);\n padding-right: var(--animus-px-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pl-md {\n padding-left: var(--animus-pl-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-px-md {\n padding-left: var(--animus-px-md);\n padding-right: var(--animus-px-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pl-lg {\n padding-left: var(--animus-pl-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-px-lg {\n padding-left: var(--animus-px-lg);\n padding-right: var(--animus-px-lg);\n }\n }\n .animus-dyn-pr {\n padding-right: var(--animus-pr);\n }\n @media (min-width: 640px) {\n .animus-dyn-pr-sm {\n padding-right: var(--animus-pr-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pr-md {\n padding-right: var(--animus-pr-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pr-lg {\n padding-right: var(--animus-pr-lg);\n }\n }\n .animus-dyn-pt {\n padding-top: var(--animus-pt);\n }\n .animus-dyn-py {\n padding-top: var(--animus-py);\n padding-bottom: var(--animus-py);\n }\n @media (min-width: 640px) {\n .animus-dyn-pt-sm {\n padding-top: var(--animus-pt-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-py-sm {\n padding-top: var(--animus-py-sm);\n padding-bottom: var(--animus-py-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pt-md {\n padding-top: var(--animus-pt-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-py-md {\n padding-top: var(--animus-py-md);\n padding-bottom: var(--animus-py-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pt-lg {\n padding-top: var(--animus-pt-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-py-lg {\n padding-top: var(--animus-py-lg);\n padding-bottom: var(--animus-py-lg);\n }\n }\n .animus-dyn-pos {\n position: var(--animus-pos);\n }\n .animus-dyn-position {\n position: var(--animus-position);\n }\n @media (min-width: 640px) {\n .animus-dyn-pos-sm {\n position: var(--animus-pos-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-position-sm {\n position: var(--animus-position-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-pos-md {\n position: var(--animus-pos-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-position-md {\n position: var(--animus-position-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-pos-lg {\n position: var(--animus-pos-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-position-lg {\n position: var(--animus-position-lg);\n }\n }\n .animus-dyn-right {\n right: var(--animus-right);\n }\n @media (min-width: 640px) {\n .animus-dyn-right-sm {\n right: var(--animus-right-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-right-md {\n right: var(--animus-right-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-right-lg {\n right: var(--animus-right-lg);\n }\n }\n .animus-dyn-row-gap {\n row-gap: var(--animus-row-gap);\n }\n @media (min-width: 640px) {\n .animus-dyn-row-gap-sm {\n row-gap: var(--animus-row-gap-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-row-gap-md {\n row-gap: var(--animus-row-gap-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-row-gap-lg {\n row-gap: var(--animus-row-gap-lg);\n }\n }\n .animus-dyn-inset {\n top: var(--animus-inset);\n right: var(--animus-inset);\n bottom: var(--animus-inset);\n left: var(--animus-inset);\n }\n .animus-dyn-top {\n top: var(--animus-top);\n }\n @media (min-width: 640px) {\n .animus-dyn-inset-sm {\n top: var(--animus-inset-sm);\n right: var(--animus-inset-sm);\n bottom: var(--animus-inset-sm);\n left: var(--animus-inset-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-top-sm {\n top: var(--animus-top-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-inset-md {\n top: var(--animus-inset-md);\n right: var(--animus-inset-md);\n bottom: var(--animus-inset-md);\n left: var(--animus-inset-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-top-md {\n top: var(--animus-top-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-inset-lg {\n top: var(--animus-inset-lg);\n right: var(--animus-inset-lg);\n bottom: var(--animus-inset-lg);\n left: var(--animus-inset-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-top-lg {\n top: var(--animus-top-lg);\n }\n }\n .animus-dyn-vertical-align {\n vertical-align: var(--animus-vertical-align);\n }\n @media (min-width: 640px) {\n .animus-dyn-vertical-align-sm {\n vertical-align: var(--animus-vertical-align-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-vertical-align-md {\n vertical-align: var(--animus-vertical-align-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-vertical-align-lg {\n vertical-align: var(--animus-vertical-align-lg);\n }\n }\n .animus-dyn-size {\n width: var(--animus-size);\n height: var(--animus-size);\n }\n .animus-dyn-w {\n width: var(--animus-w);\n }\n .animus-dyn-width {\n width: var(--animus-width);\n }\n @media (min-width: 640px) {\n .animus-dyn-size-sm {\n width: var(--animus-size-sm);\n height: var(--animus-size-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-w-sm {\n width: var(--animus-w-sm);\n }\n }\n @media (min-width: 640px) {\n .animus-dyn-width-sm {\n width: var(--animus-width-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-size-md {\n width: var(--animus-size-md);\n height: var(--animus-size-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-w-md {\n width: var(--animus-w-md);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-width-md {\n width: var(--animus-width-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-size-lg {\n width: var(--animus-size-lg);\n height: var(--animus-size-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-w-lg {\n width: var(--animus-w-lg);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-width-lg {\n width: var(--animus-width-lg);\n }\n }\n .animus-dyn-z-index {\n z-index: var(--animus-z-index);\n }\n @media (min-width: 640px) {\n .animus-dyn-z-index-sm {\n z-index: var(--animus-z-index-sm);\n }\n }\n @media (min-width: 768px) {\n .animus-dyn-z-index-md {\n z-index: var(--animus-z-index-md);\n }\n }\n @media (min-width: 1024px) {\n .animus-dyn-z-index-lg {\n z-index: var(--animus-z-index-lg);\n }\n }\n}\n","custom":""},"diagnostics":[{"file":"../../packages/test-ds/src/components/Alert.tsx","component":"Alert","kind":"external-token-candidate","message":"'space.0.75rem' in 'padding' did not resolve against the consumer theme","token":"space.0.75rem"},{"file":"../../packages/test-ds/src/components/Alert.tsx","component":"Alert","kind":"external-token-candidate","message":"'radii.4px' in 'border-radius' did not resolve against the consumer theme","token":"radii.4px"},{"file":"../../packages/test-ds/src/components/Alert.tsx","component":"Alert","kind":"external-token-candidate","message":"'fontSizes.0.875rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.0.875rem"},{"file":"../../packages/test-ds/src/components/Alert.tsx","component":"Alert","kind":"external-token-candidate","message":"'lineHeights.1.5' in 'line-height' did not resolve against the consumer theme","token":"lineHeights.1.5"},{"file":"../../packages/test-ds/src/components/Alert.tsx","component":"Alert","kind":"external-token-candidate","message":"'borderWidths.1px' in 'border-width' did not resolve against the consumer theme","token":"borderWidths.1px"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'space.0.5rem' in 'padding-left' did not resolve against the consumer theme","token":"space.0.5rem"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'space.0.5rem' in 'padding-right' did not resolve against the consumer theme","token":"space.0.5rem"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'space.0.25rem' in 'padding-top' did not resolve against the consumer theme","token":"space.0.25rem"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'space.0.25rem' in 'padding-bottom' did not resolve against the consumer theme","token":"space.0.25rem"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'radii.9999px' in 'border-radius' did not resolve against the consumer theme","token":"radii.9999px"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'fontSizes.0.75rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.0.75rem"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'fontWeights.500' in 'font-weight' did not resolve against the consumer theme","token":"fontWeights.500"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'lineHeights.1' in 'line-height' did not resolve against the consumer theme","token":"lineHeights.1"},{"file":"../../packages/test-ds/src/components/Badge.tsx","component":"Badge","kind":"external-token-candidate","message":"'opacities.0.5' in 'opacity' did not resolve against the consumer theme","token":"opacities.0.5"},{"file":"../../packages/test-ds/src/components/Button.tsx","component":"Button","kind":"external-token-candidate","message":"'radii.4px' in 'border-radius' did not resolve against the consumer theme","token":"radii.4px"},{"file":"../../packages/test-ds/src/components/Button.tsx","component":"Button","kind":"external-token-candidate","message":"'fontWeights.600' in 'font-weight' did not resolve against the consumer theme","token":"fontWeights.600"},{"file":"../../packages/test-ds/src/components/Button.tsx","component":"Button","kind":"external-token-candidate","message":"'lineHeights.1' in 'line-height' did not resolve against the consumer theme","token":"lineHeights.1"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'space.1rem' in 'padding' did not resolve against the consumer theme","token":"space.1rem"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'radii.8px' in 'border-radius' did not resolve against the consumer theme","token":"radii.8px"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'space.1.5rem' in 'padding' did not resolve against the consumer theme","token":"space.1.5rem"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'fontSizes.0.875rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.0.875rem"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'fontSizes.1rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.1rem"},{"file":"../../packages/test-ds/src/components/Card.tsx","component":"Card","kind":"external-token-candidate","message":"'space.2cqi' in 'gap' did not resolve against the consumer theme","token":"space.2cqi"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardBody","kind":"external-token-candidate","message":"'fontSizes.0.875rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.0.875rem"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardBody","kind":"external-token-candidate","message":"'lineHeights.1.5' in 'line-height' did not resolve against the consumer theme","token":"lineHeights.1.5"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardBody","kind":"external-token-candidate","message":"'fontSizes.1rem' in 'font-size' did not resolve against the consumer theme","token":"fontSizes.1rem"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardMedia","kind":"external-token-candidate","message":"'radii.4px' in 'border-radius' did not resolve against the consumer theme","token":"radii.4px"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardRoot","kind":"external-token-candidate","message":"'space.0.5rem' in 'gap' did not resolve against the consumer theme","token":"space.0.5rem"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardRoot","kind":"external-token-candidate","message":"'space.1rem' in 'padding' did not resolve against the consumer theme","token":"space.1rem"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardRoot","kind":"external-token-candidate","message":"'radii.8px' in 'border-radius' did not resolve against the consumer theme","token":"radii.8px"},{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","component":"ContainerCardRoot","kind":"external-token-candidate","message":"'space.1.5rem' in 'padding' did not resolve against the consumer theme","token":"space.1.5rem"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'space.0.5rem' in 'padding-left' did not resolve against the consumer theme","token":"space.0.5rem"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'space.0.5rem' in 'padding-right' did not resolve against the consumer theme","token":"space.0.5rem"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'space.0.25rem' in 'padding-top' did not resolve against the consumer theme","token":"space.0.25rem"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'space.0.25rem' in 'padding-bottom' did not resolve against the consumer theme","token":"space.0.25rem"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'radii.4px' in 'border-radius' did not resolve against the consumer theme","token":"radii.4px"},{"file":"../../packages/test-ds/src/components/GroupItem.tsx","component":"GroupItem","kind":"external-token-candidate","message":"'opacities.0.9' in 'opacity' did not resolve against the consumer theme","token":"opacities.0.9"}],"report":{"components_total":10,"components_extracted":10,"components_eliminated":0,"variants_total":16,"variants_used":16,"variants_eliminated":0,"states_total":2,"states_used":2,"states_eliminated":0,"components_forced":0,"variants_forced":0,"states_forced":0,"eliminated_details":[]},"system_prop_map":{},"dynamic_props":{"alignContent":{"varName":"--animus-align-content","slotClass":"animus-dyn-align-content","property":"alignContent","transformName":null,"transformFnSource":null,"scaleValues":{}},"alignItems":{"varName":"--animus-align-items","slotClass":"animus-dyn-align-items","property":"alignItems","transformName":null,"transformFnSource":null,"scaleValues":{}},"alignSelf":{"varName":"--animus-align-self","slotClass":"animus-dyn-align-self","property":"alignSelf","transformName":null,"transformFnSource":null,"scaleValues":{}},"area":{"varName":"--animus-area","slotClass":"animus-dyn-area","property":"gridArea","transformName":null,"transformFnSource":null,"scaleValues":{}},"bottom":{"varName":"--animus-bottom","slotClass":"animus-dyn-bottom","property":"bottom","transformName":"size","transformFnSource":null,"scaleValues":{}},"columnGap":{"varName":"--animus-column-gap","slotClass":"animus-dyn-column-gap","property":"columnGap","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"display":{"varName":"--animus-display","slotClass":"animus-dyn-display","property":"display","transformName":null,"transformFnSource":null,"scaleValues":{}},"flex":{"varName":"--animus-flex","slotClass":"animus-dyn-flex","property":"flex","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexBasis":{"varName":"--animus-flex-basis","slotClass":"animus-dyn-flex-basis","property":"flexBasis","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexDir":{"varName":"--animus-flex-dir","slotClass":"animus-dyn-flex-dir","property":"flexDirection","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexDirection":{"varName":"--animus-flex-direction","slotClass":"animus-dyn-flex-direction","property":"flexDirection","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexGrow":{"varName":"--animus-flex-grow","slotClass":"animus-dyn-flex-grow","property":"flexGrow","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexShrink":{"varName":"--animus-flex-shrink","slotClass":"animus-dyn-flex-shrink","property":"flexShrink","transformName":null,"transformFnSource":null,"scaleValues":{}},"flexWrap":{"varName":"--animus-flex-wrap","slotClass":"animus-dyn-flex-wrap","property":"flexWrap","transformName":null,"transformFnSource":null,"scaleValues":{}},"fontSize":{"varName":"--animus-font-size","slotClass":"animus-dyn-font-size","property":"fontSize","transformName":null,"transformFnSource":null,"scaleValues":{"12":"0.75rem","14":"0.875rem","16":"1rem","20":"1.25rem","24":"1.5rem"}},"gap":{"varName":"--animus-gap","slotClass":"animus-dyn-gap","property":"gap","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"gridArea":{"varName":"--animus-grid-area","slotClass":"animus-dyn-grid-area","property":"gridArea","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridColumn":{"varName":"--animus-grid-column","slotClass":"animus-dyn-grid-column","property":"gridColumn","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridColumnEnd":{"varName":"--animus-grid-column-end","slotClass":"animus-dyn-grid-column-end","property":"gridColumnEnd","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridColumnStart":{"varName":"--animus-grid-column-start","slotClass":"animus-dyn-grid-column-start","property":"gridColumnStart","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridRow":{"varName":"--animus-grid-row","slotClass":"animus-dyn-grid-row","property":"gridRow","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridRowEnd":{"varName":"--animus-grid-row-end","slotClass":"animus-dyn-grid-row-end","property":"gridRowEnd","transformName":null,"transformFnSource":null,"scaleValues":{}},"gridRowStart":{"varName":"--animus-grid-row-start","slotClass":"animus-dyn-grid-row-start","property":"gridRowStart","transformName":null,"transformFnSource":null,"scaleValues":{}},"h":{"varName":"--animus-h","slotClass":"animus-dyn-h","property":"height","transformName":"size","transformFnSource":null,"scaleValues":{}},"height":{"varName":"--animus-height","slotClass":"animus-dyn-height","property":"height","transformName":"size","transformFnSource":null,"scaleValues":{}},"inset":{"varName":"--animus-inset","slotClass":"animus-dyn-inset","property":"inset","properties":["top","right","bottom","left"],"transformName":"size","transformFnSource":null,"scaleValues":{}},"justifyContent":{"varName":"--animus-justify-content","slotClass":"animus-dyn-justify-content","property":"justifyContent","transformName":null,"transformFnSource":null,"scaleValues":{}},"justifyItems":{"varName":"--animus-justify-items","slotClass":"animus-dyn-justify-items","property":"justifyItems","transformName":null,"transformFnSource":null,"scaleValues":{}},"justifySelf":{"varName":"--animus-justify-self","slotClass":"animus-dyn-justify-self","property":"justifySelf","transformName":null,"transformFnSource":null,"scaleValues":{}},"left":{"varName":"--animus-left","slotClass":"animus-dyn-left","property":"left","transformName":"size","transformFnSource":null,"scaleValues":{}},"m":{"varName":"--animus-m","slotClass":"animus-dyn-m","property":"margin","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"maxH":{"varName":"--animus-max-h","slotClass":"animus-dyn-max-h","property":"maxHeight","transformName":"size","transformFnSource":null,"scaleValues":{}},"maxHeight":{"varName":"--animus-max-height","slotClass":"animus-dyn-max-height","property":"maxHeight","transformName":"size","transformFnSource":null,"scaleValues":{}},"maxW":{"varName":"--animus-max-w","slotClass":"animus-dyn-max-w","property":"maxWidth","transformName":"size","transformFnSource":null,"scaleValues":{}},"maxWidth":{"varName":"--animus-max-width","slotClass":"animus-dyn-max-width","property":"maxWidth","transformName":"size","transformFnSource":null,"scaleValues":{}},"mb":{"varName":"--animus-mb","slotClass":"animus-dyn-mb","property":"marginBottom","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"minH":{"varName":"--animus-min-h","slotClass":"animus-dyn-min-h","property":"minHeight","transformName":"size","transformFnSource":null,"scaleValues":{}},"minHeight":{"varName":"--animus-min-height","slotClass":"animus-dyn-min-height","property":"minHeight","transformName":"size","transformFnSource":null,"scaleValues":{}},"minW":{"varName":"--animus-min-w","slotClass":"animus-dyn-min-w","property":"minWidth","transformName":"size","transformFnSource":null,"scaleValues":{}},"minWidth":{"varName":"--animus-min-width","slotClass":"animus-dyn-min-width","property":"minWidth","transformName":"size","transformFnSource":null,"scaleValues":{}},"ml":{"varName":"--animus-ml","slotClass":"animus-dyn-ml","property":"marginLeft","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"mr":{"varName":"--animus-mr","slotClass":"animus-dyn-mr","property":"marginRight","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"mt":{"varName":"--animus-mt","slotClass":"animus-dyn-mt","property":"marginTop","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"mx":{"varName":"--animus-mx","slotClass":"animus-dyn-mx","property":"margin","properties":["marginLeft","marginRight"],"transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"my":{"varName":"--animus-my","slotClass":"animus-dyn-my","property":"margin","properties":["marginTop","marginBottom"],"transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"opacity":{"varName":"--animus-opacity","slotClass":"animus-dyn-opacity","property":"opacity","transformName":null,"transformFnSource":null,"scaleValues":{}},"order":{"varName":"--animus-order","slotClass":"animus-dyn-order","property":"order","transformName":null,"transformFnSource":null,"scaleValues":{}},"overflow":{"varName":"--animus-overflow","slotClass":"animus-dyn-overflow","property":"overflow","transformName":null,"transformFnSource":null,"scaleValues":{}},"overflowX":{"varName":"--animus-overflow-x","slotClass":"animus-dyn-overflow-x","property":"overflowX","transformName":null,"transformFnSource":null,"scaleValues":{}},"overflowY":{"varName":"--animus-overflow-y","slotClass":"animus-dyn-overflow-y","property":"overflowY","transformName":null,"transformFnSource":null,"scaleValues":{}},"p":{"varName":"--animus-p","slotClass":"animus-dyn-p","property":"padding","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"pb":{"varName":"--animus-pb","slotClass":"animus-dyn-pb","property":"paddingBottom","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"pl":{"varName":"--animus-pl","slotClass":"animus-dyn-pl","property":"paddingLeft","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"pos":{"varName":"--animus-pos","slotClass":"animus-dyn-pos","property":"position","transformName":null,"transformFnSource":null,"scaleValues":{}},"position":{"varName":"--animus-position","slotClass":"animus-dyn-position","property":"position","transformName":null,"transformFnSource":null,"scaleValues":{}},"pr":{"varName":"--animus-pr","slotClass":"animus-dyn-pr","property":"paddingRight","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"pt":{"varName":"--animus-pt","slotClass":"animus-dyn-pt","property":"paddingTop","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"px":{"varName":"--animus-px","slotClass":"animus-dyn-px","property":"padding","properties":["paddingLeft","paddingRight"],"transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"py":{"varName":"--animus-py","slotClass":"animus-dyn-py","property":"padding","properties":["paddingTop","paddingBottom"],"transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"right":{"varName":"--animus-right","slotClass":"animus-dyn-right","property":"right","transformName":"size","transformFnSource":null,"scaleValues":{}},"rowGap":{"varName":"--animus-row-gap","slotClass":"animus-dyn-row-gap","property":"rowGap","transformName":null,"transformFnSource":null,"scaleValues":{"0":"0","12":"0.75rem","16":"1rem","24":"1.5rem","32":"2rem","4":"0.25rem","8":"0.5rem"}},"size":{"varName":"--animus-size","slotClass":"animus-dyn-size","property":"width","properties":["width","height"],"transformName":"size","transformFnSource":null,"scaleValues":{}},"top":{"varName":"--animus-top","slotClass":"animus-dyn-top","property":"top","transformName":"size","transformFnSource":null,"scaleValues":{}},"verticalAlign":{"varName":"--animus-vertical-align","slotClass":"animus-dyn-vertical-align","property":"verticalAlign","transformName":null,"transformFnSource":null,"scaleValues":{}},"w":{"varName":"--animus-w","slotClass":"animus-dyn-w","property":"width","transformName":"size","transformFnSource":null,"scaleValues":{}},"width":{"varName":"--animus-width","slotClass":"animus-dyn-width","property":"width","transformName":"size","transformFnSource":null,"scaleValues":{}},"zIndex":{"varName":"--animus-z-index","slotClass":"animus-dyn-z-index","property":"zIndex","transformName":null,"transformFnSource":null,"scaleValues":{}}},"component_fragments":{"../../packages/test-ds/src/components/Alert.tsx::Alert":{"base":" .animus-Alert-a385f997 {\n padding: 0.75rem;\n display: flex;\n align-items: flex-start;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n }\n","variants":" .animus-Alert-a385f997--variant-filled {\n color: var(--color-background);\n }\n .animus-Alert-a385f997--variant-outline {\n border-width: 1px;\n border-style: solid;\n background-color: transparent;\n --current-bg: transparent;\n }\n .animus-Alert-a385f997--intent-info {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n }\n .animus-Alert-a385f997--intent-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n }\n .animus-Alert-a385f997--intent-success {\n background-color: var(--color-secondary);\n --current-bg: var(--color-secondary);\n }\n","compounds":" .animus-Alert-a385f997--compound-0 {\n border-color: var(--color-primary);\n color: var(--color-primary);\n }\n .animus-Alert-a385f997--compound-1 {\n border-color: var(--color-danger);\n color: var(--color-danger);\n }\n .animus-Alert-a385f997--compound-2 {\n border-color: var(--color-secondary);\n color: var(--color-secondary);\n }\n"},"../../packages/test-ds/src/components/Badge.tsx::Badge":{"base":" .animus-Badge-99781d29 {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 500;\n line-height: 1;\n }\n","variants":" .animus-Badge-99781d29--color-neutral {\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n .animus-Badge-99781d29--color-danger {\n background-color: var(--color-danger);\n --current-bg: var(--color-danger);\n color: var(--color-background);\n }\n","states":" .animus-Badge-99781d29--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .animus-Badge-99781d29--active {\n outline: 2px solid;\n outline-color: var(--color-primary);\n }\n"},"../../packages/test-ds/src/components/Card.tsx::Card":{"base":" .animus-Card-9aa7af5d {\n padding: 1rem;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n border-radius: 8px;\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n @container card (min-width: 400px) {\n .animus-Card-9aa7af5d {\n padding: 1.5rem;\n width: 50cqw;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .animus-Card-9aa7af5d {\n transition: none;\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d {\n display: grid;\n font-size: 0.875rem;\n }\n }\n @supports (display: grid) {\n @media (min-width: 640px) {\n .animus-Card-9aa7af5d {\n font-size: 1rem;\n }\n }\n }\n @supports (display: grid) {\n .animus-Card-9aa7af5d:focus-visible {\n outline: 2px solid;\n }\n }\n @supports (display: grid) {\n @container card (min-width: 600px) {\n .animus-Card-9aa7af5d {\n gap: 2cqi;\n }\n }\n }\n"},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardBody":{"base":" .animus-ContainerCardBody-133c6ad9 {\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--color-text);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardBody-133c6ad9 {\n font-size: 1rem;\n }\n }\n"},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardMedia":{"base":" .animus-ContainerCardMedia-51db5d07 {\n display: block;\n width: 100%;\n min-height: 64px;\n border-radius: 4px;\n background: var(--current-bg);\n }\n @container card (min-width: 400px) {\n .animus-ContainerCardMedia-51db5d07 {\n min-height: 120px;\n width: 50cqw;\n }\n }\n"},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardRoot":{"base":" .animus-ContainerCardRoot-01c5b011 {\n gap: 0.5rem;\n padding: 1rem;\n display: flex;\n flex-direction: column;\n border-radius: 8px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n container-type: inline-size;\n container-name: card;\n }\n","variants":" .animus-ContainerCardRoot-01c5b011--size-lg {\n padding: 1.5rem;\n }\n"},"../../packages/test-ds/src/components/GroupItem.tsx::GroupItem":{"base":" .animus-GroupItem-32b2d32f {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n display: inline-flex;\n align-items: center;\n border-radius: 4px;\n background-color: var(--color-surface);\n --current-bg: var(--color-surface);\n color: var(--color-text);\n }\n [data-active=\"true\"] .animus-GroupItem-32b2d32f {\n background-color: var(--color-primary);\n --current-bg: var(--color-primary);\n color: var(--color-background);\n }\n .group:hover .animus-GroupItem-32b2d32f {\n opacity: 0.9;\n }\n [data-color-mode=\"dark\"] .animus-GroupItem-32b2d32f {\n color: var(--color-text-muted);\n }\n"},"src/Box.tsx::Box":{"base":" .animus-Box-399302cf {\n display: flex;\n position: relative;\n }\n"},"src/Button.tsx::Button":{"base":" .animus-Button-b3718a43 {\n border-radius: 4px;\n padding: 8px;\n background-color: var(--color-blue-500);\n }\n","variants":" .animus-Button-b3718a43--tone-quiet {\n background-color: var(--color-gray-700);\n }\n .animus-Button-b3718a43--tone-loud {\n font-weight: 700;\n background-color: var(--color-blue-700);\n }\n"}},"reverse_provenance":{},"components":{"../../packages/test-ds/src/components/Alert.tsx::Alert":{"file":"../../packages/test-ds/src/components/Alert.tsx","binding":"Alert","class_name":"animus-Alert-a385f997","extends_from":null,"terminal":"asElement","tag":"div","replacement":"createComponent('div', 'animus-Alert-a385f997', {\"variants\":{\"variant\":{\"options\":[\"filled\",\"outline\"]},\"intent\":{\"options\":[\"info\",\"danger\",\"success\"]}},\"compounds\":[{\"conditions\":{\"intent\":\"info\",\"variant\":\"outline\"},\"className\":\"animus-Alert-a385f997--compound-0\"},{\"conditions\":{\"intent\":\"danger\",\"variant\":\"outline\"},\"className\":\"animus-Alert-a385f997--compound-1\"},{\"conditions\":{\"intent\":\"success\",\"variant\":\"outline\"},\"className\":\"animus-Alert-a385f997--compound-2\"}]})","system_prop_names":[]},"../../packages/test-ds/src/components/Badge.tsx::Badge":{"file":"../../packages/test-ds/src/components/Badge.tsx","binding":"Badge","class_name":"animus-Badge-99781d29","extends_from":null,"terminal":"asElement","tag":"span","replacement":"createComponent('span', 'animus-Badge-99781d29', {\"variants\":{\"color\":{\"options\":[\"neutral\",\"danger\"]}},\"states\":[\"disabled\",\"active\"]})","system_prop_names":[]},"../../packages/test-ds/src/components/Button.tsx::Button":{"file":"../../packages/test-ds/src/components/Button.tsx","binding":"Button","class_name":"animus-Button-c63b6dcd","extends_from":null,"terminal":"asElement","tag":"button","replacement":"createComponent('button', 'animus-Button-c63b6dcd', {\"variants\":{\"variant\":{\"options\":[\"primary\",\"secondary\",\"ghost\"]}},\"systemPropNames\":[\"fontSize\",\"px\",\"py\"]}, systemPropMap, dynamicPropConfig)","system_prop_names":["fontSize","px","py"]},"../../packages/test-ds/src/components/Card.tsx::Card":{"file":"../../packages/test-ds/src/components/Card.tsx","binding":"Card","class_name":"animus-Card-9aa7af5d","extends_from":null,"terminal":"asElement","tag":"div","replacement":"createComponent('div', 'animus-Card-9aa7af5d', {\"systemPropNames\":[\"m\",\"mx\",\"my\"]}, systemPropMap, dynamicPropConfig)","system_prop_names":["m","mx","my"]},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardBody":{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","binding":"ContainerCardBody","class_name":"animus-ContainerCardBody-133c6ad9","extends_from":null,"terminal":"asElement","tag":"div","replacement":"createComponent('div', 'animus-ContainerCardBody-133c6ad9', {})","system_prop_names":[]},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardMedia":{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","binding":"ContainerCardMedia","class_name":"animus-ContainerCardMedia-51db5d07","extends_from":null,"terminal":"asElement","tag":"div","replacement":"createComponent('div', 'animus-ContainerCardMedia-51db5d07', {\"variants\":{\"size\":{\"options\":[\"md\",\"lg\"],\"default\":\"md\"}}})","system_prop_names":[]},"../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardRoot":{"file":"../../packages/test-ds/src/components/ContainerCard.tsx","binding":"ContainerCardRoot","class_name":"animus-ContainerCardRoot-01c5b011","extends_from":null,"terminal":"asElement","tag":"article","replacement":"createComponent('article', 'animus-ContainerCardRoot-01c5b011', {\"variants\":{\"size\":{\"options\":[\"md\",\"lg\"],\"default\":\"md\"}}})","system_prop_names":[]},"../../packages/test-ds/src/components/GroupItem.tsx::GroupItem":{"file":"../../packages/test-ds/src/components/GroupItem.tsx","binding":"GroupItem","class_name":"animus-GroupItem-32b2d32f","extends_from":null,"terminal":"asElement","tag":"span","replacement":"createComponent('span', 'animus-GroupItem-32b2d32f', {})","system_prop_names":[]},"src/Box.tsx::Box":{"file":"src/Box.tsx","binding":"Box","class_name":"animus-Box-399302cf","extends_from":null,"terminal":"asElement","tag":"div","replacement":"createComponent('div', 'animus-Box-399302cf', {\"systemPropNames\":[].concat(systemPropGroups.layout,systemPropGroups.positioning,systemPropGroups.space)}, systemPropMap, dynamicPropConfig)","system_prop_names":["alignContent","alignItems","alignSelf","area","bottom","columnGap","display","flex","flexBasis","flexDir","flexDirection","flexGrow","flexShrink","flexWrap","gap","gridArea","gridColumn","gridColumnEnd","gridColumnStart","gridRow","gridRowEnd","gridRowStart","h","height","inset","justifyContent","justifyItems","justifySelf","left","m","maxH","maxHeight","maxW","maxWidth","mb","minH","minHeight","minW","minWidth","ml","mr","mt","mx","my","opacity","order","overflow","overflowX","overflowY","p","pb","pl","pos","position","pr","pt","px","py","right","rowGap","size","top","verticalAlign","w","width","zIndex"]},"src/Button.tsx::Button":{"file":"src/Button.tsx","binding":"Button","class_name":"animus-Button-b3718a43","extends_from":null,"terminal":"asElement","tag":"button","replacement":"createComponent('button', 'animus-Button-b3718a43', {\"variants\":{\"tone\":{\"options\":[\"quiet\",\"loud\"]}}})","system_prop_names":[]}},"files":{"../../packages/test-ds/src/components/Alert.tsx":["../../packages/test-ds/src/components/Alert.tsx::Alert"],"../../packages/test-ds/src/components/Badge.tsx":["../../packages/test-ds/src/components/Badge.tsx::Badge"],"../../packages/test-ds/src/components/Button.tsx":["../../packages/test-ds/src/components/Button.tsx::Button"],"../../packages/test-ds/src/components/Card.tsx":["../../packages/test-ds/src/components/Card.tsx::Card"],"../../packages/test-ds/src/components/ContainerCard.tsx":["../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardBody","../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardMedia","../../packages/test-ds/src/components/ContainerCard.tsx::ContainerCardRoot"],"../../packages/test-ds/src/components/GroupItem.tsx":["../../packages/test-ds/src/components/GroupItem.tsx::GroupItem"],"src/Box.tsx":["src/Box.tsx::Box"],"src/Button.tsx":["src/Button.tsx::Button"]},"timing":{"parseCount":17}} \ No newline at end of file diff --git a/packages/oracle/__tests__/fixtures/rollup-app/styles.css b/packages/oracle/__tests__/fixtures/rollup-app/styles.css index c67a6ab6..e1342c81 100644 --- a/packages/oracle/__tests__/fixtures/rollup-app/styles.css +++ b/packages/oracle/__tests__/fixtures/rollup-app/styles.css @@ -81,4 +81,4 @@ --color-text-muted: #737373; color-scheme: light; } -@layer anm-global{@font-face{font-family:AnimusTestFont;src:url(./assets/test-font.f2a09939.woff2)format("woff2");font-display:swap}*,:before,:after{box-sizing:border-box}body{background-color:var(--color-background);--current-bg:var(--color-background);color:var(--color-text);margin:0;font-family:system-ui,sans-serif}@keyframes animus-kf-1x7guim{0%{opacity:0;background-color:var(--color-background);--current-bg:var(--color-background)}to{opacity:1;background-color:var(--color-surface);--current-bg:var(--color-surface)}}@keyframes animus-kf-1yqv0zl{0%,to{transform:scale(1)}50%{transform:scale(1.05)}}@keyframes animus-kf-muo7kp{0%,to{opacity:1}50%{opacity:.6}}}@layer anm-base{.animus-Alert-a385f997{border-radius:4px;align-items:flex-start;padding:.75rem;font-size:.875rem;line-height:1.5;display:flex}.animus-Badge-99781d29{border-radius:9999px;align-items:center;padding:.25rem .5rem;font-size:.75rem;font-weight:500;line-height:1;display:inline-flex}.animus-Card-9aa7af5d{background-color:var(--color-surface);--current-bg:var(--color-surface);color:var(--color-text);border-radius:8px;padding:1rem;container:card/inline-size}@container card (width>=400px){.animus-Card-9aa7af5d{width:50cqw;padding:1.5rem}}@media (prefers-reduced-motion:reduce){.animus-Card-9aa7af5d{transition:none}}@supports (display:grid){.animus-Card-9aa7af5d{font-size:.875rem;display:grid}@media (width>=640px){.animus-Card-9aa7af5d{font-size:1rem}}.animus-Card-9aa7af5d:focus-visible{outline:2px solid}@container card (width>=600px){.animus-Card-9aa7af5d{gap:2cqi}}}.animus-ContainerCardBody-133c6ad9{color:var(--color-text);font-size:.875rem;line-height:1.5}@container card (width>=400px){.animus-ContainerCardBody-133c6ad9{font-size:1rem}}.animus-ContainerCardMedia-51db5d07{background:var(--current-bg);border-radius:4px;width:100%;min-height:64px;display:block}@container card (width>=400px){.animus-ContainerCardMedia-51db5d07{width:50cqw;min-height:120px}}.animus-ContainerCardRoot-01c5b011{background-color:var(--color-surface);--current-bg:var(--color-surface);color:var(--color-text);border-radius:8px;flex-direction:column;gap:.5rem;padding:1rem;display:flex;container:card/inline-size}.animus-GroupItem-32b2d32f{background-color:var(--color-surface);--current-bg:var(--color-surface);color:var(--color-text);border-radius:4px;align-items:center;padding:.25rem .5rem;display:inline-flex}[data-active=true] .animus-GroupItem-32b2d32f{background-color:var(--color-primary);--current-bg:var(--color-primary);color:var(--color-background)}.group:hover .animus-GroupItem-32b2d32f{opacity:.9}[data-color-mode=dark] .animus-GroupItem-32b2d32f{color:var(--color-text-muted)}.animus-Box-399302cf{display:flex;position:relative}.animus-Button-c63b6dcd{cursor:pointer;border:none;border-radius:4px;justify-content:center;align-items:center;font-weight:600;line-height:1;display:inline-flex}.animus-Button-b3718a43{background-color:var(--color-blue-500);border-radius:4px;padding:8px}}@layer anm-variants{@layer standalone{.animus-Alert-a385f997--variant-filled{color:var(--color-background)}.animus-Alert-a385f997--variant-outline{--current-bg:transparent;background-color:#0000;border-style:solid;border-width:1px}.animus-Alert-a385f997--intent-info{background-color:var(--color-primary);--current-bg:var(--color-primary)}.animus-Alert-a385f997--intent-danger{background-color:var(--color-danger);--current-bg:var(--color-danger)}.animus-Alert-a385f997--intent-success{background-color:var(--color-secondary);--current-bg:var(--color-secondary)}.animus-Badge-99781d29--color-neutral{background-color:var(--color-surface);--current-bg:var(--color-surface);color:var(--color-text)}.animus-Badge-99781d29--color-danger{background-color:var(--color-danger);--current-bg:var(--color-danger);color:var(--color-background)}.animus-ContainerCardRoot-01c5b011--size-lg{padding:1.5rem}.animus-Button-c63b6dcd--variant-primary{background-color:var(--color-primary);--current-bg:var(--color-primary);color:var(--color-background)}.animus-Button-c63b6dcd--variant-secondary{background-color:var(--color-secondary);--current-bg:var(--color-secondary);color:var(--color-background)}.animus-Button-c63b6dcd--variant-ghost{--current-bg:transparent;color:var(--color-text);background-color:#0000}.animus-Button-b3718a43--tone-quiet{background-color:var(--color-gray-700)}.animus-Button-b3718a43--tone-loud{background-color:var(--color-blue-700);font-weight:700}}@layer composed;}@layer anm-compounds{.animus-Alert-a385f997--compound-0{border-color:var(--color-primary);color:var(--color-primary)}.animus-Alert-a385f997--compound-1{border-color:var(--color-danger);color:var(--color-danger)}.animus-Alert-a385f997--compound-2{border-color:var(--color-secondary);color:var(--color-secondary)}}@layer anm-states{.animus-Badge-99781d29--disabled{opacity:.5;cursor:not-allowed}.animus-Badge-99781d29--active{outline:2px solid;outline-color:var(--color-primary)}}@layer anm-system{.animus-dyn-flex{flex:var(--animus-flex)}@media (width>=640px){.animus-dyn-flex-sm{flex:var(--animus-flex-sm)}}@media (width>=768px){.animus-dyn-flex-md{flex:var(--animus-flex-md)}}@media (width>=1024px){.animus-dyn-flex-lg{flex:var(--animus-flex-lg)}}.animus-dyn-m{margin:var(--animus-m)}@media (width>=640px){.animus-dyn-m-sm{margin:var(--animus-m-sm)}}@media (width>=768px){.animus-dyn-m-md{margin:var(--animus-m-md)}}@media (width>=1024px){.animus-dyn-m-lg{margin:var(--animus-m-lg)}}.animus-dyn-p{padding:var(--animus-p)}@media (width>=640px){.animus-dyn-p-sm{padding:var(--animus-p-sm)}}@media (width>=768px){.animus-dyn-p-md{padding:var(--animus-p-md)}}@media (width>=1024px){.animus-dyn-p-lg{padding:var(--animus-p-lg)}}.animus-dyn-gap{gap:var(--animus-gap)}@media (width>=640px){.animus-dyn-gap-sm{gap:var(--animus-gap-sm)}}@media (width>=768px){.animus-dyn-gap-md{gap:var(--animus-gap-md)}}@media (width>=1024px){.animus-dyn-gap-lg{gap:var(--animus-gap-lg)}}.animus-dyn-area{grid-area:var(--animus-area)}.animus-dyn-grid-area{grid-area:var(--animus-grid-area)}@media (width>=640px){.animus-dyn-area-sm{grid-area:var(--animus-area-sm)}.animus-dyn-grid-area-sm{grid-area:var(--animus-grid-area-sm)}}@media (width>=768px){.animus-dyn-area-md{grid-area:var(--animus-area-md)}.animus-dyn-grid-area-md{grid-area:var(--animus-grid-area-md)}}@media (width>=1024px){.animus-dyn-area-lg{grid-area:var(--animus-area-lg)}.animus-dyn-grid-area-lg{grid-area:var(--animus-grid-area-lg)}}.animus-dyn-grid-column{grid-column:var(--animus-grid-column)}@media (width>=640px){.animus-dyn-grid-column-sm{grid-column:var(--animus-grid-column-sm)}}@media (width>=768px){.animus-dyn-grid-column-md{grid-column:var(--animus-grid-column-md)}}@media (width>=1024px){.animus-dyn-grid-column-lg{grid-column:var(--animus-grid-column-lg)}}.animus-dyn-grid-row{grid-row:var(--animus-grid-row)}@media (width>=640px){.animus-dyn-grid-row-sm{grid-row:var(--animus-grid-row-sm)}}@media (width>=768px){.animus-dyn-grid-row-md{grid-row:var(--animus-grid-row-md)}}@media (width>=1024px){.animus-dyn-grid-row-lg{grid-row:var(--animus-grid-row-lg)}}.animus-dyn-overflow{overflow:var(--animus-overflow)}@media (width>=640px){.animus-dyn-overflow-sm{overflow:var(--animus-overflow-sm)}}@media (width>=768px){.animus-dyn-overflow-md{overflow:var(--animus-overflow-md)}}@media (width>=1024px){.animus-dyn-overflow-lg{overflow:var(--animus-overflow-lg)}}.animus-dyn-align-content{align-content:var(--animus-align-content)}@media (width>=640px){.animus-dyn-align-content-sm{align-content:var(--animus-align-content-sm)}}@media (width>=768px){.animus-dyn-align-content-md{align-content:var(--animus-align-content-md)}}@media (width>=1024px){.animus-dyn-align-content-lg{align-content:var(--animus-align-content-lg)}}.animus-dyn-align-items{align-items:var(--animus-align-items)}@media (width>=640px){.animus-dyn-align-items-sm{align-items:var(--animus-align-items-sm)}}@media (width>=768px){.animus-dyn-align-items-md{align-items:var(--animus-align-items-md)}}@media (width>=1024px){.animus-dyn-align-items-lg{align-items:var(--animus-align-items-lg)}}.animus-dyn-align-self{align-self:var(--animus-align-self)}@media (width>=640px){.animus-dyn-align-self-sm{align-self:var(--animus-align-self-sm)}}@media (width>=768px){.animus-dyn-align-self-md{align-self:var(--animus-align-self-md)}}@media (width>=1024px){.animus-dyn-align-self-lg{align-self:var(--animus-align-self-lg)}}.animus-dyn-bottom{bottom:var(--animus-bottom)}@media (width>=640px){.animus-dyn-bottom-sm{bottom:var(--animus-bottom-sm)}}@media (width>=768px){.animus-dyn-bottom-md{bottom:var(--animus-bottom-md)}}@media (width>=1024px){.animus-dyn-bottom-lg{bottom:var(--animus-bottom-lg)}}.animus-dyn-column-gap{column-gap:var(--animus-column-gap)}@media (width>=640px){.animus-dyn-column-gap-sm{column-gap:var(--animus-column-gap-sm)}}@media (width>=768px){.animus-dyn-column-gap-md{column-gap:var(--animus-column-gap-md)}}@media (width>=1024px){.animus-dyn-column-gap-lg{column-gap:var(--animus-column-gap-lg)}}.animus-dyn-display{display:var(--animus-display)}@media (width>=640px){.animus-dyn-display-sm{display:var(--animus-display-sm)}}@media (width>=768px){.animus-dyn-display-md{display:var(--animus-display-md)}}@media (width>=1024px){.animus-dyn-display-lg{display:var(--animus-display-lg)}}.animus-dyn-flex-basis{flex-basis:var(--animus-flex-basis)}@media (width>=640px){.animus-dyn-flex-basis-sm{flex-basis:var(--animus-flex-basis-sm)}}@media (width>=768px){.animus-dyn-flex-basis-md{flex-basis:var(--animus-flex-basis-md)}}@media (width>=1024px){.animus-dyn-flex-basis-lg{flex-basis:var(--animus-flex-basis-lg)}}.animus-dyn-flex-dir{flex-direction:var(--animus-flex-dir)}.animus-dyn-flex-direction{flex-direction:var(--animus-flex-direction)}@media (width>=640px){.animus-dyn-flex-dir-sm{flex-direction:var(--animus-flex-dir-sm)}.animus-dyn-flex-direction-sm{flex-direction:var(--animus-flex-direction-sm)}}@media (width>=768px){.animus-dyn-flex-dir-md{flex-direction:var(--animus-flex-dir-md)}.animus-dyn-flex-direction-md{flex-direction:var(--animus-flex-direction-md)}}@media (width>=1024px){.animus-dyn-flex-dir-lg{flex-direction:var(--animus-flex-dir-lg)}.animus-dyn-flex-direction-lg{flex-direction:var(--animus-flex-direction-lg)}}.animus-dyn-flex-grow{flex-grow:var(--animus-flex-grow)}@media (width>=640px){.animus-dyn-flex-grow-sm{flex-grow:var(--animus-flex-grow-sm)}}@media (width>=768px){.animus-dyn-flex-grow-md{flex-grow:var(--animus-flex-grow-md)}}@media (width>=1024px){.animus-dyn-flex-grow-lg{flex-grow:var(--animus-flex-grow-lg)}}.animus-dyn-flex-shrink{flex-shrink:var(--animus-flex-shrink)}@media (width>=640px){.animus-dyn-flex-shrink-sm{flex-shrink:var(--animus-flex-shrink-sm)}}@media (width>=768px){.animus-dyn-flex-shrink-md{flex-shrink:var(--animus-flex-shrink-md)}}@media (width>=1024px){.animus-dyn-flex-shrink-lg{flex-shrink:var(--animus-flex-shrink-lg)}}.animus-dyn-flex-wrap{flex-wrap:var(--animus-flex-wrap)}@media (width>=640px){.animus-dyn-flex-wrap-sm{flex-wrap:var(--animus-flex-wrap-sm)}}@media (width>=768px){.animus-dyn-flex-wrap-md{flex-wrap:var(--animus-flex-wrap-md)}}@media (width>=1024px){.animus-dyn-flex-wrap-lg{flex-wrap:var(--animus-flex-wrap-lg)}}.animus-dyn-font-size{font-size:var(--animus-font-size)}@media (width>=640px){.animus-dyn-font-size-sm{font-size:var(--animus-font-size-sm)}}@media (width>=768px){.animus-dyn-font-size-md{font-size:var(--animus-font-size-md)}}@media (width>=1024px){.animus-dyn-font-size-lg{font-size:var(--animus-font-size-lg)}}.animus-dyn-grid-column-end{grid-column-end:var(--animus-grid-column-end)}@media (width>=640px){.animus-dyn-grid-column-end-sm{grid-column-end:var(--animus-grid-column-end-sm)}}@media (width>=768px){.animus-dyn-grid-column-end-md{grid-column-end:var(--animus-grid-column-end-md)}}@media (width>=1024px){.animus-dyn-grid-column-end-lg{grid-column-end:var(--animus-grid-column-end-lg)}}.animus-dyn-grid-column-start{grid-column-start:var(--animus-grid-column-start)}@media (width>=640px){.animus-dyn-grid-column-start-sm{grid-column-start:var(--animus-grid-column-start-sm)}}@media (width>=768px){.animus-dyn-grid-column-start-md{grid-column-start:var(--animus-grid-column-start-md)}}@media (width>=1024px){.animus-dyn-grid-column-start-lg{grid-column-start:var(--animus-grid-column-start-lg)}}.animus-dyn-grid-row-end{grid-row-end:var(--animus-grid-row-end)}@media (width>=640px){.animus-dyn-grid-row-end-sm{grid-row-end:var(--animus-grid-row-end-sm)}}@media (width>=768px){.animus-dyn-grid-row-end-md{grid-row-end:var(--animus-grid-row-end-md)}}@media (width>=1024px){.animus-dyn-grid-row-end-lg{grid-row-end:var(--animus-grid-row-end-lg)}}.animus-dyn-grid-row-start{grid-row-start:var(--animus-grid-row-start)}@media (width>=640px){.animus-dyn-grid-row-start-sm{grid-row-start:var(--animus-grid-row-start-sm)}}@media (width>=768px){.animus-dyn-grid-row-start-md{grid-row-start:var(--animus-grid-row-start-md)}}@media (width>=1024px){.animus-dyn-grid-row-start-lg{grid-row-start:var(--animus-grid-row-start-lg)}}.animus-dyn-h{height:var(--animus-h)}.animus-dyn-height{height:var(--animus-height)}@media (width>=640px){.animus-dyn-h-sm{height:var(--animus-h-sm)}.animus-dyn-height-sm{height:var(--animus-height-sm)}}@media (width>=768px){.animus-dyn-h-md{height:var(--animus-h-md)}.animus-dyn-height-md{height:var(--animus-height-md)}}@media (width>=1024px){.animus-dyn-h-lg{height:var(--animus-h-lg)}.animus-dyn-height-lg{height:var(--animus-height-lg)}}.animus-dyn-justify-content{justify-content:var(--animus-justify-content)}@media (width>=640px){.animus-dyn-justify-content-sm{justify-content:var(--animus-justify-content-sm)}}@media (width>=768px){.animus-dyn-justify-content-md{justify-content:var(--animus-justify-content-md)}}@media (width>=1024px){.animus-dyn-justify-content-lg{justify-content:var(--animus-justify-content-lg)}}.animus-dyn-justify-items{justify-items:var(--animus-justify-items)}@media (width>=640px){.animus-dyn-justify-items-sm{justify-items:var(--animus-justify-items-sm)}}@media (width>=768px){.animus-dyn-justify-items-md{justify-items:var(--animus-justify-items-md)}}@media (width>=1024px){.animus-dyn-justify-items-lg{justify-items:var(--animus-justify-items-lg)}}.animus-dyn-justify-self{justify-self:var(--animus-justify-self)}@media (width>=640px){.animus-dyn-justify-self-sm{justify-self:var(--animus-justify-self-sm)}}@media (width>=768px){.animus-dyn-justify-self-md{justify-self:var(--animus-justify-self-md)}}@media (width>=1024px){.animus-dyn-justify-self-lg{justify-self:var(--animus-justify-self-lg)}}.animus-dyn-left{left:var(--animus-left)}@media (width>=640px){.animus-dyn-left-sm{left:var(--animus-left-sm)}}@media (width>=768px){.animus-dyn-left-md{left:var(--animus-left-md)}}@media (width>=1024px){.animus-dyn-left-lg{left:var(--animus-left-lg)}}.animus-dyn-mb{margin-bottom:var(--animus-mb)}@media (width>=640px){.animus-dyn-mb-sm{margin-bottom:var(--animus-mb-sm)}}@media (width>=768px){.animus-dyn-mb-md{margin-bottom:var(--animus-mb-md)}}@media (width>=1024px){.animus-dyn-mb-lg{margin-bottom:var(--animus-mb-lg)}}.animus-dyn-ml{margin-left:var(--animus-ml)}.animus-dyn-mx{margin-left:var(--animus-mx);margin-right:var(--animus-mx)}@media (width>=640px){.animus-dyn-ml-sm{margin-left:var(--animus-ml-sm)}.animus-dyn-mx-sm{margin-left:var(--animus-mx-sm);margin-right:var(--animus-mx-sm)}}@media (width>=768px){.animus-dyn-ml-md{margin-left:var(--animus-ml-md)}.animus-dyn-mx-md{margin-left:var(--animus-mx-md);margin-right:var(--animus-mx-md)}}@media (width>=1024px){.animus-dyn-ml-lg{margin-left:var(--animus-ml-lg)}.animus-dyn-mx-lg{margin-left:var(--animus-mx-lg);margin-right:var(--animus-mx-lg)}}.animus-dyn-mr{margin-right:var(--animus-mr)}@media (width>=640px){.animus-dyn-mr-sm{margin-right:var(--animus-mr-sm)}}@media (width>=768px){.animus-dyn-mr-md{margin-right:var(--animus-mr-md)}}@media (width>=1024px){.animus-dyn-mr-lg{margin-right:var(--animus-mr-lg)}}.animus-dyn-mt{margin-top:var(--animus-mt)}.animus-dyn-my{margin-top:var(--animus-my);margin-bottom:var(--animus-my)}@media (width>=640px){.animus-dyn-mt-sm{margin-top:var(--animus-mt-sm)}.animus-dyn-my-sm{margin-top:var(--animus-my-sm);margin-bottom:var(--animus-my-sm)}}@media (width>=768px){.animus-dyn-mt-md{margin-top:var(--animus-mt-md)}.animus-dyn-my-md{margin-top:var(--animus-my-md);margin-bottom:var(--animus-my-md)}}@media (width>=1024px){.animus-dyn-mt-lg{margin-top:var(--animus-mt-lg)}.animus-dyn-my-lg{margin-top:var(--animus-my-lg);margin-bottom:var(--animus-my-lg)}}.animus-dyn-max-h{max-height:var(--animus-max-h)}.animus-dyn-max-height{max-height:var(--animus-max-height)}@media (width>=640px){.animus-dyn-max-h-sm{max-height:var(--animus-max-h-sm)}.animus-dyn-max-height-sm{max-height:var(--animus-max-height-sm)}}@media (width>=768px){.animus-dyn-max-h-md{max-height:var(--animus-max-h-md)}.animus-dyn-max-height-md{max-height:var(--animus-max-height-md)}}@media (width>=1024px){.animus-dyn-max-h-lg{max-height:var(--animus-max-h-lg)}.animus-dyn-max-height-lg{max-height:var(--animus-max-height-lg)}}.animus-dyn-max-w{max-width:var(--animus-max-w)}.animus-dyn-max-width{max-width:var(--animus-max-width)}@media (width>=640px){.animus-dyn-max-w-sm{max-width:var(--animus-max-w-sm)}.animus-dyn-max-width-sm{max-width:var(--animus-max-width-sm)}}@media (width>=768px){.animus-dyn-max-w-md{max-width:var(--animus-max-w-md)}.animus-dyn-max-width-md{max-width:var(--animus-max-width-md)}}@media (width>=1024px){.animus-dyn-max-w-lg{max-width:var(--animus-max-w-lg)}.animus-dyn-max-width-lg{max-width:var(--animus-max-width-lg)}}.animus-dyn-min-h{min-height:var(--animus-min-h)}.animus-dyn-min-height{min-height:var(--animus-min-height)}@media (width>=640px){.animus-dyn-min-h-sm{min-height:var(--animus-min-h-sm)}.animus-dyn-min-height-sm{min-height:var(--animus-min-height-sm)}}@media (width>=768px){.animus-dyn-min-h-md{min-height:var(--animus-min-h-md)}.animus-dyn-min-height-md{min-height:var(--animus-min-height-md)}}@media (width>=1024px){.animus-dyn-min-h-lg{min-height:var(--animus-min-h-lg)}.animus-dyn-min-height-lg{min-height:var(--animus-min-height-lg)}}.animus-dyn-min-w{min-width:var(--animus-min-w)}.animus-dyn-min-width{min-width:var(--animus-min-width)}@media (width>=640px){.animus-dyn-min-w-sm{min-width:var(--animus-min-w-sm)}.animus-dyn-min-width-sm{min-width:var(--animus-min-width-sm)}}@media (width>=768px){.animus-dyn-min-w-md{min-width:var(--animus-min-w-md)}.animus-dyn-min-width-md{min-width:var(--animus-min-width-md)}}@media (width>=1024px){.animus-dyn-min-w-lg{min-width:var(--animus-min-w-lg)}.animus-dyn-min-width-lg{min-width:var(--animus-min-width-lg)}}.animus-dyn-opacity{opacity:var(--animus-opacity)}@media (width>=640px){.animus-dyn-opacity-sm{opacity:var(--animus-opacity-sm)}}@media (width>=768px){.animus-dyn-opacity-md{opacity:var(--animus-opacity-md)}}@media (width>=1024px){.animus-dyn-opacity-lg{opacity:var(--animus-opacity-lg)}}.animus-dyn-order{order:var(--animus-order)}@media (width>=640px){.animus-dyn-order-sm{order:var(--animus-order-sm)}}@media (width>=768px){.animus-dyn-order-md{order:var(--animus-order-md)}}@media (width>=1024px){.animus-dyn-order-lg{order:var(--animus-order-lg)}}.animus-dyn-overflow-x{overflow-x:var(--animus-overflow-x)}@media (width>=640px){.animus-dyn-overflow-x-sm{overflow-x:var(--animus-overflow-x-sm)}}@media (width>=768px){.animus-dyn-overflow-x-md{overflow-x:var(--animus-overflow-x-md)}}@media (width>=1024px){.animus-dyn-overflow-x-lg{overflow-x:var(--animus-overflow-x-lg)}}.animus-dyn-overflow-y{overflow-y:var(--animus-overflow-y)}@media (width>=640px){.animus-dyn-overflow-y-sm{overflow-y:var(--animus-overflow-y-sm)}}@media (width>=768px){.animus-dyn-overflow-y-md{overflow-y:var(--animus-overflow-y-md)}}@media (width>=1024px){.animus-dyn-overflow-y-lg{overflow-y:var(--animus-overflow-y-lg)}}.animus-dyn-pb{padding-bottom:var(--animus-pb)}@media (width>=640px){.animus-dyn-pb-sm{padding-bottom:var(--animus-pb-sm)}}@media (width>=768px){.animus-dyn-pb-md{padding-bottom:var(--animus-pb-md)}}@media (width>=1024px){.animus-dyn-pb-lg{padding-bottom:var(--animus-pb-lg)}}.animus-dyn-pl{padding-left:var(--animus-pl)}.animus-dyn-px{padding-left:var(--animus-px);padding-right:var(--animus-px)}@media (width>=640px){.animus-dyn-pl-sm{padding-left:var(--animus-pl-sm)}.animus-dyn-px-sm{padding-left:var(--animus-px-sm);padding-right:var(--animus-px-sm)}}@media (width>=768px){.animus-dyn-pl-md{padding-left:var(--animus-pl-md)}.animus-dyn-px-md{padding-left:var(--animus-px-md);padding-right:var(--animus-px-md)}}@media (width>=1024px){.animus-dyn-pl-lg{padding-left:var(--animus-pl-lg)}.animus-dyn-px-lg{padding-left:var(--animus-px-lg);padding-right:var(--animus-px-lg)}}.animus-dyn-pr{padding-right:var(--animus-pr)}@media (width>=640px){.animus-dyn-pr-sm{padding-right:var(--animus-pr-sm)}}@media (width>=768px){.animus-dyn-pr-md{padding-right:var(--animus-pr-md)}}@media (width>=1024px){.animus-dyn-pr-lg{padding-right:var(--animus-pr-lg)}}.animus-dyn-pt{padding-top:var(--animus-pt)}.animus-dyn-py{padding-top:var(--animus-py);padding-bottom:var(--animus-py)}@media (width>=640px){.animus-dyn-pt-sm{padding-top:var(--animus-pt-sm)}.animus-dyn-py-sm{padding-top:var(--animus-py-sm);padding-bottom:var(--animus-py-sm)}}@media (width>=768px){.animus-dyn-pt-md{padding-top:var(--animus-pt-md)}.animus-dyn-py-md{padding-top:var(--animus-py-md);padding-bottom:var(--animus-py-md)}}@media (width>=1024px){.animus-dyn-pt-lg{padding-top:var(--animus-pt-lg)}.animus-dyn-py-lg{padding-top:var(--animus-py-lg);padding-bottom:var(--animus-py-lg)}}.animus-dyn-pos{position:var(--animus-pos)}.animus-dyn-position{position:var(--animus-position)}@media (width>=640px){.animus-dyn-pos-sm{position:var(--animus-pos-sm)}.animus-dyn-position-sm{position:var(--animus-position-sm)}}@media (width>=768px){.animus-dyn-pos-md{position:var(--animus-pos-md)}.animus-dyn-position-md{position:var(--animus-position-md)}}@media (width>=1024px){.animus-dyn-pos-lg{position:var(--animus-pos-lg)}.animus-dyn-position-lg{position:var(--animus-position-lg)}}.animus-dyn-right{right:var(--animus-right)}@media (width>=640px){.animus-dyn-right-sm{right:var(--animus-right-sm)}}@media (width>=768px){.animus-dyn-right-md{right:var(--animus-right-md)}}@media (width>=1024px){.animus-dyn-right-lg{right:var(--animus-right-lg)}}.animus-dyn-row-gap{row-gap:var(--animus-row-gap)}@media (width>=640px){.animus-dyn-row-gap-sm{row-gap:var(--animus-row-gap-sm)}}@media (width>=768px){.animus-dyn-row-gap-md{row-gap:var(--animus-row-gap-md)}}@media (width>=1024px){.animus-dyn-row-gap-lg{row-gap:var(--animus-row-gap-lg)}}.animus-dyn-inset{top:var(--animus-inset);right:var(--animus-inset);bottom:var(--animus-inset);left:var(--animus-inset)}.animus-dyn-top{top:var(--animus-top)}@media (width>=640px){.animus-dyn-inset-sm{top:var(--animus-inset-sm);right:var(--animus-inset-sm);bottom:var(--animus-inset-sm);left:var(--animus-inset-sm)}.animus-dyn-top-sm{top:var(--animus-top-sm)}}@media (width>=768px){.animus-dyn-inset-md{top:var(--animus-inset-md);right:var(--animus-inset-md);bottom:var(--animus-inset-md);left:var(--animus-inset-md)}.animus-dyn-top-md{top:var(--animus-top-md)}}@media (width>=1024px){.animus-dyn-inset-lg{top:var(--animus-inset-lg);right:var(--animus-inset-lg);bottom:var(--animus-inset-lg);left:var(--animus-inset-lg)}.animus-dyn-top-lg{top:var(--animus-top-lg)}}.animus-dyn-vertical-align{vertical-align:var(--animus-vertical-align)}@media (width>=640px){.animus-dyn-vertical-align-sm{vertical-align:var(--animus-vertical-align-sm)}}@media (width>=768px){.animus-dyn-vertical-align-md{vertical-align:var(--animus-vertical-align-md)}}@media (width>=1024px){.animus-dyn-vertical-align-lg{vertical-align:var(--animus-vertical-align-lg)}}.animus-dyn-size{width:var(--animus-size);height:var(--animus-size)}.animus-dyn-w{width:var(--animus-w)}.animus-dyn-width{width:var(--animus-width)}@media (width>=640px){.animus-dyn-size-sm{width:var(--animus-size-sm);height:var(--animus-size-sm)}.animus-dyn-w-sm{width:var(--animus-w-sm)}.animus-dyn-width-sm{width:var(--animus-width-sm)}}@media (width>=768px){.animus-dyn-size-md{width:var(--animus-size-md);height:var(--animus-size-md)}.animus-dyn-w-md{width:var(--animus-w-md)}.animus-dyn-width-md{width:var(--animus-width-md)}}@media (width>=1024px){.animus-dyn-size-lg{width:var(--animus-size-lg);height:var(--animus-size-lg)}.animus-dyn-w-lg{width:var(--animus-w-lg)}.animus-dyn-width-lg{width:var(--animus-width-lg)}}.animus-dyn-z-index{z-index:var(--animus-z-index)}@media (width>=640px){.animus-dyn-z-index-sm{z-index:var(--animus-z-index-sm)}}@media (width>=768px){.animus-dyn-z-index-md{z-index:var(--animus-z-index-md)}}@media (width>=1024px){.animus-dyn-z-index-lg{z-index:var(--animus-z-index-lg)}}} \ No newline at end of file +@layer anm-global{@font-face{font-family:AnimusTestFont;src:url(./assets/test-font.f2a09939.woff2)format("woff2");font-display:swap}*,:before,:after{box-sizing:border-box}body{background-color:var(--color-background);--current-bg:var(--color-background);color:var(--color-text);margin:0;font-family:system-ui,sans-serif}@keyframes animus-kf-muo7kp{0%,to{opacity:1}50%{opacity:.6}}@keyframes animus-kf-1x7guim{0%{opacity:0;background-color:var(--color-background);--current-bg:var(--color-background)}to{opacity:1;background-color:var(--color-surface);--current-bg:var(--color-surface)}}@keyframes animus-kf-1yqv0zl{0%,to{transform:scale(1)}50%{transform:scale(1.05)}}}@layer anm-base{.animus-Alert-a385f997{border-radius:4px;align-items:flex-start;padding:.75rem;font-size:.875rem;line-height:1.5;display:flex}.animus-Badge-99781d29{border-radius:9999px;align-items:center;padding:.25rem .5rem;font-size:.75rem;font-weight:500;line-height:1;display:inline-flex}.animus-Card-9aa7af5d{background-color:var(--color-surface);--current-bg:var(--color-surface);color:var(--color-text);border-radius:8px;padding:1rem;container:card/inline-size}@container card (width>=400px){.animus-Card-9aa7af5d{width:50cqw;padding:1.5rem}}@media (prefers-reduced-motion:reduce){.animus-Card-9aa7af5d{transition:none}}@supports (display:grid){.animus-Card-9aa7af5d{font-size:.875rem;display:grid}@media (width>=640px){.animus-Card-9aa7af5d{font-size:1rem}}.animus-Card-9aa7af5d:focus-visible{outline:2px solid}@container card (width>=600px){.animus-Card-9aa7af5d{gap:2cqi}}}.animus-ContainerCardBody-133c6ad9{color:var(--color-text);font-size:.875rem;line-height:1.5}@container card (width>=400px){.animus-ContainerCardBody-133c6ad9{font-size:1rem}}.animus-ContainerCardMedia-51db5d07{background:var(--current-bg);border-radius:4px;width:100%;min-height:64px;display:block}@container card (width>=400px){.animus-ContainerCardMedia-51db5d07{width:50cqw;min-height:120px}}.animus-ContainerCardRoot-01c5b011{background-color:var(--color-surface);--current-bg:var(--color-surface);color:var(--color-text);border-radius:8px;flex-direction:column;gap:.5rem;padding:1rem;display:flex;container:card/inline-size}.animus-GroupItem-32b2d32f{background-color:var(--color-surface);--current-bg:var(--color-surface);color:var(--color-text);border-radius:4px;align-items:center;padding:.25rem .5rem;display:inline-flex}[data-active=true] .animus-GroupItem-32b2d32f{background-color:var(--color-primary);--current-bg:var(--color-primary);color:var(--color-background)}.group:hover .animus-GroupItem-32b2d32f{opacity:.9}[data-color-mode=dark] .animus-GroupItem-32b2d32f{color:var(--color-text-muted)}.animus-Box-399302cf{display:flex;position:relative}.animus-Button-c63b6dcd{cursor:pointer;border:none;border-radius:4px;justify-content:center;align-items:center;font-weight:600;line-height:1;display:inline-flex}.animus-Button-b3718a43{background-color:var(--color-blue-500);border-radius:4px;padding:8px}}@layer anm-variants{@layer standalone{.animus-Alert-a385f997--variant-filled{color:var(--color-background)}.animus-Alert-a385f997--variant-outline{--current-bg:transparent;background-color:#0000;border-style:solid;border-width:1px}.animus-Alert-a385f997--intent-info{background-color:var(--color-primary);--current-bg:var(--color-primary)}.animus-Alert-a385f997--intent-danger{background-color:var(--color-danger);--current-bg:var(--color-danger)}.animus-Alert-a385f997--intent-success{background-color:var(--color-secondary);--current-bg:var(--color-secondary)}.animus-Badge-99781d29--color-neutral{background-color:var(--color-surface);--current-bg:var(--color-surface);color:var(--color-text)}.animus-Badge-99781d29--color-danger{background-color:var(--color-danger);--current-bg:var(--color-danger);color:var(--color-background)}.animus-ContainerCardRoot-01c5b011--size-lg{padding:1.5rem}.animus-Button-c63b6dcd--variant-primary{background-color:var(--color-primary);--current-bg:var(--color-primary);color:var(--color-background)}.animus-Button-c63b6dcd--variant-secondary{background-color:var(--color-secondary);--current-bg:var(--color-secondary);color:var(--color-background)}.animus-Button-c63b6dcd--variant-ghost{--current-bg:transparent;color:var(--color-text);background-color:#0000}.animus-Button-b3718a43--tone-quiet{background-color:var(--color-gray-700)}.animus-Button-b3718a43--tone-loud{background-color:var(--color-blue-700);font-weight:700}}@layer composed;}@layer anm-compounds{.animus-Alert-a385f997--compound-0{border-color:var(--color-primary);color:var(--color-primary)}.animus-Alert-a385f997--compound-1{border-color:var(--color-danger);color:var(--color-danger)}.animus-Alert-a385f997--compound-2{border-color:var(--color-secondary);color:var(--color-secondary)}}@layer anm-states{.animus-Badge-99781d29--disabled{opacity:.5;cursor:not-allowed}.animus-Badge-99781d29--active{outline:2px solid;outline-color:var(--color-primary)}}@layer anm-system{.animus-dyn-flex{flex:var(--animus-flex)}@media (width>=640px){.animus-dyn-flex-sm{flex:var(--animus-flex-sm)}}@media (width>=768px){.animus-dyn-flex-md{flex:var(--animus-flex-md)}}@media (width>=1024px){.animus-dyn-flex-lg{flex:var(--animus-flex-lg)}}.animus-dyn-m{margin:var(--animus-m)}@media (width>=640px){.animus-dyn-m-sm{margin:var(--animus-m-sm)}}@media (width>=768px){.animus-dyn-m-md{margin:var(--animus-m-md)}}@media (width>=1024px){.animus-dyn-m-lg{margin:var(--animus-m-lg)}}.animus-dyn-p{padding:var(--animus-p)}@media (width>=640px){.animus-dyn-p-sm{padding:var(--animus-p-sm)}}@media (width>=768px){.animus-dyn-p-md{padding:var(--animus-p-md)}}@media (width>=1024px){.animus-dyn-p-lg{padding:var(--animus-p-lg)}}.animus-dyn-gap{gap:var(--animus-gap)}@media (width>=640px){.animus-dyn-gap-sm{gap:var(--animus-gap-sm)}}@media (width>=768px){.animus-dyn-gap-md{gap:var(--animus-gap-md)}}@media (width>=1024px){.animus-dyn-gap-lg{gap:var(--animus-gap-lg)}}.animus-dyn-area{grid-area:var(--animus-area)}.animus-dyn-grid-area{grid-area:var(--animus-grid-area)}@media (width>=640px){.animus-dyn-area-sm{grid-area:var(--animus-area-sm)}.animus-dyn-grid-area-sm{grid-area:var(--animus-grid-area-sm)}}@media (width>=768px){.animus-dyn-area-md{grid-area:var(--animus-area-md)}.animus-dyn-grid-area-md{grid-area:var(--animus-grid-area-md)}}@media (width>=1024px){.animus-dyn-area-lg{grid-area:var(--animus-area-lg)}.animus-dyn-grid-area-lg{grid-area:var(--animus-grid-area-lg)}}.animus-dyn-grid-column{grid-column:var(--animus-grid-column)}@media (width>=640px){.animus-dyn-grid-column-sm{grid-column:var(--animus-grid-column-sm)}}@media (width>=768px){.animus-dyn-grid-column-md{grid-column:var(--animus-grid-column-md)}}@media (width>=1024px){.animus-dyn-grid-column-lg{grid-column:var(--animus-grid-column-lg)}}.animus-dyn-grid-row{grid-row:var(--animus-grid-row)}@media (width>=640px){.animus-dyn-grid-row-sm{grid-row:var(--animus-grid-row-sm)}}@media (width>=768px){.animus-dyn-grid-row-md{grid-row:var(--animus-grid-row-md)}}@media (width>=1024px){.animus-dyn-grid-row-lg{grid-row:var(--animus-grid-row-lg)}}.animus-dyn-overflow{overflow:var(--animus-overflow)}@media (width>=640px){.animus-dyn-overflow-sm{overflow:var(--animus-overflow-sm)}}@media (width>=768px){.animus-dyn-overflow-md{overflow:var(--animus-overflow-md)}}@media (width>=1024px){.animus-dyn-overflow-lg{overflow:var(--animus-overflow-lg)}}.animus-dyn-align-content{align-content:var(--animus-align-content)}@media (width>=640px){.animus-dyn-align-content-sm{align-content:var(--animus-align-content-sm)}}@media (width>=768px){.animus-dyn-align-content-md{align-content:var(--animus-align-content-md)}}@media (width>=1024px){.animus-dyn-align-content-lg{align-content:var(--animus-align-content-lg)}}.animus-dyn-align-items{align-items:var(--animus-align-items)}@media (width>=640px){.animus-dyn-align-items-sm{align-items:var(--animus-align-items-sm)}}@media (width>=768px){.animus-dyn-align-items-md{align-items:var(--animus-align-items-md)}}@media (width>=1024px){.animus-dyn-align-items-lg{align-items:var(--animus-align-items-lg)}}.animus-dyn-align-self{align-self:var(--animus-align-self)}@media (width>=640px){.animus-dyn-align-self-sm{align-self:var(--animus-align-self-sm)}}@media (width>=768px){.animus-dyn-align-self-md{align-self:var(--animus-align-self-md)}}@media (width>=1024px){.animus-dyn-align-self-lg{align-self:var(--animus-align-self-lg)}}.animus-dyn-bottom{bottom:var(--animus-bottom)}@media (width>=640px){.animus-dyn-bottom-sm{bottom:var(--animus-bottom-sm)}}@media (width>=768px){.animus-dyn-bottom-md{bottom:var(--animus-bottom-md)}}@media (width>=1024px){.animus-dyn-bottom-lg{bottom:var(--animus-bottom-lg)}}.animus-dyn-column-gap{column-gap:var(--animus-column-gap)}@media (width>=640px){.animus-dyn-column-gap-sm{column-gap:var(--animus-column-gap-sm)}}@media (width>=768px){.animus-dyn-column-gap-md{column-gap:var(--animus-column-gap-md)}}@media (width>=1024px){.animus-dyn-column-gap-lg{column-gap:var(--animus-column-gap-lg)}}.animus-dyn-display{display:var(--animus-display)}@media (width>=640px){.animus-dyn-display-sm{display:var(--animus-display-sm)}}@media (width>=768px){.animus-dyn-display-md{display:var(--animus-display-md)}}@media (width>=1024px){.animus-dyn-display-lg{display:var(--animus-display-lg)}}.animus-dyn-flex-basis{flex-basis:var(--animus-flex-basis)}@media (width>=640px){.animus-dyn-flex-basis-sm{flex-basis:var(--animus-flex-basis-sm)}}@media (width>=768px){.animus-dyn-flex-basis-md{flex-basis:var(--animus-flex-basis-md)}}@media (width>=1024px){.animus-dyn-flex-basis-lg{flex-basis:var(--animus-flex-basis-lg)}}.animus-dyn-flex-dir{flex-direction:var(--animus-flex-dir)}.animus-dyn-flex-direction{flex-direction:var(--animus-flex-direction)}@media (width>=640px){.animus-dyn-flex-dir-sm{flex-direction:var(--animus-flex-dir-sm)}.animus-dyn-flex-direction-sm{flex-direction:var(--animus-flex-direction-sm)}}@media (width>=768px){.animus-dyn-flex-dir-md{flex-direction:var(--animus-flex-dir-md)}.animus-dyn-flex-direction-md{flex-direction:var(--animus-flex-direction-md)}}@media (width>=1024px){.animus-dyn-flex-dir-lg{flex-direction:var(--animus-flex-dir-lg)}.animus-dyn-flex-direction-lg{flex-direction:var(--animus-flex-direction-lg)}}.animus-dyn-flex-grow{flex-grow:var(--animus-flex-grow)}@media (width>=640px){.animus-dyn-flex-grow-sm{flex-grow:var(--animus-flex-grow-sm)}}@media (width>=768px){.animus-dyn-flex-grow-md{flex-grow:var(--animus-flex-grow-md)}}@media (width>=1024px){.animus-dyn-flex-grow-lg{flex-grow:var(--animus-flex-grow-lg)}}.animus-dyn-flex-shrink{flex-shrink:var(--animus-flex-shrink)}@media (width>=640px){.animus-dyn-flex-shrink-sm{flex-shrink:var(--animus-flex-shrink-sm)}}@media (width>=768px){.animus-dyn-flex-shrink-md{flex-shrink:var(--animus-flex-shrink-md)}}@media (width>=1024px){.animus-dyn-flex-shrink-lg{flex-shrink:var(--animus-flex-shrink-lg)}}.animus-dyn-flex-wrap{flex-wrap:var(--animus-flex-wrap)}@media (width>=640px){.animus-dyn-flex-wrap-sm{flex-wrap:var(--animus-flex-wrap-sm)}}@media (width>=768px){.animus-dyn-flex-wrap-md{flex-wrap:var(--animus-flex-wrap-md)}}@media (width>=1024px){.animus-dyn-flex-wrap-lg{flex-wrap:var(--animus-flex-wrap-lg)}}.animus-dyn-font-size{font-size:var(--animus-font-size)}@media (width>=640px){.animus-dyn-font-size-sm{font-size:var(--animus-font-size-sm)}}@media (width>=768px){.animus-dyn-font-size-md{font-size:var(--animus-font-size-md)}}@media (width>=1024px){.animus-dyn-font-size-lg{font-size:var(--animus-font-size-lg)}}.animus-dyn-grid-column-end{grid-column-end:var(--animus-grid-column-end)}@media (width>=640px){.animus-dyn-grid-column-end-sm{grid-column-end:var(--animus-grid-column-end-sm)}}@media (width>=768px){.animus-dyn-grid-column-end-md{grid-column-end:var(--animus-grid-column-end-md)}}@media (width>=1024px){.animus-dyn-grid-column-end-lg{grid-column-end:var(--animus-grid-column-end-lg)}}.animus-dyn-grid-column-start{grid-column-start:var(--animus-grid-column-start)}@media (width>=640px){.animus-dyn-grid-column-start-sm{grid-column-start:var(--animus-grid-column-start-sm)}}@media (width>=768px){.animus-dyn-grid-column-start-md{grid-column-start:var(--animus-grid-column-start-md)}}@media (width>=1024px){.animus-dyn-grid-column-start-lg{grid-column-start:var(--animus-grid-column-start-lg)}}.animus-dyn-grid-row-end{grid-row-end:var(--animus-grid-row-end)}@media (width>=640px){.animus-dyn-grid-row-end-sm{grid-row-end:var(--animus-grid-row-end-sm)}}@media (width>=768px){.animus-dyn-grid-row-end-md{grid-row-end:var(--animus-grid-row-end-md)}}@media (width>=1024px){.animus-dyn-grid-row-end-lg{grid-row-end:var(--animus-grid-row-end-lg)}}.animus-dyn-grid-row-start{grid-row-start:var(--animus-grid-row-start)}@media (width>=640px){.animus-dyn-grid-row-start-sm{grid-row-start:var(--animus-grid-row-start-sm)}}@media (width>=768px){.animus-dyn-grid-row-start-md{grid-row-start:var(--animus-grid-row-start-md)}}@media (width>=1024px){.animus-dyn-grid-row-start-lg{grid-row-start:var(--animus-grid-row-start-lg)}}.animus-dyn-h{height:var(--animus-h)}.animus-dyn-height{height:var(--animus-height)}@media (width>=640px){.animus-dyn-h-sm{height:var(--animus-h-sm)}.animus-dyn-height-sm{height:var(--animus-height-sm)}}@media (width>=768px){.animus-dyn-h-md{height:var(--animus-h-md)}.animus-dyn-height-md{height:var(--animus-height-md)}}@media (width>=1024px){.animus-dyn-h-lg{height:var(--animus-h-lg)}.animus-dyn-height-lg{height:var(--animus-height-lg)}}.animus-dyn-justify-content{justify-content:var(--animus-justify-content)}@media (width>=640px){.animus-dyn-justify-content-sm{justify-content:var(--animus-justify-content-sm)}}@media (width>=768px){.animus-dyn-justify-content-md{justify-content:var(--animus-justify-content-md)}}@media (width>=1024px){.animus-dyn-justify-content-lg{justify-content:var(--animus-justify-content-lg)}}.animus-dyn-justify-items{justify-items:var(--animus-justify-items)}@media (width>=640px){.animus-dyn-justify-items-sm{justify-items:var(--animus-justify-items-sm)}}@media (width>=768px){.animus-dyn-justify-items-md{justify-items:var(--animus-justify-items-md)}}@media (width>=1024px){.animus-dyn-justify-items-lg{justify-items:var(--animus-justify-items-lg)}}.animus-dyn-justify-self{justify-self:var(--animus-justify-self)}@media (width>=640px){.animus-dyn-justify-self-sm{justify-self:var(--animus-justify-self-sm)}}@media (width>=768px){.animus-dyn-justify-self-md{justify-self:var(--animus-justify-self-md)}}@media (width>=1024px){.animus-dyn-justify-self-lg{justify-self:var(--animus-justify-self-lg)}}.animus-dyn-left{left:var(--animus-left)}@media (width>=640px){.animus-dyn-left-sm{left:var(--animus-left-sm)}}@media (width>=768px){.animus-dyn-left-md{left:var(--animus-left-md)}}@media (width>=1024px){.animus-dyn-left-lg{left:var(--animus-left-lg)}}.animus-dyn-mb{margin-bottom:var(--animus-mb)}@media (width>=640px){.animus-dyn-mb-sm{margin-bottom:var(--animus-mb-sm)}}@media (width>=768px){.animus-dyn-mb-md{margin-bottom:var(--animus-mb-md)}}@media (width>=1024px){.animus-dyn-mb-lg{margin-bottom:var(--animus-mb-lg)}}.animus-dyn-ml{margin-left:var(--animus-ml)}.animus-dyn-mx{margin-left:var(--animus-mx);margin-right:var(--animus-mx)}@media (width>=640px){.animus-dyn-ml-sm{margin-left:var(--animus-ml-sm)}.animus-dyn-mx-sm{margin-left:var(--animus-mx-sm);margin-right:var(--animus-mx-sm)}}@media (width>=768px){.animus-dyn-ml-md{margin-left:var(--animus-ml-md)}.animus-dyn-mx-md{margin-left:var(--animus-mx-md);margin-right:var(--animus-mx-md)}}@media (width>=1024px){.animus-dyn-ml-lg{margin-left:var(--animus-ml-lg)}.animus-dyn-mx-lg{margin-left:var(--animus-mx-lg);margin-right:var(--animus-mx-lg)}}.animus-dyn-mr{margin-right:var(--animus-mr)}@media (width>=640px){.animus-dyn-mr-sm{margin-right:var(--animus-mr-sm)}}@media (width>=768px){.animus-dyn-mr-md{margin-right:var(--animus-mr-md)}}@media (width>=1024px){.animus-dyn-mr-lg{margin-right:var(--animus-mr-lg)}}.animus-dyn-mt{margin-top:var(--animus-mt)}.animus-dyn-my{margin-top:var(--animus-my);margin-bottom:var(--animus-my)}@media (width>=640px){.animus-dyn-mt-sm{margin-top:var(--animus-mt-sm)}.animus-dyn-my-sm{margin-top:var(--animus-my-sm);margin-bottom:var(--animus-my-sm)}}@media (width>=768px){.animus-dyn-mt-md{margin-top:var(--animus-mt-md)}.animus-dyn-my-md{margin-top:var(--animus-my-md);margin-bottom:var(--animus-my-md)}}@media (width>=1024px){.animus-dyn-mt-lg{margin-top:var(--animus-mt-lg)}.animus-dyn-my-lg{margin-top:var(--animus-my-lg);margin-bottom:var(--animus-my-lg)}}.animus-dyn-max-h{max-height:var(--animus-max-h)}.animus-dyn-max-height{max-height:var(--animus-max-height)}@media (width>=640px){.animus-dyn-max-h-sm{max-height:var(--animus-max-h-sm)}.animus-dyn-max-height-sm{max-height:var(--animus-max-height-sm)}}@media (width>=768px){.animus-dyn-max-h-md{max-height:var(--animus-max-h-md)}.animus-dyn-max-height-md{max-height:var(--animus-max-height-md)}}@media (width>=1024px){.animus-dyn-max-h-lg{max-height:var(--animus-max-h-lg)}.animus-dyn-max-height-lg{max-height:var(--animus-max-height-lg)}}.animus-dyn-max-w{max-width:var(--animus-max-w)}.animus-dyn-max-width{max-width:var(--animus-max-width)}@media (width>=640px){.animus-dyn-max-w-sm{max-width:var(--animus-max-w-sm)}.animus-dyn-max-width-sm{max-width:var(--animus-max-width-sm)}}@media (width>=768px){.animus-dyn-max-w-md{max-width:var(--animus-max-w-md)}.animus-dyn-max-width-md{max-width:var(--animus-max-width-md)}}@media (width>=1024px){.animus-dyn-max-w-lg{max-width:var(--animus-max-w-lg)}.animus-dyn-max-width-lg{max-width:var(--animus-max-width-lg)}}.animus-dyn-min-h{min-height:var(--animus-min-h)}.animus-dyn-min-height{min-height:var(--animus-min-height)}@media (width>=640px){.animus-dyn-min-h-sm{min-height:var(--animus-min-h-sm)}.animus-dyn-min-height-sm{min-height:var(--animus-min-height-sm)}}@media (width>=768px){.animus-dyn-min-h-md{min-height:var(--animus-min-h-md)}.animus-dyn-min-height-md{min-height:var(--animus-min-height-md)}}@media (width>=1024px){.animus-dyn-min-h-lg{min-height:var(--animus-min-h-lg)}.animus-dyn-min-height-lg{min-height:var(--animus-min-height-lg)}}.animus-dyn-min-w{min-width:var(--animus-min-w)}.animus-dyn-min-width{min-width:var(--animus-min-width)}@media (width>=640px){.animus-dyn-min-w-sm{min-width:var(--animus-min-w-sm)}.animus-dyn-min-width-sm{min-width:var(--animus-min-width-sm)}}@media (width>=768px){.animus-dyn-min-w-md{min-width:var(--animus-min-w-md)}.animus-dyn-min-width-md{min-width:var(--animus-min-width-md)}}@media (width>=1024px){.animus-dyn-min-w-lg{min-width:var(--animus-min-w-lg)}.animus-dyn-min-width-lg{min-width:var(--animus-min-width-lg)}}.animus-dyn-opacity{opacity:var(--animus-opacity)}@media (width>=640px){.animus-dyn-opacity-sm{opacity:var(--animus-opacity-sm)}}@media (width>=768px){.animus-dyn-opacity-md{opacity:var(--animus-opacity-md)}}@media (width>=1024px){.animus-dyn-opacity-lg{opacity:var(--animus-opacity-lg)}}.animus-dyn-order{order:var(--animus-order)}@media (width>=640px){.animus-dyn-order-sm{order:var(--animus-order-sm)}}@media (width>=768px){.animus-dyn-order-md{order:var(--animus-order-md)}}@media (width>=1024px){.animus-dyn-order-lg{order:var(--animus-order-lg)}}.animus-dyn-overflow-x{overflow-x:var(--animus-overflow-x)}@media (width>=640px){.animus-dyn-overflow-x-sm{overflow-x:var(--animus-overflow-x-sm)}}@media (width>=768px){.animus-dyn-overflow-x-md{overflow-x:var(--animus-overflow-x-md)}}@media (width>=1024px){.animus-dyn-overflow-x-lg{overflow-x:var(--animus-overflow-x-lg)}}.animus-dyn-overflow-y{overflow-y:var(--animus-overflow-y)}@media (width>=640px){.animus-dyn-overflow-y-sm{overflow-y:var(--animus-overflow-y-sm)}}@media (width>=768px){.animus-dyn-overflow-y-md{overflow-y:var(--animus-overflow-y-md)}}@media (width>=1024px){.animus-dyn-overflow-y-lg{overflow-y:var(--animus-overflow-y-lg)}}.animus-dyn-pb{padding-bottom:var(--animus-pb)}@media (width>=640px){.animus-dyn-pb-sm{padding-bottom:var(--animus-pb-sm)}}@media (width>=768px){.animus-dyn-pb-md{padding-bottom:var(--animus-pb-md)}}@media (width>=1024px){.animus-dyn-pb-lg{padding-bottom:var(--animus-pb-lg)}}.animus-dyn-pl{padding-left:var(--animus-pl)}.animus-dyn-px{padding-left:var(--animus-px);padding-right:var(--animus-px)}@media (width>=640px){.animus-dyn-pl-sm{padding-left:var(--animus-pl-sm)}.animus-dyn-px-sm{padding-left:var(--animus-px-sm);padding-right:var(--animus-px-sm)}}@media (width>=768px){.animus-dyn-pl-md{padding-left:var(--animus-pl-md)}.animus-dyn-px-md{padding-left:var(--animus-px-md);padding-right:var(--animus-px-md)}}@media (width>=1024px){.animus-dyn-pl-lg{padding-left:var(--animus-pl-lg)}.animus-dyn-px-lg{padding-left:var(--animus-px-lg);padding-right:var(--animus-px-lg)}}.animus-dyn-pr{padding-right:var(--animus-pr)}@media (width>=640px){.animus-dyn-pr-sm{padding-right:var(--animus-pr-sm)}}@media (width>=768px){.animus-dyn-pr-md{padding-right:var(--animus-pr-md)}}@media (width>=1024px){.animus-dyn-pr-lg{padding-right:var(--animus-pr-lg)}}.animus-dyn-pt{padding-top:var(--animus-pt)}.animus-dyn-py{padding-top:var(--animus-py);padding-bottom:var(--animus-py)}@media (width>=640px){.animus-dyn-pt-sm{padding-top:var(--animus-pt-sm)}.animus-dyn-py-sm{padding-top:var(--animus-py-sm);padding-bottom:var(--animus-py-sm)}}@media (width>=768px){.animus-dyn-pt-md{padding-top:var(--animus-pt-md)}.animus-dyn-py-md{padding-top:var(--animus-py-md);padding-bottom:var(--animus-py-md)}}@media (width>=1024px){.animus-dyn-pt-lg{padding-top:var(--animus-pt-lg)}.animus-dyn-py-lg{padding-top:var(--animus-py-lg);padding-bottom:var(--animus-py-lg)}}.animus-dyn-pos{position:var(--animus-pos)}.animus-dyn-position{position:var(--animus-position)}@media (width>=640px){.animus-dyn-pos-sm{position:var(--animus-pos-sm)}.animus-dyn-position-sm{position:var(--animus-position-sm)}}@media (width>=768px){.animus-dyn-pos-md{position:var(--animus-pos-md)}.animus-dyn-position-md{position:var(--animus-position-md)}}@media (width>=1024px){.animus-dyn-pos-lg{position:var(--animus-pos-lg)}.animus-dyn-position-lg{position:var(--animus-position-lg)}}.animus-dyn-right{right:var(--animus-right)}@media (width>=640px){.animus-dyn-right-sm{right:var(--animus-right-sm)}}@media (width>=768px){.animus-dyn-right-md{right:var(--animus-right-md)}}@media (width>=1024px){.animus-dyn-right-lg{right:var(--animus-right-lg)}}.animus-dyn-row-gap{row-gap:var(--animus-row-gap)}@media (width>=640px){.animus-dyn-row-gap-sm{row-gap:var(--animus-row-gap-sm)}}@media (width>=768px){.animus-dyn-row-gap-md{row-gap:var(--animus-row-gap-md)}}@media (width>=1024px){.animus-dyn-row-gap-lg{row-gap:var(--animus-row-gap-lg)}}.animus-dyn-inset{top:var(--animus-inset);right:var(--animus-inset);bottom:var(--animus-inset);left:var(--animus-inset)}.animus-dyn-top{top:var(--animus-top)}@media (width>=640px){.animus-dyn-inset-sm{top:var(--animus-inset-sm);right:var(--animus-inset-sm);bottom:var(--animus-inset-sm);left:var(--animus-inset-sm)}.animus-dyn-top-sm{top:var(--animus-top-sm)}}@media (width>=768px){.animus-dyn-inset-md{top:var(--animus-inset-md);right:var(--animus-inset-md);bottom:var(--animus-inset-md);left:var(--animus-inset-md)}.animus-dyn-top-md{top:var(--animus-top-md)}}@media (width>=1024px){.animus-dyn-inset-lg{top:var(--animus-inset-lg);right:var(--animus-inset-lg);bottom:var(--animus-inset-lg);left:var(--animus-inset-lg)}.animus-dyn-top-lg{top:var(--animus-top-lg)}}.animus-dyn-vertical-align{vertical-align:var(--animus-vertical-align)}@media (width>=640px){.animus-dyn-vertical-align-sm{vertical-align:var(--animus-vertical-align-sm)}}@media (width>=768px){.animus-dyn-vertical-align-md{vertical-align:var(--animus-vertical-align-md)}}@media (width>=1024px){.animus-dyn-vertical-align-lg{vertical-align:var(--animus-vertical-align-lg)}}.animus-dyn-size{width:var(--animus-size);height:var(--animus-size)}.animus-dyn-w{width:var(--animus-w)}.animus-dyn-width{width:var(--animus-width)}@media (width>=640px){.animus-dyn-size-sm{width:var(--animus-size-sm);height:var(--animus-size-sm)}.animus-dyn-w-sm{width:var(--animus-w-sm)}.animus-dyn-width-sm{width:var(--animus-width-sm)}}@media (width>=768px){.animus-dyn-size-md{width:var(--animus-size-md);height:var(--animus-size-md)}.animus-dyn-w-md{width:var(--animus-w-md)}.animus-dyn-width-md{width:var(--animus-width-md)}}@media (width>=1024px){.animus-dyn-size-lg{width:var(--animus-size-lg);height:var(--animus-size-lg)}.animus-dyn-w-lg{width:var(--animus-w-lg)}.animus-dyn-width-lg{width:var(--animus-width-lg)}}.animus-dyn-z-index{z-index:var(--animus-z-index)}@media (width>=640px){.animus-dyn-z-index-sm{z-index:var(--animus-z-index-sm)}}@media (width>=768px){.animus-dyn-z-index-md{z-index:var(--animus-z-index-md)}}@media (width>=1024px){.animus-dyn-z-index-lg{z-index:var(--animus-z-index-lg)}}} \ No newline at end of file diff --git a/packages/oracle/__tests__/host-universe.test.ts b/packages/oracle/__tests__/host-universe.test.ts index a7aed5d3..a3744c7b 100644 --- a/packages/oracle/__tests__/host-universe.test.ts +++ b/packages/oracle/__tests__/host-universe.test.ts @@ -58,7 +58,7 @@ describe('createAnimusHost — the style universe over the emitted artifacts', ( } expect(host.program).toMatchObject({ kind: 'analysis-artifacts' }); expect(host.program.label).toBe( - 'animus-commit:410fa0bb91141167e1cad2d6cd6dd150' + 'animus-commit:b06d23b64b8afc57701072492fe0ec10' ); }); diff --git a/packages/showcase/CLAUDE.md b/packages/showcase/CLAUDE.md index 150fdc16..57ecdfb1 100644 --- a/packages/showcase/CLAUDE.md +++ b/packages/showcase/CLAUDE.md @@ -31,7 +31,7 @@ Each component is in its own file (1 named export per file). This structure exer ## Design System (`src/ds.ts`) -- `createSystem().addGroup().build()` returns `{ system: ds, createGlobalStyles }` +- `createSystem().addGroup().build()` returns the bundle (`{ system, createGlobalStyles, createKeyframes, registerKeyframes, seal }`); `ds` is the SEALED instance from `bundle.seal()` (vocabulary-registration) - `createGlobalStyles()` is a factory returned from `.build()`, used to define global/reset styles - Tokens built separately via `createTheme()` and exported as `tokens`. Theme type augmented via `declare module`. - Custom transforms: `fluid` (clamp-based responsive), `ratio` (aspect-ratio) diff --git a/packages/showcase/src/content/advanced/svelte.mdx b/packages/showcase/src/content/advanced/svelte.mdx index 482a6729..37f97789 100644 --- a/packages/showcase/src/content/advanced/svelte.mdx +++ b/packages/showcase/src/content/advanced/svelte.mdx @@ -79,7 +79,7 @@ export const theme = /* @__PURE__ */ (() => }) .build())(); -export const { system: ds } = /* @__PURE__ */ (() => createSystem().build())(); +export const ds = /* @__PURE__ */ (() => createSystem().build().seal())(); ``` Define runtime resolvers in a second TypeScript module through the diff --git a/packages/showcase/src/content/architecture/global-styles.mdx b/packages/showcase/src/content/architecture/global-styles.mdx index e86d862f..df97b911 100644 --- a/packages/showcase/src/content/architecture/global-styles.mdx +++ b/packages/showcase/src/content/architecture/global-styles.mdx @@ -16,16 +16,19 @@ Every component style layer sits above it. Global styles — resets, body defaul ## createGlobalStyles -`createGlobalStyles` is returned alongside the system instance from `createSystem().build()`. It is not a standalone import — it is bound to your system's prop config, so it knows how to resolve the props you've registered. +`createGlobalStyles` is a member of the `createSystem().build()` bundle, beside the system instance and the sealing terminal. It is not a standalone import — it is bound to your system's prop config, so it knows how to resolve the props you've registered. ```ts -export const { system: ds, createGlobalStyles } = createSystem() +const bundle = createSystem() .addGroup('surface', { ...color, ...border, ...background }) .addGroup('space', space) .build(); + +export const { createGlobalStyles } = bundle; +export const ds = bundle.seal(); ``` -Call it once with a `GlobalStyleMap` to produce a `GlobalStyleBlock`. Export the result — the plugin discovers it by the `__brand` marker and emits it into `@layer global` at build time. +Call it once with a `GlobalStyleMap` to produce a `GlobalStyleBlock`. Export the result — the plugin reads it from the loaded system configuration and emits it into `@layer global` at build time. -Finalizes the builder. Returns two values — destructure both: +Finalizes the builder and opens the registration window. Destructure the factories, then export the sealed instance: ```ts -export const { system: ds, createGlobalStyles } = createSystem() +const bundle = createSystem() .addGroup('surface', { ...color }) .build(); + +export const { createGlobalStyles } = bundle; +export const ds = bundle.seal(); ``` -The `system` return is renamed via destructuring — `ds` is the conventional name. All component authoring calls (`ds.styles()`, `ds.variant()`, etc.) go through this instance. +`ds` is the conventional name for the sealed instance. All component authoring calls (`ds.styles()`, `ds.variant()`, etc.) go through it, and the loader (and `.extend()`) consume it. `createGlobalStyles` is a factory for defining global and reset styles. It accepts a `GlobalStyleMap` — a nested `Record>` of selector to style object — and returns a `GlobalStyleBlock` that the Vite plugin serializes into `@layer global`. @@ -472,7 +476,7 @@ declare module '@animus-ui/system' { // ── System ──────────────────────────────────────────────────── -export const { system: ds, createGlobalStyles } = createSystem() +const bundle = createSystem() .addGroup('surface', { ...color, ...border, @@ -495,6 +499,11 @@ export const { system: ds, createGlobalStyles } = createSystem() }) .build(); +export const { createGlobalStyles } = bundle; + +// Registration closes at seal(); the loader consumes the sealed instance. +export const ds = bundle.seal(); + // ── Global styles ───────────────────────────────────────────── export const globalStyles = createGlobalStyles({ diff --git a/packages/showcase/src/content/authoring/selectors.mdx b/packages/showcase/src/content/authoring/selectors.mdx index 620965c0..0935d414 100644 --- a/packages/showcase/src/content/authoring/selectors.mdx +++ b/packages/showcase/src/content/authoring/selectors.mdx @@ -256,14 +256,15 @@ This means a component can have no interactive styles when the `interactive` sta ```ts import { createSystem } from '@animus-ui/system'; -const { system: ds } = createSystem() +const ds = createSystem() .addSelectors({ // The selector string must include & _highlighted: '&[data-highlighted], &[data-active]', _dragging: '&[data-dragging="true"]', _dropTarget: '&[data-drop-target]', }) - .build(); + .build() + .seal(); ``` **Selector string requirement:** The value must include `&`. The `&` is replaced with the component's generated class name at extraction time. A selector without `&` produces invalid CSS. @@ -274,11 +275,12 @@ const { system: ds } = createSystem() ```ts // Override _hover to also match data-hover (e.g., for touch devices) -const { system: ds } = createSystem() +const ds = createSystem() .addSelectors({ _hover: '&:hover, &[data-hover]', // order stays 30 }) - .build(); + .build() + .seal(); ``` Custom aliases registered via `addSelectors()` are serialized into the system config and passed to the Rust extractor at build time. They work identically to built-in aliases — the extractor has no distinction between them. Custom selector aliases are also **typed at the call site**: publish them once via module augmentation and your `_`-keys gain autocomplete and typo-rejection alongside the built-ins: diff --git a/packages/showcase/src/content/reference/builder-chain.mdx b/packages/showcase/src/content/reference/builder-chain.mdx index 531bb67f..5ccddd99 100644 --- a/packages/showcase/src/content/reference/builder-chain.mdx +++ b/packages/showcase/src/content/reference/builder-chain.mdx @@ -107,7 +107,7 @@ The primary chain consists of seven classes arranged in a backwards-inheritance ## Entry: `Animus` class -`Animus` is created by `createSystem().build()` and exposed as the `ds` object. It holds the prop registry and group registry for the entire design system. +`Animus` is created by `createSystem().build()` and exposed as the sealed `ds` object (`bundle.seal()`). It holds the prop registry and group registry for the entire design system. -The `ds` instance is a singleton per system. Do not instantiate `Animus` directly — use `createSystem().build()`. +The `ds` instance is a singleton per system. Do not instantiate `Animus` directly — use `createSystem().build().seal()`. --- diff --git a/packages/showcase/src/content/reference/create-system.mdx b/packages/showcase/src/content/reference/create-system.mdx index c448157e..10884e4d 100644 --- a/packages/showcase/src/content/reference/create-system.mdx +++ b/packages/showcase/src/content/reference/create-system.mdx @@ -9,15 +9,17 @@ import { APIBlock } from '../../components/docs/APIBlock'; `createSystem()` returns a `SystemBuilder` — an immutable, type-state accumulator that registers props, groups, and selector aliases. Every method returns a new builder instance with widened generics. `.build()` finalizes the accumulator into a `SystemInstance` and a `createGlobalStyles` factory. -One system per application. Every component in the app imports from the built system instance. +One system per application. Every component in the app imports from the SEALED system instance. ```ts import { createSystem } from '@animus-ui/system'; -export const { system: ds, createGlobalStyles } = createSystem() +const bundle = createSystem() .addGroup('surface', { ...color, ...border }) .addGroup('space', space) .build(); + +export const ds = bundle.seal(); ``` --- @@ -63,10 +65,11 @@ export const { system: ds, createGlobalStyles } = createSystem() label: 'build', layer: 'output', description: - 'Finalizes the builder. Returns { system, createGlobalStyles }. No further chaining after build().', - code: `const { system: ds, createGlobalStyles } = createSystem() + 'Finalizes the builder and opens the registration window; seal() closes it and returns the instance the loader consumes.', + code: `const bundle = createSystem() .addGroup('surface', { ...color }) - .build();`, + .build(); +const ds = bundle.seal();`, }, ]} /> @@ -305,7 +308,7 @@ The same collision and overlap rules as `addGroup()` apply: name: 'source', type: 'SystemInstance | LibraryBundle', default: 'required', - desc: "A built system from SystemBuilder.build(), or a library bundle ({ system, theme }) — extend() consumes the system half and ignores the rest. The merge reads the source's registry snapshot captured at its build(), never a serialized round-trip.", + desc: "A SEALED system (the seal() terminal's return), or a library bundle ({ system, theme }) whose system half is sealed — extend() consumes the system half and ignores the rest. A built-but-unsealed source throws. The merge reads the source's registry snapshot and vocabulary record, never a serialized round-trip.", }, ]} /> @@ -317,11 +320,12 @@ The same collision and overlap rules as `addGroup()` apply: import { system as baseDs } from '@acme/design-system'; import { transitions } from '@animus-ui/system/groups'; -export const { system: ds } = createSystem() - .extend(baseDs) +const bundle = createSystem() + .extend(baseDs) // a sealed kit instance // Additive only — the kit's groups and props arrive through the merge. .addGroup('motion', transitions) .build(); +export const ds = bundle.seal(); ``` Rules: @@ -362,15 +366,19 @@ Rules: /> -Finalizes the builder. Returns two values — always destructure both: +Finalizes the builder and opens the registration window. Destructure the factories from the bundle, register any keyframe collections, and export the sealed instance: ```ts -export const { system: ds, createGlobalStyles } = createSystem() +const bundle = createSystem() .addGroup('surface', { ...color, ...border }) .build(); + +export const { createGlobalStyles, createKeyframes } = bundle; + +export const ds = bundle.seal(); ``` -The `system` return is renamed via destructuring. `ds` is the conventional name. All component authoring calls (`ds.styles()`, `ds.variant()`, etc.) go through this instance. +`ds` is the conventional name for the sealed instance. All component authoring calls (`ds.styles()`, `ds.variant()`, etc.) go through it, and the loader (and `.extend()`) consume it. ### SystemInstance diff --git a/packages/showcase/src/content/start.mdx b/packages/showcase/src/content/start.mdx index 1678fdbd..837802fd 100644 --- a/packages/showcase/src/content/start.mdx +++ b/packages/showcase/src/content/start.mdx @@ -86,12 +86,17 @@ declare module '@animus-ui/system' { // ── System ──────────────────────────────────────────────────── -export const { system: ds, createGlobalStyles } = createSystem() +const bundle = createSystem() .addGroup('color', color) .addGroup('space', space) .addGroup('typography', typography) .build(); +export const { createGlobalStyles } = bundle; + +// Registration closes at seal(); the loader consumes the sealed instance. +export const ds = bundle.seal(); + // ── Global Styles ───────────────────────────────────────────── export const globalStyles = createGlobalStyles({ diff --git a/packages/showcase/src/content/support/component-test.mdx b/packages/showcase/src/content/support/component-test.mdx index c185b679..3c2019c7 100644 --- a/packages/showcase/src/content/support/component-test.mdx +++ b/packages/showcase/src/content/support/component-test.mdx @@ -530,11 +530,12 @@ export const tokens = createTheme() }) .build(); -export const { system: ds } = createSystem() +export const ds = createSystem() .addGroup('surface', { ...color, ...border, ...shadows }) .addGroup('text', { ...typography }) .addGroup('space', { ...space }) - .build();`} + .build() + .seal();`} ### Line Highlighting + Diffs diff --git a/packages/showcase/src/content/support/migration.mdx b/packages/showcase/src/content/support/migration.mdx index 40700a47..f9fafc12 100644 --- a/packages/showcase/src/content/support/migration.mdx +++ b/packages/showcase/src/content/support/migration.mdx @@ -83,6 +83,7 @@ createSystem() .addGroup('space') // enables p, m, px, py, mx, my, ... .addGroup('typography') // enables fontSize, fontWeight, ... .build() +.seal() // Component accepts typed system props directly // `, diff --git a/packages/showcase/src/ds.ts b/packages/showcase/src/ds.ts index 47ff5562..b2d4aef6 100644 --- a/packages/showcase/src/ds.ts +++ b/packages/showcase/src/ds.ts @@ -701,19 +701,15 @@ declare module '@animus-ui/system' { // ─── System ───────────────────────────────────────────────── -export const { - system: ds, - createGlobalStyles, - createKeyframes, - // DELIBERATE holdout on the deprecated `includes:` alias (openspec: - // first-class-extension, inc 07/row 13): this system re-spreads - // `border`/`layout` into custom `surface`/`arrange` groups (Home.tsx - // passes `border={1}` through `surface: true`). Under restored D12 - // transform equality (name + captured source) that re-spread now - // COALESCES, so migration to `.extend(testDs)` is unblocked — it is - // deferred to registry row 13 only to keep this increment's lane sweep - // stable. Migrate there; do not add new `includes:` consumers. -} = createSystem({ +// DELIBERATE holdout on the deprecated `includes:` alias (openspec: +// first-class-extension, inc 07/row 13): this system re-spreads +// `border`/`layout` into custom `surface`/`arrange` groups (Home.tsx +// passes `border={1}` through `surface: true`). Under restored D12 +// transform equality (name + captured source) that re-spread now +// COALESCES, so migration to `.extend(testDs)` is unblocked — it is +// deferred to registry row 13 only to keep this increment's lane sweep +// stable. Migrate there; do not add new `includes:` consumers. +const bundle = createSystem({ includes: [testDs], }) .addGroup('surface', { @@ -738,6 +734,8 @@ export const { .addGroup('positioning', positioning) .build(); +export const { createGlobalStyles, createKeyframes } = bundle; + // ─── Animations ──────────────────────────────────────────── export const animations = createKeyframes({ @@ -755,6 +753,12 @@ export const animations = createKeyframes({ }, }); +// Sealed system (vocabulary-registration): `animations` registers under its +// export name. The `includes:` holdout above cannot carry the kit's +// registered `kitMotion` — the sealed record carries the coded +// `animus.vocabulary.legacy-verb` witness the host surfaces as a warning. +export const ds = bundle.registerKeyframes({ animations }).seal(); + // ─── Global Styles ────────────────────────────────────────── export const globalStyles = createGlobalStyles({ diff --git a/packages/system/README.md b/packages/system/README.md index b5c07d0a..d7ac09d0 100644 --- a/packages/system/README.md +++ b/packages/system/README.md @@ -60,12 +60,15 @@ import { layout, } from '@animus-ui/system/groups'; -export const { system: ds, createGlobalStyles } = createSystem() +const bundle = createSystem() .addGroup('surface', { ...color, ...border, ...shadows, ...background }) .addGroup('space', space) .addGroup('text', typography) .addGroup('arrange', { ...flex, ...layout }) .build(); + +export const { createGlobalStyles, createKeyframes } = bundle; +export const ds = bundle.seal(); ``` To build on a published design-system kit, start either chain with @@ -77,11 +80,12 @@ kit through the same edge: import { system as kitSystem, theme as kitTheme } from '@acme/kit'; const theme = createTheme().extend(kitTheme).build(); -export const { system: ds } = createSystem() - .extend(kitSystem) +const bundle = createSystem() + .extend(kitSystem) // a SEALED kit instance — extend() consumes sealed systems // Additive only — the kit's groups/props arrive through the merge. .addProps({ cursor: { property: 'cursor' } }) .build(); +export const ds = bundle.seal(); ``` (`createSystem({ includes: [...] })` and `.from()` are deprecated aliases from diff --git a/packages/system/__tests__/extend.test.ts b/packages/system/__tests__/extend.test.ts index 33f2b272..905bad78 100644 --- a/packages/system/__tests__/extend.test.ts +++ b/packages/system/__tests__/extend.test.ts @@ -127,7 +127,8 @@ describe('SystemBuilder extend()', () => { .addGroup('layout', { gap: prop({ property: 'gap', scale: 'space' }), }) - .build().system; + .build() + .seal(); // Scenario: "Extended prop is present end to end" (runtime half; the type // half lives in types.test-d.tsx) + the G5 runtime witness. @@ -148,7 +149,8 @@ describe('SystemBuilder extend()', () => { const kitDs = createSystem() .addSelectors({ _cardHover: '&[data-card]:hover' }) .addConditions({ _compact: '@media (max-width: 400px)' }) - .build().system; + .build() + .seal(); const config = createSystem().extend(kitDs).build().system.toConfig(); expect(JSON.parse(config.selectorAliases)._cardHover).toBe( @@ -163,7 +165,8 @@ describe('SystemBuilder extend()', () => { it('adopts a kit override of a built-in selector, preserving its order', () => { const kitDs = createSystem() .addSelectors({ _hover: '&:hover:not([data-frozen])' }) - .build().system; + .build() + .seal(); const config = createSystem().extend(kitDs).build().system.toConfig(); expect(JSON.parse(config.selectorAliases)._hover).toBe( @@ -179,10 +182,12 @@ describe('SystemBuilder extend()', () => { it('allocates distinct selector orders across repeated extends, stable under re-ordering', () => { const kitA = createSystem() .addSelectors({ _cardHover: '&[data-card]:hover' }) - .build().system; + .build() + .seal(); const kitB = createSystem() .addSelectors({ _railOpen: '&[data-rail][data-open]' }) - .build().system; + .build() + .seal(); const ab = snapshotOf( createSystem().extend(kitA).extend(kitB).build().system @@ -245,7 +250,8 @@ describe('SystemBuilder extend()', () => { it('coalesces byte-equivalent definitions from source and consumer', () => { const kitDs = createSystem() .addProps({ m: prop({ scale: 'space' }) }) - .build().system; + .build() + .seal(); const { system } = createSystem() .extend(kitDs) .addProps({ m: prop({ scale: 'space' }) }) @@ -284,10 +290,12 @@ describe('SystemBuilder extend()', () => { it('fails divergent sibling selector aliases naming both sources, order-independent', () => { const kitA = createSystem() .addSelectors({ _hover: '&:hover:not([data-frozen])' }) - .build().system; + .build() + .seal(); const kitB = createSystem() .addSelectors({ _hover: '&:hover, &[data-hover]' }) - .build().system; + .build() + .seal(); expect(() => createSystem().extend(kitA).extend(kitB)).toThrow( /selector alias "_hover".*extended source #1.*extended source #2/s @@ -301,10 +309,12 @@ describe('SystemBuilder extend()', () => { it('fails divergent sibling prop definitions naming both sources, order-independent', () => { const kitA = createSystem() .addProps({ gap: prop({ property: 'gap', scale: 'space' }) }) - .build().system; + .build() + .seal(); const kitB = createSystem() .addProps({ gap: prop({ property: 'gap', scale: 'sizes' }) }) - .build().system; + .build() + .seal(); expect(() => createSystem().extend(kitA).extend(kitB)).toThrow( /Prop "gap".*Existing \(extended source #1\).*Incoming \(extended source #2\)/ @@ -318,10 +328,12 @@ describe('SystemBuilder extend()', () => { // Simulates the same kit at two versions: same names, divergent values. const v1 = createSystem() .addProps({ gap: prop({ property: 'gap', scale: 'space' }) }) - .build().system; + .build() + .seal(); const v2 = createSystem() .addProps({ gap: prop({ property: 'gap', scale: 'spacing' }) }) - .build().system; + .build() + .seal(); expect(() => createSystem().extend(v1).extend(v2)).toThrow( /extended source #1.*extended source #2/s @@ -331,10 +343,12 @@ describe('SystemBuilder extend()', () => { it('fails divergent sibling condition aliases naming both sources', () => { const kitA = createSystem() .addConditions({ _compact: '@media (max-width: 400px)' }) - .build().system; + .build() + .seal(); const kitB = createSystem() .addConditions({ _compact: '@media (max-width: 500px)' }) - .build().system; + .build() + .seal(); expect(() => createSystem().extend(kitA).extend(kitB)).toThrow( /condition alias "_compact".*extended source #1.*extended source #2/s @@ -344,13 +358,15 @@ describe('SystemBuilder extend()', () => { it('fails divergent sibling group membership naming both sources', () => { const kitA = createSystem() .addGroup('layout', { gap: prop({ property: 'gap' }) }) - .build().system; + .build() + .seal(); const kitB = createSystem() .addGroup('layout', { gap: prop({ property: 'gap' }), rowGap: prop({ property: 'rowGap' }), }) - .build().system; + .build() + .seal(); expect(() => createSystem().extend(kitA).extend(kitB)).toThrow( /group "layout".*Existing \(extended source #1\): \[gap\].*Incoming \(extended source #2\): \[gap, rowGap\]/ @@ -360,10 +376,12 @@ describe('SystemBuilder extend()', () => { it('fails cross-registry name collisions between extended sources, both directions', () => { const conditionKit = createSystem() .addConditions({ _compact: '@media (max-width: 400px)' }) - .build().system; + .build() + .seal(); const selectorKit = createSystem() .addSelectors({ _compact: '&[data-compact]' }) - .build().system; + .build() + .seal(); expect(() => createSystem().extend(conditionKit).extend(selectorKit) @@ -376,10 +394,12 @@ describe('SystemBuilder extend()', () => { it('fails group-name-vs-prop-name cross-collisions between extended sources', () => { const propKit = createSystem() .addProps({ card: prop({ property: 'gridArea' }) }) - .build().system; + .build() + .seal(); const groupKit = createSystem() .addGroup('card', { cardPad: prop({ property: 'padding' }) }) - .build().system; + .build() + .seal(); expect(() => createSystem().extend(propKit).extend(groupKit)).toThrow( /group name "card".*collides with an existing prop name/ @@ -407,7 +427,8 @@ describe('SystemBuilder extend()', () => { }) .addSelectors({ _cardHover: '&[data-card]:hover' }) .addConditions({ _compact: '@media (max-width: 400px)' }) - .build().system; + .build() + .seal(); const kit = buildDualKit(); const once = createSystem().extend(kit).build().system.toConfig(); @@ -442,7 +463,8 @@ describe('SystemBuilder extend()', () => { transform: createTransform('unit', (value) => `${value}${unit}`), }), }) - .build().system; + .build() + .seal(); const { system } = createSystem() .extend(buildUnitKit('px')) @@ -461,7 +483,8 @@ describe('SystemBuilder extend()', () => { mapped: prop({ scale: { sm: '4px', lg: '8px' } }), listed: prop({ scale: ['4px', '8px'] }), }) - .build().system; + .build() + .seal(); expect(() => createSystem().extend(buildScaledKit()).extend(buildScaledKit()).build() @@ -495,7 +518,8 @@ describe('SystemBuilder extend()', () => { .addProps({ glow: prop({ property: 'boxShadow', transform: glowTransform }), }) - .build().system; + .build() + .seal(); // The serialized form drops the unnamed transform — reconstruction from // toConfig() would lose it (the G7 failure mode). @@ -518,9 +542,10 @@ describe('SystemBuilder extend()', () => { // Scenario: "Post-build mutation does not leak" + snapshot immutability. it('ignores post-build registry mutation in toConfig() and extension', () => { - const { system } = createSystem() + const system = createSystem() .addGroup('space', { m: prop({ scale: 'space' }) }) - .build(); + .build() + .seal(); const before = system.toConfig(); installUndeclaredEntry(system.propRegistry, 'rogue', { @@ -578,7 +603,7 @@ describe('SystemBuilder extend()', () => { }); it('ignores post-build mutation of nested properties arrays and object scales', () => { - const { system } = createSystem() + const system = createSystem() .addGroup('space', { mx: prop({ property: 'margin', @@ -586,7 +611,8 @@ describe('SystemBuilder extend()', () => { scale: { sm: '4px' }, }), }) - .build(); + .build() + .seal(); const before = system.toConfig(); const { properties, scale } = system.propRegistry.mx; @@ -651,7 +677,8 @@ describe('deprecated extension aliases (frozen semantics)', () => { const buildKit = () => createSystem() .addGroup('kitSurface', { kitGlow: prop({ property: 'boxShadow' }) }) - .build().system; + .build() + .seal(); // Scenario: "from() behavior is unchanged during the window" — byte-identical // to a builder that never called from() (no registry merge). diff --git a/packages/system/__tests__/test-system.ts b/packages/system/__tests__/test-system.ts index fbe142c1..543c6984 100644 --- a/packages/system/__tests__/test-system.ts +++ b/packages/system/__tests__/test-system.ts @@ -39,11 +39,7 @@ declare module '../src' { interface Theme extends TestTheme {} } -export const { - system: ds, - createGlobalStyles, - createKeyframes, -} = createSystem() +const bundle = createSystem() .addGroup('space', space) .addGroup('text', typography) .addGroup('surface', color) @@ -67,6 +63,12 @@ export const { }) .build(); +export const { createGlobalStyles, createKeyframes } = bundle; + +// Sealed (vocabulary-registration): the fixture mirrors the consumer shape — +// runtime tests author from the sealed instance exactly as an app would. +export const ds = bundle.seal(); + // Publish the registered condition + selector aliases through module // augmentation (design D9 — the same mechanism as the augmented `Theme` // below). This flips the `ThemedCSSProps` arms from permissive to VALIDATING: diff --git a/packages/system/__tests__/transform-identity.test.ts b/packages/system/__tests__/transform-identity.test.ts index 807ea005..5578a260 100644 --- a/packages/system/__tests__/transform-identity.test.ts +++ b/packages/system/__tests__/transform-identity.test.ts @@ -33,12 +33,14 @@ describe('anonymous transform identity across extend()', () => { .addGroup('a', { gap: prop({ property: 'gap', transform: (v) => `${v}px` }), }) - .build().system; + .build() + .seal(); const kitB = createSystem() .addGroup('b', { gap: prop({ property: 'gap', transform: (v) => `${v}rem` }), }) - .build().system; + .build() + .seal(); expect(() => createSystem().extend(kitA).extend(kitB)).toThrow(/gap/); }); @@ -48,7 +50,8 @@ describe('anonymous transform identity across extend()', () => { .addGroup('a', { gap: prop({ property: 'gap', transform: (v) => `${v}px` }), }) - .build().system; + .build() + .seal(); expect(() => createSystem().extend(kit).extend(kit)).not.toThrow(); }); @@ -57,7 +60,8 @@ describe('anonymous transform identity across extend()', () => { const shared: TransformFn = (v) => `${v}px`; const kit = createSystem() .addGroup('a', { gap: prop({ property: 'gap', transform: shared }) }) - .build().system; + .build() + .seal(); expect(() => createSystem() @@ -121,7 +125,8 @@ describe('structural scale comparison in addGroup/addProps', () => { .addGroup('grid', { flow: prop({ property: 'gridAutoFlow', scale: [] }), }) - .build().system; + .build() + .seal(); it('re-registering an identical object-scaled prop after extend() coalesces', () => { expect(() => diff --git a/packages/system/__tests__/types.test-d.tsx b/packages/system/__tests__/types.test-d.tsx index 00b3855a..91dd2835 100644 --- a/packages/system/__tests__/types.test-d.tsx +++ b/packages/system/__tests__/types.test-d.tsx @@ -2094,8 +2094,10 @@ void (); void sealedKit.registerKeyframes; // Negative: a value that is not a factory-shaped collection is rejected - // @ts-expect-error — shape mismatch: not a Keyframes collection - void createSystem().build().registerKeyframes({ bogus: { frames: {} } }); + void createSystem() + .build() + // @ts-expect-error — shape mismatch: not a Keyframes collection + .registerKeyframes({ bogus: { frames: {} } }); // Extending a sealed kit threads its vocabulary into the consumer chain; // a colliding consumer registration is a compile error (the dist-kit path @@ -2122,7 +2124,9 @@ void (); // Positive: an ANNOTATED LibraryBundle preserves the vocabulary axis // (the erasure amendment) — collisions stay compile errors - const publishedVocabBundle: LibraryBundle<'kitMotion'> = { system: sealedKit }; + const publishedVocabBundle: LibraryBundle<'kitMotion'> = { + system: sealedKit, + }; const viaAnnotated = createSystem().extend(publishedVocabBundle).build(); // @ts-expect-error — "kitMotion" arrives through the annotated bundle axis void viaAnnotated.registerKeyframes({ kitMotion: consumerMotion }); diff --git a/packages/system/__tests__/vocabulary.test.ts b/packages/system/__tests__/vocabulary.test.ts index 3294d32a..761b3c60 100644 --- a/packages/system/__tests__/vocabulary.test.ts +++ b/packages/system/__tests__/vocabulary.test.ts @@ -1,11 +1,32 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { createSystem } from '../src'; -import type { VocabularyRecord } from '../src'; + +import type { + KeyframesFrameData, + RegisterableKeyframes, + VocabularyRecord, +} from '../src'; const FRAMES_A = { '0%': { opacity: 0 }, '100%': { opacity: 1 } }; const FRAMES_B = { '0%': { opacity: 1 }, '100%': { opacity: 0 } }; +/** A value the untyped path may hand to registration: a real collection, + * or a malformed shape the runtime rejection is under test for. */ +type ErasedRegistrable = RegisterableKeyframes | { frames: object }; + +/** The deliberately type-erased bundle view the untyped-path tests drive: + * the runtime linearity/collision witnesses — not the compiler — are under + * test here. Real bundles are structurally assignable (method params check + * bivariantly), so the erasure is a plain parameter widening — no + * assertion anywhere. */ +interface ErasedBundle { + registerKeyframes(map: Record): ErasedBundle; + seal(): { getVocabularyRecord?(): VocabularyRecord }; +} + +const erased = (bundle: ErasedBundle): ErasedBundle => bundle; + function recordOf(sealed: { getVocabularyRecord?(): VocabularyRecord; }): VocabularyRecord { @@ -56,9 +77,11 @@ describe('vocabulary registration — two-phase terminal (runtime)', () => { expect(Object.isFrozen(entry.frames.pulse)).toBe(true); expect(Object.isFrozen(entry.frames.pulse?.frames)).toBe(true); - ( - motion.__frames.pulse.frames as Record> - )['0%'] = { opacity: 0.5 }; + // SAFETY: the readonly typing is compile-time only — mutating through + // the owner frame-map type is exactly the hazard under test. + (motion.__frames.pulse.frames as KeyframesFrameData[string]['frames'])[ + '0%' + ] = { opacity: 0.5 }; // Literal expectation — the module const aliases the live collection. expect(entry.frames.pulse?.frames['0%']).toEqual({ opacity: 0 }); }); @@ -68,11 +91,9 @@ describe('vocabulary registration — two-phase terminal (runtime)', () => { const motion = bundle.createKeyframes({ pulse: FRAMES_A }); const next = bundle.registerKeyframes({ motion }); - expect(() => - (bundle as { registerKeyframes(map: object): unknown }).registerKeyframes( - { motion } - ) - ).toThrow(/superseded|linear/); + expect(() => erased(bundle).registerKeyframes({ motion })).toThrow( + /superseded|linear/ + ); expect(() => bundle.seal()).toThrow(/superseded|linear/); expect(recordOf(next.seal()).keyframes.map((e) => e.name)).toEqual([ 'motion', @@ -84,11 +105,9 @@ describe('vocabulary registration — two-phase terminal (runtime)', () => { const motion = bundle.createKeyframes({ pulse: FRAMES_A }); bundle.seal(); - expect(() => - (bundle as { registerKeyframes(map: object): unknown }).registerKeyframes( - { motion } - ) - ).toThrow(/sealed/); + expect(() => erased(bundle).registerKeyframes({ motion })).toThrow( + /sealed/ + ); }); it('a second seal() throws — one sealed instance per bundle', () => { @@ -100,9 +119,7 @@ describe('vocabulary registration — two-phase terminal (runtime)', () => { it('a non-collection value is rejected at runtime naming the key', () => { const bundle = createSystem().build(); expect(() => - (bundle as { registerKeyframes(map: object): unknown }).registerKeyframes( - { bogus: { frames: {} } } - ) + erased(bundle).registerKeyframes({ bogus: { frames: {} } }) ).toThrow(/bogus/); }); @@ -113,9 +130,11 @@ describe('vocabulary registration — two-phase terminal (runtime)', () => { const sealed = bundle.seal(); const before = sealed.toConfig().propConfig; - ( - sealed as unknown as { propRegistry: Record } - ).propRegistry.injected = { property: 'color' }; + // SAFETY: the public registry field is runtime-mutable by design; the + // widened record type simulates a consumer mutating it after seal. + (sealed.propRegistry as Record).injected = { + property: 'color', + }; expect(sealed.toConfig().propConfig).toBe(before); }); @@ -143,13 +162,9 @@ describe('vocabulary registration — two-phase terminal (runtime)', () => { const consumerBundle = createSystem().extend(kit).build(); const localMotion = consumerBundle.createKeyframes({ fade: FRAMES_B }); - const sealed = ( - consumerBundle as unknown as { - registerKeyframes(map: object): { seal(): unknown }; - } - ) + const sealed = erased(consumerBundle) .registerKeyframes({ motion: localMotion }) - .seal() as Parameters[0]; + .seal(); const record = recordOf(sealed); expect(record.keyframes.map((entry) => entry.name)).toEqual(['motion']); @@ -180,14 +195,7 @@ describe('vocabulary registration — two-phase terminal (runtime)', () => { spin: { '0%': { opacity: 0.25 } }, }); const bOverride = consumerBundle.createKeyframes({ blink: FRAMES_B }); - const sealed = ( - consumerBundle - .registerKeyframes({ c }) as unknown as { - registerKeyframes(map: object): { - seal(): Parameters[0]; - }; - } - ) + const sealed = erased(consumerBundle.registerKeyframes({ c })) .registerKeyframes({ b: bOverride }) .seal(); @@ -229,17 +237,123 @@ describe('vocabulary registration — two-phase terminal (runtime)', () => { }); // SPEC(vocabulary-registration §"Extending an unsealed instance fails - // loud"): the strict rejection is DEFERRED to the atomic migration - // increment (design Ledger DEF-11) — verify:compile sweeps un-migrated - // fixtures until then. `it.fails` pins the obligation: when the flip - // lands, this test starts passing and MUST be inverted to a plain `it`. - it.fails( - 'extending a built-but-unsealed system instance fails loud (flips at the migration increment — DEF-11)', - () => { - const kit = createSystem() - .addGroup('kitSurface', { kitGlow: { property: 'boxShadow' } }) - .build().system; - expect(() => createSystem().extend(kit)).toThrow(/seal/); - } - ); + // loud") — the DEF-11 flip, landed with the migration increment. + it('extending a built-but-unsealed system instance fails loud', () => { + const kit = createSystem() + .addGroup('kitSurface', { kitGlow: { property: 'boxShadow' } }) + .build().system; + expect(() => createSystem().extend(kit)).toThrow(/seal/); + }); + + it('a sealed kit with registered vocabulary consumed through includes: is witnessed and not merged', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const kitBundle = createSystem().build(); + const kitMotion = kitBundle.createKeyframes({ pulse: FRAMES_A }); + const kit = kitBundle.registerKeyframes({ kitMotion }).seal(); + + const sealed = createSystem({ includes: [kit] }) + .build() + .seal(); + const record = recordOf(sealed); + + expect(record.keyframes).toEqual([]); + expect(record.legacyVerbs).toHaveLength(1); + expect(record.legacyVerbs[0]).toMatchObject({ + code: 'animus.vocabulary.legacy-verb', + verb: 'includes', + source: 'includes source #1', + names: ['kitMotion'], + }); + // The record is the SOLE witness channel — a runtime warn would ship in + // production consumer bundles and be swallowed by the extraction host. + expect(warn).not.toHaveBeenCalled(); + }); + + it('a sealed kit with registered vocabulary consumed through from() is witnessed and not merged', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const kitBundle = createSystem().build(); + const kitMotion = kitBundle.createKeyframes({ pulse: FRAMES_A }); + const kit = kitBundle.registerKeyframes({ kitMotion }).seal(); + + const sealed = createSystem().from(kit).build().seal(); + const record = recordOf(sealed); + + expect(record.keyframes).toEqual([]); + expect(record.legacyVerbs).toHaveLength(1); + expect(record.legacyVerbs[0]).toMatchObject({ + verb: 'from', + source: 'from source #1', + names: ['kitMotion'], + }); + expect(warn).not.toHaveBeenCalled(); + }); + + it('a source consumed through BOTH a legacy verb and .extend() is not falsely witnessed — delivered names are filtered at seal', () => { + const kitBundle = createSystem().build(); + const kitMotion = kitBundle.createKeyframes({ pulse: FRAMES_A }); + const kit = kitBundle.registerKeyframes({ kitMotion }).seal(); + + const sealed = createSystem({ includes: [kit] }) + .extend(kit) + .build() + .seal(); + const record = recordOf(sealed); + + expect(record.keyframes.map((entry) => entry.name)).toEqual(['kitMotion']); + expect(record.legacyVerbs).toEqual([]); + }); + + it('partial delivery narrows the witness to the genuinely refused names', () => { + const kitABundle = createSystem().build(); + const kitMotion = kitABundle.createKeyframes({ pulse: FRAMES_A }); + const kitFade = kitABundle.createKeyframes({ fade: FRAMES_B }); + const kitA = kitABundle + .registerKeyframes({ kitMotion }) + .registerKeyframes({ kitFade }) + .seal(); + // A second kit registering ONLY kitMotion, extended — delivering one of + // kitA's two names through a different source. + const kitBBundle = createSystem().build(); + const kitB = kitBBundle + .registerKeyframes({ + kitMotion: kitBBundle.createKeyframes({ pulse: FRAMES_A }), + }) + .seal(); + + const sealed = createSystem({ includes: [kitA] }) + .extend(kitB) + .build() + .seal(); + const record = recordOf(sealed); + + expect(record.legacyVerbs).toHaveLength(1); + expect(record.legacyVerbs[0]?.names).toEqual(['kitFade']); + }); + + it('.extend() of the same sealed kit produces no legacy-verb witness — it carries', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const kitBundle = createSystem().build(); + const kitMotion = kitBundle.createKeyframes({ pulse: FRAMES_A }); + const kit = kitBundle.registerKeyframes({ kitMotion }).seal(); + + const sealed = createSystem().extend(kit).build().seal(); + const record = recordOf(sealed); + + expect(record.keyframes.map((entry) => entry.name)).toEqual(['kitMotion']); + expect(record.legacyVerbs).toEqual([]); + expect(warn).not.toHaveBeenCalled(); + }); + + it('a vocabulary-free sealed source through legacy verbs stays silent', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const plainKit = createSystem().build().seal(); + const sealed = createSystem({ includes: [plainKit] }) + .build() + .seal(); + expect(recordOf(sealed).legacyVerbs).toEqual([]); + expect(warn).not.toHaveBeenCalled(); + }); }); diff --git a/packages/system/src/SystemBuilder.ts b/packages/system/src/SystemBuilder.ts index cd83ad7f..198dca26 100644 --- a/packages/system/src/SystemBuilder.ts +++ b/packages/system/src/SystemBuilder.ts @@ -164,6 +164,25 @@ export interface VocabularyCollisionEntry { readonly loser: string; } +/** + * Witness for a sealed kit with registered vocabulary arriving through a + * legacy verb (`from()` / `includes:`) that performs no registry merge — + * the vocabulary named here does NOT reach the consumer. Registered + * vocabulary requires `.extend()`. Entries whose names DO arrive through a + * separate `.extend()` of the same vocabulary are filtered out at `seal()` + * — the witness never claims an undelivered name that was delivered. + */ +export interface VocabularyLegacyVerbEntry { + readonly code: 'animus.vocabulary.legacy-verb'; + readonly verb: 'from' | 'includes'; + /** Positional source label (`includes source #1`, `from source #2`) — + * the same origin-label vocabulary the collision entries use; sources + * have no knowable export name at this seam. */ + readonly source: string; + /** The registered vocabulary names the verb could not carry. */ + readonly names: readonly string[]; +} + /** * The declaration-ordered, version-marked registration record a sealed * system carries (vocabulary-registration). The loader reads collections @@ -175,6 +194,7 @@ export interface VocabularyRecord { readonly keyframes: readonly VocabularyKeyframesEntry[]; readonly globalStyles: readonly VocabularyGlobalStyleEntry[]; readonly collisions: readonly VocabularyCollisionEntry[]; + readonly legacyVerbs: readonly VocabularyLegacyVerbEntry[]; } /** Internal pending/merged vocabulary state (origin powers witness text). */ @@ -184,6 +204,37 @@ interface VocabularyKeyframesState { origin: string; } +/** + * Legacy-verb witness helper shared by `from()` and the `includes:` config + * path: a sealed source carrying registered vocabulary cannot deliver it + * through a verb that performs no registry merge. The RECORD is the sole + * witness channel (hosts surface it as a coded diagnostic; the extraction + * host shims `console`, and a runtime warn here would ship in production + * consumer bundles) — no console output. `seal()` filters out names that + * a separate `.extend()` of the same vocabulary DID deliver. + */ +function legacyVerbWitness( + source: IncludableSystem, + verb: 'from' | 'includes', + sourceIndex: number +): VocabularyLegacyVerbEntry | null { + const record = ( + source as { getVocabularyRecord?(): VocabularyRecord } + ).getVocabularyRecord?.(); + if (!record) return null; + const names = [ + ...record.keyframes.map((entry) => entry.name), + ...record.globalStyles.map((entry) => entry.name), + ]; + if (names.length === 0) return null; + return { + code: 'animus.vocabulary.legacy-verb', + verb, + source: `${verb} source #${sourceIndex}`, + names, + }; +} + /** * Registration-time snapshot of a collection's frame data: copied and frozen * two levels deep (frame entries + stop bodies), so post-registration @@ -235,10 +286,11 @@ function mergeVocabularyKeyframes( winner: incomingOrigin, loser: loser.origin, }); + // oxlint-disable-next-line no-console -- intentional runtime diagnostic console.warn( - `animus vocabulary collision: keyframes "${name}" is registered by ` + - `both ${loser.origin} and ${incomingOrigin} — ${incomingOrigin} ` + - 'wins. Rename one collection to silence this.' + `animus: keyframes vocabulary "${name}" is registered by both ` + + `${loser.origin} and ${incomingOrigin} — ${incomingOrigin} wins; ` + + 'rename one collection (animus.vocabulary.collision)' ); entries.splice(existingIndex, 1); } @@ -247,7 +299,7 @@ function mergeVocabularyKeyframes( return { entries, collisions }; } -declare const VOCABULARY_COLLISION: unique symbol; +declare const VOCABULARY_COLLISION_BRAND: unique symbol; /** * Impossible-to-satisfy marker type that surfaces a template-literal label @@ -255,7 +307,7 @@ declare const VOCABULARY_COLLISION: unique symbol; * vocabulary name instead of a bare structural mismatch. */ export interface VocabularyNameCollision { - readonly [VOCABULARY_COLLISION]: `Vocabulary name "${Name}" is already registered on this system`; + readonly [VOCABULARY_COLLISION_BRAND]: `Vocabulary name "${Name}" is already registered on this system`; } declare const VOCABULARY_INDEX_SIGNATURE: unique symbol; @@ -575,6 +627,7 @@ export class SystemBuilder< // (kit-vs-kit); registration-time collisions accumulate in the bundle. #vocabularyRegistry: readonly VocabularyKeyframesState[]; #vocabularyCollisions: readonly VocabularyCollisionEntry[]; + #legacyVerbWitnesses: readonly VocabularyLegacyVerbEntry[]; constructor( propRegistry?: PropReg, @@ -585,7 +638,8 @@ export class SystemBuilder< extendProvenance?: ReadonlyMap, extendCount?: number, vocabularyRegistry?: readonly VocabularyKeyframesState[], - vocabularyCollisions?: readonly VocabularyCollisionEntry[] + vocabularyCollisions?: readonly VocabularyCollisionEntry[], + legacyVerbWitnesses?: readonly VocabularyLegacyVerbEntry[] ) { this.#propRegistry = propRegistry || ({} as PropReg); this.#groupRegistry = groupRegistry || ({} as GroupReg); @@ -596,6 +650,7 @@ export class SystemBuilder< this.#extendCount = extendCount || 0; this.#vocabularyRegistry = vocabularyRegistry || []; this.#vocabularyCollisions = vocabularyCollisions || []; + this.#legacyVerbWitnesses = legacyVerbWitnesses || []; } // Origin label for divergence errors: where did the existing entry for @@ -665,6 +720,10 @@ export class SystemBuilder< const instance = isLibraryBundle(source) ? source.system : (source as IncludableSystem); + const fromCount = this.#legacyVerbWitnesses.filter( + (entry) => entry.verb === 'from' + ).length; + const witness = legacyVerbWitness(instance, 'from', fromCount + 1); return new SystemBuilder( this.#propRegistry, this.#groupRegistry, @@ -674,7 +733,10 @@ export class SystemBuilder< this.#extendProvenance, this.#extendCount, this.#vocabularyRegistry, - this.#vocabularyCollisions + this.#vocabularyCollisions, + witness + ? [...this.#legacyVerbWitnesses, witness] + : this.#legacyVerbWitnesses ); } @@ -909,15 +971,23 @@ export class SystemBuilder< // resolves to the later extension — one merge policy for both call // sites, see `mergeVocabularyKeyframes` — with a coded witness entry; // on typed paths the collision is a compile error at the consumer's - // registration site. An unsealed source carries no record and - // contributes nothing (the strict sealed-source requirement lands with - // the hard-cut migration increment). + // registration site. A source WITHOUT a record fails loud: `.extend()` + // consumes sealed instances only (the hard cut — registered vocabulary + // has exactly one carriage channel). const sourceRecord = ( instance as { getVocabularyRecord?(): VocabularyRecord } ).getVocabularyRecord?.(); + 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.' + ); + } let nextVocabulary = this.#vocabularyRegistry; let nextVocabularyCollisions = this.#vocabularyCollisions; - if (sourceRecord && sourceRecord.keyframes.length > 0) { + if (sourceRecord.keyframes.length > 0) { const merged = mergeVocabularyKeyframes( this.#vocabularyRegistry, this.#vocabularyCollisions, @@ -939,7 +1009,8 @@ export class SystemBuilder< provenance, sourceIndex, nextVocabulary, - nextVocabularyCollisions + nextVocabularyCollisions, + this.#legacyVerbWitnesses ); } @@ -1004,7 +1075,8 @@ export class SystemBuilder< this.#extendProvenance, this.#extendCount, this.#vocabularyRegistry, - this.#vocabularyCollisions + this.#vocabularyCollisions, + this.#legacyVerbWitnesses ); } @@ -1069,7 +1141,8 @@ export class SystemBuilder< this.#extendProvenance, this.#extendCount, this.#vocabularyRegistry, - this.#vocabularyCollisions + this.#vocabularyCollisions, + this.#legacyVerbWitnesses ); } @@ -1148,7 +1221,8 @@ export class SystemBuilder< this.#extendProvenance, this.#extendCount, this.#vocabularyRegistry, - this.#vocabularyCollisions + this.#vocabularyCollisions, + this.#legacyVerbWitnesses ); } @@ -1212,7 +1286,8 @@ export class SystemBuilder< this.#extendProvenance, this.#extendCount, this.#vocabularyRegistry, - this.#vocabularyCollisions + this.#vocabularyCollisions, + this.#legacyVerbWitnesses ); } @@ -1283,6 +1358,7 @@ export class SystemBuilder< }; const system = mintInstance(); + const legacyVerbWitnessRecord = this.#legacyVerbWitnesses; const createGlobalStyles = (( styles: GlobalStyleMap, @@ -1357,7 +1433,11 @@ export class SystemBuilder< `local registration #${localCallCount + 1}` ); consumedBy = 'register'; - return makeBundle(merged.entries, merged.collisions, localCallCount + 1); + return makeBundle( + merged.entries, + merged.collisions, + localCallCount + 1 + ); }; const seal = (): SealedSystemInstance< @@ -1392,6 +1472,26 @@ export class SystemBuilder< collisions: Object.freeze( collisions.map((entry) => Object.freeze({ ...entry })) ), + legacyVerbs: Object.freeze( + legacyVerbWitnessRecord + // A name that DID arrive (a separate `.extend()` of the same + // vocabulary) must not be claimed undelivered — narrow each + // entry to its genuinely refused names, dropping emptied + // entries (the false-witness guard). + .map((entry) => ({ + ...entry, + names: entry.names.filter( + (name) => !entries.some((kept) => kept.name === name) + ), + })) + .filter((entry) => entry.names.length > 0) + .map((entry) => + Object.freeze({ + ...entry, + names: Object.freeze([...entry.names]), + }) + ) + ), }); const sealed = mintInstance() as SealedSystemInstance< @@ -1620,10 +1720,26 @@ function serializeInstance< } export function createSystem(config?: CreateSystemConfig): SystemBuilder { + const includes = config?.includes ?? []; + // Legacy-verb witness (vocabulary-registration): the deprecated + // `includes:` alias performs no registry merge, so a sealed source's + // registered vocabulary cannot reach this consumer — witnessed per + // source, carried on the eventual sealed record. + const witnesses: VocabularyLegacyVerbEntry[] = []; + includes.forEach((source, index) => { + const witness = legacyVerbWitness(source, 'includes', index + 1); + if (witness) witnesses.push(witness); + }); return new SystemBuilder( undefined, undefined, undefined, - config?.includes ?? [] + includes, + undefined, + undefined, + undefined, + undefined, + undefined, + witnesses ); } diff --git a/packages/system/src/index.ts b/packages/system/src/index.ts index dce2ecaa..707e0d18 100644 --- a/packages/system/src/index.ts +++ b/packages/system/src/index.ts @@ -38,6 +38,7 @@ export type { VocabularyCollisionEntry, VocabularyGlobalStyleEntry, VocabularyKeyframesEntry, + VocabularyLegacyVerbEntry, VocabularyNameCollision, VocabularyOf, VocabularyRecord, diff --git a/packages/system/src/keyframes.ts b/packages/system/src/keyframes.ts index b85b7b13..46e02f59 100644 --- a/packages/system/src/keyframes.ts +++ b/packages/system/src/keyframes.ts @@ -3,7 +3,8 @@ * as a branded collection of typed per-key references. * * The returned collection is: - * - Branded (`__brand: 'Keyframes'`) for plugin discovery via named-export scan. + * - Branded (`__brand: 'Keyframes'`) — the registration shape check reads it + * (vocabulary-registration; collections are declared, never discovered). * - Carries raw frame data on `__frames` as `{ [key]: { name, frames } }`, * where `name` is the resolved keyframes identifier emitted into CSS. * - Exposes one `KeyframeRef` per named key — each ref coerces to its diff --git a/packages/test-ds/src/index.ts b/packages/test-ds/src/index.ts index 7cfc1d14..38234acd 100644 --- a/packages/test-ds/src/index.ts +++ b/packages/test-ds/src/index.ts @@ -1,5 +1,3 @@ -import { createKeyframes } from './system'; - export { Alert } from './components/Alert'; export { Badge } from './components/Badge'; export { Button } from './components/Button'; @@ -23,18 +21,13 @@ export const kitSizes = { lg: { fontSize: 20, px: 24, py: 12 }, } as const; -// External keyframe collection (rust-extraction-pipeline › -// external-collection scenario): exported from the package's source ENTRY -// module (what `main`/exports resolve to under src/), which the plugin's -// keyframes-only scan evaluates for `__brand === 'Keyframes'` named exports. -// A consumer authoring `animationName: kitMotion.pulse` through a plain named -// import must resolve to the FNV-hashed `animus-kf-` name with the -// matching `@keyframes` block emitted exactly once. The frame body is -// deliberately DISTINCT from the vite-app's inline `pulse` (opacity, not -// scale) so the kit block is its own unique body, not a dedupe alias. -export const kitMotion = createKeyframes({ - pulse: { - '0%, 100%': { opacity: 1 }, - '50%': { opacity: 0.6 }, - }, -}); +// Kit keyframe collection (vocabulary-registration › sealed-kit carriage): +// DEFINED in `./system` inside the definition graph and registered on the +// sealed kit; the root re-export preserves the historical consumer import +// path (`import { kitMotion } from '@animus-ui/test-ds'`). The engine +// resolves the re-export chain to the defining module's export name, which +// equals the registration key — a consumer authoring +// `animationName: kitMotion.pulse` through this plain named import resolves +// to the FNV-hashed `animus-kf-` name with the matching `@keyframes` +// block emitted exactly once. +export { kitMotion } from './system'; diff --git a/packages/test-ds/src/system.ts b/packages/test-ds/src/system.ts index 6a808020..da8aeab8 100644 --- a/packages/test-ds/src/system.ts +++ b/packages/test-ds/src/system.ts @@ -16,7 +16,7 @@ import { typography, } from '@animus-ui/system/groups'; -export const { system: ds, createKeyframes } = createSystem() +const kitBundle = createSystem() .addGroup('space', space) .addGroup('layout', { ...layout, ...flex }) .addGroup('text', typography) @@ -44,3 +44,24 @@ export const { system: ds, createKeyframes } = createSystem() _dark: '[data-color-mode="dark"] &', }) .build(); + +export const { createKeyframes } = kitBundle; + +// Kit keyframe collection (vocabulary-registration › sealed-kit carriage): +// DEFINED inside the loader-evaluated definition graph (this module sits on +// the `definition.ts` path) and re-exported from the package root so a +// consumer's plain named import keeps resolving — the engine follows the +// re-export chain to THIS module's export name, which equals the +// registration key below. The frame body is deliberately DISTINCT from the +// vite-app's inline `pulse` (opacity, not scale) so the kit block is its +// own unique body, not a dedupe alias. +export const kitMotion = createKeyframes({ + pulse: { + '0%, 100%': { opacity: 1 }, + '50%': { opacity: 0.6 }, + }, +}); + +// The sealed kit instance: registration closes here, and `.extend()` +// consumers inherit `kitMotion` through the vocabulary record. +export const ds = kitBundle.registerKeyframes({ kitMotion }).seal(); diff --git a/packages/vite-plugin/src/build-start.ts b/packages/vite-plugin/src/build-start.ts index 9d06c529..030adbb3 100644 --- a/packages/vite-plugin/src/build-start.ts +++ b/packages/vite-plugin/src/build-start.ts @@ -100,7 +100,6 @@ export async function runBuildStart( const packageSpecifiers = extractSystemFilePackages(ctx.resolvedSystemPath!); ctx.externalSourceEntries.clear(); - ctx.externalKeyframesScanEntries.clear(); // Shared traversal/ingest (spec: external-package-file-discovery); // only specifier resolution, MDX handling, and the hash/cache policy @@ -123,9 +122,6 @@ export async function runBuildStart( for (const [specifier, srcEntry] of collected.sourceEntries) { ctx.externalSourceEntries.set(specifier, srcEntry); } - for (const [specifier, scanEntry] of collected.keyframesScanEntries) { - ctx.externalKeyframesScanEntries.set(specifier, scanEntry); - } for (const entry of collected.entries) { const hash = !ctx.isProd ? contentHash(entry.source) : undefined; rawEntries.push({ path: entry.path, source: entry.source, hash }); @@ -137,11 +133,6 @@ export async function runBuildStart( // external dirs must register here or they are never watched. ctx.registerSystemWatchPaths(); - // Keyframes-only carve-out: external package entries contribute their - // `Keyframes` collections to the analysis inputs; everything else about - // the consumer system's authority is untouched. - ctx.applyExternalKeyframes(); - const packageFileCount = rawEntries.length - localFileCount; ctx.log( `Discovered ${rawEntries.length} files (${packageFileCount} from packages) (${Math.round(performance.now() - t0)}ms)` diff --git a/packages/vite-plugin/src/context.ts b/packages/vite-plugin/src/context.ts index bf849748..97bc7b60 100644 --- a/packages/vite-plugin/src/context.ts +++ b/packages/vite-plugin/src/context.ts @@ -12,7 +12,7 @@ import { findAssetSpecifiers, formatRustTimingWaterfall, loadSystemConfig, - mergeExternalKeyframes, + vocabularyWitnessDiagnostics, parseFilesJson, projectExternalFileOwners, resolveAssetFile, @@ -202,7 +202,7 @@ export function pruneFileCache( * Of this class's ~50 fields, roughly 26 duplicate an `ExtractionSession` * authority field-for-field — `options`, `verbose`, `staticCssJson`, * `rootDir`, `system`, `lcssTargets`, `pathAliasesJson`, `extensionsSet`, - * `excludeMatcher`, `externalKeyframesDiagnostics`, `fileCache`, + * `excludeMatcher`, `systemVocabularyDiagnostics`, `fileCache`, * `analysisEntryCache`, `sourceOwnership`, `packageMap`, the asset-pass * trio, the five `external*` ownership maps, `externalPackageOutcomes`, * `resolvedSystemPath`, the two `systemDependency*` sets, and @@ -292,49 +292,12 @@ export class PluginContext { this.transformOutputHashes.set(relativePath, contentHash(code)); } - /** - * The consumer system's OWN keyframes collections, captured at load time - * before any external merge touches `system.keyframesJson`. Every - * `applyExternalKeyframes` merge starts from this baseline, so repeated - * merges (a --watch rebuild's loadSystem + rediscovery, a geological - * reset) never compound prior external state — a removed include's - * keyframes disappear with it. - */ - private consumerKeyframesJson: string | null = null; - - /** - * Merge `Keyframes` collections from discovered external package entries - * into the system's collections (keyframes-only carve-out — the consumer - * system stays the singular config authority). Runs after buildStart - * discovery AND after every geological-reset system reload, since a reload - * rebuilds `this.system` from the consumer entry alone. Merges from the - * consumer-only baseline, never from the previously merged value. - */ - applyExternalKeyframes(): void { - if (this.externalKeyframesScanEntries.size === 0) { - // No external entries: the system carries exactly its own collections - // (byte-identical restore), and no external diagnostics remain to ride - // the next analysis. - this.system.keyframesJson = this.consumerKeyframesJson; - this.externalKeyframesDiagnostics = []; - return; - } - const merge = mergeExternalKeyframes( - (entry, root) => this.engineApi().scanKeyframesExports(entry, root), - this.consumerKeyframesJson, - this.externalKeyframesScanEntries.values(), - this.rootDir - ); - this.system.keyframesJson = merge.keyframesJson; - // Surfacing stays with the single shared policy point inside - // runProjectAnalysis (next-plugin pins that there is exactly one - // surfacing call site) — stash for the next analysis to carry. - this.externalKeyframesDiagnostics = merge.diagnostics; - } - - /** Discovery-time keyframes diagnostics awaiting the next analysis's - * shared surfacing pass. */ - externalKeyframesDiagnostics: ManifestDiagnostic[] = []; + /** Vocabulary witness diagnostics from the sealed system's registration + * record (vocabulary-registration), awaiting the next analysis's shared + * surfacing pass. Surfacing stays with the single policy point inside + * runProjectAnalysis (next-plugin pins that there is exactly one + * surfacing call site). */ + systemVocabularyDiagnostics: ManifestDiagnostic[] = []; // Reverse provenance: parent_id → [child_ids] for transitive invalidation reverseProvenance: Record = {}; @@ -462,11 +425,6 @@ export class PluginContext { // External package specifier → absolute source entry (resolveId redirect) externalSourceEntries = new Map(); - // External package specifier → absolute keyframes scan entry — one per - // admitted package whatever its shape (src entry or dist entry), so - // dist-only packages' `Keyframes` collections merge like src-shipping ones. - externalKeyframesScanEntries = new Map(); - // Per-specifier discovery outcomes from buildStart (self-verify input) externalPackageOutcomes: ExternalPackageOutcome[] = []; @@ -616,13 +574,13 @@ export class PluginContext { this.systemDependencyKeys = keys; this.systemDependencyPaths = deps; this.registerSystemWatchPaths(); - // The freshly loaded config carries the consumer's own collections — - // capture the merge baseline BEFORE the carve-out overwrites it. A - // failed reload keeps the previous system AND its matching baseline. - this.consumerKeyframesJson = this.system.keyframesJson; - // A reload rebuilds `this.system` from the consumer entry alone — - // re-apply the external keyframes carve-out (no-op before discovery). - this.applyExternalKeyframes(); + // The sealed record is the witness channel (the loader's evaluation + // host shims console): map its coded entries for the next analysis's + // shared surfacing pass. A failed reload keeps the previous system + // AND its matching witnesses. + this.systemVocabularyDiagnostics = vocabularyWitnessDiagnostics( + this.system.vocabularyWitnessesJson + ); } catch (e) { if (this.options.strict) { throw new Error( @@ -667,7 +625,7 @@ export class PluginContext { devMode: !this.emissionProd, warn: (m) => this.warn(m), strict: this.options.strict, - extraDiagnostics: this.externalKeyframesDiagnostics, + extraDiagnostics: this.systemVocabularyDiagnostics, }); } catch (e) { if (this.options.strict) { diff --git a/packages/vite-plugin/tests/dev-lane/fixture.ts b/packages/vite-plugin/tests/dev-lane/fixture.ts index 7e4ade46..d9c629d8 100644 --- a/packages/vite-plugin/tests/dev-lane/fixture.ts +++ b/packages/vite-plugin/tests/dev-lane/fixture.ts @@ -98,10 +98,11 @@ import { color, space } from '@animus-ui/system/groups'; export { tokens } from './theme'; // ${marker} -export const { system: ds } = createSystem() +export const ds = createSystem() .addGroup('space', space) .addGroup('surface', color) - .build(); + .build() + .seal(); `; } diff --git a/packages/vite-plugin/tests/external-keyframes-lifecycle.test.ts b/packages/vite-plugin/tests/external-keyframes-lifecycle.test.ts deleted file mode 100644 index d3b28af7..00000000 --- a/packages/vite-plugin/tests/external-keyframes-lifecycle.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { afterAll, describe, expect, test } from 'vitest'; - -import { runBuildStart } from '../src/build-start'; -import { PluginContext } from '../src/context'; -import { makeManifest } from './manifest-fixture'; - -/** - * External-keyframes state across REPEATED buildStarts on one PluginContext - * (--watch rebuilds, multi-environment builds): each run's merged - * `keyframesJson` must reflect ONLY the includes the current system file - * declares. `loadSystem()` re-applies the carve-out from whatever external - * entries exist (for geological resets), so a rebuild after an include was - * removed must not resurrect the removed package's keyframes — the merge - * starts from the consumer-only baseline, never from compounded state. - */ - -const scratch = mkdtempSync(join(tmpdir(), 'animus-external-kf-')); - -afterAll(() => { - rmSync(scratch, { recursive: true, force: true }); -}); - -const KIT_A_KF = '{"kitAKeyframes":{"spinA":{"to":{"opacity":1}}}}'; -const KIT_B_KF = '{"kitBKeyframes":{"spinB":{"to":{"opacity":0}}}}'; -const KIT_C_KF = '{"kitCKeyframes":{"spinC":{"to":{"opacity":1}}}}'; - -function writeKit(name: string): void { - const src = join(scratch, name, 'src'); - mkdirSync(src, { recursive: true }); - writeFileSync(join(scratch, name, 'package.json'), `{"name":"${name}"}\n`); - writeFileSync(join(src, 'index.ts'), `export default {};\n`); -} - -/** A published-shape kit: dist entry only, no src/ tree. */ -function writeDistKit(name: string): void { - const dist = join(scratch, name, 'dist'); - mkdirSync(dist, { recursive: true }); - writeFileSync(join(scratch, name, 'package.json'), `{"name":"${name}"}\n`); - writeFileSync(join(dist, 'index.mjs'), `export default {};\n`); -} - -/** `importPath` is relative to src/ds.ts (e.g. '../kit-a/src'). */ -function writeSystemFile(importPath: string | null): void { - const importLine = importPath ? `import kit from '${importPath}';\n` : ''; - const chain = importPath ? '.extend(kit)' : ''; - writeFileSync( - join(scratch, 'src', 'ds.ts'), - `${importLine}export const ds = createSystem({})${chain};\n` - ); -} - -function makeContext(): PluginContext { - mkdirSync(join(scratch, 'src'), { recursive: true }); - writeKit('kit-a'); - writeKit('kit-b'); - writeDistKit('kit-c'); - - const engine = { - loadSystemModule: () => ({ - propConfig: '{}', - groupRegistry: '{}', - scalesJson: '{}', - variableMapJson: '{}', - variableCss: '', - dependencies: [], - }), - extractFacts: () => JSON.stringify({ files: {}, parseCount: 0 }), - analyzeProject: () => JSON.stringify(makeManifest()), - scanKeyframesExports: (entry: string) => { - if (entry.includes('kit-a')) return KIT_A_KF; - if (entry.includes('kit-b')) return KIT_B_KF; - if (entry.includes('kit-c')) return KIT_C_KF; - return null; - }, - }; - - const ctx = new PluginContext({ system: 'src/ds.ts' }, () => engine); - ctx.rootDir = scratch; - ctx.isProd = true; - return ctx; -} - -// src-shipping includes probe the fs from their absolute specifier; the -// dist-only kit resolves like a bundler would (package entry → dist file). -const resolveSpecifier = async (specifier: string) => - specifier.endsWith('kit-c') - ? join(scratch, 'kit-c', 'dist', 'index.mjs') - : null; - -describe('external keyframes across repeated buildStarts', () => { - test('a removed include leaves no keyframes behind on the next run', async () => { - const ctx = makeContext(); - - writeSystemFile('../kit-a/src'); - await runBuildStart(ctx, resolveSpecifier); - expect(ctx.system.keyframesJson).toContain('kitAKeyframes'); - - // The include moves from kit-a to kit-b between runs (--watch rebuild). - writeSystemFile('../kit-b/src'); - await runBuildStart(ctx, resolveSpecifier); - expect(ctx.system.keyframesJson).toContain('kitBKeyframes'); - expect(ctx.system.keyframesJson).not.toContain('kitAKeyframes'); - - // All includes removed: back to the consumer-only baseline, with no - // stale external diagnostics riding the next analysis. - writeSystemFile(null); - await runBuildStart(ctx, resolveSpecifier); - expect(ctx.system.keyframesJson).toBeNull(); - expect(ctx.externalKeyframesDiagnostics).toEqual([]); - }); - - test('a dist-only include contributes its keyframes collections', async () => { - // Published packages routinely ship dist without src/ — their imported - // `Keyframes` collections must reach the merged system exactly like a - // src-shipping package's. - const ctx = makeContext(); - - writeSystemFile('../kit-c'); - await runBuildStart(ctx, resolveSpecifier); - expect(ctx.system.keyframesJson).toContain('kitCKeyframes'); - }); -}); diff --git a/packages/vite-plugin/tests/svelte-source-lifecycle.test.ts b/packages/vite-plugin/tests/svelte-source-lifecycle.test.ts index 37a6cde3..41e29370 100644 --- a/packages/vite-plugin/tests/svelte-source-lifecycle.test.ts +++ b/packages/vite-plugin/tests/svelte-source-lifecycle.test.ts @@ -142,7 +142,6 @@ function makeEngineProbe(): EngineProbe { return { code: '', hasComponents: false }; }, clearAnalysisCache: () => {}, - scanKeyframesExports: () => null, } satisfies EngineApi; return { engine, @@ -183,7 +182,6 @@ function makeStatefulResetProbe() { clears += 1; active = false; }, - scanKeyframesExports: () => null, } satisfies EngineApi; return { @@ -300,7 +298,6 @@ describe('opted-in Svelte source ownership in the Vite lifecycle', () => { test('keeps legacy EngineApi objects source-compatible and fails loud at ingestion', async () => { const legacyEngine = { loadSystemModule: () => ({}), - scanKeyframesExports: () => null, analyzeProject: () => '{}', transformFile: () => ({ code: '', hasComponents: false }), clearAnalysisCache: () => {}, @@ -677,7 +674,6 @@ describe('opted-in Svelte source ownership in the Vite lifecycle', () => { }, transformFile: () => ({ code: '', hasComponents: false }), clearAnalysisCache: () => {}, - scanKeyframesExports: () => null, }; const ctx = makeContext(appRoot, engine, ['.ts', '.svelte']); diff --git a/vite.config.ts b/vite.config.ts index 1e31d2f2..5d37aefc 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -26,7 +26,6 @@ const typescriptTestTargets = [ 'packages/extract/tests/discover-packages.test.ts', 'packages/extract/tests/dynamic-prop-config.test.ts', 'packages/extract/tests/error-diagnostics.test.ts', - 'packages/extract/tests/external-keyframes.test.ts', 'packages/extract/tests/files-json-decode.test.ts', 'packages/extract/tests/manifest-diagnostics.test.ts', 'packages/extract/tests/path-aliases.test.ts', @@ -39,6 +38,7 @@ const typescriptTestTargets = [ 'packages/extract/tests/svelte-source-origin.test.ts', 'packages/extract/tests/timing-waterfall.test.ts', 'packages/extract/tests/tsconfig-paths.test.ts', + 'packages/extract/tests/vocabulary-witness-diagnostics.test.ts', 'packages/extract/tests/watch-keys.test.ts', 'scripts/verify/owner-graph.test.ts', 'scripts/verify/ci-graph.test.ts', From 2b7d360ddb3c123e6fe25ce6e2a06ac3d587fa06 Mon Sep 17 00:00:00 2001 From: codecaaron Date: Fri, 21 Aug 2026 23:15:10 -0400 Subject: [PATCH 6/8] feat(system,extract): global styles join the registration lifecycle registerGlobalStyles() rides the same linear window as registerKeyframes on ONE shared vocabulary name-space (cross-kind collisions are compile errors and runtime-witnessed); the internal entry state is a tagged union with a single merge policy. The sealed record's globalStyles array carries { name, styles, fontFaces? } declaration-ordered; the loader maps it into the unchanged { exportName: { styles, fontFaces } } wire in record order and the export scan (extract_global_style_blocks, the loader's last HashMap-ordered collection path) is deleted. Every fixture registers its globalStyles block before seal(); pre/post global-layer rule sets are SET-IDENTICAL on vite-app and showcase; fresh-load byte determinism is test-pinned. DEF-8 audit: zero __brand discovery paths remain. Route green: system 23 vocab tests + shared-axis test-d proofs, loader 59, clippy/hygiene/unit:rust, canary, parity 66/66 (register []), integration, lint/compile/types/unit:ts (146f/1777), all eight owner lanes, packed. Increment 06 of openspec change system-vocabulary-registration (local). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DQuCHBc6HCuzoa77JyBwA4 --- e2e/next-app/src/ds.ts | 17 +- e2e/next16-app/src/ds.ts | 13 +- e2e/packed-app/src/ds.ts | 5 +- e2e/react-router-app/src/ds.ts | 12 +- e2e/rollup-app/src/ds.ts | 5 +- e2e/vinext-app/src/ds.ts | 8 +- e2e/vite-app/src/ds.ts | 5 +- .../extract/crates/system-loader/src/lib.rs | 161 ++++++----- packages/showcase/src/ds.ts | 15 +- packages/system/__tests__/types.test-d.tsx | 20 ++ packages/system/__tests__/vocabulary.test.ts | 82 ++++++ packages/system/src/SystemBuilder.ts | 249 +++++++++++++----- packages/system/src/index.ts | 1 + 13 files changed, 431 insertions(+), 162 deletions(-) diff --git a/e2e/next-app/src/ds.ts b/e2e/next-app/src/ds.ts index 20b32eee..fe337049 100644 --- a/e2e/next-app/src/ds.ts +++ b/e2e/next-app/src/ds.ts @@ -227,13 +227,6 @@ export const animations = createKeyframes({ }, }); -// Sealed system (vocabulary-registration): `animations` registers under its -// export name. The `includes:` alias above deliberately CANNOT carry the -// kit's registered `kitMotion` — this lane is the legacy-verb witness: the -// sealed record carries the coded `animus.vocabulary.legacy-verb` entry the -// hosts surface as a warning. -export const ds = bundle.registerKeyframes({ animations }).seal(); - // ─── Global Styles ────────────────────────────────────────── export const globalStyles = createGlobalStyles({ @@ -249,3 +242,13 @@ export const globalStyles = createGlobalStyles({ a: { color: 'primary', textDecoration: 'none' }, 'code, kbd': { fontFamily: 'ui-monospace, monospace', fontSize: 14 }, }); + +// Sealed system (vocabulary-registration): `animations` registers under its +// export name. The `includes:` alias above deliberately CANNOT carry the +// kit's registered `kitMotion` — this lane is the legacy-verb witness: the +// sealed record carries the coded `animus.vocabulary.legacy-verb` entry the +// hosts surface as a warning. +export const ds = bundle + .registerKeyframes({ animations }) + .registerGlobalStyles({ globalStyles }) + .seal(); diff --git a/e2e/next16-app/src/ds.ts b/e2e/next16-app/src/ds.ts index d57667c3..503d7ea8 100644 --- a/e2e/next16-app/src/ds.ts +++ b/e2e/next16-app/src/ds.ts @@ -189,11 +189,6 @@ export const animations = createKeyframes({ }, }); -// Sealed system (vocabulary-registration): `animations` registers under its -// export name; the kit's `kitMotion` arrives through the sealed test-ds -// record via `.extend()`. -export const ds = bundle.registerKeyframes({ animations }).seal(); - // ─── Global Styles ────────────────────────────────────────── export const globalStyles = createGlobalStyles({ @@ -209,3 +204,11 @@ export const globalStyles = createGlobalStyles({ a: { color: 'primary', textDecoration: 'none' }, 'code, kbd': { fontFamily: 'ui-monospace, monospace', fontSize: 14 }, }); + +// Sealed system (vocabulary-registration): `animations` registers under its +// export name; the kit's `kitMotion` arrives through the sealed test-ds +// record via `.extend()`. +export const ds = bundle + .registerKeyframes({ animations }) + .registerGlobalStyles({ globalStyles }) + .seal(); diff --git a/e2e/packed-app/src/ds.ts b/e2e/packed-app/src/ds.ts index ca55bf72..6b3f549f 100644 --- a/e2e/packed-app/src/ds.ts +++ b/e2e/packed-app/src/ds.ts @@ -99,4 +99,7 @@ export const animations = createKeyframes({ // Sealed system (vocabulary-registration): `animations` registers under // its export name; registration closes at seal(). -export const ds = bundle.registerKeyframes({ animations }).seal(); +export const ds = bundle + .registerKeyframes({ animations }) + .registerGlobalStyles({ globalStyles }) + .seal(); diff --git a/e2e/react-router-app/src/ds.ts b/e2e/react-router-app/src/ds.ts index 35b16a3a..9d6e9a77 100644 --- a/e2e/react-router-app/src/ds.ts +++ b/e2e/react-router-app/src/ds.ts @@ -89,12 +89,6 @@ const bundle = createSystem() export const { createGlobalStyles } = bundle; -// Sealed system (vocabulary-registration): the `from()` verb above cannot -// carry the kit's registered `kitMotion` — this lane is the from()-side -// legacy-verb witness (`animus.vocabulary.legacy-verb` on the sealed -// record, surfaced by the host as a warning). -export const ds = bundle.seal(); - export const globalStyles = createGlobalStyles({ '*, *::before, *::after': { boxSizing: 'border-box' }, body: { @@ -104,3 +98,9 @@ export const globalStyles = createGlobalStyles({ fontFamily: 'system-ui, sans-serif', }, }); + +// Sealed system (vocabulary-registration): the `from()` verb above cannot +// carry the kit's registered `kitMotion` — this lane is the from()-side +// legacy-verb witness (`animus.vocabulary.legacy-verb` on the sealed +// record, surfaced by the host as a warning). +export const ds = bundle.registerGlobalStyles({ globalStyles }).seal(); diff --git a/e2e/rollup-app/src/ds.ts b/e2e/rollup-app/src/ds.ts index 72218409..aa1df8ef 100644 --- a/e2e/rollup-app/src/ds.ts +++ b/e2e/rollup-app/src/ds.ts @@ -153,4 +153,7 @@ export const animations = createKeyframes({ // Sealed system (vocabulary-registration): `animations` registers under its // export name; the kit's `kitMotion` arrives through the sealed test-ds // record via `.extend()`. -export const ds = bundle.registerKeyframes({ animations }).seal(); +export const ds = bundle + .registerKeyframes({ animations }) + .registerGlobalStyles({ globalStyles }) + .seal(); diff --git a/e2e/vinext-app/src/ds.ts b/e2e/vinext-app/src/ds.ts index ea004dde..a3a2f684 100644 --- a/e2e/vinext-app/src/ds.ts +++ b/e2e/vinext-app/src/ds.ts @@ -68,10 +68,6 @@ const bundle = createSystem().extend(testDs).build(); export const { createGlobalStyles } = bundle; -// Sealed system (vocabulary-registration): no local collections; the kit's -// `kitMotion` arrives through the sealed test-ds record via `.extend()`. -export const ds = bundle.seal(); - export const globalStyles = createGlobalStyles({ '*, *::before, *::after': { boxSizing: 'border-box' }, body: { @@ -81,3 +77,7 @@ export const globalStyles = createGlobalStyles({ fontFamily: 'system-ui, sans-serif', }, }); + +// Sealed system (vocabulary-registration): no local collections; the kit's +// `kitMotion` arrives through the sealed test-ds record via `.extend()`. +export const ds = bundle.registerGlobalStyles({ globalStyles }).seal(); diff --git a/e2e/vite-app/src/ds.ts b/e2e/vite-app/src/ds.ts index ff74ecf3..48fb3357 100644 --- a/e2e/vite-app/src/ds.ts +++ b/e2e/vite-app/src/ds.ts @@ -157,4 +157,7 @@ export const animations = createKeyframes({ // Sealed system (vocabulary-registration): `animations` registers under its // export name; the kit's `kitMotion` arrives through the sealed test-ds // record via `.extend()` — no local step, and no export scan anywhere. -export const ds = bundle.registerKeyframes({ animations }).seal(); +export const ds = bundle + .registerKeyframes({ animations }) + .registerGlobalStyles({ globalStyles }) + .seal(); diff --git a/packages/extract/crates/system-loader/src/lib.rs b/packages/extract/crates/system-loader/src/lib.rs index 85cecaaa..41fe1b56 100644 --- a/packages/extract/crates/system-loader/src/lib.rs +++ b/packages/extract/crates/system-loader/src/lib.rs @@ -47,8 +47,8 @@ pub struct SystemConfig { /// value. `None` against a system built by an older @animus-ui/system. pub transform_sources: Option, pub global_style_blocks: Option, - /// Keyframes exports — collections produced by the top-level `keyframes()` - /// factory (objects with `__brand === 'Keyframes'`). JSON shape: + /// Keyframe collections from the sealed system's registration record + /// (vocabulary-registration; nothing is export-scanned). JSON shape: /// `{ exportName: { keyName: { name, frames } } }`. `name` is the runtime- /// generated stable hash (`animus-kf-`); `frames` is the percent-stop /// style map ready for theme resolution via the existing `@keyframes` @@ -1556,16 +1556,12 @@ fn extract_system_config<'js>( .get("contextualVarsJson") .map_err(|e| format!("contextualVarsJson not found: {}", e))?; - // Find GlobalStyleBlock exports (registration conformance for global - // styles is a later increment — export scan stays their channel here). - let global_style_blocks = extract_global_style_blocks(namespace); - - // Keyframe collections (vocabulary-registration): a sealed system's - // registration record is the ONLY source — an exported-but-unregistered - // collection does not carry, and a system WITHOUT the record accessor - // fails the load loud (unsealed, or built by an older - // @animus-ui/system) rather than loading with silently empty - // collections. + // Vocabulary (vocabulary-registration): a sealed system's registration + // record is the ONLY source for keyframe collections AND global-style + // blocks — an exported-but-unregistered value does not carry, and a + // system WITHOUT the record accessor fails the load loud (unsealed, or + // built by an older @animus-ui/system) rather than loading with + // silently empty vocabulary. if system_obj .get::<_, Function>("getVocabularyRecord") .is_err() @@ -1578,7 +1574,7 @@ fn extract_system_config<'js>( .to_string(), ); } - let (keyframes_blocks, vocabulary_witnesses) = + let (keyframes_blocks, global_style_blocks, vocabulary_witnesses) = extract_vocabulary_record(ctx, &system_obj)?; Ok(SystemConfig { @@ -1620,19 +1616,25 @@ fn find_exports_with_method<'js>( found } +/// The three wires the vocabulary record yields: keyframes blocks, +/// global-style blocks, and the coded witness array (each `None` when +/// empty). +type VocabularyWires = (Option, Option, Option); + /// Read the sealed system's vocabulary record (vocabulary-registration). -/// Returns `(keyframes_blocks, vocabulary_witnesses)`: the record's -/// declaration-ordered `keyframes` array becomes the unchanged -/// `{ exportName: { keyName: { name, frames } } }` wire (insertion order -/// preserved end to end — `Object.fromEntries` + `JSON.stringify` in the -/// evaluation context, `preserve_order` on the Rust side), and its -/// `collisions` + `legacyVerbs` entries carry verbatim as one coded -/// host-facing witness array. An incompatible version marker fails the -/// load loud. +/// Returns `(keyframes_blocks, global_style_blocks, vocabulary_witnesses)`: +/// the record's declaration-ordered `keyframes` array becomes the unchanged +/// `{ exportName: { keyName: { name, frames } } }` wire, the `globalStyles` +/// array the unchanged `{ exportName: { styles, fontFaces } }` wire +/// (insertion order preserved end to end — `Object.fromEntries` + +/// `JSON.stringify` in the evaluation context, `preserve_order` on the +/// Rust side), and its `collisions` + `legacyVerbs` entries carry verbatim +/// as one coded host-facing witness array. An incompatible version marker +/// fails the load loud. fn extract_vocabulary_record<'js>( ctx: &rquickjs::Ctx<'js>, system_obj: &Object<'js>, -) -> Result<(Option, Option), String> { +) -> Result { let script = r#"(() => { const record = globalThis.__sys_ref.getVocabularyRecord(); if (!record || typeof record !== 'object') { @@ -1642,11 +1644,14 @@ fn extract_vocabulary_record<'js>( return JSON.stringify({ skew: String(record.version) }); } const keyframes = Array.isArray(record.keyframes) ? record.keyframes : []; + const globalStyles = Array.isArray(record.globalStyles) ? record.globalStyles : []; const collisions = Array.isArray(record.collisions) ? record.collisions : []; const legacyVerbs = Array.isArray(record.legacyVerbs) ? record.legacyVerbs : []; return JSON.stringify({ keyframeCount: keyframes.length, keyframes: Object.fromEntries(keyframes.map((entry) => [entry.name, entry.frames])), + globalStyleCount: globalStyles.length, + globalStyles: Object.fromEntries(globalStyles.map((entry) => [entry.name, { styles: entry.styles, fontFaces: entry.fontFaces || [] }])), witnesses: collisions.concat(legacyVerbs), }); })()"#; @@ -1682,13 +1687,24 @@ fn extract_vocabulary_record<'js>( .get("keyframes") .map(|v| serde_json::to_string(v).unwrap_or_default()) }; + let global_style_count = parsed + .get("globalStyleCount") + .and_then(|v| v.as_u64()) + .unwrap_or(0); + let global_style_blocks = if global_style_count == 0 { + None + } else { + parsed + .get("globalStyles") + .map(|v| serde_json::to_string(v).unwrap_or_default()) + }; let vocabulary_witnesses = match parsed.get("witnesses").and_then(|v| v.as_array()) { Some(list) if !list.is_empty() => { Some(serde_json::to_string(list).unwrap_or_default()) } _ => None, }; - Ok((keyframes_blocks, vocabulary_witnesses)) + Ok((keyframes_blocks, global_style_blocks, vocabulary_witnesses)) } /// List all export keys from a module namespace. @@ -1700,49 +1716,6 @@ fn list_export_keys(namespace: &Object<'_>) -> Vec { keys } -/// Extract GlobalStyleBlock exports (objects with __brand === 'GlobalStyleBlock'). -/// Uses JSON.stringify inside the rquickjs context to serialize the styles object. -fn extract_global_style_blocks(namespace: &Object<'_>) -> Option { - let keys = list_export_keys(namespace); - let mut blocks: HashMap = HashMap::new(); - let ctx = namespace.ctx().clone(); - - for key in &keys { - if let Ok(obj) = namespace.get::<_, Object>(key.as_str()) { - if let Ok(brand) = obj.get::<_, String>("__brand") { - if brand == "GlobalStyleBlock" { - // Wrapped form: selector map plus the block's typed - // font-face descriptors (global-styles-system). The - // extractor renders fontFaces ahead of selector rules. - let script = format!( - "JSON.stringify({{styles: globalThis.__ns_ref[\"{key}\"].styles, fontFaces: globalThis.__ns_ref[\"{key}\"].fontFaces || []}})" - ); - // Temporarily assign namespace to globalThis for access - let _ = ctx.globals().set("__ns_ref", namespace.clone()); - if let Ok(json_str) = ctx.eval::(script.as_bytes()) { - let _ = ctx.globals().remove("__ns_ref"); - if let Ok(parsed) = serde_json::from_str(&json_str) { - blocks.insert(key.clone(), parsed); - } - } else { - let _ = ctx.globals().remove("__ns_ref"); - } - } - } - } - } - - if blocks.is_empty() { - None - } else { - Some(serde_json::to_string(&blocks).unwrap_or_default()) - } -} - -// --------------------------------------------------------------------------- -// 6. Public entry point -// --------------------------------------------------------------------------- - /// Load a system module and return its serialized configuration. /// /// Pipeline: read → OXC strip types → resolve deps → bundle → rquickjs eval → extract config. @@ -2445,6 +2418,52 @@ export const ds = tokens; result.expect("the load must succeed without touching the root barrel"); } + #[test] + fn registered_global_styles_carry_in_record_order() { + // global-styles-system §"Global style registration and cascade + // order": record order reaches the wire; an exported-but- + // unregistered branded block does NOT carry. + let dir = scratch_dir("vocab-globals-order"); + let entry = dir.join("entry.ts"); + let mut source = sealed_system_fixture( + "{ version: 1, keyframes: [], globalStyles: [\n\ + { name: 'reset', styles: { body: { margin: 0 } } },\n\ + { name: 'typo', styles: { h1: { fontWeight: 700 } } },\n\ + ], collisions: [], legacyVerbs: [] }", + ); + source.push_str( + "export const rogue = { __brand: 'GlobalStyleBlock', styles: { p: { margin: 0 } } };\n", + ); + write_fixture(&entry, &source); + + let first = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None) + .expect("sealed system must load"); + let second = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None) + .expect("second load"); + let _ = fs::remove_dir_all(&dir); + + let blocks = first + .global_style_blocks + .clone() + .expect("registered blocks must carry"); + let reset_at = blocks.find("\"reset\"").expect("reset present"); + let typo_at = blocks.find("\"typo\"").expect("typo present"); + assert!(reset_at < typo_at, "record order must reach the wire: {blocks}"); + assert!( + !blocks.contains("rogue"), + "unregistered branded export must not carry: {blocks}" + ); + // Wire shape parity with the retired export scan: wrapped + // {{ styles, fontFaces }} per name. + let parsed: serde_json::Value = serde_json::from_str(&blocks).expect("valid JSON"); + assert!(parsed["reset"]["styles"]["body"].is_object()); + assert!(parsed["reset"]["fontFaces"].is_array()); + assert_eq!( + first.global_style_blocks, second.global_style_blocks, + "fresh-process loads must serialize identical block bytes" + ); + } + #[test] fn recordless_system_fails_the_load() { // rust-system-loader §"Registration-record version skew fails the @@ -2715,10 +2734,10 @@ export const ds = tokens; #[test] fn asset_placeholder_survives_the_loader_round_trip() { // standardize-inheritance-and-assets (rust-system-loader delta): an - // `asset()` placeholder inside a global style block's fontFaces - // serializes through evaluation with its specifier bytes intact and - // WITHOUT any resolution attempt — the scratch dir contains no such - // file, and the load must not care. + // `asset()` placeholder inside a REGISTERED global style block's + // fontFaces serializes through the record with its specifier bytes + // intact and WITHOUT any resolution attempt — the scratch dir + // contains no such file, and the load must not care. let dir = scratch_dir("asset-placeholder"); let entry = dir.join("entry.ts"); write_fixture( @@ -2740,7 +2759,7 @@ export const ds = tokens; contextualVarsJson: '{}',\n\ }),\n\ };\n\ - export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [], collisions: [], legacyVerbs: [] }) };\n", + export const system = { toConfig: () => ({ propConfig: '{}', groupRegistry: '{}' }), getVocabularyRecord: () => ({ version: 1, keyframes: [], globalStyles: [{ name: 'globals', styles: globals.styles, fontFaces: globals.fontFaces }], collisions: [], legacyVerbs: [] }) };\n", ); let result = load_system_module(&entry.to_string_lossy(), &dir.to_string_lossy(), None); diff --git a/packages/showcase/src/ds.ts b/packages/showcase/src/ds.ts index b2d4aef6..47a0f038 100644 --- a/packages/showcase/src/ds.ts +++ b/packages/showcase/src/ds.ts @@ -753,12 +753,6 @@ export const animations = createKeyframes({ }, }); -// Sealed system (vocabulary-registration): `animations` registers under its -// export name. The `includes:` holdout above cannot carry the kit's -// registered `kitMotion` — the sealed record carries the coded -// `animus.vocabulary.legacy-verb` witness the host surfaces as a warning. -export const ds = bundle.registerKeyframes({ animations }).seal(); - // ─── Global Styles ────────────────────────────────────────── export const globalStyles = createGlobalStyles({ @@ -822,3 +816,12 @@ export const globalStyles = createGlobalStyles({ backgroundSize: '150px 150px', }, }); + +// Sealed system (vocabulary-registration): `animations` registers under its +// export name. The `includes:` holdout above cannot carry the kit's +// registered `kitMotion` — the sealed record carries the coded +// `animus.vocabulary.legacy-verb` witness the host surfaces as a warning. +export const ds = bundle + .registerKeyframes({ animations }) + .registerGlobalStyles({ globalStyles }) + .seal(); diff --git a/packages/system/__tests__/types.test-d.tsx b/packages/system/__tests__/types.test-d.tsx index 91dd2835..0ddc449a 100644 --- a/packages/system/__tests__/types.test-d.tsx +++ b/packages/system/__tests__/types.test-d.tsx @@ -2161,6 +2161,26 @@ void (); const widened: Record = { anything: kitMotion }; // @ts-expect-error — index-signature maps cannot register vocabulary void createSystem().build().registerKeyframes(widened); + + // Global styles ride the SAME axis (inc 06 — one shared name-space): + // registering a block under an already-registered keyframes name is a + // compile error, and fresh block names union into VocabularyOf. + const gsBundle = createSystem().build(); + const gsMotion = gsBundle.createKeyframes({ + spin: { '0%': { opacity: 0 } }, + }); + const gsReset = gsBundle.createGlobalStyles({ body: { margin: 0 } }); + void gsBundle + .registerKeyframes({ motion: gsMotion }) + // @ts-expect-error — "motion" is already registered vocabulary + .registerGlobalStyles({ motion: gsReset }); + const gsSealed = gsBundle + .registerKeyframes({ motion: gsMotion }) + .registerGlobalStyles({ gsReset }) + .seal(); + type _MixedVocab = Assert< + IsExact, 'motion' | 'gsReset'> + >; } void TypeTests; diff --git a/packages/system/__tests__/vocabulary.test.ts b/packages/system/__tests__/vocabulary.test.ts index 761b3c60..4996f51d 100644 --- a/packages/system/__tests__/vocabulary.test.ts +++ b/packages/system/__tests__/vocabulary.test.ts @@ -4,6 +4,7 @@ import { createSystem } from '../src'; import type { KeyframesFrameData, + RegisterableGlobalStyles, RegisterableKeyframes, VocabularyRecord, } from '../src'; @@ -22,6 +23,9 @@ type ErasedRegistrable = RegisterableKeyframes | { frames: object }; * assertion anywhere. */ interface ErasedBundle { registerKeyframes(map: Record): ErasedBundle; + registerGlobalStyles( + map: Record + ): ErasedBundle; seal(): { getVocabularyRecord?(): VocabularyRecord }; } @@ -347,6 +351,84 @@ describe('vocabulary registration — two-phase terminal (runtime)', () => { expect(warn).not.toHaveBeenCalled(); }); + it('registerGlobalStyles rides the same window: record order, fontFaces payload, shared name-space', () => { + const bundle = createSystem().build(); + const motion = bundle.createKeyframes({ pulse: FRAMES_A }); + const reset = bundle.createGlobalStyles( + { body: { margin: 0 } }, + { + fontFaces: [ + { family: 'TestFont', src: [{ url: 'font.woff2', format: 'woff2' }] }, + ], + } + ); + const typographyBlock = bundle.createGlobalStyles({ + h1: { fontWeight: 700 }, + }); + + const sealed = bundle + .registerKeyframes({ motion }) + .registerGlobalStyles({ reset }) + .registerGlobalStyles({ typographyBlock }) + .seal(); + + const record = recordOf(sealed); + expect(record.keyframes.map((entry) => entry.name)).toEqual(['motion']); + expect(record.globalStyles.map((entry) => entry.name)).toEqual([ + 'reset', + 'typographyBlock', + ]); + expect(record.globalStyles[0]?.styles).toEqual({ body: { margin: 0 } }); + expect(record.globalStyles[0]?.fontFaces?.[0]?.family).toBe('TestFont'); + expect(record.globalStyles[1]?.fontFaces).toBeUndefined(); + expect(Object.isFrozen(record.globalStyles[0])).toBe(true); + expect(Object.isFrozen(record.globalStyles[0]?.styles)).toBe(true); + }); + + it('a global-style block colliding with a keyframes name is witnessed cross-kind — one name-space', () => { + vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const bundle = createSystem().build(); + const motion = bundle.createKeyframes({ pulse: FRAMES_A }); + const block = bundle.createGlobalStyles({ body: { margin: 0 } }); + + const sealed = erased(bundle.registerKeyframes({ motion })) + .registerGlobalStyles({ motion: block }) + .seal(); + + const record = recordOf(sealed); + expect(record.keyframes).toEqual([]); + expect(record.globalStyles.map((entry) => entry.name)).toEqual(['motion']); + expect(record.collisions).toHaveLength(1); + expect(record.collisions[0]).toMatchObject({ name: 'motion' }); + }); + + it('a sealed kit global-style block carries through .extend() ahead of local blocks', () => { + const kitBundle = createSystem().build(); + const kitReset = kitBundle.createGlobalStyles({ body: { margin: 0 } }); + const kit = kitBundle.registerGlobalStyles({ kitReset }).seal(); + + const consumerBundle = createSystem().extend(kit).build(); + const appStyles = consumerBundle.createGlobalStyles({ + main: { padding: 0 }, + }); + const sealed = consumerBundle.registerGlobalStyles({ appStyles }).seal(); + + expect(recordOf(sealed).globalStyles.map((entry) => entry.name)).toEqual([ + 'kitReset', + 'appStyles', + ]); + }); + + it('registration after seal throws for global styles too', () => { + const bundle = createSystem().build(); + const block = bundle.createGlobalStyles({ body: { margin: 0 } }); + bundle.seal(); + expect(() => erased(bundle).registerGlobalStyles({ block })).toThrow( + /sealed/ + ); + }); + it('a vocabulary-free sealed source through legacy verbs stays silent', () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); const plainKit = createSystem().build().seal(); diff --git a/packages/system/src/SystemBuilder.ts b/packages/system/src/SystemBuilder.ts index 198dca26..c878a5d0 100644 --- a/packages/system/src/SystemBuilder.ts +++ b/packages/system/src/SystemBuilder.ts @@ -139,6 +139,16 @@ export interface RegisterableKeyframes { readonly __frames: object; } +/** + * The structural shape `registerGlobalStyles` accepts: any + * `createGlobalStyles` return value qualifies (the brand stays structural, + * mirroring `RegisterableKeyframes`). + */ +export interface RegisterableGlobalStyles { + readonly __brand: 'GlobalStyleBlock'; + readonly styles: object; +} + /** The per-key frame data a collection carries (`Keyframes['__frames']`). */ export type KeyframesFrameData = Record< string, @@ -152,7 +162,8 @@ export interface VocabularyKeyframesEntry { export interface VocabularyGlobalStyleEntry { readonly name: string; - readonly block: GlobalStyleBlock; + readonly styles: GlobalStyleMap; + readonly fontFaces?: readonly FontFace[]; } export interface VocabularyCollisionEntry { @@ -197,12 +208,33 @@ export interface VocabularyRecord { readonly legacyVerbs: readonly VocabularyLegacyVerbEntry[]; } -/** Internal pending/merged vocabulary state (origin powers witness text). */ -interface VocabularyKeyframesState { - name: string; - frames: KeyframesFrameData; - origin: string; -} +/** Internal pending/merged vocabulary state (origin powers witness text). + * ONE name-space across both kinds: a global-style block and a keyframes + * collection cannot share a registered name. */ +type VocabularyEntryState = + | { + kind: 'keyframes'; + name: string; + frames: KeyframesFrameData; + origin: string; + } + | { + kind: 'globalStyles'; + name: string; + styles: GlobalStyleMap; + fontFaces?: readonly FontFace[]; + origin: string; + }; + +/** A merge input — an entry state minus its origin (assigned by the merge). */ +type VocabularyEntryInput = + | { kind: 'keyframes'; name: string; frames: KeyframesFrameData } + | { + kind: 'globalStyles'; + name: string; + styles: GlobalStyleMap; + fontFaces?: readonly FontFace[]; + }; /** * Legacy-verb witness helper shared by `from()` and the `includes:` config @@ -265,36 +297,39 @@ function snapshotFrameData(frames: KeyframesFrameData): KeyframesFrameData { * order of the surviving registrations (inherited region first, then * locals; a later extension's win sits at that extension's position). */ -function mergeVocabularyKeyframes( - existingEntries: readonly VocabularyKeyframesState[], +function mergeVocabularyEntries( + existingEntries: readonly VocabularyEntryState[], existingCollisions: readonly VocabularyCollisionEntry[], - incoming: ReadonlyArray<{ name: string; frames: KeyframesFrameData }>, + incoming: ReadonlyArray, incomingOrigin: string ): { - entries: VocabularyKeyframesState[]; + entries: VocabularyEntryState[]; collisions: VocabularyCollisionEntry[]; } { const entries = existingEntries.map((entry) => ({ ...entry })); const collisions = [...existingCollisions]; - for (const { name, frames } of incoming) { - const existingIndex = entries.findIndex((entry) => entry.name === name); + for (const input of incoming) { + // ONE name-space: the collision check spans both kinds. + const existingIndex = entries.findIndex( + (entry) => entry.name === input.name + ); if (existingIndex !== -1) { const loser = entries[existingIndex]; collisions.push({ code: 'animus.vocabulary.collision', - name, + name: input.name, winner: incomingOrigin, loser: loser.origin, }); // oxlint-disable-next-line no-console -- intentional runtime diagnostic console.warn( - `animus: keyframes vocabulary "${name}" is registered by both ` + + `animus: vocabulary "${input.name}" is registered by both ` + `${loser.origin} and ${incomingOrigin} — ${incomingOrigin} wins; ` + - 'rename one collection (animus.vocabulary.collision)' + 'rename one entry (animus.vocabulary.collision)' ); entries.splice(existingIndex, 1); } - entries.push({ name, frames, origin: incomingOrigin }); + entries.push({ ...input, origin: incomingOrigin }); } return { entries, collisions }; } @@ -319,7 +354,7 @@ declare const VOCABULARY_INDEX_SIGNATURE: unique symbol; * the accumulated axis to `string`. Registration maps require literal keys. */ export interface VocabularyIndexSignatureRejected { - readonly [VOCABULARY_INDEX_SIGNATURE]: 'registerKeyframes requires literal keys — an index-signature map cannot prove its vocabulary names'; + readonly [VOCABULARY_INDEX_SIGNATURE]: 'vocabulary registration requires literal keys — an index-signature map cannot prove its names'; } type LiteralKeyMap = string extends keyof M @@ -390,6 +425,19 @@ export interface SystemBundle< [K in Extract]: VocabularyNameCollision; } ): SystemBundle; + /** + * Register global-style blocks between the terminals — the SAME linear + * lifecycle, record carriage, and ONE shared vocabulary name-space as + * `registerKeyframes` (a block cannot share a registered name with a + * keyframes collection). Keys equal export names; blocks stay + * module-scope named exports. + */ + registerGlobalStyles>( + map: M & + LiteralKeyMap & { + [K in Extract]: VocabularyNameCollision; + } + ): SystemBundle; seal(): SealedSystemInstance; } @@ -625,7 +673,7 @@ export class SystemBuilder< // (vocabulary-registration: inherited entries precede local registrations // in the eventual record). Collisions recorded here are extend-time // (kit-vs-kit); registration-time collisions accumulate in the bundle. - #vocabularyRegistry: readonly VocabularyKeyframesState[]; + #vocabularyRegistry: readonly VocabularyEntryState[]; #vocabularyCollisions: readonly VocabularyCollisionEntry[]; #legacyVerbWitnesses: readonly VocabularyLegacyVerbEntry[]; @@ -637,7 +685,7 @@ export class SystemBuilder< conditionRegistry?: ConditionAliasMap, extendProvenance?: ReadonlyMap, extendCount?: number, - vocabularyRegistry?: readonly VocabularyKeyframesState[], + vocabularyRegistry?: readonly VocabularyEntryState[], vocabularyCollisions?: readonly VocabularyCollisionEntry[], legacyVerbWitnesses?: readonly VocabularyLegacyVerbEntry[] ) { @@ -987,11 +1035,24 @@ export class SystemBuilder< } let nextVocabulary = this.#vocabularyRegistry; let nextVocabularyCollisions = this.#vocabularyCollisions; - if (sourceRecord.keyframes.length > 0) { - const merged = mergeVocabularyKeyframes( + const inheritedEntries: VocabularyEntryInput[] = [ + ...sourceRecord.keyframes.map((entry) => ({ + kind: 'keyframes' as const, + name: entry.name, + frames: entry.frames, + })), + ...sourceRecord.globalStyles.map((entry) => ({ + kind: 'globalStyles' as const, + name: entry.name, + styles: entry.styles, + ...(entry.fontFaces ? { fontFaces: entry.fontFaces } : {}), + })), + ]; + if (inheritedEntries.length > 0) { + const merged = mergeVocabularyEntries( this.#vocabularyRegistry, this.#vocabularyCollisions, - sourceRecord.keyframes, + inheritedEntries, incomingOrigin ); nextVocabulary = merged.entries; @@ -1384,30 +1445,48 @@ export class SystemBuilder< // extended sources) seed the record in extension order; local // registrations append after them, labeled by 1-based call index. const makeBundle = ( - entries: readonly VocabularyKeyframesState[], + entries: readonly VocabularyEntryState[], collisions: readonly VocabularyCollisionEntry[], localCallCount: number ): SystemBundle => { let consumedBy: 'register' | 'seal' | undefined; - const registerKeyframes = ( - map: Record + // One linear-window guard + merge for both registration kinds. + const registerEntries = ( + label: string, + incoming: VocabularyEntryInput[] ): SystemBundle => { if (consumedBy === 'seal') { throw new Error( - 'registerKeyframes: this system is already sealed — ' + - 'registration happens between build() and seal().' + `${label}: this system is already sealed — registration ` + + 'happens between build() and seal().' ); } if (consumedBy === 'register') { throw new Error( - 'registerKeyframes: this bundle was superseded by a later ' + - 'registration call — registration is linear; chain the calls ' + - 'and seal the final bundle.' + `${label}: this bundle was superseded by a later registration ` + + 'call — registration is linear; chain the calls and seal the ' + + 'final bundle.' ); } - const incoming: Array<{ name: string; frames: KeyframesFrameData }> = - []; + const merged = mergeVocabularyEntries( + entries, + collisions, + incoming, + `local registration #${localCallCount + 1}` + ); + consumedBy = 'register'; + return makeBundle( + merged.entries, + merged.collisions, + localCallCount + 1 + ); + }; + + const registerKeyframes = ( + map: Record + ): SystemBundle => { + const incoming: VocabularyEntryInput[] = []; for (const [name, collection] of Object.entries(map)) { if ( !collection || @@ -1420,24 +1499,59 @@ export class SystemBuilder< ); } incoming.push({ + kind: 'keyframes', name, frames: snapshotFrameData( (collection as { __frames: KeyframesFrameData }).__frames ), }); } - const merged = mergeVocabularyKeyframes( - entries, - collisions, - incoming, - `local registration #${localCallCount + 1}` - ); - consumedBy = 'register'; - return makeBundle( - merged.entries, - merged.collisions, - localCallCount + 1 - ); + return registerEntries('registerKeyframes', incoming); + }; + + const registerGlobalStyles = ( + map: Record + ): SystemBundle => { + const incoming: VocabularyEntryInput[] = []; + for (const [name, block] of Object.entries(map)) { + if ( + !block || + (block as { __brand?: unknown }).__brand !== 'GlobalStyleBlock' || + typeof (block as { styles?: unknown }).styles !== 'object' + ) { + throw new TypeError( + `registerGlobalStyles: "${name}" is not a createGlobalStyles ` + + 'block — register the factory return value itself.' + ); + } + const blockValue = block as unknown as GlobalStyleBlock; + // Registration-time snapshot mirroring snapshotFrameData: the top + // two levels are copied and frozen (blind spot: deeper selector + // bodies stay aliased). + const styles = Object.freeze( + Object.fromEntries( + Object.entries(blockValue.styles).map(([selector, body]) => [ + selector, + Object.freeze({ ...body }), + ]) + ) + ) as GlobalStyleMap; + incoming.push({ + kind: 'globalStyles', + name, + styles, + ...(blockValue.fontFaces?.length + ? { + fontFaces: Object.freeze( + blockValue.fontFaces.map((face) => + Object.freeze({ ...face }) + ) + ) as readonly FontFace[], + } + : {}), + }); + } + return registerEntries('registerGlobalStyles', incoming); }; const seal = (): SealedSystemInstance< @@ -1462,13 +1576,25 @@ export class SystemBuilder< const record: VocabularyRecord = Object.freeze({ version: 1 as const, keyframes: Object.freeze( - entries.map((entry) => - // frames were deep-copied and frozen at registration (or - // arrived frozen from a sealed source's record). - Object.freeze({ name: entry.name, frames: entry.frames }) - ) + entries + .filter((entry) => entry.kind === 'keyframes') + .map((entry) => + // frames were deep-copied and frozen at registration (or + // arrived frozen from a sealed source's record). + Object.freeze({ name: entry.name, frames: entry.frames }) + ) + ), + globalStyles: Object.freeze( + entries + .filter((entry) => entry.kind === 'globalStyles') + .map((entry) => + Object.freeze({ + name: entry.name, + styles: entry.styles, + ...(entry.fontFaces ? { fontFaces: entry.fontFaces } : {}), + }) + ) ), - globalStyles: Object.freeze([]), collisions: Object.freeze( collisions.map((entry) => Object.freeze({ ...entry })) ), @@ -1511,15 +1637,17 @@ export class SystemBuilder< // stays a compile error): registration attempted on the sealed // instance itself names the sealed state instead of a bare // "not a function". - Object.defineProperty(sealed, 'registerKeyframes', { - value: (): never => { - throw new Error( - 'registerKeyframes: this system is sealed — registration ' + - 'happens between build() and seal().' - ); - }, - enumerable: false, - }); + for (const member of ['registerKeyframes', 'registerGlobalStyles']) { + Object.defineProperty(sealed, member, { + value: (): never => { + throw new Error( + `${member}: this system is sealed — registration happens ` + + 'between build() and seal().' + ); + }, + enumerable: false, + }); + } consumedBy = 'seal'; return sealed; }; @@ -1529,6 +1657,7 @@ export class SystemBuilder< createGlobalStyles, createKeyframes, registerKeyframes, + registerGlobalStyles, seal, } as SystemBundle; }; diff --git a/packages/system/src/index.ts b/packages/system/src/index.ts index 707e0d18..cf6a785d 100644 --- a/packages/system/src/index.ts +++ b/packages/system/src/index.ts @@ -28,6 +28,7 @@ export type { GlobalStylesFactory, KeyframesFrameData, LibraryBundle, + RegisterableGlobalStyles, RegisterableKeyframes, RegistrySnapshot, SealedSystemInstance, From c9e75050a11b48b22e0700e5f1373ee3dd205a09 Mon Sep 17 00:00:00 2001 From: codecaaron Date: Sat, 22 Aug 2026 01:25:35 -0400 Subject: [PATCH 7/8] docs!: remove written guides until the registration API settles Every teaching surface had drifted into shapes the current pipeline rejects (round-2 review: Quick Start and global-styles pages still taught export discovery; the root README destructured an unsealed system). Rather than repair them a third time, the written docs are deleted until the API freezes: - packages/showcase/src/content/** (29 MDX pages) deleted; nav trimmed to Overview (an honest offline placeholder) + Examples, which stay because they are extracted and asserted every verify run - root README and @animus-ui/system README stripped to install + a documentation-withheld note pointing at the type definitions and the verify-pinned in-repo consumers as the sources of truth - MDX toolchain and MDXProvider stay wired so the extraction surface is unchanged and restoration is additive verify:lint, verify:compile, @animus-ui/showcase#verify all green. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DQuCHBc6HCuzoa77JyBwA4 --- README.md | 152 +-- packages/showcase/src/App.tsx | 122 +-- packages/showcase/src/constants/docsNav.ts | 86 +- .../src/content/advanced/extraction.mdx | 291 ------ .../content/advanced/framework-agnostic.mdx | 321 ------- .../showcase/src/content/advanced/svelte.mdx | 242 ----- .../src/content/advanced/typescript.mdx | 614 ------------- .../src/content/architecture/color-modes.mdx | 342 ------- .../content/architecture/global-styles.mdx | 281 ------ .../architecture/library-authoring.mdx | 131 --- .../src/content/architecture/system-setup.mdx | 514 ----------- .../content/architecture/theme-extension.mdx | 342 ------- .../src/content/architecture/theming.mdx | 486 ---------- .../src/content/authoring/base-styling.mdx | 413 --------- .../src/content/authoring/composition.mdx | 412 --------- .../src/content/authoring/conditions.mdx | 144 --- .../src/content/authoring/custom-props.mdx | 612 ------------- .../src/content/authoring/recipes.mdx | 828 ----------------- .../src/content/authoring/selectors.mdx | 375 -------- .../src/content/authoring/system-props.mdx | 864 ------------------ .../src/content/authoring/variants-states.mdx | 381 -------- .../src/content/compiler/nextjs-remix.mdx | 238 ----- .../src/content/compiler/vite-plugin.mdx | 285 ------ .../showcase/src/content/introduction.mdx | 178 ---- .../src/content/reference/builder-chain.mdx | 573 ------------ .../src/content/reference/compose.mdx | 257 ------ .../src/content/reference/create-system.mdx | 492 ---------- .../src/content/reference/create-theme.mdx | 630 ------------- packages/showcase/src/content/start.mdx | 227 ----- .../src/content/support/component-test.mdx | 698 -------------- .../src/content/support/migration.mdx | 261 ------ .../src/content/support/troubleshooting.mdx | 232 ----- packages/showcase/src/pages/Home.tsx | 4 +- packages/system/README.md | 296 +----- 34 files changed, 67 insertions(+), 12257 deletions(-) delete mode 100644 packages/showcase/src/content/advanced/extraction.mdx delete mode 100644 packages/showcase/src/content/advanced/framework-agnostic.mdx delete mode 100644 packages/showcase/src/content/advanced/svelte.mdx delete mode 100644 packages/showcase/src/content/advanced/typescript.mdx delete mode 100644 packages/showcase/src/content/architecture/color-modes.mdx delete mode 100644 packages/showcase/src/content/architecture/global-styles.mdx delete mode 100644 packages/showcase/src/content/architecture/library-authoring.mdx delete mode 100644 packages/showcase/src/content/architecture/system-setup.mdx delete mode 100644 packages/showcase/src/content/architecture/theme-extension.mdx delete mode 100644 packages/showcase/src/content/architecture/theming.mdx delete mode 100644 packages/showcase/src/content/authoring/base-styling.mdx delete mode 100644 packages/showcase/src/content/authoring/composition.mdx delete mode 100644 packages/showcase/src/content/authoring/conditions.mdx delete mode 100644 packages/showcase/src/content/authoring/custom-props.mdx delete mode 100644 packages/showcase/src/content/authoring/recipes.mdx delete mode 100644 packages/showcase/src/content/authoring/selectors.mdx delete mode 100644 packages/showcase/src/content/authoring/system-props.mdx delete mode 100644 packages/showcase/src/content/authoring/variants-states.mdx delete mode 100644 packages/showcase/src/content/compiler/nextjs-remix.mdx delete mode 100644 packages/showcase/src/content/compiler/vite-plugin.mdx delete mode 100644 packages/showcase/src/content/introduction.mdx delete mode 100644 packages/showcase/src/content/reference/builder-chain.mdx delete mode 100644 packages/showcase/src/content/reference/compose.mdx delete mode 100644 packages/showcase/src/content/reference/create-system.mdx delete mode 100644 packages/showcase/src/content/reference/create-theme.mdx delete mode 100644 packages/showcase/src/content/start.mdx delete mode 100644 packages/showcase/src/content/support/component-test.mdx delete mode 100644 packages/showcase/src/content/support/migration.mdx delete mode 100644 packages/showcase/src/content/support/troubleshooting.mdx diff --git a/README.md b/README.md index 927e8dd9..c7a55386 100644 --- a/README.md +++ b/README.md @@ -8,35 +8,6 @@ A design system builder where the TypeScript types ARE the product. Define compo No Emotion. No styled-components. No runtime style injection. The builder chain compiles to static CSS via `@layer`, extracted by a Rust pipeline. -```tsx -import { ds } from './ds'; - -const Card = ds - .styles({ - padding: '{space.md}', - borderRadius: 8, - backgroundColor: '{colors.surface}', - }) - .variant({ - elevation: { - prop: 'elevation', - variants: { - flat: { boxShadow: 'none' }, - raised: { boxShadow: '{shadows.sm}' }, - floating: { boxShadow: '{shadows.lg}' }, - }, - }, - }) - .states({ - disabled: { opacity: 0.5, pointerEvents: 'none' }, - }) - .system({ surface: true, space: true }) - .asElement('div'); - -// Fully typed — elevation, disabled, + all surface and space props -; -``` - ## Install ```bash @@ -58,112 +29,19 @@ Not on Vite or Next? The transform host (`@animus-ui/unplugin`) and the resolution, the artifact set, exit codes, and a copy-pasteable rollup quickstart. -## Setup - -Two files define your design system: - -**`theme.ts`** — define your theme: - -```tsx -import { createTheme } from '@animus-ui/system'; - -export const theme = createTheme() - .addBreakpoints({ sm: 480, md: 768, lg: 1024 }) - .addColors({ - gray: { 50: '#fafafa', 500: '#555', 900: '#080808' }, - blue: { 400: '#3d94ff', 700: '#003d99' }, - }) - .addColorModes('dark', { - dark: { - primary: 'blue.400', - bg: 'gray.900', - text: 'gray.50', - }, - light: { - primary: 'blue.700', - bg: 'gray.50', - text: 'gray.900', - }, - }) - .addScale({ - name: 'space', - values: { sm: '0.5rem', md: '1rem', lg: '1.5rem' }, - }) - .build(); - -// Type augmentation — token names autocomplete everywhere -type AppTheme = typeof theme; - -declare module '@animus-ui/system' { - interface Theme extends AppTheme {} -} -``` - -**`ds.ts`** — configure your system: - -```tsx -import { createSystem } from '@animus-ui/system'; -import { - space, - color, - typography, - layout, - flex, - border, - shadows, - background, -} from '@animus-ui/system/groups'; - -// Pre-built groups compose into your own semantic groups -export const { system: ds, createGlobalStyles } = createSystem() - .addGroup('surface', { ...color, ...border, ...shadows, ...background }) - .addGroup('space', space) - .addGroup('text', typography) - .addGroup('arrange', { ...flex, ...layout }) - .build(); -``` - -Consuming a published design-system kit? `.extend()` (available on both -builders, first in the chain) merges the kit's registries and tokens into -yours — its props type-check, extract, and resolve through your single merged -config, and your local definitions win on conflict: - -```tsx -import { system as kitSystem, theme as kitTheme } from '@acme/kit'; +## Documentation -export const theme = createTheme().extend(kitTheme).build(); -export const { system: ds } = createSystem().extend(kitSystem).build(); -``` +Deliberately withheld. The system-definition API is still settling +(vocabulary registration: `build()` → register → `seal()`), and written +guides repeatedly drifted into teaching shapes the current pipeline +rejects. Rather than keep wrong docs, they are removed until the API +freezes. Until then, the sources of truth are: -**`vite.config.ts`**: - -```tsx -import react from '@vitejs/plugin-react'; -import { animusExtract } from '@animus-ui/vite-plugin'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - plugins: [react(), animusExtract({ system: './src/ds.ts' })], -}); -``` - -Using Svelte 5? See the [Svelte guide](packages/showcase/src/content/advanced/svelte.mdx) -for the native `.attrs()` and element-spread authoring pattern, and -[e2e/svelte-app](e2e/svelte-app/src/App.svelte) for the working consumer. - -## The Builder Chain - -Each method maps to a CSS `@layer`. The type system enforces the ordering. - -``` -ds.styles() → @layer base always-on styles - .variant() → @layer variants prop-driven variations - .compound() → @layer compounds variant combinations - .states() → @layer states boolean interaction states - .system() → @layer system opt into prop groups (space, color, etc.) - .props() → @layer custom component-scoped dynamic props - .asElement() → seal as typed React component -``` +- the packages' TypeScript definitions — the types are the contract; +- the in-repo consumers, which are compiled, extracted, and asserted on + every verify run and therefore cannot silently drift: + `packages/test-ds/src/system.ts` (a kit), the `e2e/*/src/ds.ts` apps, + and `packages/showcase/src/ds.ts` with its live Examples pages. ## Packages @@ -177,14 +55,6 @@ ds.styles() → @layer base always-on styles | [`@animus-ui/extract`](packages/extract) | Rust/NAPI extraction engine + the shared extraction session every driver (plugins, host, CLI) drives | | [`@animus-ui/properties`](packages/properties) | CSS property data (transitive dep of system) | -## Key Ideas - -- **Compiler completeness**: If the types accept it, the pipeline extracts it. No silent failures for well-typed code. -- **Token refs**: `'{colors.primary}'` resolves to `var(--color-primary)` at build time. Color modes shift the value automatically. -- **Pre-built groups**: Import `space`, `color`, `typography`, etc. from `@animus-ui/system/groups` and compose them into your own semantic groups. -- **Slot composition**: `compose()` wires components into families with shared variant propagation via React context. -- **Terminals**: `.asElement('div')` for HTML elements, `.asComponent(Existing)` for wrapping React components. Both produce typed, extractable output. - ## Legacy `@animus-ui/core` and `@animus-ui/theming` are the original Emotion-based packages. They are pinned at their last published versions and no longer actively developed. diff --git a/packages/showcase/src/App.tsx b/packages/showcase/src/App.tsx index d871c369..d0cb0703 100644 --- a/packages/showcase/src/App.tsx +++ b/packages/showcase/src/App.tsx @@ -1,94 +1,38 @@ -import type { ComponentType } from 'react'; -import { lazy, Suspense, useEffect, useState } from 'react'; -import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; +import { lazy, Suspense } from 'react'; +import { BrowserRouter, Link, Route, Routes } from 'react-router-dom'; -import { DOCS_NAV, hasChildren } from './constants/docsNav'; import { DocsLayout } from './layout/DocsLayout'; import { Shell } from './layout/Shell'; -import type { NavEntry } from './constants/docsNav'; - const Home = lazy(() => import('./pages/Home')); const Examples = lazy(() => import('./pages/Examples')); -const contentModules = import.meta.glob('./content/**/*.mdx', { - import: 'default', -}); - -function DocPage({ contentKey }: { contentKey: string }) { - const [Content, setContent] = useState(null); - const loader = contentModules[`./content/${contentKey}.mdx`]; - - useEffect(() => { - setContent(null); - if (loader) { - loader().then((mod) => setContent(() => mod)); - } - }, [loader]); - - if (!loader) return ; - if (Content === null) return null; - return ; -} - -function generateDocRoutes(nav: NavEntry[]) { - return nav.map((entry) => { - if (hasChildren(entry)) { - const segment = entry.path.replace('/docs/', ''); - const firstChild = entry.children[0]; - return ( - - } /> - {entry.children.map((child) => { - const childSegment = child.path.split('/').pop()!; - const contentKey = child.path.replace('/docs/', ''); - return ( - } - /> - ); - })} - - ); - } - - // Top-level leaf entries - if (entry.path === '/docs') { - return ( - } - /> - ); - } - - // Examples is a custom component, not markdown - if (entry.path === '/docs/examples') { - return ( - - - - } - /> - ); - } - - const segment = entry.path.replace('/docs/', ''); - return ( - } - /> - ); - }); +// The MDX guides were deleted, not archived: the system-definition API is +// still settling and every written page had drifted into teaching shapes +// the current pipeline rejects. Examples stay — they are extracted, built, +// and asserted on every verify run, so they cannot silently drift. +function DocsPlaceholder() { + return ( +
+

+ Documentation is offline +

+

+ The system-definition API is still settling, and written guides kept + drifting out of truth. They have been removed until the API freezes. The{' '} + Examples are live, extracted code and + remain accurate. +

+
+ ); } function NotFound() { @@ -118,7 +62,15 @@ export default function App() { } /> }> - {generateDocRoutes(DOCS_NAV)} + } /> + + + + } + /> } /> diff --git a/packages/showcase/src/constants/docsNav.ts b/packages/showcase/src/constants/docsNav.ts index 48ff7273..d0ab3e93 100644 --- a/packages/showcase/src/constants/docsNav.ts +++ b/packages/showcase/src/constants/docsNav.ts @@ -15,89 +15,9 @@ export function hasChildren(entry: NavEntry): entry is NavSection { return 'children' in entry && entry.children.length > 0; } +// The written guides were removed until the system-definition API settles +// (vocabulary-registration); restore sections here when they return. export const DOCS_NAV: NavEntry[] = [ - { label: 'Introduction', path: '/docs' }, - { label: 'Getting Started', path: '/docs/start' }, - { - label: 'Component Authoring', - path: '/docs/authoring', - children: [ - { label: 'Base Styling', path: '/docs/authoring/base-styling' }, - { - label: 'Variants & States', - path: '/docs/authoring/variants-states', - }, - { label: 'Selectors & Nesting', path: '/docs/authoring/selectors' }, - { label: 'Conditions', path: '/docs/authoring/conditions' }, - { label: 'System Props', path: '/docs/authoring/system-props' }, - { - label: 'Custom Props & Transforms', - path: '/docs/authoring/custom-props', - }, - { label: 'Composition', path: '/docs/authoring/composition' }, - { label: 'Recipes & Patterns', path: '/docs/authoring/recipes' }, - ], - }, - { - label: 'Architecture & Theming', - path: '/docs/architecture', - children: [ - { label: 'Theming & Tokens', path: '/docs/architecture/theming' }, - { label: 'Color Modes', path: '/docs/architecture/color-modes' }, - { label: 'System Setup', path: '/docs/architecture/system-setup' }, - { - label: 'Theme Extension', - path: '/docs/architecture/theme-extension', - }, - { - label: 'Library Authoring', - path: '/docs/architecture/library-authoring', - }, - { label: 'Global Styles', path: '/docs/architecture/global-styles' }, - ], - }, - { - label: 'Integrations', - path: '/docs/compiler', - children: [ - { label: 'Vite Plugin', path: '/docs/compiler/vite-plugin' }, - { label: 'Next.js & Remix', path: '/docs/compiler/nextjs-remix' }, - ], - }, - { - label: 'Advanced', - path: '/docs/advanced', - children: [ - { label: 'TypeScript', path: '/docs/advanced/typescript' }, - { - label: 'Framework Agnostic', - path: '/docs/advanced/framework-agnostic', - }, - { label: 'Svelte', path: '/docs/advanced/svelte' }, - { - label: 'Extraction & CSS Output', - path: '/docs/advanced/extraction', - }, - ], - }, - { - label: 'Reference', - path: '/docs/reference', - children: [ - { label: 'Builder Chain', path: '/docs/reference/builder-chain' }, - { label: 'createTheme()', path: '/docs/reference/create-theme' }, - { label: 'createSystem()', path: '/docs/reference/create-system' }, - { label: 'compose()', path: '/docs/reference/compose' }, - ], - }, - { - label: 'Support', - path: '/docs/support', - children: [ - { label: 'Troubleshooting', path: '/docs/support/troubleshooting' }, - { label: 'Migration & Adoption', path: '/docs/support/migration' }, - { label: 'Kitchen Sink', path: '/docs/support/component-test' }, - ], - }, + { label: 'Overview', path: '/docs' }, { label: 'Examples', path: '/docs/examples' }, ]; diff --git a/packages/showcase/src/content/advanced/extraction.mdx b/packages/showcase/src/content/advanced/extraction.mdx deleted file mode 100644 index ce84123f..00000000 --- a/packages/showcase/src/content/advanced/extraction.mdx +++ /dev/null @@ -1,291 +0,0 @@ -import { ChainStep } from '../../components/docs/ChainStep'; -import { CodeExample } from '../../components/docs/CodeExample'; -import { BeforeAfter } from '../../components/docs/BeforeAfter'; -import { Callout } from '../../components/docs/Callout'; - -# Static Extraction & CSS Output - -Animus performs static analysis at build time — no runtime style computation, no style injection. The Vite plugin drives a Rust NAPI crate that reads every source file, walks builder chains, evaluates style values, and emits a complete `@layer`-structured stylesheet before the browser ever loads a byte. - -This page explains how that pipeline works from source to CSS. - ---- - -## The Extraction Pipeline - -`project_analyzer.rs` orchestrates extraction as a six-phase pipeline over the entire source file set. - - → records "primary" usage for Button.intent -// An "intent" option never used in JSX is pruned before CSS emit`, - }, - { - label: 'Generate', - layer: 'emit', - description: - 'css_generator emits @layer-structured CSS for all surviving component definitions. transform_emitter builds createComponent() replacement strings for each builder chain terminal. The manifest (UniverseManifest) is returned as JSON to the plugin.', - code: `// Output: manifest JSON containing css, sheets, components, utilities -// Plugin then runs transform resolution subprocess if __TRANSFORM__ markers exist`, - }, - ]} -/> - - - Chain walking and JSX scanning are separate passes because they need different - traversal strategies. The chain walker walks expression statements backward - from terminals. The JSX scanner walks element trees looking for component - usage. Combining them into one pass would require a shared traversal that - serves neither well. - - - - `