diff --git a/.changepacks/changepack_log_argument_spread_contract.json b/.changepacks/changepack_log_argument_spread_contract.json
new file mode 100644
index 000000000..6201d0dbc
--- /dev/null
+++ b/.changepacks/changepack_log_argument_spread_contract.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Keep JavaScript argument-spread semantics in css() and styled(): compose supported literal arrays statically, and reject strings, other iterables and unknown spreads with a located composition error instructing users to pass the value itself instead. No iterators execute at build time or at styling runtime. Preserve optional producer coordinates and checked prelude offsets for located collision diagnostics.",
+ "date": "2026-10-07T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_atom_hoist_names.json b/.changepacks/changepack_log_atom_hoist_names.json
new file mode 100644
index 000000000..83747a572
--- /dev/null
+++ b/.changepacks/changepack_log_atom_hoist_names.json
@@ -0,0 +1,10 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch",
+ "packages/next-plugin/package.json": "Patch",
+ "packages/rsbuild-plugin/package.json": "Patch",
+ "packages/vite-plugin/package.json": "Patch"
+ },
+ "note": "Make atomHoist class names deterministic and match generated references to emitted CSS, freeze hoisting from predeclared route reach, and preserve dynamic !important, keyframes, and first-value responsive-token identities. No new build errors.",
+ "date": "2026-10-01T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_coordinator_port_isolation.json b/.changepacks/changepack_log_coordinator_port_isolation.json
new file mode 100644
index 000000000..667a75271
--- /dev/null
+++ b/.changepacks/changepack_log_coordinator_port_isolation.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "packages/next-plugin/package.json": "Patch"
+ },
+ "note": "Isolate the coordinator port file per owning process when another live next dev/build holds it, record the owner pid, and report port, pid and recovery when the coordinator is missing or unreachable",
+ "date": "2026-10-02T12:41:12.3693972Z"
+}
diff --git a/.changepacks/changepack_log_counter_constructor_fixture.json b/.changepacks/changepack_log_counter_constructor_fixture.json
new file mode 100644
index 000000000..c23ce30dd
--- /dev/null
+++ b/.changepacks/changepack_log_counter_constructor_fixture.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Qualify authentic original-numbered counter constructors across Rust test crates through non-default dev-only fixture support. Normal extraction, generated JavaScript, CSS naming and cache format remain unchanged; no new build errors or runtime helpers.",
+ "date": "2026-10-09T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_counter_owner_separation.json b/.changepacks/changepack_log_counter_owner_separation.json
new file mode 100644
index 000000000..87d598aca
--- /dev/null
+++ b/.changepacks/changepack_log_counter_owner_separation.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Separate original CSS allocation ownership from naming policy so the counter naming restoration can retain deferred original owners. Generated CSS, JavaScript and cache behavior are unchanged. No new build errors.",
+ "date": "2026-10-09T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_counter_slot_preparation.json b/.changepacks/changepack_log_counter_slot_preparation.json
new file mode 100644
index 000000000..cec864e1a
--- /dev/null
+++ b/.changepacks/changepack_log_counter_slot_preparation.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Prepare typed access to actual CSS counter slots while preserving existing allocation keys, namespace streams and rollback behavior. Generated CSS, JavaScript, cache behavior and snapshots are unchanged. No new build errors.",
+ "date": "2026-10-09T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_deterministic_numbering.json b/.changepacks/changepack_log_deterministic_numbering.json
new file mode 100644
index 000000000..053739d3d
--- /dev/null
+++ b/.changepacks/changepack_log_deterministic_numbering.json
@@ -0,0 +1,13 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch",
+ "packages/plugin-utils/package.json": "Patch",
+ "packages/vite-plugin/package.json": "Patch",
+ "packages/webpack-plugin/package.json": "Patch",
+ "packages/rsbuild-plugin/package.json": "Patch",
+ "packages/next-plugin/package.json": "Patch",
+ "packages/bun-plugin/package.json": "Patch"
+ },
+ "note": "Class names and file numbers no longer depend on the order files are processed in. Every plugin walks the files it can extract (the project's source and the included packages) before the first transform and numbers them in path order (a file collapsed into a bucket is numbered as the bucket, and only files that mention the package, StyleX or an aliased package take numbers) through the new seedFileMap, so identical builds give identical class prefixes, CSS file names and output hashes; in development, files that appear later are numbered after the existing ones. Styles are given class names in a fixed order, not the order of a hash set. The exported class, file and canonical maps are canonical JSON (sorted keys). Stylesheet evaluation (vanilla-extract) refuses Math.random(), Date.now(), new Date() and Date(), which differ between builds, with a build error; fixed dates such as new Date(0) are unchanged. resetBuildState starts a build from its own options: the prefix, atom hoisting, routes, buckets, names, numbers and styles an earlier build in the process left are cleared, and the prefix is set on every setup even when it is absent. Engine snapshots from concurrent transforms are written one after another per file, so an older snapshot is never written last. File lists are ordered by code point rather than the locale",
+ "date": "2026-10-01T00:00:00.000Z"
+}
\ No newline at end of file
diff --git a/.changepacks/changepack_log_dormant_counter_allocator.json b/.changepacks/changepack_log_dormant_counter_allocator.json
new file mode 100644
index 000000000..527a7daf8
--- /dev/null
+++ b/.changepacks/changepack_log_dormant_counter_allocator.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Prepare the exact legacy counter allocator and captured, map-independent naming inputs for the authoritative counter-cache transition. The new path is dormant until activation; current CSS, JavaScript and cache behavior are unchanged. No new build errors.",
+ "date": "2026-10-09T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_dormant_counter_kernel.json b/.changepacks/changepack_log_dormant_counter_kernel.json
new file mode 100644
index 000000000..01550ec58
--- /dev/null
+++ b/.changepacks/changepack_log_dormant_counter_kernel.json
@@ -0,0 +1,5 @@
+{
+ "changes": { "bindings/devup-ui-wasm/package.json": "Patch" },
+ "note": "Add a dormant Rust-only scoped prepared/live Counter kernel with independent retained evidence and exact rollback. Production Current extraction, cache4 and JavaScript output remain unchanged; no new activated JavaScript build errors.",
+ "date": "2026-10-09T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_dormant_counter_producers.json b/.changepacks/changepack_log_dormant_counter_producers.json
new file mode 100644
index 000000000..41271cad3
--- /dev/null
+++ b/.changepacks/changepack_log_dormant_counter_producers.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Prepare dormant original-owned static, dynamic and keyframe counter producers with exact legacy allocation inputs and request order, retaining source-site variable separation. Current production naming, generated output and cache behavior remain unchanged. No new build errors.",
+ "date": "2026-10-09T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_dormant_counter_replay.json b/.changepacks/changepack_log_dormant_counter_replay.json
new file mode 100644
index 000000000..63921bfda
--- /dev/null
+++ b/.changepacks/changepack_log_dormant_counter_replay.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Prepare dormant frozen-input declaration replay and complete counter expansion witnesses for the authoritative cache transition. The test-driven path preserves reset, typography, keyframe and cleanup evidence without changing current CSS, JavaScript or cache behavior. No new build errors.",
+ "date": "2026-10-09T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_dormant_numbered_sites.json b/.changepacks/changepack_log_dormant_numbered_sites.json
new file mode 100644
index 000000000..b254de6ff
--- /dev/null
+++ b/.changepacks/changepack_log_dormant_numbered_sites.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Prepare dormant original-file numeric registration and numbered source-scope capture for the counter transition, preserving source positions, roles and ownership independently of delivery. Current production naming, generated output and cache behavior remain unchanged. No new build errors.",
+ "date": "2026-10-09T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_dormant_producer_policy.json b/.changepacks/changepack_log_dormant_producer_policy.json
new file mode 100644
index 000000000..1fd549a03
--- /dev/null
+++ b/.changepacks/changepack_log_dormant_producer_policy.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Prepare dormant construction-time producer policy so original counter ownership survives deferred style unions, including dynamic and keyframe producers. Current production naming, generated output and cache behavior remain unchanged. No new build errors.",
+ "date": "2026-10-09T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_dynamic_scaled_values.json b/.changepacks/changepack_log_dynamic_scaled_values.json
new file mode 100644
index 000000000..dd96a9eae
--- /dev/null
+++ b/.changepacks/changepack_log_dynamic_scaled_values.json
@@ -0,0 +1,8 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch",
+ "apps/landing/package.json": "Patch"
+ },
+ "note": "Preserve zero-valued logical controllers like static literals, scale proven dynamic numbers in CSS, and inline-convert ambiguous numeric values and numeric strings using the static numeric grammar. Omit dynamic declaration classes for absent values through single-evaluation captures, including selectors and responsive values. Keep source evaluation order and sparse variable sites; separate mixed-unit shorthand consumers by canonical target-ordered roles, leaving single-unit names unchanged and additional groups disjoint from responsive levels. No new build errors.",
+ "date": "2026-10-08T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_environment_stable_names.json b/.changepacks/changepack_log_environment_stable_names.json
new file mode 100644
index 000000000..dcd8ba16e
--- /dev/null
+++ b/.changepacks/changepack_log_environment_stable_names.json
@@ -0,0 +1,12 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch",
+ "packages/react/package.json": "Patch",
+ "packages/components/package.json": "Patch",
+ "packages/next-plugin/package.json": "Patch",
+ "packages/vite-plugin/package.json": "Patch",
+ "packages/rsbuild-plugin/package.json": "Patch"
+ },
+ "note": "Stabilize class and runtime-variable names across build environments, bound unnumbered source identities with compact deterministic names, reset generated variables on their owning elements, and preserve authored evaluation order and lazy selection. Exact content-name and source-name collisions report both contents and their best real source locations; conflicting source-variable roles and unprotected naming caches now fail closed without rejecting literal CSS content strings.",
+ "date": "2026-10-05T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_exclusive_attempt.json b/.changepacks/changepack_log_exclusive_attempt.json
new file mode 100644
index 000000000..83e571cef
--- /dev/null
+++ b/.changepacks/changepack_log_exclusive_attempt.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Enforce build-time exclusive ownership of naming state and exact rollback when extraction or output construction fails, without changing production naming or cache format. Native lock-held recursive callbacks and same-owner configuration administration during an active exact attempt fail synchronously as programmer-contract violations; normal compilation gains no new build errors.",
+ "date": "2026-10-09T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_static_scoped_names.json b/.changepacks/changepack_log_static_scoped_names.json
new file mode 100644
index 000000000..7267d0485
--- /dev/null
+++ b/.changepacks/changepack_log_static_scoped_names.json
@@ -0,0 +1,13 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch",
+ "packages/vite-plugin/package.json": "Patch",
+ "packages/webpack-plugin/package.json": "Patch",
+ "packages/rsbuild-plugin/package.json": "Patch",
+ "packages/next-plugin/package.json": "Patch",
+ "packages/bun-plugin/package.json": "Patch",
+ "packages/components/package.json": "Patch"
+ },
+ "note": "Compile proven static responsive arrays directly to classes and isolate content-named atoms by their existing delivered sheet identity. Safe original sources keep independent D9 counter namespaces even when their CSS shares a collapsed bucket. Build adapters supply an independent names-only project root; resolver IDs, D9 numbering and stylesheet placement stay unchanged. Versioned self-contained naming snapshots ignore and rebuild obsolete or invalid cache data that reaches WASM; cache build errors are reserved for real exact name collisions with located diagnostics. Static Textarea font-size arrays retain their declarations and DOM while compiling directly to classes.",
+ "date": "2026-10-06T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_styled_creation.json b/.changepacks/changepack_log_styled_creation.json
new file mode 100644
index 000000000..748ebc975
--- /dev/null
+++ b/.changepacks/changepack_log_styled_creation.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Evaluate supported conditional styled rule arguments once when the component is created, preserving branch laziness, authored field order, static selectors and layers, typography and runtime value associations. Keep rule capture outside attrs wrappers while authored attrs callbacks remain render-time. Responsive typography retains its shared base class and order-aware class selection without introducing empty styles for proven inert base-preset lookups. This scheduling repair adds no build errors or styling runtime.",
+ "date": "2026-10-07T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_typography_classes.json b/.changepacks/changepack_log_typography_classes.json
new file mode 100644
index 000000000..c47791b47
--- /dev/null
+++ b/.changepacks/changepack_log_typography_classes.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Select dynamic typography and enum classes from the authored value exactly once in source order, without typography CSS variables or declarations. Preserve responsive and selector-scoped presets, conditional style order, template coercion and the existing spread contract. No new build errors or styling runtime are introduced.",
+ "date": "2026-10-07T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_w31s_u1.json b/.changepacks/changepack_log_w31s_u1.json
new file mode 100644
index 000000000..677972168
--- /dev/null
+++ b/.changepacks/changepack_log_w31s_u1.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch"
+ },
+ "note": "Keep dormant Rust counter state owned by the sheet with pure linkage checks and explicit literal ownership. The doc-hidden CounterSheet constructor now takes only the sheet. Production Current naming and cache4 remain unchanged; no new JavaScript build errors.",
+ "date": "2026-10-10T00:00:00.000Z"
+}
diff --git a/.changepacks/changepack_log_w31t_u2.json b/.changepacks/changepack_log_w31t_u2.json
new file mode 100644
index 000000000..9e4572937
--- /dev/null
+++ b/.changepacks/changepack_log_w31t_u2.json
@@ -0,0 +1,5 @@
+{
+ "changes": { "bindings/devup-ui-wasm/package.json": "Patch" },
+ "note": "Add dormant Rust strict snapshot6 export and consuming admission APIs without changing production cache4 or JavaScript extraction. Invalid candidates and stale installation authority return typed Rust rejections; no new ordinary JavaScript build errors.",
+ "date": "2026-10-10T00:00:00.000Z"
+}
diff --git a/apps/landing/src/app/(detail)/docs/features/page.mdx b/apps/landing/src/app/(detail)/docs/features/page.mdx
index da8b97717..365f833d9 100644
--- a/apps/landing/src/app/(detail)/docs/features/page.mdx
+++ b/apps/landing/src/app/(detail)/docs/features/page.mdx
@@ -62,6 +62,10 @@ const generated =
// .a { background-color: var(--a); }
```
+Dynamic numbers follow the static scale when the resulting CSS is valid for the property. Invalid numeric declarations are an exception: with an earlier `padding: 7px` rule, static `p={-0.5}` emits invalid `padding: -2px`, so the browser keeps 7px, whereas the dynamic variable path computes 0px. Proven-number `calc()` also clamps negative width to 0 and overflow to the browser's maximum; an invalid substituted value instead uses the property's initial or inherited value, not an earlier declaration. Valid negative values such as margin retain literal parity. Devup UI does not add a numeric-validity class policy or runtime validity check.
+
+An absent dynamic value applies neither its site's declaration class nor its own-element variable reset, so the element behaves as if that prop were omitted while the unused generated variable intentionally inherits from ancestors, allowing an ancestor's descendant-selector rule to reach children through an absent same-site owner.
+
A value imported from another module stays static when that module declares it with `const` — a string or number literal, a member of an object literal (`as const` included), an alias of another constant, a re-export or a default export:
```tsx
diff --git a/bindings/devup-ui-wasm/Cargo.toml b/bindings/devup-ui-wasm/Cargo.toml
index fa769a310..e79916945 100644
--- a/bindings/devup-ui-wasm/Cargo.toml
+++ b/bindings/devup-ui-wasm/Cargo.toml
@@ -42,6 +42,8 @@ getrandom = { version = "0.4", features = ["wasm_js"] }
boa_engine = { version = "0.22", features = ["js"] }
[dev-dependencies]
+extractor = { path = "../../libs/extractor", features = ["counter-fixtures"] }
+css = { path = "../../libs/css", features = ["admission-observer"] }
wasm-bindgen-test = "0.3.78"
serial_test = "4.0.1"
insta = "1.48.0"
diff --git a/bindings/devup-ui-wasm/src/administration.rs b/bindings/devup-ui-wasm/src/administration.rs
new file mode 100644
index 000000000..dc90f1f38
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/administration.rs
@@ -0,0 +1,8 @@
+/// Native programmer contract: ordinary extraction never administers build state.
+/// Real linkage activation will replace this panic with a recoverable typed error.
+pub(crate) fn with_administration(operation: &str, build: impl FnOnce() -> R) -> R {
+ css::admission::with_admission(|| {
+ css::admission::assert_administration_allowed(operation);
+ build()
+ })
+}
diff --git a/bindings/devup-ui-wasm/src/admission_contract_tests.rs b/bindings/devup-ui-wasm/src/admission_contract_tests.rs
new file mode 100644
index 000000000..47312985c
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/admission_contract_tests.rs
@@ -0,0 +1,131 @@
+use super::*;
+use exact_test_support::{authority, fixture};
+use rstest::rstest;
+use serial_test::serial;
+use std::panic::{AssertUnwindSafe, catch_unwind};
+
+#[rstest]
+#[case("set_debug", || set_debug(true))]
+#[case("set_prefix", || set_prefix(Some("changed".into())))]
+#[case("set_naming_root", || set_naming_root(Some("/changed".into()), None))]
+#[case("set_atom_hoist", || set_atom_hoist(Some(2)))]
+#[case("import_canonical_map_internal", || import_canonical_map_internal(HashMap::new()))]
+#[case("import_file_routes_internal", || import_file_routes_internal(HashMap::new()))]
+#[case("register_theme_internal", || register_theme_internal(sheet::theme::Theme::default()))]
+#[case("register_shorthands_internal", || register_shorthands_internal(BTreeMap::new()))]
+#[case("set_module_resolver", || set_module_resolver_internal(None))]
+#[case("seed_file_map", || seed_file_map(vec!["changed.tsx".into()]))]
+#[case("reset_build_state_internal", reset_build_state_internal)]
+#[case("import_sheet_internal", || { import_sheet_internal(StyleSheet::default()).unwrap_or_else(|error| panic!("sheet import failed: {error}")); })]
+#[case("cache_restore::clear", cache_restore::clear)]
+#[case("cache_restore::seeded", || cache_restore::seeded(&["changed.tsx".into()]))]
+#[case("cache_restore::classes", || cache_restore::classes(None))]
+#[case("cache_restore::files", || cache_restore::files(None))]
+#[case("cache_restore::import", || { cache_restore::import(StyleSheet::default()).unwrap_or_else(|error| panic!("cache import failed: {error}")); })]
+#[case("StyleSheet::set_theme", || StyleSheet::default().set_theme(sheet::theme::Theme::default()))]
+#[serial]
+fn guarded_entry_panics_with_exact_message_before_mutation(
+ #[case] operation: &str,
+ #[case] administer: fn(),
+) {
+ // Given
+ fixture();
+ set_prefix(Some("retained".into()));
+ set_atom_hoist(Some(7));
+ register_shorthands_internal(BTreeMap::from([("custom".into(), vec!["color".into()])]));
+ let before = authority();
+ cache_names::record(&Err("sticky retained".into()));
+ // When
+ let panic = match catch_unwind(AssertUnwindSafe(|| {
+ let _: Result<(), ()> = css::exact_attempt::with_exclusive_attempt(|| {
+ administer();
+ Ok(())
+ });
+ })) {
+ Err(payload) => payload,
+ Ok(()) => panic!("guarded administration did not panic"),
+ };
+ // Then
+ let message = match panic.downcast_ref::() {
+ Some(message) => message.as_str(),
+ None => *panic
+ .downcast_ref::<&str>()
+ .unwrap_or_else(|| panic!("guarded administration panic is not a string")),
+ };
+ assert_eq!(message, format!("{operation}: an exact attempt is active"));
+ assert_eq!(authority(), before);
+ assert_eq!(get_prefix(), Some("retained".into()));
+ assert_eq!(css::atom_hoist::atom_hoist_threshold(), Some(7));
+ assert_eq!(
+ css::get_custom_shorthand_names(),
+ vec!["custom".to_string()]
+ );
+ assert_eq!(cache_names::check(), Err("sticky retained".into()));
+ register_shorthands_internal(BTreeMap::new());
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn sheet_entry_marker_recovers_after_unwind_and_allows_sequential_calls() {
+ // Given
+ reset_build_state_internal();
+ let _poison_cleanup = exact_test_support::SheetPoisonCleanup::new();
+ // When
+ let result = catch_unwind(AssertUnwindSafe(|| {
+ with_style_sheet_mut(|_| panic!("sheet unwind"));
+ }));
+ // Then
+ assert!(result.is_err());
+ with_admission(|| {
+ with_style_sheet_mut(|sheet| sheet.add_css("after.tsx", concat!("body{", "color:red}")));
+ with_style_sheet(|sheet| assert!(sheet.css.contains_key("after.tsx")));
+ });
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn failed_extraction_leaves_cache_authority_and_ordered_fresh_seed_replay_unchanged() {
+ // Given
+ reset_build_state_internal();
+ let serialized = export_sheet_internal()
+ .unwrap_or_else(|error| panic!("cache fixture export failed: {error}"));
+ seed_file_map(vec!["before.tsx".into()]);
+ import_sheet_internal(
+ serde_json::from_str(&serialized)
+ .unwrap_or_else(|error| panic!("cache fixture decoding failed: {error}")),
+ )
+ .unwrap_or_else(|error| panic!("cache fixture import failed: {error}"));
+ seed_file_map(vec!["b.tsx".into(), "b.tsx".into()]);
+ seed_file_map(vec!["a.tsx".into(), "b.tsx".into()]);
+ let before = authority();
+ // When
+ let failed = exact_test_support::compile(
+ "broken.tsx",
+ "import {Box} from '@devup-ui/react'; const x= {
+ export_sheet_internal()
+ .unwrap_or_else(|error| panic!("gateway sheet export failed: {error}"));
+ }
+ Self::GetCss => {
+ get_css_internal(None, false)
+ .unwrap_or_else(|error| panic!("gateway CSS read failed: {error}"));
+ }
+ Self::Theme => register_theme_internal(sheet::theme::Theme::default()),
+ Self::ClassCompanion => cache_restore::classes(None),
+ Self::FileCompanion => cache_restore::files(None),
+ Self::SheetImport => {
+ import_sheet_internal(StyleSheet::default())
+ .unwrap_or_else(|error| panic!("gateway sheet import failed: {error}"));
+ }
+ Self::Reset => reset_build_state(),
+ Self::Seed => seed_file_map(vec!["worker.tsx".into()]),
+ Self::Prefix => set_prefix(Some("worker".into())),
+ Self::Debug => set_debug(false),
+ Self::NamingRoot => set_naming_root(None, None),
+ Self::Canonical => import_canonical_map_internal(HashMap::new()),
+ Self::Routes => import_file_routes_internal(HashMap::new()),
+ Self::Hoist => set_atom_hoist(None),
+ Self::Shorthands => register_shorthands_internal(BTreeMap::new()),
+ Self::Resolver => set_module_resolver_internal(None),
+ Self::NativeTheme => StyleSheet::default().set_theme(sheet::theme::Theme::default()),
+ Self::NativeExport => {
+ serde_json::to_string(&StyleSheet::default().export_snapshot())
+ .unwrap_or_else(|error| panic!("native export encoding failed: {error}"));
+ }
+ }
+ }
+}
+
+#[rstest]
+#[case(Gateway::ExportSheet)]
+#[case(Gateway::GetCss)]
+#[case(Gateway::Theme)]
+#[case(Gateway::ClassCompanion)]
+#[case(Gateway::FileCompanion)]
+#[case(Gateway::SheetImport)]
+#[case(Gateway::Reset)]
+#[case(Gateway::Seed)]
+#[case(Gateway::Prefix)]
+#[case(Gateway::Debug)]
+#[case(Gateway::NamingRoot)]
+#[case(Gateway::Canonical)]
+#[case(Gateway::Routes)]
+#[case(Gateway::Hoist)]
+#[case(Gateway::Shorthands)]
+#[case(Gateway::Resolver)]
+#[case(Gateway::NativeTheme)]
+#[case(Gateway::NativeExport)]
+#[serial]
+fn public_gateway_waits_for_exact_release(
+ #[case] gateway: Gateway,
+ #[values(false, true)] commit: bool,
+) {
+ // Given
+ fixture();
+ let (done, received) = channel();
+ // When
+ std::thread::scope(|scope| {
+ let result = exact(|| {
+ mutate_authority();
+ let contender = scope.spawn(move || {
+ gateway.call();
+ done.send(())
+ .unwrap_or_else(|error| panic!("gateway completion send failed: {error}"));
+ });
+ css::admission::observer::wait_for_thread(contender.thread().id());
+ assert_eq!(received.try_recv(), Err(TryRecvError::Empty));
+ if commit { Ok(()) } else { Err("abort".into()) }
+ });
+ // Then
+ assert_eq!(result, if commit { Ok(()) } else { Err("abort".into()) });
+ received
+ .recv()
+ .unwrap_or_else(|error| panic!("gateway completion receive failed: {error}"));
+ });
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn export_observes_only_aborted_or_committed_sheet(#[case] commit: bool) {
+ // Given
+ fixture();
+ let before = with_style_sheet(extraction_rollback::SheetData::capture);
+ let (done, received) = channel();
+ // When
+ std::thread::scope(|scope| {
+ let mut committed = None;
+ let result = exact(|| {
+ mutate_authority();
+ committed = Some(with_style_sheet(extraction_rollback::SheetData::capture));
+ let contender = scope.spawn(move || {
+ let exported = export_sheet_internal()
+ .unwrap_or_else(|error| panic!("contender sheet export failed: {error}"));
+ let sheet = with_style_sheet(extraction_rollback::SheetData::capture);
+ done.send((exported, sheet))
+ .unwrap_or_else(|error| panic!("export observation send failed: {error}"));
+ });
+ css::admission::observer::wait_for_thread(contender.thread().id());
+ assert_eq!(received.try_recv(), Err(TryRecvError::Empty));
+ if commit { Ok(()) } else { Err("abort".into()) }
+ });
+ // Then
+ assert_eq!(result.is_ok(), commit);
+ let (exported, observed) = received
+ .recv()
+ .unwrap_or_else(|error| panic!("export observation receive failed: {error}"));
+ assert_eq!(
+ observed,
+ if commit {
+ committed.unwrap_or_else(|| panic!("committed sheet capture missing"))
+ } else {
+ before
+ }
+ );
+ assert_eq!(
+ exported,
+ export_sheet_internal()
+ .unwrap_or_else(|error| panic!("released sheet export failed: {error}"))
+ );
+ });
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn admission_precedes_sheet_lock_for_sheet_to_css_callbacks() {
+ // Given
+ fixture();
+ let (done, received) = channel();
+ // When
+ std::thread::scope(|scope| {
+ with_admission(|| {
+ let contender = scope.spawn(move || {
+ with_style_sheet_mut(|sheet| {
+ let _name = css::keyframes_to_keyframes_name("worker", None);
+ sheet.add_css("worker.tsx", "body{margin:3px}");
+ });
+ done.send(()).unwrap_or_else(|error| {
+ panic!("sheet callback completion send failed: {error}")
+ });
+ });
+ css::admission::observer::wait_for_thread(contender.thread().id());
+ with_style_sheet(|sheet| assert!(sheet.css.contains_key("raw.tsx")));
+ });
+ // Then
+ received
+ .recv()
+ .unwrap_or_else(|error| panic!("sheet callback completion receive failed: {error}"));
+ });
+ assert!(with_style_sheet(|sheet| sheet
+ .css
+ .contains_key("worker.tsx")));
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/admission_guard_tests.rs b/bindings/devup-ui-wasm/src/admission_guard_tests.rs
new file mode 100644
index 000000000..a5888ac1c
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/admission_guard_tests.rs
@@ -0,0 +1,182 @@
+use super::*;
+use serial_test::serial;
+
+fn during_exact(build: impl FnOnce()) {
+ let result: Result<(), ()> = css::exact_attempt::with_exclusive_attempt(|| {
+ build();
+ Ok(())
+ });
+ assert_eq!(result, Ok(()));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "set_debug: an exact attempt is active")]
+fn debug_rejects_owner_administration() {
+ during_exact(|| set_debug(true));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "set_prefix: an exact attempt is active")]
+fn prefix_rejects_owner_administration() {
+ during_exact(|| set_prefix(Some("other".into())));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "set_naming_root: an exact attempt is active")]
+fn naming_root_rejects_owner_administration() {
+ during_exact(|| set_naming_root(Some("/other".into()), None));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "import_canonical_map_internal: an exact attempt is active")]
+fn canonical_import_rejects_owner_administration() {
+ during_exact(|| import_canonical_map_internal(HashMap::new()));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "import_file_routes_internal: an exact attempt is active")]
+fn routes_import_rejects_owner_administration() {
+ during_exact(|| import_file_routes_internal(HashMap::new()));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "set_atom_hoist: an exact attempt is active")]
+fn hoist_rejects_owner_administration() {
+ during_exact(|| set_atom_hoist(Some(2)));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "register_theme_internal: an exact attempt is active")]
+fn theme_registration_rejects_owner_administration() {
+ during_exact(|| register_theme_internal(sheet::theme::Theme::default()));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "register_shorthands_internal: an exact attempt is active")]
+fn shorthands_reject_owner_administration() {
+ during_exact(|| register_shorthands_internal(BTreeMap::new()));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "set_module_resolver: an exact attempt is active")]
+fn resolver_selection_rejects_owner_administration() {
+ during_exact(|| set_module_resolver_internal(None));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "seed_file_map: an exact attempt is active")]
+fn seed_rejects_owner_administration() {
+ during_exact(|| seed_file_map(vec!["other.tsx".into()]));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "reset_build_state_internal: an exact attempt is active")]
+fn reset_rejects_owner_administration() {
+ during_exact(reset_build_state_internal);
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "import_sheet_internal: an exact attempt is active")]
+fn sheet_import_rejects_owner_administration() {
+ during_exact(|| {
+ import_sheet_internal(StyleSheet::default())
+ .unwrap_or_else(|error| panic!("sheet import failed: {error}"));
+ });
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "cache_restore::clear: an exact attempt is active")]
+fn companion_clear_rejects_owner_administration() {
+ during_exact(cache_restore::clear);
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "cache_restore::seeded: an exact attempt is active")]
+fn companion_seed_rejects_owner_administration() {
+ during_exact(|| cache_restore::seeded(&["other.tsx".into()]));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "cache_restore::classes: an exact attempt is active")]
+fn class_companion_rejects_owner_administration() {
+ during_exact(|| cache_restore::classes(None));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "cache_restore::files: an exact attempt is active")]
+fn file_companion_rejects_owner_administration() {
+ during_exact(|| cache_restore::files(None));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "cache_restore::import: an exact attempt is active")]
+fn cache_import_rejects_owner_administration() {
+ during_exact(|| {
+ cache_restore::import(StyleSheet::default())
+ .unwrap_or_else(|error| panic!("cache import failed: {error}"));
+ });
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "StyleSheet::set_theme: an exact attempt is active")]
+fn native_theme_setter_rejects_owner_administration() {
+ during_exact(|| StyleSheet::default().set_theme(sheet::theme::Theme::default()));
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "style sheet: recursive lock-held entry")]
+fn sheet_read_rejects_recursive_read() {
+ let _poison_cleanup = exact_test_support::SheetPoisonCleanup::new();
+ with_style_sheet(|_| {
+ with_style_sheet(|_| {});
+ });
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "style sheet: recursive lock-held entry")]
+fn sheet_read_rejects_recursive_write() {
+ let _poison_cleanup = exact_test_support::SheetPoisonCleanup::new();
+ with_style_sheet(|_| {
+ with_style_sheet_mut(|_| {});
+ });
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "style sheet: recursive lock-held entry")]
+fn sheet_write_rejects_recursive_read() {
+ let _poison_cleanup = exact_test_support::SheetPoisonCleanup::new();
+ with_style_sheet_mut(|_| {
+ with_style_sheet(|_| {});
+ });
+}
+
+#[test]
+#[serial]
+#[should_panic(expected = "style sheet: recursive lock-held entry")]
+fn sheet_write_rejects_recursive_write() {
+ let _poison_cleanup = exact_test_support::SheetPoisonCleanup::new();
+ with_style_sheet_mut(|_| {
+ with_style_sheet_mut(|_| {});
+ });
+}
diff --git a/bindings/devup-ui-wasm/src/atom_hoist_measurement.rs b/bindings/devup-ui-wasm/src/atom_hoist_measurement.rs
new file mode 100644
index 000000000..64038e6e2
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/atom_hoist_measurement.rs
@@ -0,0 +1,244 @@
+//! Test-only measurements under immutable, predeclared module reach.
+
+mod error_tests;
+mod report;
+mod snapshot;
+
+use snapshot::Snapshot;
+
+use super::{
+ code_extract_internal, register_theme_internal, reset_build_state_internal, seed_file_map,
+ set_atom_hoist, with_style_sheet,
+};
+use std::collections::{HashMap, HashSet};
+
+const ROUTE_IDS: [u32; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
+const ROUTES: usize = ROUTE_IDS.len();
+const SHARED_MODULES: usize = 32;
+const SHARED_ATOMS: usize = 80;
+const PRIVATE_ATOMS: usize = 25;
+const PROPS: [(&str, &str); 8] = [
+ ("w", "width"),
+ ("h", "height"),
+ ("p", "padding"),
+ ("m", "margin"),
+ ("minW", "min-width"),
+ ("minH", "min-height"),
+ ("maxW", "max-width"),
+ ("maxH", "max-height"),
+];
+
+struct Module {
+ path: String,
+ atoms: Vec,
+}
+
+struct Atom {
+ property: (&'static str, &'static str),
+ value: String,
+}
+
+impl Module {
+ fn source(&self) -> String {
+ let mut elements = String::new();
+ for atom in &self.atoms {
+ elements.push_str(" ");
+ }
+ format!("import {{ Box }} from '@devup-ui/react'; const x = <>{elements}>;")
+ }
+
+ fn declarations(&self) -> Vec {
+ self.atoms
+ .iter()
+ .map(|atom| format!("{}:{}", atom.property.1, atom.value))
+ .collect()
+ }
+}
+
+fn fixture() -> Vec {
+ let atoms = |count, start| {
+ (0..count)
+ .map(|i| Atom {
+ property: PROPS[i % PROPS.len()],
+ value: format!("{}px", start + i),
+ })
+ .collect()
+ };
+ let mut modules: Vec = (0..SHARED_MODULES)
+ .map(|i| Module {
+ path: format!("src/shared/s{i:02}.tsx"),
+ atoms: atoms(SHARED_ATOMS, 100_000),
+ })
+ .collect();
+ modules.extend((0..ROUTES).map(|route| Module {
+ path: format!("src/routes/r{route}.tsx"),
+ atoms: atoms(PRIVATE_ATOMS, 1_000_000 + route * PRIVATE_ATOMS),
+ }));
+ modules.push(Module {
+ path: "src/unknown.tsx".to_string(),
+ atoms: atoms(1, 2_000_000),
+ });
+ modules
+}
+
+#[derive(Debug)]
+pub(super) struct ExtractionError {
+ file: String,
+ message: String,
+}
+
+impl std::fmt::Display for ExtractionError {
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(formatter, "{}: {}", self.file, self.message)
+ }
+}
+
+impl std::error::Error for ExtractionError {}
+
+struct BuildState;
+
+impl Drop for BuildState {
+ fn drop(&mut self) {
+ reset_build_state_internal();
+ }
+}
+
+struct Measurement {
+ before: Snapshot,
+ after: Snapshot,
+}
+
+fn build(modules: &[Module], hoist: bool) -> Result {
+ reset_build_state_internal();
+ let _state = BuildState;
+ register_theme_internal(sheet::theme::Theme::default());
+ seed_file_map(modules.iter().map(|m| m.path.clone()).collect());
+ let all_routes: HashSet = ROUTE_IDS.into_iter().collect();
+ let mut reach: HashMap> = modules[..SHARED_MODULES]
+ .iter()
+ .map(|m| (m.path.clone(), all_routes.clone()))
+ .collect();
+ reach.extend(ROUTE_IDS.iter().enumerate().map(|(r, id)| {
+ (
+ modules[SHARED_MODULES + r].path.clone(),
+ HashSet::from([*id]),
+ )
+ }));
+ super::import_file_routes_internal(reach);
+ set_atom_hoist(hoist.then_some(2));
+ let code = modules
+ .iter()
+ .map(|module| {
+ code_extract_internal(
+ &module.path,
+ &module.source(),
+ "@devup-ui/react",
+ "df".to_string(),
+ false,
+ false,
+ true,
+ HashMap::new(),
+ )
+ .map(|output| output.code())
+ .map_err(|message| ExtractionError {
+ file: module.path.clone(),
+ message,
+ })
+ })
+ .collect::, _>>()?;
+ Ok(Snapshot::capture(modules, &code))
+}
+
+fn measure(hoist: bool) -> Result {
+ // Given: every shared module reaches all routes before any extraction.
+ let mut modules = fixture();
+ let before = build(&modules, hoist)?;
+ before.assert_loaded_definitions(&modules);
+ let copies = if hoist { 1 } else { SHARED_MODULES };
+ for declaration in modules[0].declarations() {
+ let needle = format!("{{{declaration}}}");
+ assert_eq!(
+ before
+ .sheets
+ .iter()
+ .map(|s| s.matches(&needle).count())
+ .sum::(),
+ copies
+ );
+ assert_eq!(
+ before.sheets[0].matches(&needle).count(),
+ usize::from(hoist)
+ );
+ }
+ // When: deploy a fresh complete build after changing one upstream constant.
+ for module in &mut modules[..SHARED_MODULES] {
+ module.atoms[0].value = "900000px".to_string();
+ }
+ let after = build(&modules, hoist)?;
+ // Then: fresh transformed code still loads the exact new declarations.
+ after.assert_loaded_definitions(&modules);
+ assert_eq!(after.changed(&before).0, copies);
+ assert_eq!(after.sheets[0] != before.sheets[0], hoist);
+ assert_eq!(
+ after
+ .sheets
+ .iter()
+ .map(|s| s.matches("{width:900000px}").count())
+ .sum::(),
+ copies
+ );
+ assert_eq!(
+ after
+ .sheets
+ .iter()
+ .map(|s| s.matches("{width:100000px}").count())
+ .sum::(),
+ 0
+ );
+ for (i, module) in modules.iter().enumerate().skip(SHARED_MODULES) {
+ for declaration in module.declarations() {
+ assert!(after.sheets[i + 1].contains(&format!("{{{declaration}}}")));
+ assert!(!after.sheets[0].contains(&format!("{{{declaration}}}")));
+ }
+ }
+ Ok(Measurement { before, after })
+}
+
+fn checked_measurements() -> Result<(Measurement, Measurement), ExtractionError> {
+ let baseline = measure(false)?;
+ let atom = measure(true)?;
+ let redundant_payload = (SHARED_MODULES - 1)
+ * fixture()[0]
+ .declarations()
+ .iter()
+ .map(String::len)
+ .sum::();
+ // Retain at least one route's share of eliminated declaration payload after
+ // paying the ACTUAL lossless-name/header/import costs. No fixed percentage.
+ assert!(baseline.before.raw_bytes() >= atom.before.raw_bytes() + redundant_payload / ROUTES);
+ let baseline_changed = baseline.after.changed(&baseline.before);
+ let atom_changed = atom.after.changed(&atom.before);
+ assert_eq!(baseline_changed.0 - atom_changed.0, SHARED_MODULES - 1);
+ assert!(baseline_changed.1 >= atom_changed.1 + (SHARED_MODULES - 1) * "width:900000px".len());
+ Ok((baseline, atom))
+}
+
+/// Callable replacement body; the caller must retain its serial-test guard.
+pub(super) fn assert_predeclared_shared_savings() -> Result<(), ExtractionError> {
+ let artifacts = build_predeclared_measurement_artifacts()?;
+ report::assert_artifacts(&artifacts);
+ println!("{}", artifacts["report"]);
+ Ok(())
+}
+
+/// Build complete artifacts independently of the caller's stdout/env policy.
+/// The caller must retain its serial-test guard.
+pub(super) fn build_predeclared_measurement_artifacts() -> Result
+{
+ let (baseline, atom) = checked_measurements()?;
+ Ok(report::artifacts(&baseline, &atom))
+}
diff --git a/bindings/devup-ui-wasm/src/atom_hoist_measurement/error_tests.rs b/bindings/devup-ui-wasm/src/atom_hoist_measurement/error_tests.rs
new file mode 100644
index 000000000..00a1410cc
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/atom_hoist_measurement/error_tests.rs
@@ -0,0 +1,30 @@
+use super::{ExtractionError, build, fixture};
+use serial_test::serial;
+
+#[test]
+#[serial]
+fn build_reports_failing_file_when_fixture_contains_malformed_jsx() {
+ // Given: a malformed first atom in the otherwise unchanged real fixture.
+ let mut modules = fixture();
+ modules[0].atoms[0].value = "\"".to_string();
+ // When: extraction fails through the measurement build boundary.
+ let error: ExtractionError = match build(&modules, true) {
+ Ok(_) => panic!("malformed JSX must fail extraction"),
+ Err(error) => error,
+ };
+ // Then: the typed error retains its failing file and parser context.
+ assert_eq!(error.file, "src/shared/s00.tsx");
+ assert_eq!(error.message, "Parser panicked");
+ assert_eq!(error.to_string(), "src/shared/s00.tsx: Parser panicked");
+ assert_eq!(css::atom_hoist::atom_hoist_threshold(), None);
+ assert_eq!(css::file_routes::get_file_routes().len(), 0);
+
+ // Given: a valid fixture after the failed build has released its state.
+ let valid = fixture();
+ // When: a subsequent build extracts that fixture without hoisting.
+ let snapshot = build(&valid, false).unwrap_or_else(|error| panic!("{error}"));
+ // Then: every emitted class loads the valid fixture's exact declaration.
+ snapshot.assert_loaded_definitions(&valid);
+ assert!(!snapshot.sheets[0].contains("{width:100000px}"));
+ assert!(snapshot.sheets[1].contains("{width:100000px}"));
+}
diff --git a/bindings/devup-ui-wasm/src/atom_hoist_measurement/report.rs b/bindings/devup-ui-wasm/src/atom_hoist_measurement/report.rs
new file mode 100644
index 000000000..505f7e38e
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/atom_hoist_measurement/report.rs
@@ -0,0 +1,119 @@
+use super::{Measurement, PRIVATE_ATOMS, ROUTES, SHARED_ATOMS, SHARED_MODULES, Snapshot, fixture};
+use serde_json::{Value, json};
+
+fn report(baseline: &Measurement, atom: &Measurement) -> Value {
+ json!({
+ "invalidationModel": "fresh deployment builds; exact stylesheet content comparison",
+ "fixture": {"routes": ROUTES, "sharedModules": SHARED_MODULES,
+ "sharedAtoms": SHARED_ATOMS, "privateAtomsPerRoute": PRIVATE_ATOMS,
+ "unknownLocalModules": 1, "threshold": 2},
+ "sessionRaw": {"baseline": baseline.before.raw_bytes(), "atom": atom.before.raw_bytes()},
+ "invalidationRaw": {"baseline": baseline.after.changed(&baseline.before).1,
+ "atom": atom.after.changed(&atom.before).1},
+ "changedSheets": {"baseline": baseline.after.changed(&baseline.before).0,
+ "atom": atom.after.changed(&atom.before).0},
+ "sheetCountSavings": {"numerator": SHARED_MODULES - 1, "denominator": SHARED_MODULES},
+ "jsxClassNameRawBytes": {"baseline": baseline.before.name_bytes(), "atom": atom.before.name_bytes()},
+ "cssSelectorNameRawBytes": {"baseline": baseline.before.selector_name_bytes(),
+ "atom": atom.before.selector_name_bytes()},
+ "gzip": "external measurement required; no compression dependency added"
+ })
+}
+
+pub(super) fn artifacts(baseline: &Measurement, atom: &Measurement) -> Value {
+ json!({
+ "report": report(baseline, atom),
+ "baseline": {"beforeSheets": baseline.before.sheets, "afterSheets": baseline.after.sheets,
+ "beforeCode": baseline.before.code, "afterCode": baseline.after.code},
+ "atom": {"beforeSheets": atom.before.sheets, "afterSheets": atom.after.sheets,
+ "beforeCode": atom.before.code, "afterCode": atom.after.code},
+ "paths": fixture().iter().map(|m| &m.path).collect::>()
+ })
+}
+
+pub(super) fn assert_artifacts(artifacts: &Value) {
+ // Given: the original fixture, independently of the exported JSON.
+ let modules = fixture();
+ // When: consume the complete stdout representation as an external tool does.
+ let serialized = artifacts.to_string();
+ let decoded: Value =
+ serde_json::from_str(&serialized).unwrap_or_else(|error| panic!("{error}"));
+ // Then: paths, code and sheets retain their fixture identity and deployment.
+ let paths: Vec =
+ serde_json::from_value(decoded["paths"].clone()).unwrap_or_else(|error| panic!("{error}"));
+ assert_eq!(
+ paths,
+ modules.iter().map(|m| m.path.clone()).collect::>()
+ );
+ let summary = &decoded["report"];
+ assert_eq!(
+ summary["fixture"],
+ json!({"routes": ROUTES, "sharedModules": SHARED_MODULES,
+ "sharedAtoms": SHARED_ATOMS, "privateAtomsPerRoute": PRIVATE_ATOMS,
+ "unknownLocalModules": 1, "threshold": 2})
+ );
+ assert_eq!(
+ summary["sheetCountSavings"],
+ json!({"numerator": SHARED_MODULES - 1,
+ "denominator": SHARED_MODULES})
+ );
+ for (name, copies) in [("baseline", SHARED_MODULES), ("atom", 1)] {
+ let mut modules = fixture();
+ let before = Snapshot {
+ sheets: serde_json::from_value(decoded[name]["beforeSheets"].clone())
+ .unwrap_or_else(|error| panic!("{error}")),
+ code: serde_json::from_value(decoded[name]["beforeCode"].clone())
+ .unwrap_or_else(|error| panic!("{error}")),
+ };
+ let after = Snapshot {
+ sheets: serde_json::from_value(decoded[name]["afterSheets"].clone())
+ .unwrap_or_else(|error| panic!("{error}")),
+ code: serde_json::from_value(decoded[name]["afterCode"].clone())
+ .unwrap_or_else(|error| panic!("{error}")),
+ };
+ assert_eq!(before.sheets.len(), modules.len() + 1);
+ assert_eq!(before.code.len(), modules.len());
+ before.assert_loaded_definitions(&modules);
+ for module in &mut modules[..SHARED_MODULES] {
+ module.atoms[0].value = "900000px".to_string();
+ }
+ assert_eq!(after.sheets.len(), modules.len() + 1);
+ assert_eq!(after.code.len(), modules.len());
+ after.assert_loaded_definitions(&modules);
+ assert_eq!(
+ before.sheets[0].contains("{width:100000px}"),
+ name == "atom"
+ );
+ assert_eq!(
+ after
+ .sheets
+ .iter()
+ .map(|s| s.matches("{width:900000px}").count())
+ .sum::(),
+ copies
+ );
+ assert_eq!(
+ after
+ .sheets
+ .iter()
+ .map(|s| s.matches("{width:100000px}").count())
+ .sum::(),
+ 0
+ );
+ assert_eq!(after.changed(&before).0, copies);
+ assert_eq!(summary["sessionRaw"][name], json!(before.raw_bytes()));
+ assert_eq!(
+ summary["invalidationRaw"][name],
+ json!(after.changed(&before).1)
+ );
+ assert_eq!(summary["changedSheets"][name], json!(copies));
+ assert_eq!(
+ summary["jsxClassNameRawBytes"][name],
+ json!(before.name_bytes())
+ );
+ assert_eq!(
+ summary["cssSelectorNameRawBytes"][name],
+ json!(before.selector_name_bytes())
+ );
+ }
+}
diff --git a/bindings/devup-ui-wasm/src/atom_hoist_measurement/snapshot.rs b/bindings/devup-ui-wasm/src/atom_hoist_measurement/snapshot.rs
new file mode 100644
index 000000000..4bcb098df
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/atom_hoist_measurement/snapshot.rs
@@ -0,0 +1,87 @@
+use super::{Module, with_style_sheet};
+use std::collections::HashSet;
+
+fn classes(code: &str) -> Vec<&str> {
+ code.split("className=\"")
+ .skip(1)
+ .flat_map(|part| part.split('"').take(1).flat_map(str::split_whitespace))
+ .collect()
+}
+
+pub(super) struct Snapshot {
+ pub(super) sheets: Vec,
+ pub(super) code: Vec,
+}
+
+impl Snapshot {
+ pub(super) fn capture(modules: &[Module], code: &[String]) -> Self {
+ let mut sheets = vec![with_style_sheet(|s| s.create_css(None, false))];
+ sheets.extend(
+ modules
+ .iter()
+ .map(|m| with_style_sheet(|s| s.create_css(Some(&m.path), true))),
+ );
+ Self {
+ sheets,
+ code: code.to_vec(),
+ }
+ }
+
+ pub(super) fn assert_loaded_definitions(&self, modules: &[Module]) {
+ for (i, module) in modules.iter().enumerate() {
+ let names = classes(&self.code[i]);
+ let expected = module.declarations();
+ assert_eq!(
+ names.len(),
+ expected.len(),
+ "{}: static JSX classes",
+ module.path
+ );
+ assert!(!self.code[i].contains(" usize {
+ self.sheets.iter().map(String::len).sum()
+ }
+
+ pub(super) fn name_bytes(&self) -> usize {
+ self.code
+ .iter()
+ .flat_map(|code| classes(code))
+ .map(str::len)
+ .sum()
+ }
+
+ pub(super) fn selector_name_bytes(&self) -> usize {
+ let names: HashSet<&str> = self.code.iter().flat_map(|code| classes(code)).collect();
+ names
+ .iter()
+ .map(|name| {
+ name.len()
+ * self
+ .sheets
+ .iter()
+ .map(|sheet| sheet.matches(&format!(".{name}{{")).count())
+ .sum::()
+ })
+ .sum()
+ }
+
+ pub(super) fn changed(&self, before: &Self) -> (usize, usize) {
+ let changed: Vec<&String> = self
+ .sheets
+ .iter()
+ .zip(&before.sheets)
+ .filter_map(|(after, before)| (after != before).then_some(after))
+ .collect();
+ (changed.len(), changed.iter().map(|sheet| sheet.len()).sum())
+ }
+}
diff --git a/bindings/devup-ui-wasm/src/atom_tests.rs b/bindings/devup-ui-wasm/src/atom_tests.rs
new file mode 100644
index 000000000..cf8253505
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/atom_tests.rs
@@ -0,0 +1,286 @@
+use super::*;
+use serial_test::serial;
+use std::collections::HashSet;
+
+mod lifecycle;
+
+fn setup() {
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ register_theme_internal(sheet::theme::Theme::default());
+ css::atom_hoist::set_atom_hoist(Some(2));
+ css::file_routes::set_file_routes(HashMap::from([
+ ("a.tsx".to_string(), HashSet::from([0, 1])),
+ ("b.tsx".to_string(), HashSet::from([0, 1])),
+ ("private.tsx".to_string(), HashSet::from([0])),
+ ("styles.css.ts".to_string(), HashSet::from([0, 1])),
+ ]));
+ seed_file_map(vec!["a.tsx".into(), "b.tsx".into(), "private.tsx".into()]);
+}
+
+fn compile(file: &str, source: &str) -> Output {
+ code_extract_internal(
+ file,
+ source,
+ "@devup-ui/react",
+ "df".to_string(),
+ false,
+ false,
+ false,
+ HashMap::from([(
+ "@vanilla-extract/css".to_string(),
+ ImportAlias::NamedToNamed,
+ )]),
+ )
+ .unwrap_or_else(|error| panic!("{error}"))
+}
+
+fn emitted(file: &str) -> String {
+ with_style_sheet(|sheet| {
+ format!(
+ "{}{}",
+ sheet.create_css(None, false),
+ sheet.create_css(Some(file), false)
+ )
+ })
+}
+
+fn assert_references_resolve(code: &str, css: &str) {
+ // Read generated payloads, not import paths, identifiers or CSS property keys.
+ let literals =
+ css::utils::compile_regex(r#""(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`"#);
+ let class = css::utils::compile_regex(
+ r"^(?:du-)?(?:(?:(?:[a-z_][a-z0-9_]*|[a-z0-9_]*a-d|F(?:L[a-z0-9_]{0,16}|H[a-z0-9_]{16}))-)?[OR](?:L[a-z0-9_-]+|H[a-z0-9_]{16})|(?:[a-z_][a-z0-9_]*|[a-z0-9_]*a-d)-(?:[a-z_][a-z0-9_]*|[a-z0-9_]*a-d))$",
+ );
+ let animation = css::utils::compile_regex(r"^(?:du-)?K(?:L[a-z0-9_-]+|H[a-z0-9_]{16})$");
+ let variable = css::utils::compile_regex(
+ r"^---(?:du-)?S(?:[a-z_][a-z0-9_]*|U[a-z0-9_]+)-[a-z_][a-z0-9_]*(?:-[a-z_][a-z0-9_]*)?$",
+ );
+ let mut classes = 0;
+ for literal in literals.find_iter(code) {
+ let before = code[..literal.start()].trim_end();
+ if before.ends_with("import") || before.ends_with("from") {
+ continue;
+ }
+ let quoted = literal.as_str();
+ let property_key = matches!(before.chars().last(), Some('{' | ','))
+ && code[literal.end()..].trim_start().starts_with(':');
+ for token in quoted[1..quoted.len() - 1].split_whitespace() {
+ if variable.is_match(token) {
+ assert!(
+ css.contains(&format!("var({token})")),
+ "unemitted variable {token}"
+ );
+ } else if animation.is_match(token) {
+ assert!(
+ css.contains(&format!("@keyframes {token}{{")),
+ "unemitted keyframes {token}"
+ );
+ } else if class.is_match(token) && !property_key {
+ let selector = format!(".{token}");
+ assert!(
+ css.match_indices(&selector).any(|(at, _)| {
+ matches!(
+ css[at + selector.len()..].chars().next(),
+ Some('{' | ':' | '[' | '.' | '#' | ' ' | ',' | '>' | '+' | '~')
+ )
+ }),
+ "unemitted class {token}\n{css}"
+ );
+ classes += 1;
+ }
+ }
+ }
+ assert!(classes > 0, "fixture must generate classes: {code}");
+}
+
+#[rstest::rstest]
+#[case(r#"
"#)]
+#[case(r#"
"#)]
+#[case(r#"
"#)]
+#[case(r#"
"#)]
+#[case(r#"
"#)]
+#[case(r#"const fade="KHaaaaaaaaaaaaaaaa"; const cls="OLcolor-vred";"#)]
+#[case(r#"const fade="du-KLsfrom-e-"; const cls="OLcolor-vred";"#)]
+#[case(r#""#)]
+#[case(r#"const cls="OLcolor-vred"; const conditional=rawPresent ? "c-a" : "";"#)]
+#[case(r#"const cls="OLcolor-vred"; const conditional=rawPresent ? "" : "c-a";"#)]
+#[should_panic(expected = "unemitted")]
+fn reference_checker_rejects_dangling_payloads_when_another_class_resolves(#[case] code: &str) {
+ // Given / When / Then: a valid class cannot hide another unresolved reference.
+ assert_references_resolve(code, &format!(".{}{{color:red}}", "OLcolor-vred"));
+}
+
+#[test]
+fn reference_checker_resolves_classes_when_only_a_conditional_payload_is_present() {
+ // Given
+ let code = r#"const cls=rawPresent ? "c-a" : "";"#;
+ // When / Then
+ assert_references_resolve(code, &format!(".{}{{color:red}}", "c-a"));
+}
+
+#[test]
+fn reference_checker_ignores_nonpayload_names_when_valid_classes_are_present() {
+ // Given
+ let code = r#"import "devup-ui"; import "df/devup-ui/a.css";
+ const OLmissing=()=>({"font-family":"Arial", "c-missing":"Arial"});
+ export const cls="du-c-a du-OLcolor-vred";"#;
+ // When / Then
+ assert_references_resolve(
+ code,
+ &format!(
+ ".{}{{padding:0}}.{}{{color:red}}",
+ "du-c-a", "du-OLcolor-vred"
+ ),
+ );
+}
+
+#[test]
+#[should_panic(expected = "fixture must generate classes")]
+fn reference_checker_rejects_fixtures_when_no_class_is_generated() {
+ assert_references_resolve("export const x=
;", "");
+}
+
+const SOURCE_A: &str = r#"
+import { Box, css, styled, keyframes } from '@devup-ui/react';
+const fade = keyframes({from:{opacity:0,transform:'scale(0)'},to:{opacity:1}});
+const cls = css({color:'red', _hover:{color:'blue'}, p:['1px',null,'2px']});
+const Card = styled('div', {display:'flex', color:'red'});
+export const view = <>
>;
+"#;
+
+const SOURCE_B: &str = r#"
+import { Box, css, keyframes } from '@devup-ui/react';
+const fade = keyframes({from:{opacity:0,transform:'scale(0)'},to:{opacity:1}});
+const cls = css({color:'red', _hover:{color:'blue'}, p:['1px',null,'2px']});
+export const view = ;
+"#;
+
+#[test]
+#[serial]
+fn atom_references_and_output_are_stable_when_extraction_order_changes() {
+ // Given
+ let build = |reverse: bool| {
+ setup();
+ let mut sources = [("a.tsx", SOURCE_A), ("b.tsx", SOURCE_B)];
+ if reverse {
+ sources.reverse();
+ }
+ let mut output = BTreeMap::new();
+ // When
+ for (file, source) in sources {
+ let result = compile(file, source);
+ assert!(result.updated_base_style);
+ output.insert(file, result.code);
+ }
+ // Then
+ for (file, code) in &output {
+ assert_references_resolve(code, &emitted(file));
+ }
+ let css = with_style_sheet(|sheet| sheet.create_css(None, false));
+ assert!(css.contains("@layer o1,o2"), "{css}");
+ assert!(css.contains("!important"));
+ assert!(!css.contains("differentRuntimeCode"));
+ assert!(
+ !with_style_sheet(|sheet| sheet.create_css(Some("a.tsx"), false))
+ .contains("background:red")
+ );
+ (output, css, emitted("a.tsx"), emitted("b.tsx"))
+ };
+ assert_eq!(build(false), build(true));
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn atom_names_and_emission_are_stable_when_debug_changes_with_prefix() {
+ // Given
+ let build = |debug| {
+ setup();
+ set_prefix(Some("du-".to_string()));
+ css::debug::set_debug(debug);
+ // When
+ let output = compile("a.tsx", SOURCE_A);
+ let css = emitted("a.tsx");
+ // Then
+ assert_references_resolve(&output.code, &css);
+ (output.code, css)
+ };
+ assert_eq!(build(false), build(true));
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+}
+
+#[test]
+#[serial]
+fn same_atom_stays_local_when_a_private_bucket_also_uses_it() {
+ // Given
+ setup();
+ let source = r#"import {Box} from '@devup-ui/react'; export const x= ;"#;
+ // When
+ let shared = compile("a.tsx", source);
+ let private = compile("private.tsx", source);
+ // Then
+ assert_references_resolve(&shared.code, &emitted("a.tsx"));
+ assert_references_resolve(&private.code, &emitted("private.tsx"));
+ let global = with_style_sheet(|sheet| sheet.create_css(None, false));
+ let local = with_style_sheet(|sheet| sheet.create_css(Some("private.tsx"), false));
+ assert_eq!(global.matches("color:red").count(), 1);
+ assert_eq!(local.matches("color:red").count(), 1);
+ assert_ne!(shared.code, private.code);
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn compat_apis_reference_emitted_atoms_when_bucket_is_predeclared() {
+ // Given
+ setup();
+ let sources = [
+ (
+ "a.tsx",
+ r"import * as stylex from '@stylexjs/stylex';
+ const s=stylex.create({root:{color:'red',':hover':{color:'blue'}}});
+ export const x=
;",
+ ),
+ (
+ "styles.css.ts",
+ r"import {style,layer,keyframes} from '@vanilla-extract/css';
+ const l=layer('cards'); const fade=keyframes({from:{opacity:0},to:{opacity:1}});
+ export const card=style({'@layer':{[l]:{color:'red',animation:`${fade} 1s`}},
+ selectors:{'&:hover':{color:'blue'}}});",
+ ),
+ ];
+ // When
+ for (file, source) in sources {
+ let output = compile(file, source);
+ // Then
+ assert_references_resolve(&output.code, &emitted(file));
+ }
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn local_ordered_styles_signal_shared_layer_order_changes() {
+ // Given
+ setup();
+ let plain = compile(
+ "private.tsx",
+ r#"import {Box} from '@devup-ui/react'; export const x= ;"#,
+ );
+ assert!(!plain.updated_base_style);
+ let source = r#"import {Box} from '@devup-ui/react'; export const x=;"#;
+ // When
+ let output = compile("private.tsx", source);
+ // Then
+ assert!(output.updated_base_style);
+ assert_references_resolve(&output.code, &emitted("private.tsx"));
+ assert!(with_style_sheet(|sheet| sheet.create_css(None, false)).contains("@layer o1;"));
+ assert!(!compile("private.tsx", source).updated_base_style);
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/atom_tests/lifecycle.rs b/bindings/devup-ui-wasm/src/atom_tests/lifecycle.rs
new file mode 100644
index 000000000..d6828f82f
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/atom_tests/lifecycle.rs
@@ -0,0 +1,285 @@
+use super::*;
+
+const RED: &str = "import {Box} from '@devup-ui/react'; export const x=;";
+
+#[test]
+#[serial]
+fn unknown_and_late_reach_stay_local_until_reset() {
+ // Given
+ setup();
+ compile("a.tsx", RED);
+ let mut routes = css::file_routes::get_file_routes();
+ routes.insert("unknown.tsx".into(), HashSet::from([0, 1]));
+ routes.insert("private.tsx".into(), HashSet::from([0, 1]));
+ css::file_routes::set_file_routes(routes.clone());
+ css::atom_hoist::set_atom_hoist(Some(1));
+ // When
+ for file in ["unknown.tsx", "private.tsx"] {
+ let output = compile(file, RED);
+ // Then
+ assert_references_resolve(&output.code, &emitted(file));
+ assert!(
+ with_style_sheet(|sheet| sheet.create_css(Some(file), false)).contains("color:red")
+ );
+ }
+ reset_build_state_internal();
+ css::atom_hoist::set_atom_hoist(Some(2));
+ css::file_routes::set_file_routes(routes);
+ let output = compile("unknown.tsx", RED);
+ assert_references_resolve(&output.code, &emitted("unknown.tsx"));
+ assert!(
+ !with_style_sheet(|sheet| sheet.create_css(Some("unknown.tsx"), false))
+ .contains("color:red")
+ );
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn imported_sheet_keeps_recorded_placement_after_configuration_reset() {
+ // Given
+ setup();
+ let shared = compile("a.tsx", RED);
+ let private = compile("private.tsx", RED);
+ let global = with_style_sheet(|sheet| sheet.create_css(None, false));
+ let local = with_style_sheet(|sheet| sheet.create_css(Some("private.tsx"), false));
+ let serialized = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ reset_build_state_internal();
+ let imported = serde_json::from_str(&serialized).unwrap_or_else(|error| panic!("{error}"));
+ // When
+ import_sheet_internal(imported).unwrap_or_else(|error| panic!("{error}"));
+ seed_file_map(vec!["a.tsx".into(), "b.tsx".into(), "private.tsx".into()]);
+ // Then
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(None, false)),
+ global
+ );
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("private.tsx"), false)),
+ local
+ );
+ assert_references_resolve(&shared.code, &emitted("a.tsx"));
+ assert_references_resolve(&private.code, &emitted("private.tsx"));
+ css::atom_hoist::set_atom_hoist(Some(2));
+ css::file_routes::set_file_routes(HashMap::from([(
+ "private.tsx".into(),
+ HashSet::from([0, 1]),
+ )]));
+ let resumed = compile("private.tsx", RED);
+ assert_eq!(private.code, resumed.code);
+ let shared_again = compile("b.tsx", RED);
+ assert_references_resolve(&shared_again.code, &emitted("b.tsx"));
+ assert!(
+ !with_style_sheet(|sheet| sheet.create_css(Some("b.tsx"), false)).contains("color:red")
+ );
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn canonical_bucket_reach_is_frozen_before_imported_styles_are_processed() {
+ // Given
+ setup();
+ import_canonical_map_internal(HashMap::from([
+ ("member.tsx".into(), "bucket.tsx".into()),
+ ("peer.tsx".into(), "bucket.tsx".into()),
+ ]));
+ css::file_routes::set_file_routes(HashMap::from([
+ ("member.tsx".into(), HashSet::from([0])),
+ ("peer.tsx".into(), HashSet::from([1])),
+ ]));
+ let resolver = |specifier: &str, _importer: &str| {
+ (specifier == "./tokens").then(|| ResolvedModule {
+ path: "tokens.ts".to_string(),
+ code: "export const styles={color:'red',_hover:{color:'blue'}};".to_string(),
+ })
+ };
+ // When
+ let output = code_extract_with_modules_internal(
+ "member.tsx",
+ "import {Box} from '@devup-ui/react'; import {styles} from './tokens'; export const x= ;",
+ "@devup-ui/react",
+ "df".to_string(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ &resolver,
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ assert_references_resolve(&output.code, &emitted("bucket.tsx"));
+ assert!(with_style_sheet(|sheet| sheet.create_css(None, false)).contains("color:red"));
+ assert!(
+ !with_style_sheet(|sheet| sheet.create_css(Some("bucket.tsx"), false))
+ .contains("color:red")
+ );
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn important_and_plain_dynamic_values_emit_distinct_rules_in_every_mode() {
+ // Given
+ let source = r"import {Box} from '@devup-ui/react';
+ export const x=<> >;";
+ for atom in [false, true] {
+ setup();
+ if !atom {
+ css::atom_hoist::set_atom_hoist(None);
+ }
+ // When
+ let output = compile("private.tsx", source);
+ // Then
+ let css = emitted("private.tsx");
+ let properties = with_style_sheet(|sheet| {
+ sheet.properties["private.tsx"]
+ .values()
+ .flat_map(BTreeMap::values)
+ .flatten()
+ .cloned()
+ .collect::>()
+ });
+ let rules: Vec<_> = properties
+ .iter()
+ .filter(|prop| prop.property == "color")
+ .collect();
+ let resets: Vec<_> = properties.iter().filter(|prop| prop.owner_reset).collect();
+ assert_eq!(rules.len(), 3);
+ assert_eq!(resets.len(), 3);
+ assert_eq!(
+ rules
+ .iter()
+ .map(|prop| &prop.class_name)
+ .collect::>()
+ .len(),
+ 3
+ );
+ assert_eq!(
+ rules
+ .iter()
+ .map(|prop| &prop.value)
+ .collect::>()
+ .len(),
+ 3
+ );
+ let assignments = css::utils::compile_regex(
+ r#"(?s)["'](---(?:du-)?S[a-z0-9_U-]+)["']\s*:\s*.*?\]\)\((tone\b|differentCode\b|`\$\{tone\}`)\)"#,
+ );
+ let sites: Vec<_> = assignments.captures_iter(&output.code).collect();
+ assert_eq!(sites.len(), 3, "{}", output.code);
+ assert_eq!(
+ sites
+ .iter()
+ .map(|site| &site[1])
+ .collect::>()
+ .len(),
+ 3
+ );
+ assert_eq!(&sites[0][2], "tone");
+ assert_eq!(&sites[1][2], "differentCode");
+ assert!(matches!(&sites[2][2], "tone" | "`${tone}`"));
+ assert_references_resolve(&output.code, &css);
+ for (index, site) in sites.iter().enumerate() {
+ let variable = &site[1];
+ let value = format!(
+ "var({variable}){}",
+ if index == 2 { " !important" } else { "" }
+ );
+ let rule = rules
+ .iter()
+ .find(|prop| prop.value == value)
+ .unwrap_or_else(|| panic!("missing site color rule for {value}\n{css}"));
+ assert!(!rule.owner_reset);
+ assert!(output.code.contains(&rule.class_name));
+ let owned: Vec<_> = resets
+ .iter()
+ .filter(|prop| prop.class_name == rule.class_name)
+ .collect();
+ assert_eq!(owned.len(), 1);
+ assert_eq!(owned[0].property, variable);
+ assert_eq!(owned[0].value, "initial");
+ assert_eq!(owned[0].selector, None);
+ assert_eq!(owned[0].layer, None);
+ let selector = format!(".{}{{", rule.class_name);
+ assert_eq!(css.matches(&selector).count(), 1, "{css}");
+ let block = css
+ .split_once(&selector)
+ .unwrap_or_else(|| panic!("missing own class block {selector}\n{css}"))
+ .1;
+ let block = block
+ .split_once('}')
+ .unwrap_or_else(|| panic!("unclosed class block {selector}\n{css}"))
+ .0;
+ let declarations: HashSet<_> =
+ block.split(';').filter(|value| !value.is_empty()).collect();
+ assert_eq!(
+ declarations,
+ HashSet::from([
+ format!("{variable}:initial").as_str(),
+ format!("color:{value}").as_str(),
+ ])
+ );
+ }
+ }
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn keyframe_content_and_scope_resolve_when_using_single_css() {
+ // Given
+ setup();
+ let source = r"import {Box,keyframes} from '@devup-ui/react';
+ const a=keyframes({from:{opacity:0,transform:'scale(0)'},to:{opacity:1}});
+ const b=keyframes({from:{transform:'scale(0)',opacity:0},to:{opacity:1}});
+ const c=keyframes({from:{opacity:0,transform:'scale(0)'},to:{opacity:0.5}});
+ export const x=<> >;";
+ // When
+ let output = code_extract_internal(
+ "a.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ let css = with_style_sheet(|sheet| sheet.create_css(None, false));
+ assert_references_resolve(&output.code, &css);
+ with_style_sheet(|sheet| {
+ assert_eq!(sheet.keyframes[""].len(), 2);
+ for name in sheet.keyframes[""].keys() {
+ assert!(output.code.contains(name));
+ assert!(css.contains(&format!("@keyframes {name}{{")));
+ }
+ });
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn global_css_keyframes_use_the_same_scope_as_their_declaration() {
+ // Given
+ setup();
+ let source = r"import {Box,keyframes,globalCss} from '@devup-ui/react';
+ const fade=keyframes({from:{opacity:0},to:{opacity:1}});
+ globalCss({body:{animation:`${fade} 1s`}}); export const x= ;";
+ // When
+ let output = compile("a.tsx", source);
+ // Then
+ assert_references_resolve(&output.code, &emitted("a.tsx"));
+ let global = with_style_sheet(|sheet| sheet.create_css(None, false));
+ let local = with_style_sheet(|sheet| sheet.create_css(Some("a.tsx"), false));
+ with_style_sheet(|sheet| {
+ for name in sheet.keyframes["a.tsx"].keys() {
+ assert!(global.contains(&format!("animation:{name} 1s")), "{global}");
+ assert!(local.contains(&format!("@keyframes {name}{{")));
+ assert!(!global.contains(&format!("@keyframes {name}{{")));
+ }
+ });
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/cache6_adoption_tests.rs b/bindings/devup-ui-wasm/src/cache6_adoption_tests.rs
new file mode 100644
index 000000000..72911af66
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache6_adoption_tests.rs
@@ -0,0 +1,163 @@
+use super::cache6_test_support::*;
+
+#[rstest]
+#[case(0)]
+#[case(1)]
+#[case(2)]
+#[case(3)]
+#[case(4)]
+#[case(5)]
+#[serial]
+fn genuine_state_is_adopted_before_work_when_raw6_is_complete(#[case] family: u8) {
+ // Given
+ let _guard = Guard::new();
+ configure_typography();
+ output("a", family);
+ with_style_sheet_mut(|sheet| {
+ sheet.add_css("literal", "body{}");
+ sheet.add_import("literal", "literal.css");
+ sheet.add_font_face(
+ "literal",
+ &BTreeMap::from([("font-family".into(), "literal".into())]),
+ );
+ sheet.add_property("first", "color", 0, "blue", None, None, None);
+ sheet.add_property("second", "color", 0, "blue", None, None, None);
+ });
+ seed_file_map(vec!["unused".into()]);
+ css::class_map::with_class_map_mut(|map| {
+ map.insert("unused".into(), HashMap::from([("reservation".into(), 0)]));
+ });
+ let bytes = encoded();
+ let expected_maps = cache6_session::Maps::capture();
+ with_style_sheet_mut(|sheet| sheet.source_ids = css::file_map::get_original_ids());
+ let expected_sheet = with_style_sheet(LiveCheckpoint::capture);
+ fresh();
+ configure_typography();
+ exact_test_support::compile(
+ "manual.tsx",
+ "import {css} from '@devup-ui/react';export const x=css({color:'green'});",
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ assert_ne!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ // When
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ // Then
+ assert_eq!(cache6_session::Maps::capture(), expected_maps);
+ assert_eq!(with_style_sheet(LiveCheckpoint::capture), expected_sheet);
+ assert_eq!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ cache6_restore::RESTORE.with_borrow(|state| assert!(state.session.is_some()));
+}
+
+#[rstest]
+#[case(None)]
+#[case(Some(BTreeSet::new()))]
+#[case(Some(BTreeSet::from(["a".into()])))]
+#[serial]
+fn empty_counter_state_and_plan_are_adopted_when_snapshot_has_no_records(
+ #[case] plan: Option>,
+) {
+ // Given
+ let _guard = Guard::new();
+ update(
+ &Default::default(),
+ OutputRoute {
+ raw_source: "a",
+ single_css: true,
+ import_main_css: false,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ css::atom_hoist::restore_atom_plan(plan.clone());
+ with_style_sheet_mut(|sheet| sheet.atom_plan.clone_from(&plan));
+ let bytes = encoded();
+ let expected = authority();
+ fresh();
+ let absent = with_style_sheet(LiveCheckpoint::capture);
+ // When
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ // Then
+ assert_eq!(authority(), expected);
+ assert_ne!(with_style_sheet(LiveCheckpoint::capture), absent);
+ assert_eq!(css::atom_hoist::atom_plan(), plan);
+}
+
+#[test]
+#[serial]
+fn two_lineages_survive_adoption_when_equal_names_share_a_slot() {
+ // Given
+ let _guard = Guard::new();
+ for source in ["a", "b"] {
+ let items = with_original(
+ FixtureRequest {
+ filename: source,
+ source: "abcdef",
+ },
+ || {
+ let mut declaration = ExtractStaticStyle::new("color", "red", 0, None);
+ declaration.style_order = Some(0);
+ FxHashSet::from_iter([ExtractStyleValue::Static(declaration)])
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ update(
+ &items,
+ OutputRoute {
+ raw_source: "a",
+ single_css: false,
+ import_main_css: false,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ }
+ let bytes = encoded();
+ let value: serde_json::Value =
+ serde_json::from_slice(&bytes).unwrap_or_else(|error| panic!("{error:?}"));
+ let witnesses: Vec<_> = value["evidence"]["counters"]
+ .as_object()
+ .unwrap_or_else(|| panic!("namespaces"))
+ .values()
+ .flat_map(|slots| {
+ slots
+ .as_object()
+ .unwrap_or_else(|| panic!("slots"))
+ .values()
+ })
+ .flat_map(|value| value.as_array().unwrap_or_else(|| panic!("witnesses")))
+ .collect();
+ assert_eq!(witnesses.len(), 2);
+ assert_eq!(witnesses[0]["proof"], witnesses[1]["proof"]);
+ assert_ne!(witnesses[0]["lineage"], witnesses[1]["lineage"]);
+ with_style_sheet_mut(|sheet| sheet.source_ids = css::file_map::get_original_ids());
+ let expected = authority();
+ fresh();
+ // When
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ // Then
+ assert_eq!(authority(), expected);
+}
+
+#[test]
+#[serial]
+fn ordinary_compiler_stays_current_when_constructor_feature_is_enabled() {
+ // Given
+ let _guard = Guard::new();
+ // When
+ with_original(
+ FixtureRequest {
+ filename: "fixture",
+ source: "const a=1;",
+ },
+ || {
+ exact_test_support::compile(
+ "ordinary.tsx",
+ "import {css} from '@devup-ui/react';export const x=css({color:'red'});",
+ )
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"))
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ // Then
+ assert_eq!(css::file_map::original_id("ordinary.tsx"), None);
+ assert_ne!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ assert!(with_style_sheet_mut(|sheet| CounterSheet::new(sheet).export_snapshot6()).is_err());
+}
diff --git a/bindings/devup-ui-wasm/src/cache6_configuration_tests.rs b/bindings/devup-ui-wasm/src/cache6_configuration_tests.rs
new file mode 100644
index 000000000..1dd7b589c
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache6_configuration_tests.rs
@@ -0,0 +1,215 @@
+use super::cache6_test_support::*;
+
+#[path = "cache6_transition_tests.rs"]
+mod transitions;
+
+#[rstest]
+#[case(false, 0)]
+#[case(false, 1)]
+#[case(false, 2)]
+#[case(false, 3)]
+#[case(false, 4)]
+#[case(true, 0)]
+#[case(true, 1)]
+#[case(true, 2)]
+#[case(true, 3)]
+#[case(true, 4)]
+#[serial]
+fn active_ingress_rejects_immediately_when_exact_depth_is_one_or_two(
+ #[case] nested: bool,
+ #[case] ingress: u8,
+) {
+ // Given
+ let _guard = Guard::new();
+ output("cached", 0);
+ let bytes = encoded();
+ fresh();
+ with_style_sheet_mut(|sheet| {
+ sheet.add_css("sentinel", concat!("body", "{", "color:blue", "}"))
+ });
+ seed_file_map(vec!["sentinel".into()]);
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ output("new", 1);
+ seed_file_map(vec!["journal".into()]);
+ let before = authority();
+ // When
+ cache6_restore::RESTORE.with_borrow(|before_session| {
+ let refusal = || {
+ let result = match ingress {
+ 0 => cache6_restore::import(b"malformed"),
+ 1 => cache6_restore::classes(None),
+ 2 => cache6_restore::files(Some(BTreeMap::from([("wrong".into(), 77)]))),
+ 3 => cache6_restore::seeded(&["wrong".into()]),
+ 4 => cache6_restore::clear(),
+ _ => panic!("ingress"),
+ };
+ // Then: observe inside the deepest callback, before CSS Err rollback.
+ assert_eq!(
+ result,
+ Err(EvidenceError::ActiveExact(
+ css::admission::ActiveExactAttempt
+ ))
+ );
+ assert_eq!(authority(), before);
+ cache6_restore::RESTORE.with_borrow(|state| assert_eq!(state, before_session));
+ result
+ };
+ let result = css::exact_attempt::with_exclusive_attempt(|| {
+ if nested {
+ css::exact_attempt::with_exclusive_attempt(refusal)
+ } else {
+ refusal()
+ }
+ });
+ assert_eq!(
+ result,
+ Err(EvidenceError::ActiveExact(
+ css::admission::ActiveExactAttempt
+ ))
+ );
+ });
+}
+
+#[rstest]
+#[case(0)]
+#[case(1)]
+#[case(2)]
+#[case(3)]
+#[serial]
+fn install_rechecks_actual_authority_when_validation_precedes_configuration_change(
+ #[case] change: u8,
+) {
+ // Given
+ let _guard = Guard::new();
+ output("cached", 0);
+ let bytes = encoded();
+ let certificate = snapshot6::validate_snapshot(
+ snapshot6::parse(&bytes).unwrap_or_else(|error| panic!("{error:?}")),
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ fresh();
+ with_style_sheet_mut(|sheet| {
+ sheet.add_css("sentinel", concat!("body", "{", "color:blue", "}"))
+ });
+ seed_file_map(vec!["sentinel".into()]);
+ match change {
+ 0 => set_prefix(Some("different".into())),
+ 1 => set_debug(true),
+ 2 => set_atom_hoist(Some(2)),
+ 3 => css::atom_hoist::restore_atom_plan(Some(BTreeSet::new())),
+ _ => panic!("change"),
+ }
+ let before = authority();
+ // When
+ let result = with_style_sheet_mut(|sheet| certificate.install_into(sheet));
+ // Then
+ assert_eq!(result, Err(EvidenceError::Configuration));
+ assert_eq!(authority(), before);
+}
+
+#[rstest]
+#[case(0)]
+#[case(1)]
+#[case(2)]
+#[case(3)]
+#[case(4)]
+#[case(5)]
+#[serial]
+fn replacement_cannot_clear_target_latch_when_a_restore_session_exists(#[case] ingress: u8) {
+ // Given
+ let _guard = Guard::new();
+ output("a", 0);
+ let bytes = encoded();
+ fresh();
+ with_style_sheet_mut(|sheet| sheet.add_css("prior", concat!("body", "{", "color:blue", "}")));
+ seed_file_map(vec!["prior-seed".into()]);
+ let (before_maps, before_sheet) = cache6_session::Restore::before();
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ let mut expected_session = cache6_session::Session::installed(before_maps, before_sheet);
+ let seeds = vec![
+ vec!["journal-b".into(), "journal-b".into()],
+ vec!["journal-a".into(), "journal-b".into()],
+ ];
+ for batch in &seeds {
+ seed_file_map(batch.clone());
+ }
+ expected_session.seeds = seeds;
+ let expected_restore = cache6_session::Restore {
+ session: Some(expected_session),
+ ..Default::default()
+ };
+ with_style_sheet_mut(|sheet| {
+ sheet.properties.clear();
+ sheet.add_css("literal", "body{}");
+ });
+ let before = authority();
+ let rejection = with_style_sheet(snapshot6::check_install_target);
+ assert!(matches!(rejection, Err(EvidenceError::Kernel(_))));
+ // When
+ let result = match ingress {
+ 0 => cache6_restore::import(&bytes),
+ 1 => cache6_restore::import(b"bad"),
+ 2 => cache6_restore::classes(None),
+ 3 => cache6_restore::files(None),
+ 4 => cache6_restore::classes(Some(BTreeMap::new())),
+ 5 => cache6_restore::files(Some(BTreeMap::new())),
+ _ => panic!("ingress"),
+ };
+ // Then
+ assert_eq!(result, Ok(()));
+ assert_eq!(authority(), before);
+ assert_eq!(with_style_sheet(snapshot6::check_install_target), rejection);
+ cache6_restore::RESTORE.with_borrow(|state| assert_eq!(state, &expected_restore));
+}
+
+#[test]
+#[serial]
+fn fresh_build_settings_survive_adoption_and_late_rejection_when_cached_routing_differs() {
+ // Given
+ let _guard = Guard::new();
+ configure_typography();
+ output("a", 3);
+ let bytes = encoded();
+ fresh();
+ let theme = sheet::theme::Theme {
+ breakpoints: vec![19, 31],
+ ..Default::default()
+ };
+ register_theme_internal(theme);
+ register_shorthands_internal(BTreeMap::from([("freshInset".into(), vec!["left".into()])]));
+ import_canonical_map_internal(HashMap::from([("a".into(), "fresh-root".into())]));
+ import_file_routes_internal(HashMap::from([(
+ "a".into(),
+ std::collections::HashSet::from([9]),
+ )]));
+ let theme_css = with_style_sheet(|sheet| sheet.theme.to_css());
+ // When
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ assert_eq!(cache6_restore::files(None), Ok(()));
+ // Then
+ assert_eq!(with_style_sheet(|sheet| sheet.theme.to_css()), theme_css);
+ assert_eq!(css::file_map::canonical("a"), "fresh-root");
+ assert_eq!(
+ css::get_custom_shorthand_names(),
+ vec!["freshInset".to_string()]
+ );
+ assert_eq!(get_prefix(), None);
+ assert!(!is_debug());
+ assert_eq!(css::atom_hoist::atom_hoist_threshold(), None);
+ let resolver = |_: &str, _: &str| {
+ Some(ResolvedModule {
+ path: "fresh-tokens.ts".into(),
+ code: "export const COLOR='blue';".into(),
+ })
+ };
+ let result = code_extract_with_modules_internal("fresh.ts", concat!("import {css} from '@devup-ui/react';import {COLOR} from './tokens';export const value=css(", "{", "color:COLOR", "}", ");"),
+ "@devup-ui/react", "df".into(), false, false, false, HashMap::new(), &resolver)
+ .unwrap_or_else(|error| panic!("{error}"));
+ assert!(
+ result
+ .css()
+ .unwrap_or_else(|| panic!("css"))
+ .contains("color:blue")
+ );
+ register_shorthands_internal(BTreeMap::new());
+}
diff --git a/bindings/devup-ui-wasm/src/cache6_damage_tests.rs b/bindings/devup-ui-wasm/src/cache6_damage_tests.rs
new file mode 100644
index 000000000..4c62a123b
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache6_damage_tests.rs
@@ -0,0 +1,152 @@
+use super::cache6_test_support::*;
+use serde_json::{Value, json};
+
+fn damage(value: &mut Value, family: u8) {
+ if family >= 4 {
+ let name = value["keyframes"]["a"]
+ .as_object()
+ .unwrap_or_else(|| panic!("frames"))
+ .keys()
+ .next()
+ .unwrap_or_else(|| panic!("name"))
+ .clone();
+ value["keyframes"]["a"][&name]["from"][0][1] = json!("damaged");
+ let expansion = &mut value["evidence"]["counters"]["D9-0"]["0"][0]["proof"]["emission"]["expansion"]
+ ["Keyframes"];
+ expansion["steps"][0][1][0][1] = json!("damaged");
+ expansion["record"]["Keyframes"]["steps"][0][1][0][1] = json!("damaged");
+ } else {
+ let records = value["properties"]["a"]["255"]["0"]
+ .as_array_mut()
+ .unwrap_or_else(|| panic!("records"));
+ let record = records
+ .iter_mut()
+ .find(|record| record["r"] == false)
+ .unwrap_or_else(|| panic!("consumer"));
+ let old = record.clone();
+ record["v"] = json!("damaged");
+ let expansion =
+ &mut value["evidence"]["counters"]["D9-0"]["0"][0]["proof"]["emission"]["expansion"];
+ match family {
+ 0 => expansion["Static"][0]["Property"]["record"]["v"] = json!("damaged"),
+ 1 | 2 => expansion["Dynamic"]["consumer"]["Property"]["record"]["v"] = json!("damaged"),
+ 3 => {
+ let member = expansion["Typography"]["members"]
+ .as_array_mut()
+ .unwrap_or_else(|| panic!("members"))
+ .iter_mut()
+ .find(|member| member["Property"]["record"] == old)
+ .unwrap_or_else(|| panic!("member"));
+ member["Property"]["record"]["v"] = json!("damaged");
+ }
+ _ => panic!("family"),
+ }
+ }
+}
+
+#[rstest]
+#[case(0, false)]
+#[case(0, true)]
+#[case(1, false)]
+#[case(1, true)]
+#[case(2, false)]
+#[case(2, true)]
+#[case(3, false)]
+#[case(3, true)]
+#[case(4, false)]
+#[case(4, true)]
+#[case(5, false)]
+#[case(5, true)]
+#[serial]
+fn coordinated_record_damage_is_cold_when_independent_seed_is_unchanged(
+ #[case] family: u8,
+ #[case] supplied: bool,
+) {
+ // Given
+ let _guard = Guard::new();
+ configure_typography();
+ let cold = output("a", family);
+ let bytes = encoded();
+ let expected_maps = maps();
+ let mut value: Value =
+ serde_json::from_slice(&bytes).unwrap_or_else(|error| panic!("{error:?}"));
+ damage(&mut value, family);
+ let damaged = serde_json::to_vec(&value).unwrap_or_else(|error| panic!("{error:?}"));
+ fresh();
+ configure_typography();
+ if supplied {
+ companions(&expected_maps, false);
+ }
+ let before = authority();
+ // When
+ assert_eq!(cache6_restore::import(&damaged), Ok(()));
+ // Then
+ assert_eq!(authority(), before);
+ assert_eq!(cache_names::check(), Ok(()));
+ assert_eq!(output("a", family), cold);
+}
+
+#[rstest]
+#[case(0)]
+#[case(1)]
+#[case(2)]
+#[case(3)]
+#[case(4)]
+#[case(5)]
+#[case(6)]
+#[serial]
+fn incompatible_raw_inputs_are_cold_when_version_or_shape_is_wrong(#[case] kind: u8) {
+ // Given
+ let _guard = Guard::new();
+ let cold = output("a", 0);
+ let mut value: Value =
+ serde_json::from_slice(&encoded()).unwrap_or_else(|error| panic!("{error:?}"));
+ let bytes = match kind {
+ 0 => {
+ fresh();
+ exact_test_support::compile(
+ "current.tsx",
+ "import {css} from '@devup-ui/react';export const x=css({color:'blue'});",
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ export_sheet_internal()
+ .unwrap_or_else(|error| panic!("{error}"))
+ .into_bytes()
+ }
+ 1 => {
+ value["atomNamingVersion"] = json!(5);
+ serde_json::to_vec(&value).unwrap_or_else(|error| panic!("{error}"))
+ }
+ 2 => {
+ value["atomNamingVersion"] = json!(7);
+ serde_json::to_vec(&value).unwrap_or_else(|error| panic!("{error}"))
+ }
+ 3 => {
+ value
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("object"))
+ .remove("atomNamingVersion");
+ serde_json::to_vec(&value).unwrap_or_else(|error| panic!("{error}"))
+ }
+ 4 => b"{truncated".to_vec(),
+ 5 => b"null".to_vec(),
+ 6 => {
+ let raw = String::from_utf8(encoded()).unwrap_or_else(|error| panic!("{error}"));
+ raw.replacen(
+ "\"atomNamingVersion\":6",
+ "\"atomNamingVersion\":6,\"atomNamingVersion\":6",
+ 1,
+ )
+ .into_bytes()
+ }
+ _ => panic!("kind"),
+ };
+ fresh();
+ let before = authority();
+ // When
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ // Then
+ assert_eq!(authority(), before);
+ assert_eq!(cache_names::check(), Ok(()));
+ assert_eq!(output("a", 0), cold);
+}
diff --git a/bindings/devup-ui-wasm/src/cache6_nested_tests.rs b/bindings/devup-ui-wasm/src/cache6_nested_tests.rs
new file mode 100644
index 000000000..383faf76e
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache6_nested_tests.rs
@@ -0,0 +1,77 @@
+use super::cache6_test_support::*;
+use std::panic::{AssertUnwindSafe, catch_unwind};
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn inner_failure_restores_depth_one_state_when_outer_continues_or_aborts(#[case] commit: bool) {
+ // Given
+ let _guard = Guard::new();
+ output("a", 0);
+ let before = authority();
+ let mut local = None;
+ // When
+ let result = exact_test_support::exact(|| {
+ output("outer", 1);
+ let depth_one = authority();
+ let inner: Result<(), String> = exact_test_support::exact(|| {
+ output("inner", 2);
+ Err("depth two failure".into())
+ });
+ assert_eq!(inner, Err("depth two failure".into()));
+ assert_eq!(authority(), depth_one);
+ local = Some(depth_one);
+ if commit {
+ Ok(())
+ } else {
+ Err("outer abort".into())
+ }
+ });
+ // Then
+ assert_eq!(
+ result,
+ if commit {
+ Ok(())
+ } else {
+ Err("outer abort".into())
+ }
+ );
+ assert_eq!(
+ authority(),
+ if commit {
+ local.unwrap_or_else(|| panic!("local"))
+ } else {
+ before
+ }
+ );
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn committed_inner_success_is_subordinate_when_outer_errors_or_unwinds(#[case] unwind: bool) {
+ // Given
+ let _guard = Guard::new();
+ output("a", 0);
+ let before = authority();
+ // When
+ let result = catch_unwind(AssertUnwindSafe(|| {
+ exact_test_support::exact::<()>(|| {
+ exact_test_support::exact(|| {
+ output("inner", 1);
+ Ok(())
+ })?;
+ assert_ne!(authority(), before);
+ assert!(!unwind, "outer unwind");
+ Err("outer abort".into())
+ })
+ }));
+ // Then
+ match result {
+ Ok(value) => assert_eq!(value, Err("outer abort".into())),
+ Err(payload) => assert_eq!(payload.downcast_ref::<&str>(), Some(&"outer unwind")),
+ }
+ assert_eq!(authority(), before);
+}
diff --git a/bindings/devup-ui-wasm/src/cache6_protocol_tests.rs b/bindings/devup-ui-wasm/src/cache6_protocol_tests.rs
new file mode 100644
index 000000000..aa851de25
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache6_protocol_tests.rs
@@ -0,0 +1,189 @@
+use super::cache6_test_support::*;
+
+#[rstest]
+#[case(false, false)]
+#[case(false, true)]
+#[case(true, false)]
+#[case(true, true)]
+#[serial]
+fn complete_restore_works_when_companions_arrive_in_either_order(
+ #[case] before: bool,
+ #[case] reversed: bool,
+) {
+ // Given
+ let _guard = Guard::new();
+ output("a", 1);
+ let bytes = encoded();
+ let expected = maps();
+ fresh();
+ if before {
+ companions(&expected, reversed);
+ }
+ // When
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ if !before {
+ companions(&expected, reversed);
+ }
+ // Then
+ assert_eq!(maps(), expected);
+ assert_eq!(encoded(), bytes);
+ cache6_restore::RESTORE.with_borrow(|state| assert!(state.session.is_some()));
+}
+
+#[rstest]
+#[case(0)]
+#[case(1)]
+#[case(2)]
+#[serial]
+fn restore_accepts_optional_companions_when_only_one_or_neither_is_supplied(#[case] supplied: u8) {
+ // Given
+ let _guard = Guard::new();
+ output("a", 0);
+ let bytes = encoded();
+ let expected = maps();
+ fresh();
+ match supplied {
+ 0 => {}
+ 1 => {
+ assert_eq!(cache6_restore::classes(Some(expected.0.clone())), Ok(()));
+ }
+ 2 => {
+ assert_eq!(cache6_restore::files(Some(expected.1.clone())), Ok(()));
+ }
+ _ => panic!("supplied"),
+ }
+ // When
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ // Then
+ assert_eq!(maps(), expected);
+}
+
+#[rstest]
+#[case(false, false, false)]
+#[case(false, true, false)]
+#[case(true, false, false)]
+#[case(true, true, false)]
+#[case(false, false, true)]
+#[case(false, true, true)]
+#[case(true, false, true)]
+#[case(true, true, true)]
+#[serial]
+fn invalid_companion_leaves_exact_cold_state_when_supplied_before_or_after(
+ #[case] before: bool,
+ #[case] classes: bool,
+ #[case] unequal: bool,
+) {
+ // Given
+ let _guard = Guard::new();
+ output("cached", 0);
+ let bytes = encoded();
+ fresh();
+ with_style_sheet_mut(|sheet| {
+ sheet.add_css("sentinel", concat!("body", "{", "color:blue", "}"))
+ });
+ seed_file_map(vec!["sentinel".into()]);
+ let expected = authority();
+ let invalid = || {
+ if classes {
+ cache6_restore::classes(unequal.then(BTreeMap::new))
+ } else {
+ cache6_restore::files(unequal.then(BTreeMap::new))
+ }
+ };
+ if before {
+ assert_eq!(invalid(), Ok(()));
+ }
+ // When
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ if !before {
+ assert_eq!(invalid(), Ok(()));
+ }
+ // Then
+ assert_eq!(authority(), expected);
+ assert_eq!(cache_names::check(), Ok(()));
+ cache6_restore::RESTORE.with_borrow(|state| assert!(state.session.is_none()));
+}
+
+#[test]
+#[serial]
+fn companions_never_install_when_no_sheet_was_supplied() {
+ // Given
+ let _guard = Guard::new();
+ let before = authority();
+ // When
+ companions(
+ &(
+ BTreeMap::from([("poison".into(), BTreeMap::from([("wrong".into(), 77)]))]),
+ BTreeMap::from([("poison".into(), 88)]),
+ ),
+ true,
+ );
+ // Then
+ assert_eq!(authority(), before);
+ cache6_restore::RESTORE.with_borrow(|state| assert!(state.session.is_none()));
+}
+
+#[test]
+#[serial]
+fn late_matching_companions_preserve_fresh_work_when_live_maps_have_extended() {
+ // Given
+ let _guard = Guard::new();
+ output("cached", 0);
+ let bytes = encoded();
+ let expected = maps();
+ fresh();
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ output("fresh", 1);
+ let extended = authority();
+ assert_ne!(maps(), expected);
+ // When
+ companions(&expected, true);
+ // Then
+ assert_eq!(authority(), extended);
+ cache6_restore::RESTORE.with_borrow(|state| assert!(state.session.is_some()));
+}
+
+#[test]
+#[serial]
+fn reset_forgets_session_when_another_build_begins() {
+ // Given
+ let _guard = Guard::new();
+ output("a", 0);
+ let bytes = encoded();
+ fresh();
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ // When
+ reset_build_state_internal();
+ // Then
+ cache6_restore::RESTORE
+ .with_borrow(|state| assert_eq!(state, &cache6_session::Restore::default()));
+ assert_eq!(
+ with_style_sheet(LiveCheckpoint::capture),
+ LiveCheckpoint::capture(&StyleSheet::default())
+ );
+}
+
+#[test]
+#[serial]
+fn clear_forgets_only_protocol_when_an_inactive_genuine_session_has_fresh_work() {
+ // Given: prior state differs from installed and subsequently extended live state.
+ let _guard = Guard::new();
+ output("cached", 1);
+ let bytes = encoded();
+ fresh();
+ output("prior", 0);
+ let prior = authority();
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ output("fresh", 2);
+ seed_file_map(vec!["journal".into(), "journal".into()]);
+ let live = authority();
+ assert_ne!(live, prior);
+ cache6_restore::RESTORE.with_borrow(|state| assert!(state.session.is_some()));
+ // When
+ let result = cache6_restore::clear();
+ // Then: forgetting must not retire/replay or restore the before-checkpoint/maps.
+ assert_eq!(result, Ok(()));
+ assert_eq!(authority(), live);
+ cache6_restore::RESTORE
+ .with_borrow(|state| assert_eq!(state, &cache6_session::Restore::default()));
+}
diff --git a/bindings/devup-ui-wasm/src/cache6_restore.rs b/bindings/devup-ui-wasm/src/cache6_restore.rs
new file mode 100644
index 000000000..0058dab3e
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache6_restore.rs
@@ -0,0 +1,142 @@
+//! Dormant Rust-only raw6 protocol; public JavaScript still dispatches cache4.
+use crate::{
+ cache6_session::{Companion, Restore, Session},
+ with_style_sheet, with_style_sheet_mut,
+};
+use sheet::{
+ cache_snapshot::{ClassMap, FileMap},
+ snapshot6::{self, EvidenceError},
+};
+use std::cell::RefCell;
+
+thread_local! {
+ pub(super) static RESTORE: RefCell = RefCell::new(Restore::default());
+}
+
+fn admitted(build: impl FnOnce() -> Result) -> Result {
+ css::admission::with_admission(|| {
+ css::admission::check_administration_allowed().map_err(EvidenceError::ActiveExact)?;
+ build()
+ })
+}
+
+/// Adopt only strict admitted raw6; malformed candidates remain silent cold.
+/// # Errors
+/// Returns typed active-exact rejection before any session or live mutation.
+pub fn import(bytes: &[u8]) -> Result<(), EvidenceError> {
+ admitted(|| {
+ // Admission stays held with exact inactive through validation, retirement and install;
+ // none of these operations invokes user code or enters an exact scope.
+ if with_style_sheet(snapshot6::check_install_target).is_err() {
+ return Ok(());
+ }
+ RESTORE.with_borrow_mut(|state| {
+ let candidate = snapshot6::parse(bytes)
+ .and_then(snapshot6::validate_snapshot)
+ .and_then(|candidate| {
+ candidate
+ .compare_companions(state.classes.supplied()?, state.files.supplied()?)?;
+ Ok(candidate)
+ });
+ match candidate {
+ Err(_) => {
+ state.retire();
+ Ok(())
+ }
+ Ok(candidate) => {
+ state.retire();
+ let (maps, sheet) = Restore::before();
+ match with_style_sheet_mut(|target| candidate.install_into(target)) {
+ Ok(()) => {
+ state.session = Some(Session::installed(maps, sheet));
+ state.classes = Companion::Unseen;
+ state.files = Companion::Unseen;
+ Ok(())
+ }
+ Err(_) => Ok(()),
+ }
+ }
+ }
+ })
+ })
+}
+
+/// Corroborate the original snapshot's class map, not later live additions.
+/// # Errors
+/// Returns typed active-exact rejection before companion or session mutation.
+pub fn classes(incoming: Option) -> Result<(), EvidenceError> {
+ admitted(|| {
+ if with_style_sheet(snapshot6::check_install_target).is_err() {
+ return Ok(());
+ }
+ RESTORE.with_borrow_mut(|state| {
+ match &state.session {
+ Some(session) if incoming.as_ref() == Some(&session.classes) => {
+ state.classes = Companion::Unseen;
+ }
+ Some(_) => {
+ state.retire();
+ state.classes = Companion::from(incoming);
+ }
+ None => state.classes = Companion::from(incoming),
+ }
+ Ok(())
+ })
+ })
+}
+
+/// Corroborate the original snapshot's file map, not later live additions.
+/// # Errors
+/// Returns typed active-exact rejection before companion or session mutation.
+pub fn files(incoming: Option) -> Result<(), EvidenceError> {
+ admitted(|| {
+ if with_style_sheet(snapshot6::check_install_target).is_err() {
+ return Ok(());
+ }
+ RESTORE.with_borrow_mut(|state| {
+ match &state.session {
+ Some(session) if incoming.as_ref() == Some(&session.files) => {
+ state.files = Companion::Unseen;
+ }
+ Some(_) => {
+ state.retire();
+ state.files = Companion::from(incoming);
+ }
+ None => state.files = Companion::from(incoming),
+ }
+ Ok(())
+ })
+ })
+}
+
+/// Record one seed call without flattening batches or selecting configuration.
+/// # Errors
+/// Returns typed active-exact rejection before journal mutation.
+pub fn seeded(files: &[String]) -> Result<(), EvidenceError> {
+ admitted(|| {
+ record_seed(files);
+ Ok(())
+ })
+}
+
+/// Forget the protocol at an existing build reset, without replaying old data.
+/// # Errors
+/// Returns typed active-exact rejection before session mutation.
+pub fn clear() -> Result<(), EvidenceError> {
+ admitted(|| {
+ forget();
+ Ok(())
+ })
+}
+
+pub(crate) fn record_seed(files: &[String]) {
+ RESTORE.with_borrow_mut(|state| {
+ if let Some(session) = &mut state.session {
+ session.seeds.push(files.to_vec());
+ }
+ });
+}
+
+pub(crate) fn forget() {
+ RESTORE.with_borrow_mut(|state| *state = Restore::default());
+}
diff --git a/bindings/devup-ui-wasm/src/cache6_rollback_tests.rs b/bindings/devup-ui-wasm/src/cache6_rollback_tests.rs
new file mode 100644
index 000000000..917bb7a09
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache6_rollback_tests.rs
@@ -0,0 +1,213 @@
+use super::cache6_test_support::*;
+use std::panic::{AssertUnwindSafe, catch_unwind};
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn outer_rollback_restores_private_state_when_inner_kernel_committed_then_output_failed(
+ #[case] unwind: bool,
+) {
+ // Given
+ let _guard = Guard::new();
+ output("a", 0);
+ let before = authority();
+ // When
+ let result = catch_unwind(AssertUnwindSafe(|| {
+ exact_test_support::exact::<()>(|| {
+ output("b", 1);
+ assert_ne!(authority(), before);
+ assert!(!unwind, "after committed kernel");
+ Err("later Output error".into())
+ })
+ }));
+ // Then
+ match result {
+ Ok(value) => assert_eq!(value, Err("later Output error".into())),
+ Err(payload) => assert_eq!(
+ payload.downcast_ref::<&str>(),
+ Some(&"after committed kernel")
+ ),
+ }
+ assert_eq!(authority(), before);
+ let retry = output("b", 1);
+ fresh();
+ output("a", 0);
+ assert_eq!(output("b", 1), retry);
+}
+
+#[test]
+#[serial]
+fn rollback_restores_latch_when_committed_inner_state_is_then_damaged() {
+ // Given
+ let _guard = Guard::new();
+ output("a", 0);
+ let before = authority();
+ // When
+ let result: Result<(), String> = exact_test_support::exact(|| {
+ output("b", 1);
+ with_style_sheet_mut(|sheet| {
+ sheet.properties.clear();
+ sheet.add_css("literal", "body{}");
+ });
+ assert!(matches!(
+ with_style_sheet(snapshot6::check_install_target),
+ Err(EvidenceError::ActiveExact(_))
+ ));
+ Err("abort latched inner".into())
+ });
+ // Then
+ assert_eq!(result, Err("abort latched inner".into()));
+ assert_eq!(authority(), before);
+ assert_eq!(with_style_sheet(snapshot6::check_install_target), Ok(()));
+ let retry = output("b", 1);
+ fresh();
+ output("a", 0);
+ assert_eq!(output("b", 1), retry);
+}
+
+#[test]
+#[serial]
+fn existing_rejected_state_is_captured_when_enclosing_attempt_aborts() {
+ // Given
+ let _guard = Guard::new();
+ output("a", 0);
+ with_style_sheet_mut(|sheet| {
+ sheet.properties.clear();
+ sheet.add_css("literal", "body{}");
+ });
+ let before = authority();
+ let rejection = with_style_sheet(snapshot6::check_install_target);
+ assert!(matches!(rejection, Err(EvidenceError::Kernel(_))));
+ // When
+ let result: Result<(), String> = exact_test_support::exact(|| {
+ with_style_sheet_mut(|sheet| *sheet = StyleSheet::default());
+ output("new", 1);
+ Err("rejected outer".into())
+ });
+ // Then
+ assert_eq!(result, Err("rejected outer".into()));
+ assert_eq!(authority(), before);
+ assert_eq!(with_style_sheet(snapshot6::check_install_target), rejection);
+}
+
+#[test]
+#[serial]
+fn finish_failure_restores_guarded_cleanup_when_real_base_owner_was_registered() {
+ // Given
+ let _guard = Guard::new();
+ let items = with_original(
+ FixtureRequest {
+ filename: "a",
+ source: "abcdef",
+ },
+ || {
+ FxHashSet::from_iter([ExtractStyleValue::Dynamic(ExtractDynamicStyle::new(
+ "color",
+ 0,
+ "tone",
+ Some(css::style_selector::StyleSelector::Global(
+ "body".into(),
+ "a".into(),
+ )),
+ ))])
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ update(
+ &items,
+ OutputRoute {
+ raw_source: "a",
+ single_css: false,
+ import_main_css: false,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ let before = authority();
+ // When
+ let result = with_admission(|| {
+ let rollback = extraction_rollback::ExtractionRollback::capture();
+ let result: Result<(), UpdateError> =
+ css::exact_attempt::with_exclusive_attempt(|| {
+ with_style_sheet_mut(|sheet| {
+ CounterSheet::new(sheet).with_attempt(|attempt| {
+ attempt
+ .prepare(
+ &Default::default(),
+ UpdateRequest {
+ raw_source: "a",
+ single_css: false,
+ },
+ )?
+ .finish(|sheet, effects| {
+ assert!(effects.default_collected);
+ let _output = Output::from_prospective(
+ metadata(),
+ ProspectiveSheet { sheet, effects },
+ OutputRoute {
+ raw_source: "a",
+ single_css: false,
+ import_main_css: false,
+ },
+ );
+ Err("after real cleanup".into())
+ })
+ })
+ })
+ });
+ drop(rollback);
+ result
+ });
+ // Then
+ assert_eq!(
+ result,
+ Err(UpdateError::Output("after real cleanup".into()))
+ );
+ assert_eq!(authority(), before);
+}
+
+#[test]
+#[serial]
+fn final_validation_restores_output_when_map_authority_changes_after_finish() {
+ // Given
+ let _guard = Guard::new();
+ output("a", 0);
+ let items = ir("b", 1);
+ let before = authority();
+ // When
+ let result: Result> = with_admission(|| {
+ let rollback = extraction_rollback::ExtractionRollback::capture();
+ let result = css::exact_attempt::with_exclusive_attempt(|| {
+ with_style_sheet_mut(|sheet| {
+ CounterSheet::new(sheet).with_attempt(|attempt| {
+ let completed = attempt
+ .prepare(
+ &items,
+ UpdateRequest {
+ raw_source: "b",
+ single_css: false,
+ },
+ )?
+ .finish(|sheet, effects| {
+ Ok(Output::from_prospective(
+ metadata(),
+ ProspectiveSheet { sheet, effects },
+ OutputRoute {
+ raw_source: "b",
+ single_css: false,
+ import_main_css: false,
+ },
+ ))
+ })?;
+ css::class_map::with_class_map_mut(HashMap::clear);
+ Ok(completed)
+ })
+ })
+ });
+ drop(rollback);
+ result
+ });
+ // Then
+ assert!(matches!(result, Err(UpdateError::Kernel(_))));
+ assert_eq!(authority(), before);
+}
diff --git a/bindings/devup-ui-wasm/src/cache6_seed_tests.rs b/bindings/devup-ui-wasm/src/cache6_seed_tests.rs
new file mode 100644
index 000000000..0d079f968
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache6_seed_tests.rs
@@ -0,0 +1,161 @@
+use super::cache6_test_support::*;
+
+fn batches() {
+ seed_file_map(vec!["b".into(), "b".into()]);
+ seed_file_map(vec!["a".into(), "b".into()]);
+ seed_file_map(vec!["c".into(), "a".into(), "c".into()]);
+}
+
+#[test]
+#[serial]
+fn seeded_journals_only_when_an_inactive_genuine_session_is_installed() {
+ // Given
+ let _guard = Guard::new();
+ output("cached", 1);
+ let bytes = encoded();
+ fresh();
+ output("prior", 0);
+ let (before_maps, before_sheet) = cache6_session::Restore::before();
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ let mut expected_session = cache6_session::Session::installed(before_maps, before_sheet);
+ let before = authority();
+ let seeds = vec![
+ vec!["b".into(), "b".into()],
+ vec!["a".into(), "b".into()],
+ vec!["c".into(), "a".into(), "c".into()],
+ ];
+ // When
+ for batch in &seeds {
+ assert_eq!(cache6_restore::seeded(batch), Ok(()));
+ }
+ // Then: no allocator, sheet, private Counter state or map mutation.
+ assert_eq!(authority(), before);
+ expected_session.seeds = seeds;
+ let expected = cache6_session::Restore {
+ session: Some(expected_session),
+ ..Default::default()
+ };
+ cache6_restore::RESTORE.with_borrow(|state| assert_eq!(state, &expected));
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn seeded_batches_replay_when_a_late_invalid_companion_retires_the_session(#[case] classes: bool) {
+ // Given: independent cold replay applies seeds through the public allocator entry.
+ let _guard = Guard::new();
+ output("cached", 1);
+ let bytes = encoded();
+ fresh();
+ output("prior", 0);
+ batches();
+ let expected = authority();
+ fresh();
+ output("prior", 0);
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ for batch in [
+ vec!["b".into(), "b".into()],
+ vec!["a".into(), "b".into()],
+ vec!["c".into(), "a".into(), "c".into()],
+ ] {
+ assert_eq!(cache6_restore::seeded(&batch), Ok(()));
+ }
+ output("fresh", 2);
+ // When
+ let result = if classes {
+ cache6_restore::classes(None)
+ } else {
+ cache6_restore::files(None)
+ };
+ // Then
+ assert_eq!(result, Ok(()));
+ assert_eq!(authority(), expected);
+ let expected_restore = cache6_session::Restore {
+ classes: if classes {
+ cache6_session::Companion::Invalid
+ } else {
+ cache6_session::Companion::Unseen
+ },
+ files: if classes {
+ cache6_session::Companion::Unseen
+ } else {
+ cache6_session::Companion::Invalid
+ },
+ session: None,
+ };
+ cache6_restore::RESTORE.with_borrow(|state| assert_eq!(state, &expected_restore));
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn late_rollback_replays_seed_calls_when_batches_repeat_and_current_canonical_changes(
+ #[case] classes: bool,
+) {
+ // Given
+ let _guard = Guard::new();
+ import_canonical_map_internal(HashMap::from([("a".into(), "root".into())]));
+ batches();
+ let cold_maps = cache6_session::Maps::capture();
+ let cold = output("a", 1);
+ fresh();
+ update(
+ &Default::default(),
+ OutputRoute {
+ raw_source: "empty",
+ single_css: true,
+ import_main_css: false,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ let bytes = encoded();
+ fresh();
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ batches();
+ import_canonical_map_internal(HashMap::from([("a".into(), "root".into())]));
+ output("fresh", 0);
+ // When
+ let result = if classes {
+ cache6_restore::classes(None)
+ } else {
+ cache6_restore::files(None)
+ };
+ // Then
+ assert_eq!(result, Ok(()));
+ assert_eq!(cache6_session::Maps::capture(), cold_maps);
+ assert_eq!(output("a", 1), cold);
+ assert_eq!(
+ css::file_map::get_original_ids(),
+ BTreeMap::from([("b".into(), 0), ("a".into(), 1), ("c".into(), 2)])
+ );
+}
+
+#[test]
+#[serial]
+fn repeated_restore_keeps_fresh_seeds_when_old_session_is_retired() {
+ // Given
+ let _guard = Guard::new();
+ update(
+ &Default::default(),
+ OutputRoute {
+ raw_source: "empty",
+ single_css: true,
+ import_main_css: false,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ let bytes = encoded();
+ fresh();
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ batches();
+ // When
+ assert_eq!(cache6_restore::import(&bytes), Ok(()));
+ assert_eq!(cache6_restore::files(None), Ok(()));
+ // Then
+ assert_eq!(
+ css::file_map::get_original_ids(),
+ BTreeMap::from([("b".into(), 0), ("a".into(), 1), ("c".into(), 2)])
+ );
+}
diff --git a/bindings/devup-ui-wasm/src/cache6_session.rs b/bindings/devup-ui-wasm/src/cache6_session.rs
new file mode 100644
index 000000000..3bc3ac895
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache6_session.rs
@@ -0,0 +1,117 @@
+use crate::{with_style_sheet, with_style_sheet_mut};
+use sheet::{
+ cache_snapshot::{ClassMap, FileMap},
+ live_checkpoint::LiveCheckpoint,
+};
+use std::collections::{BTreeMap, BTreeSet};
+
+#[derive(Debug, Default, PartialEq, Eq)]
+pub(super) enum Companion {
+ #[default]
+ Unseen,
+ Invalid,
+ Supplied(T),
+}
+impl Companion {
+ pub(super) const fn supplied(&self) -> Result, sheet::snapshot6::EvidenceError> {
+ match self {
+ Self::Unseen => Ok(None),
+ Self::Invalid => Err(sheet::snapshot6::EvidenceError::Companion),
+ Self::Supplied(value) => Ok(Some(value)),
+ }
+ }
+}
+impl From> for Companion {
+ fn from(value: Option) -> Self {
+ match value {
+ Some(value) => Self::Supplied(value),
+ None => Self::Invalid,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub(super) struct Maps {
+ pub(super) classes: ClassMap,
+ pub(super) files: FileMap,
+ originals: BTreeMap,
+ plan: Option>,
+}
+impl Maps {
+ pub(super) fn capture() -> Self {
+ Self {
+ classes: css::class_map::with_class_map(|map| {
+ map.iter()
+ .map(|(file, slots)| {
+ (
+ file.clone(),
+ slots
+ .iter()
+ .map(|(key, slot)| (key.clone(), *slot))
+ .collect(),
+ )
+ })
+ .collect()
+ }),
+ files: css::file_map::get_file_map().into_iter().collect(),
+ originals: css::file_map::get_original_ids(),
+ plan: css::atom_hoist::atom_plan(),
+ }
+ }
+ fn restore(self) {
+ css::class_map::set_class_map(
+ self.classes
+ .into_iter()
+ .map(|(file, slots)| (file, slots.into_iter().collect()))
+ .collect(),
+ );
+ css::file_map::set_file_map(self.files.into_iter().collect());
+ css::file_map::set_original_ids(self.originals);
+ css::atom_hoist::restore_atom_plan(self.plan);
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub(super) struct Session {
+ pub(super) classes: ClassMap,
+ pub(super) files: FileMap,
+ before_maps: Maps,
+ before_sheet: LiveCheckpoint,
+ pub(super) seeds: Vec>,
+}
+impl Session {
+ pub(super) fn installed(before_maps: Maps, before_sheet: LiveCheckpoint) -> Self {
+ let installed = Maps::capture();
+ Self {
+ classes: installed.classes,
+ files: installed.files,
+ before_maps,
+ before_sheet,
+ seeds: Vec::new(),
+ }
+ }
+ fn retire(self) {
+ self.before_maps.restore();
+ with_style_sheet_mut(|sheet| self.before_sheet.restore(sheet));
+ for batch in self.seeds {
+ css::file_map::seed_file_numbers(&batch);
+ }
+ }
+}
+
+#[derive(Debug, Default, PartialEq, Eq)]
+pub(super) struct Restore {
+ pub(super) classes: Companion,
+ pub(super) files: Companion,
+ pub(super) session: Option,
+}
+impl Restore {
+ pub(super) fn retire(&mut self) {
+ if let Some(session) = self.session.take() {
+ session.retire();
+ }
+ }
+ pub(super) fn before() -> (Maps, LiveCheckpoint) {
+ (Maps::capture(), with_style_sheet(LiveCheckpoint::capture))
+ }
+}
diff --git a/bindings/devup-ui-wasm/src/cache6_test_support.rs b/bindings/devup-ui-wasm/src/cache6_test_support.rs
new file mode 100644
index 000000000..43fb40528
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache6_test_support.rs
@@ -0,0 +1,212 @@
+pub(super) use super::*;
+pub(super) use extractor::{
+ counter_test_support::{FixtureRequest, with_original},
+ extract_style::{
+ ExtractDynamicStyle, ExtractKeyframes, extract_static_style::ExtractStaticStyle,
+ },
+};
+pub(super) use prospective_output::{OutputMetadata, OutputRoute, ProspectiveSheet};
+pub(super) use rstest::rstest;
+pub(super) use serial_test::serial;
+pub(super) use sheet::{
+ cache_snapshot::{ClassMap, FileMap},
+ counter_kernel::{CounterSheet, UpdateError, UpdateRequest},
+ live_checkpoint::LiveCheckpoint,
+ snapshot6::{self, EvidenceError},
+};
+pub(super) use std::collections::BTreeSet;
+
+pub(super) struct Guard;
+impl Guard {
+ pub(super) fn new() -> Self {
+ fresh();
+ Self
+ }
+}
+impl Drop for Guard {
+ fn drop(&mut self) {
+ fresh();
+ GLOBAL_STYLE_SHEET.clear_poison();
+ }
+}
+pub(super) fn fresh() {
+ reset_build_state_internal();
+ set_debug(false);
+ register_theme_internal(sheet::theme::Theme::default());
+}
+pub(super) fn ir(file: &str, family: u8) -> FxHashSet {
+ with_original(
+ FixtureRequest {
+ filename: file,
+ source: "a\nbcd",
+ },
+ || {
+ let item = match family {
+ 0 => ExtractStyleValue::Static(ExtractStaticStyle::new("color", "red", 0, None)),
+ 1 => ExtractStyleValue::Dynamic(
+ ExtractDynamicStyle::new("color", 0, "tone", None).at_role(2, 1),
+ ),
+ 2 => ExtractStyleValue::Dynamic(ExtractDynamicStyle::new(
+ "color",
+ 0,
+ "tone !important",
+ None,
+ )),
+ 3 => ExtractStyleValue::Static(ExtractStaticStyle::new(
+ "typography",
+ "heading",
+ 0,
+ None,
+ )),
+ 4 | 5 => {
+ let mut frames = ExtractKeyframes::default();
+ let member = ExtractStaticStyle::new("opacity", "0", 0, None);
+ let mut members = vec![member.clone(), member];
+ if family == 5 {
+ members.push(ExtractStaticStyle::new("typography", "heading", 0, None));
+ }
+ frames.keyframes.insert("from".into(), members);
+ frames.keyframes.insert("to".into(), vec![]);
+ ExtractStyleValue::Keyframes(frames)
+ }
+ _ => panic!("family"),
+ };
+ FxHashSet::from_iter([item])
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"))
+}
+pub(super) fn metadata() -> OutputMetadata {
+ OutputMetadata {
+ code: "fixture metadata, not Counter JS".into(),
+ map: Some("fixture-map".into()),
+ css_file: Some("fixture.css".into()),
+ dependencies: vec!["tokens.ts".into()],
+ }
+}
+pub(super) fn update(
+ items: &FxHashSet,
+ route: OutputRoute<'_>,
+) -> Result> {
+ with_admission(|| {
+ let rollback = extraction_rollback::ExtractionRollback::capture();
+ let result = css::exact_attempt::with_exclusive_attempt(|| {
+ with_style_sheet_mut(|sheet| {
+ CounterSheet::new(sheet).with_attempt(|attempt| {
+ attempt
+ .prepare(
+ items,
+ UpdateRequest {
+ raw_source: route.raw_source,
+ single_css: route.single_css,
+ },
+ )?
+ .finish(|sheet, effects| {
+ Ok(Output::from_prospective(
+ metadata(),
+ ProspectiveSheet { sheet, effects },
+ route,
+ ))
+ })
+ })
+ })
+ });
+ if result.is_ok() {
+ rollback.commit();
+ }
+ result
+ })
+}
+pub(super) fn output(file: &str, family: u8) -> ObservedOutput {
+ let items = ir(file, family);
+ let output = update(
+ &items,
+ OutputRoute {
+ raw_source: file,
+ single_css: false,
+ import_main_css: false,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ ObservedOutput::capture(&output, file)
+}
+#[derive(Debug, PartialEq, Eq)]
+pub(super) struct ObservedOutput {
+ code: String,
+ map: Option,
+ css_file: Option,
+ dependencies: Vec,
+ css: Option,
+ base: bool,
+ global_css: String,
+ local_css: String,
+}
+impl ObservedOutput {
+ pub(super) fn capture(output: &Output, file: &str) -> Self {
+ Self {
+ code: output.code(),
+ map: output.map(),
+ css_file: output.css_file(),
+ dependencies: output.dependencies(),
+ css: output.css(),
+ base: output.updated_base_style(),
+ global_css: with_style_sheet(|sheet| sheet.create_css(None, false)),
+ local_css: with_style_sheet(|sheet| {
+ sheet.create_css(Some(&css::file_map::canonical(file)), false)
+ }),
+ }
+ }
+}
+pub(super) fn encoded() -> Vec {
+ with_style_sheet_mut(|sheet| {
+ serde_json::to_vec(
+ &CounterSheet::new(sheet)
+ .export_snapshot6()
+ .unwrap_or_else(|error| panic!("{error:?}")),
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"))
+ })
+}
+pub(super) fn maps() -> (ClassMap, FileMap) {
+ let maps = cache6_session::Maps::capture();
+ (maps.classes, maps.files)
+}
+#[derive(Debug, PartialEq, Eq)]
+pub(super) struct Authority {
+ sheet: LiveCheckpoint,
+ maps: cache6_session::Maps,
+ theme: String,
+}
+pub(super) fn authority() -> Authority {
+ Authority {
+ sheet: with_style_sheet(LiveCheckpoint::capture),
+ maps: cache6_session::Maps::capture(),
+ theme: with_style_sheet(|sheet| sheet.theme.to_css()),
+ }
+}
+pub(super) fn companions(maps: &(ClassMap, FileMap), reversed: bool) {
+ if reversed {
+ cache6_restore::files(Some(maps.1.clone())).unwrap_or_else(|error| panic!("{error:?}"));
+ cache6_restore::classes(Some(maps.0.clone())).unwrap_or_else(|error| panic!("{error:?}"));
+ } else {
+ cache6_restore::classes(Some(maps.0.clone())).unwrap_or_else(|error| panic!("{error:?}"));
+ cache6_restore::files(Some(maps.1.clone())).unwrap_or_else(|error| panic!("{error:?}"));
+ }
+}
+pub(super) fn configure_typography() {
+ let mut theme = sheet::theme::Theme::default();
+ let frame = sheet::theme::Typography {
+ font_size: Some("14px".into()),
+ font_family: None,
+ font_style: None,
+ font_weight: None,
+ line_height: None,
+ letter_spacing: None,
+ text_transform: None,
+ };
+ theme.typography.insert(
+ "heading".into(),
+ sheet::theme::Typographies(vec![Some(frame), None]),
+ );
+ register_theme_internal(theme);
+}
diff --git a/bindings/devup-ui-wasm/src/cache6_transition_tests.rs b/bindings/devup-ui-wasm/src/cache6_transition_tests.rs
new file mode 100644
index 000000000..b08f66b81
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache6_transition_tests.rs
@@ -0,0 +1,94 @@
+use crate::cache6_test_support::*;
+
+fn empty_export(plan: BTreeSet) -> Vec {
+ fresh();
+ update(
+ &Default::default(),
+ OutputRoute {
+ raw_source: "empty",
+ single_css: true,
+ import_main_css: false,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ css::atom_hoist::restore_atom_plan(Some(plan.clone()));
+ with_style_sheet_mut(|sheet| sheet.atom_plan = Some(plan));
+ encoded()
+}
+
+fn cold_before(plan: Option>) {
+ fresh();
+ css::atom_hoist::restore_atom_plan(plan.clone());
+ with_style_sheet_mut(|sheet| {
+ sheet.atom_plan = plan;
+ sheet.add_css("prior", concat!("body", "{", "color:blue", "}"));
+ sheet.add_import("prior", "prior.css");
+ });
+ css::class_map::with_class_map_mut(|map| {
+ map.insert("prior".into(), HashMap::from([("reserved".into(), 0)]));
+ });
+ seed_file_map(vec!["prior-seed".into()]);
+}
+
+#[test]
+#[serial]
+fn replacement_rejects_q_when_validation_must_observe_installed_p_before_retirement() {
+ // Given: independently kernel-owned empty exports, no candidate-derived settings.
+ let _guard = Guard::new();
+ let p = empty_export(BTreeSet::new());
+ let q = empty_export(BTreeSet::from(["q".into()]));
+ cold_before(None);
+ let expected_cold = authority();
+ assert_eq!(cache6_restore::import(&p), Ok(()));
+ assert_eq!(css::atom_hoist::atom_plan(), Some(BTreeSet::new()));
+ assert!(matches!(
+ snapshot6::parse(&q).and_then(snapshot6::validate_snapshot),
+ Err(EvidenceError::Configuration)
+ ));
+ // When
+ assert_eq!(cache6_restore::import(&q), Ok(()));
+ // Then: rejection retires P, never adopts Q into the unconstrained cold target.
+ assert_eq!(authority(), expected_cold);
+ cache6_restore::RESTORE.with_borrow(|state| {
+ assert_eq!(state, &cache6_session::Restore::default());
+ });
+}
+
+#[test]
+#[serial]
+fn replacement_stays_cold_when_consuming_install_refuses_after_prior_retirement() {
+ // Given: independent cold replay includes both plans, full typed state and maps.
+ let _guard = Guard::new();
+ let p = empty_export(BTreeSet::new());
+ let q = empty_export(BTreeSet::from(["q".into()]));
+ let seeds = vec![
+ vec!["b".into(), "b".into()],
+ vec!["a".into(), "b".into()],
+ vec!["c".into(), "a".into(), "c".into()],
+ ];
+ cold_before(Some(BTreeSet::new()));
+ for batch in &seeds {
+ seed_file_map(batch.clone());
+ }
+ let expected_cold = authority();
+ cold_before(Some(BTreeSet::new()));
+ assert_eq!(cache6_restore::import(&p), Ok(()));
+ for batch in &seeds {
+ seed_file_map(batch.clone());
+ }
+ // Ordinary global plan transition: target remains unlatched, no authority install.
+ css::atom_hoist::restore_atom_plan(None);
+ assert_eq!(with_style_sheet(snapshot6::check_install_target), Ok(()));
+ assert!(
+ snapshot6::parse(&q)
+ .and_then(snapshot6::validate_snapshot)
+ .is_ok()
+ );
+ // When: validation permits Q under None; retiring P restores independent Some(empty).
+ assert_eq!(cache6_restore::import(&q), Ok(()));
+ // Then: consuming recheck refuses Q, preserving the complete replayed cold authority.
+ assert_eq!(authority(), expected_cold);
+ cache6_restore::RESTORE.with_borrow(|state| {
+ assert_eq!(state, &cache6_session::Restore::default());
+ });
+}
diff --git a/bindings/devup-ui-wasm/src/cache_admission_tests.rs b/bindings/devup-ui-wasm/src/cache_admission_tests.rs
new file mode 100644
index 000000000..dc7d91dc5
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_admission_tests.rs
@@ -0,0 +1,48 @@
+use super::*;
+
+#[path = "cache_allocator_tests.rs"]
+mod allocators;
+#[path = "cache_naming_coverage_tests.rs"]
+mod naming_coverage;
+#[path = "cache_reset_tests.rs"]
+mod resets;
+#[path = "cache_seed_tests.rs"]
+mod seeds;
+#[path = "cache_typography_coverage_tests.rs"]
+mod typography_coverage;
+
+fn fresh() {
+ reset_build_state_internal();
+ set_debug(false);
+ register_theme_internal(sheet::theme::Theme::default());
+}
+
+fn output(file: &str, source: &str, single: bool) -> (String, String, String) {
+ let compiled = code_extract_internal(
+ file,
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ single,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ with_style_sheet(|sheet| {
+ (
+ compiled.code(),
+ sheet.create_css(None, false),
+ sheet.create_css(Some(file), false),
+ )
+ })
+}
+
+fn companions(value: &serde_json::Value) {
+ cache_restore::classes(Some(
+ serde_json::from_value(value["classMap"].clone()).unwrap_or_else(|error| panic!("{error}")),
+ ));
+ cache_restore::files(Some(
+ serde_json::from_value(value["fileMap"].clone()).unwrap_or_else(|error| panic!("{error}")),
+ ));
+}
diff --git a/bindings/devup-ui-wasm/src/cache_allocator_proof.rs b/bindings/devup-ui-wasm/src/cache_allocator_proof.rs
new file mode 100644
index 000000000..5b58547f8
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_allocator_proof.rs
@@ -0,0 +1,123 @@
+use css::{
+ Naming,
+ content_name::AtomContent,
+ sparse_site::{Site, SourceFile},
+};
+use sheet::{StyleSheet, StyleSheetProperty, cache_snapshot::ClassMap};
+
+fn namespace_id(namespace: &str) -> Option {
+ namespace.strip_prefix("D9-")?.parse().ok()
+}
+
+fn counter_label(number: usize) -> String {
+ let encoded = Site {
+ file: SourceFile::D9(0),
+ at: number,
+ role: 0,
+ }
+ .variable_name("");
+ let label = encoded.trim_start_matches("---Sa-");
+ match label.strip_suffix("ad") {
+ Some(head) => format!("{head}a-d"),
+ None => label.to_string(),
+ }
+}
+
+fn variable_owner(value: &str, prefix: &str, owner_label: &str) -> bool {
+ let variable = value.strip_prefix("var(").and_then(|value| {
+ value
+ .strip_suffix(')')
+ .or_else(|| value.strip_suffix(") !important"))
+ });
+ let mut number = owner_label.to_string();
+ number.retain(|character| character != '-');
+ variable.is_some_and(|variable| variable.starts_with(&format!("---{prefix}S{number}-")))
+}
+
+fn declaration_matches(
+ property: &StyleSheetProperty,
+ position: (u8, u8),
+ slot: (&str, &str),
+) -> bool {
+ let (key, owner_label) = slot;
+ let prefix = css::get_prefix().unwrap_or_default();
+ [false, true].into_iter().any(|dynamic| {
+ let content = AtomContent {
+ property: &property.property,
+ value: Some(&property.value),
+ naming: Naming::Own,
+ level: position.0,
+ order: position.1,
+ selector: property.selector.as_ref(),
+ layer: property.layer.as_deref(),
+ dynamic,
+ }
+ .content();
+ css::atom_name::hex(&content.lossless) == key
+ && (!dynamic || variable_owner(&property.value, &prefix, owner_label))
+ })
+}
+
+pub(super) fn validate(sheet: &StyleSheet, classes: &ClassMap) -> bool {
+ if classes.keys().any(|namespace| {
+ namespace_id(namespace)
+ .is_some_and(|id| !sheet.source_ids.values().any(|source| *source == id))
+ || (namespace.starts_with("D9-") && namespace_id(namespace).is_none())
+ }) {
+ return false;
+ }
+ let prefix = css::get_prefix().unwrap_or_default();
+ sheet.properties.values().all(|orders| {
+ orders.iter().all(|(order, levels)| {
+ levels.iter().all(|(level, properties)| {
+ properties.iter().all(|property| {
+ if property.owner_reset || sheet.names.contains_key(&property.class_name) {
+ return true;
+ }
+ let Some((owner, owner_label)) = sheet
+ .source_ids
+ .values()
+ .filter_map(|owner| {
+ usize::try_from(*owner)
+ .ok()
+ .map(|number| (*owner, counter_label(number)))
+ })
+ .filter(|(_, label)| {
+ property
+ .class_name
+ .starts_with(&format!("{prefix}{label}-"))
+ })
+ .max_by_key(|(_, label)| label.len())
+ else {
+ return true;
+ };
+ let Some(slots) = classes.get(&format!("D9-{owner}")) else {
+ return false;
+ };
+ slots.iter().any(|(key, slot)| {
+ property.class_name
+ == format!("{prefix}{owner_label}-{}", counter_label(*slot))
+ && if property.typography {
+ crate::cache_special_proof::counter_typography(
+ property,
+ (levels, *order),
+ key,
+ )
+ .is_some()
+ } else {
+ let owns_reset = levels.values().flatten().any(|other| {
+ other.owner_reset && other.class_name == property.class_name
+ });
+ (!owns_reset || key.starts_with("642d"))
+ && declaration_matches(
+ property,
+ (*level, *order),
+ (key, &owner_label),
+ )
+ }
+ })
+ })
+ })
+ })
+ })
+}
diff --git a/bindings/devup-ui-wasm/src/cache_allocator_tests.rs b/bindings/devup-ui-wasm/src/cache_allocator_tests.rs
new file mode 100644
index 000000000..e17021313
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_allocator_tests.rs
@@ -0,0 +1,130 @@
+use super::*;
+use std::fmt::Write;
+
+const SOURCE: &str =
+ "import {Box} from '@devup-ui/react';export const x=(p)=>;";
+
+enum MapDamage {
+ ClassGap,
+ FileGap,
+ SourceGap,
+ SwappedSlots,
+ MissingClasses,
+ MissingSources,
+ WrongKey,
+ WrongVariableSource,
+}
+
+#[rstest]
+#[case(MapDamage::ClassGap)]
+#[case(MapDamage::FileGap)]
+#[case(MapDamage::SourceGap)]
+#[case(MapDamage::SwappedSlots)]
+#[case(MapDamage::MissingClasses)]
+#[case(MapDamage::MissingSources)]
+#[case(MapDamage::WrongKey)]
+#[case(MapDamage::WrongVariableSource)]
+#[serial]
+fn invalid_allocator_snapshot_is_cold_with_matching_or_absent_companions(
+ #[case] damage: MapDamage,
+ #[values(false, true)] matching: bool,
+) {
+ // Given: real counter declarations and their complete self-contained allocator snapshot.
+ fresh();
+ seed_file_map(vec!["fresh.tsx".into(), "other.tsx".into()]);
+ let cold = output("fresh.tsx", SOURCE, false);
+ let mut value = snapshot();
+ match damage {
+ MapDamage::ClassGap => {
+ let classes = value["classMap"]["D9-0"]
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("counter namespace"));
+ *classes
+ .values_mut()
+ .find(|id| **id == 0)
+ .unwrap_or_else(|| panic!("slot zero")) = 99.into();
+ }
+ MapDamage::FileGap => value["fileMap"]["fresh.tsx"] = 99.into(),
+ MapDamage::SourceGap => value["sourceIds"]["fresh.tsx"] = 99.into(),
+ MapDamage::SwappedSlots => {
+ for id in value["classMap"]["D9-0"]
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("counter namespace"))
+ .values_mut()
+ {
+ if *id == 0 {
+ *id = 1.into();
+ } else if *id == 1 {
+ *id = 0.into();
+ }
+ }
+ }
+ MapDamage::MissingClasses => value["classMap"] = serde_json::json!({}),
+ MapDamage::MissingSources => value["sourceIds"] = serde_json::json!({}),
+ MapDamage::WrongKey => {
+ let classes = value["classMap"]["D9-0"]
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("counter namespace"));
+ let key = classes
+ .keys()
+ .next()
+ .cloned()
+ .unwrap_or_else(|| panic!("key"));
+ let slot = classes.remove(&key).unwrap_or_else(|| panic!("slot"));
+ classes.insert("wrong-key".into(), slot);
+ }
+ MapDamage::WrongVariableSource => {
+ for property in value["properties"]["fresh.tsx"]["255"]["0"]
+ .as_array_mut()
+ .unwrap_or_else(|| panic!("properties"))
+ {
+ for field in ["p", "v"] {
+ if let Some(text) = property[field].as_str() {
+ property[field] = text.replace("---Sa-", "---Sb-").into();
+ }
+ }
+ }
+ }
+ }
+ fresh();
+ seed_file_map(vec!["fresh.tsx".into(), "other.tsx".into()]);
+ let before = snapshot();
+ if matching {
+ companions(&value);
+ }
+ // When: invalid allocator state reaches authoritative admission without a companion veto.
+ assert_eq!(import(value), Ok(()));
+ // Then: fresh numbering survives, no cached declarations are adopted, and full output is cold.
+ assert_eq!(snapshot(), before);
+ assert_eq!(output("fresh.tsx", SOURCE, false), cold);
+ fresh();
+}
+
+#[rstest]
+#[case(1)]
+#[case(31)]
+#[serial]
+fn current_counter_cache_restores_file_and_slot_ad_blocker_splices(#[case] slots: usize) {
+ // Given: a complete real cache crossing both original-file and counter-slot label boundaries.
+ let configure = || {
+ fresh();
+ set_prefix(Some("du-FLa-".into()));
+ seed_file_map((0..31).map(|file| format!("{file:02}.tsx")).collect());
+ };
+ configure();
+ let mut elements = String::new();
+ for value in 0..slots {
+ write!(elements, " ").unwrap_or_else(|error| panic!("{error}"));
+ }
+ let source = format!("import {{Box}} from '@devup-ui/react';export const x=<>{elements}>; ");
+ let cold = output("30.tsx", &source, false);
+ let value = snapshot();
+ assert!(cold.0.contains("du-FLa-a-d-"), "{}", cold.0);
+ configure();
+ // When: the valid snapshot is restored without any companion map authority.
+ assert_eq!(import(value.clone()), Ok(()));
+ // Then: admission keeps the cache and the complete generated output exactly.
+ assert_eq!(snapshot(), value);
+ assert_eq!(output("30.tsx", &source, false), cold);
+ fresh();
+}
diff --git a/bindings/devup-ui-wasm/src/cache_atom_proof.rs b/bindings/devup-ui-wasm/src/cache_atom_proof.rs
new file mode 100644
index 000000000..751de4644
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_atom_proof.rs
@@ -0,0 +1,68 @@
+use css::{Naming, content_name::AtomContent};
+use sheet::StyleSheet;
+
+#[path = "cache_reset_proof.rs"]
+mod resets;
+
+pub(crate) fn validate(sheet: &StyleSheet) -> Result<(), String> {
+ if !resets::validate(sheet) {
+ return Err("cached owner reset has no exact dynamic consumer proof".into());
+ }
+ crate::cache_special_proof::validate_frames(sheet)?;
+ for orders in sheet.properties.values() {
+ for (order, levels) in orders {
+ for (level, properties) in levels {
+ for property in properties {
+ let Some(claim) = sheet.names.get(&property.class_name) else {
+ continue;
+ };
+ if property.owner_reset {
+ continue;
+ }
+ if property.typography {
+ if crate::cache_special_proof::typography(
+ property,
+ (levels, *order),
+ &claim.descriptor,
+ )
+ .is_none()
+ {
+ return Err(format!(
+ "cached typography `{}` has a corrupt exact declaration proof",
+ property.class_name
+ ));
+ }
+ continue;
+ }
+ let valid = [Naming::Own, Naming::Risky].into_iter().any(|naming| {
+ [false, true].into_iter().any(|dynamic| {
+ let content = AtomContent {
+ property: &property.property,
+ value: Some(&property.value),
+ naming,
+ level: *level,
+ order: *order,
+ selector: property.selector.as_ref(),
+ layer: property.layer.as_deref(),
+ dynamic,
+ }
+ .content();
+ content.descriptor == claim.descriptor
+ && crate::cache_descriptor::name_matches(
+ &content,
+ &property.class_name,
+ )
+ })
+ });
+ if !valid {
+ return Err(format!(
+ "cached atom `{}` has a corrupt exact declaration proof",
+ property.class_name
+ ));
+ }
+ }
+ }
+ }
+ }
+ Ok(())
+}
diff --git a/bindings/devup-ui-wasm/src/cache_descriptor.rs b/bindings/devup-ui-wasm/src/cache_descriptor.rs
new file mode 100644
index 000000000..7695eaf4d
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_descriptor.rs
@@ -0,0 +1,44 @@
+pub(crate) struct Cursor<'a>(pub(crate) &'a [u8]);
+
+impl<'a> Cursor<'a> {
+ pub(crate) fn take(&mut self, length: usize) -> Option<&'a [u8]> {
+ let (head, tail) = self.0.split_at_checked(length)?;
+ self.0 = tail;
+ Some(head)
+ }
+
+ pub(crate) fn byte(&mut self) -> Option {
+ self.take(1)?.first().copied()
+ }
+
+ pub(crate) fn count(&mut self) -> Option {
+ usize::try_from(u64::from_be_bytes(self.take(8)?.try_into().ok()?)).ok()
+ }
+
+ pub(crate) fn text(&mut self) -> Option<&'a str> {
+ let length = self.count()?;
+ std::str::from_utf8(self.take(length)?).ok()
+ }
+}
+
+pub(crate) fn name_matches(content: &css::content_name::ContentName, name: &str) -> bool {
+ name.rfind(|character: char| character.is_ascii_uppercase())
+ .and_then(|marker| marker.checked_sub(1))
+ .is_some_and(|domain| content.name(&name[..domain]) == name)
+}
+
+pub(crate) fn hex_bytes(value: &str) -> Option> {
+ let digit = |byte| match byte {
+ b'0'..=b'9' => Some(byte - b'0'),
+ b'a'..=b'f' => Some(byte - b'a' + 10),
+ _ => None,
+ };
+ let (chunks, remainder) = value.as_bytes().as_chunks::<2>();
+ if !remainder.is_empty() {
+ return None;
+ }
+ chunks
+ .iter()
+ .map(|pair| Some(digit(pair[0])? * 16 + digit(pair[1])?))
+ .collect()
+}
diff --git a/bindings/devup-ui-wasm/src/cache_names.rs b/bindings/devup-ui-wasm/src/cache_names.rs
new file mode 100644
index 000000000..a3af1e6c4
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_names.rs
@@ -0,0 +1,173 @@
+use sheet::StyleSheet;
+use std::cell::RefCell;
+
+thread_local! {
+ static ERROR: RefCell> = const { RefCell::new(None) };
+}
+
+pub(crate) fn check() -> Result<(), String> {
+ ERROR.with_borrow(|error| match error {
+ Some(error) => Err(error.clone()),
+ None => Ok(()),
+ })
+}
+
+pub(crate) fn record(result: &Result<(), String>) {
+ if let Err(incoming) = result {
+ ERROR.with_borrow_mut(|error| {
+ if error.is_none() {
+ *error = Some(incoming.clone());
+ }
+ });
+ }
+}
+
+pub(crate) fn clear() {
+ ERROR.with_borrow_mut(|error| *error = None);
+}
+
+fn content_name(name: &str) -> bool {
+ let Some(marker) = name.rfind(|character: char| character.is_ascii_uppercase()) else {
+ return false;
+ };
+ let Some(domain) = marker
+ .checked_sub(1)
+ .and_then(|index| name.as_bytes().get(index))
+ else {
+ return false;
+ };
+ if !matches!(domain, b'O' | b'R' | b'K') {
+ return false;
+ }
+ let payload = &name[marker + 1..];
+ match name.as_bytes()[marker] {
+ b'H' => {
+ payload.len() == 16
+ && payload
+ .bytes()
+ .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_')
+ }
+ b'L' => {
+ payload.len() <= 16
+ && payload.bytes().all(|byte| {
+ byte.is_ascii_lowercase()
+ || byte.is_ascii_digit()
+ || matches!(byte, b'_' | b'-')
+ })
+ }
+ _ => false,
+ }
+}
+
+pub(crate) fn validate(sheet: &StyleSheet) -> Result<(), String> {
+ crate::cache_atom_proof::validate(sheet)?;
+ for (file, orders) in &sheet.properties {
+ for property in orders.values().flat_map(|levels| levels.values()).flatten() {
+ if property.hoisted
+ && !sheet
+ .atom_plan
+ .as_ref()
+ .is_some_and(|plan| plan.contains(file))
+ {
+ return Err(format!(
+ "cached hoisted atom `{}` has no frozen bucket proof",
+ property.class_name
+ ));
+ }
+ }
+ }
+ for property in sheet
+ .properties
+ .values()
+ .flat_map(|orders| orders.values())
+ .flat_map(|levels| levels.values())
+ .flatten()
+ {
+ if property.owner_reset {
+ crate::cache_source_names::validate(&property.property, &sheet.names)?;
+ }
+ if let Some(reference) = property.value.strip_prefix("var(") {
+ crate::cache_source_names::validate(reference, &sheet.names)?;
+ }
+ }
+ let atoms = sheet.properties.iter().flat_map(|(file, orders)| {
+ orders.iter().flat_map(move |(order, levels)| {
+ levels.values().flatten().map(move |property| {
+ (
+ property.class_name.as_str(),
+ !file.is_empty() && *order != 0 && !property.hoisted,
+ )
+ })
+ })
+ });
+ let frames = sheet
+ .keyframes
+ .values()
+ .flat_map(|frames| frames.keys().map(|name| (name.as_str(), false)));
+ let prefix = css::get_prefix().unwrap_or_default();
+ for (name, per_file) in atoms.chain(frames) {
+ let generated = name.strip_prefix(&prefix).is_some_and(content_name);
+ if per_file
+ && generated
+ && name
+ .rfind(|character: char| character.is_ascii_uppercase())
+ .and_then(|index| index.checked_sub(1))
+ == Some(prefix.len())
+ && name.starts_with(&prefix)
+ {
+ return Err(format!(
+ "cached per-file generated name `{name}` has no sheet namespace; rebuild all caches with the current naming contract"
+ ));
+ }
+ if generated && !sheet.names.contains_key(name) {
+ return Err(format!(
+ "cached generated name `{name}` has no exact descriptor claim; rebuild all caches with the current naming contract"
+ ));
+ }
+ if generated
+ && let Some(domain) = name
+ .rfind(|character: char| character.is_ascii_uppercase())
+ .and_then(|index| index.checked_sub(1))
+ && let Some(scope) = name[..domain].strip_suffix('-')
+ && scope.len() >= prefix.len()
+ && let Some(marker) = scope.rfind('F')
+ && marker >= prefix.len()
+ && matches!(scope.as_bytes().get(marker + 1), Some(b'L' | b'H'))
+ {
+ let claim = sheet.names.get(scope).ok_or_else(|| format!(
+ "cached generated scope `{scope}` has no exact sheet-key claim; rebuild all caches with the current naming contract"
+ ))?;
+ let content = css::content_name::ContentName::scope(&claim.content);
+ if claim.descriptor != content.descriptor || content.name(&scope[..marker]) != scope {
+ return Err(format!(
+ "cached generated scope `{scope}` has a corrupt exact sheet-key claim; rebuild all caches"
+ ));
+ }
+ }
+ }
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn current_content_grammar_does_not_confuse_legacy_counters_or_names() {
+ for name in ["OLcolor-vred", "prefix-RHaaaaaaaaaaaaaaaa", "KL"] {
+ assert!(content_name(name), "{name}");
+ }
+ for name in [
+ "a-a",
+ "Rcolor-vred",
+ "Ksfrom",
+ "a1-h-123",
+ "OHshort",
+ "OLlonger_than_sixteen_payload",
+ "prefix-L",
+ "OXred",
+ ] {
+ assert!(!content_name(name), "{name}");
+ }
+ }
+}
diff --git a/bindings/devup-ui-wasm/src/cache_naming_coverage_tests.rs b/bindings/devup-ui-wasm/src/cache_naming_coverage_tests.rs
new file mode 100644
index 000000000..772b8a112
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_naming_coverage_tests.rs
@@ -0,0 +1,109 @@
+use super::*;
+
+#[rstest]
+#[case(0)]
+#[case(30)]
+#[serial]
+fn dynamic_counter_owner_round_trip_keeps_the_validated_label(#[case] owner: usize) {
+ // Given: real dynamic declarations at the plain and ad-blocker owner-label boundaries.
+ let configure = || {
+ fresh();
+ set_prefix(Some("du-FLa-".into()));
+ seed_file_map((0..=owner).map(|id| format!("{id:02}.tsx")).collect());
+ };
+ configure();
+ let file = format!("{owner:02}.tsx");
+ let source = "import {Box} from '@devup-ui/react';export const x=(p)=>;";
+ let cold = output(&file, source, false);
+ let value = snapshot();
+ assert!(cold.2.contains("color:var(---du-FLa-S"), "{}", cold.2);
+ configure();
+ // When: current admission reuses the already validated owner label for variable proof.
+ assert_eq!(import(value.clone()), Ok(()));
+ // Then: the complete allocator/reset/declaration snapshot and full output round trip.
+ assert_eq!(snapshot(), value);
+ assert_eq!(output(&file, source, false), cold);
+ fresh();
+}
+
+#[test]
+#[serial]
+fn hoisted_declaration_without_bucket_proof_is_cold() {
+ // Given: a genuine current atom whose stored placement falsely claims hoisting.
+ fresh();
+ let source = "import {Box} from '@devup-ui/react';export const x= ;";
+ let cold = output("fresh.tsx", source, false);
+ let mut value = snapshot();
+ value["properties"]["fresh.tsx"]["255"]["0"][0]["h"] = true.into();
+ fresh();
+ let before = snapshot();
+ companions(&value);
+ // When: admission checks actual placement against the frozen plan.
+ assert_eq!(import(value), Ok(()));
+ // Then: the entire snapshot remains cold and extraction remains usable.
+ assert_eq!(snapshot(), before);
+ assert_eq!(output("fresh.tsx", source, false), cold);
+ fresh();
+}
+
+#[test]
+#[serial]
+fn invalid_manual_generated_claim_does_not_replace_live_sheet() {
+ // Given: authored manual controls plus a generated-looking name lacking its claim.
+ fresh();
+ let mut live = StyleSheet::default();
+ live.add_property(
+ "manual-card",
+ "color",
+ 0,
+ "red",
+ None,
+ None,
+ Some("fresh.tsx"),
+ );
+ assert_eq!(import_sheet_internal(live), Ok(()));
+ let before = snapshot();
+ let css = with_style_sheet(|sheet| sheet.create_css(Some("fresh.tsx"), false));
+ assert!(
+ css.contains(&format!(".manual-card{{color:{}}}", "red")),
+ "{css}"
+ );
+ let mut incoming = StyleSheet::default();
+ incoming.add_property("OLcolor-vblue", "color", 0, "blue", None, Some(0), None);
+ // When: in-memory manual import still enforces the generated-name contract.
+ assert_eq!(import_sheet_internal(incoming), Ok(()));
+ // Then: authored controls stay intact and the rejected claim is not sticky.
+ assert_eq!(snapshot(), before);
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("fresh.tsx"), false)),
+ css
+ );
+ assert_eq!(cache_names::check(), Ok(()));
+ fresh();
+}
+
+#[test]
+#[serial]
+fn wrong_keyframe_descriptor_variant_is_cold() {
+ // Given: actual keyframe output, with an atom variant substituted in the frame proof.
+ fresh();
+ let source = "import {keyframes} from '@devup-ui/react';export const x=keyframes({from:{opacity:0},to:{opacity:1}});";
+ let cold = output("fresh.tsx", source, false);
+ let mut value = snapshot();
+ let claim = value["names"]
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("names object"))
+ .iter_mut()
+ .find(|(name, _)| name.starts_with('K'))
+ .unwrap_or_else(|| panic!("keyframe claim"))
+ .1;
+ claim["descriptor"][1] = 1.into();
+ fresh();
+ let before = snapshot();
+ // When: exact frame decoding rejects the wrong semantic variant.
+ assert_eq!(import(value), Ok(()));
+ // Then: no cached state is installed and complete binding output is cold.
+ assert_eq!(snapshot(), before);
+ assert_eq!(output("fresh.tsx", source, false), cold);
+ fresh();
+}
diff --git a/bindings/devup-ui-wasm/src/cache_protocol_tests.rs b/bindings/devup-ui-wasm/src/cache_protocol_tests.rs
new file mode 100644
index 000000000..29d5ea412
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_protocol_tests.rs
@@ -0,0 +1,148 @@
+use super::*;
+use rstest::rstest;
+use serial_test::serial;
+use std::collections::HashSet;
+
+#[test]
+#[serial]
+fn source_ids_roundtrip_restores_dynamic_names_when_extracting_in_reverse_order() {
+ // Given
+ reset_build_state_internal();
+ css::file_map::seed_file_numbers(&["a.tsx".into(), "z.tsx".into()]);
+ let source = "import {Box} from '@devup-ui/react'; export const View=({pad})=>;";
+ let before = code_extract_internal(
+ "z.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let ids = css::file_map::get_original_ids();
+ assert_eq!(
+ ids,
+ BTreeMap::from([("a.tsx".into(), 0), ("z.tsx".into(), 1)])
+ );
+ let exported = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let files = css::file_map::get_file_map();
+ let classes = with_class_map(Clone::clone);
+ let imported: StyleSheet =
+ serde_json::from_str(&exported).unwrap_or_else(|error| panic!("{error}"));
+ reset_build_state_internal();
+ // When
+ import_sheet_internal(imported).unwrap_or_else(|error| panic!("{error}"));
+ set_file_map(files);
+ set_class_map(classes);
+ let restored_ids = css::file_map::get_original_ids();
+ let after = code_extract_internal(
+ "z.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ assert_eq!(restored_ids, ids);
+ assert_eq!(after.code(), before.code());
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false, false)]
+#[case(false, true)]
+#[case(true, true)]
+#[serial]
+fn legacy_serialized_import_is_cold_and_rewritten_current(
+ #[case] with_styles: bool,
+ #[case] atom_mode: bool,
+) {
+ // Given
+ reset_build_state_internal();
+ let mut legacy = StyleSheet::default();
+ if with_styles {
+ legacy.add_property("old", "color", 0, "red", None, None, Some("private.tsx"));
+ }
+ let legacy_json = serde_json::to_string(&legacy).unwrap_or_else(|error| panic!("{error}"));
+ let local = StyleSheet::default().create_css(Some("private.tsx"), false);
+ let global = StyleSheet::default().create_css(None, false);
+ css::atom_hoist::set_atom_hoist(atom_mode.then_some(2));
+ let imported = serde_json::from_str(&legacy_json).unwrap_or_else(|error| panic!("{error}"));
+ import_sheet_internal(imported).unwrap_or_else(|error| panic!("{error}"));
+ // When
+ let exported = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ assert_ne!(exported, legacy_json);
+ let json: serde_json::Value =
+ serde_json::from_str(&exported).unwrap_or_else(|error| panic!("{error}"));
+ assert_eq!(json["atomNamingVersion"], 4);
+ let restored: StyleSheet =
+ serde_json::from_str(&exported).unwrap_or_else(|error| panic!("{error}"));
+ assert_eq!(restored.atom_plan, None);
+ assert_eq!(restored.create_css(Some("private.tsx"), false), local);
+ assert_eq!(restored.create_css(None, false), global);
+ import_sheet_internal(restored).unwrap_or_else(|error| panic!("{error}"));
+ assert_eq!(
+ export_sheet_internal().unwrap_or_else(|error| panic!("{error}")),
+ exported
+ );
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn exported_generation_roundtrips_frozen_atom_placement() {
+ // Given
+ reset_build_state_internal();
+ css::atom_hoist::set_atom_hoist(Some(2));
+ css::file_routes::set_file_routes(HashMap::from([
+ ("shared.tsx".into(), HashSet::from([0, 1])),
+ ("private.tsx".into(), HashSet::from([0])),
+ ]));
+ for file in ["shared.tsx", "private.tsx"] {
+ code_extract_internal(
+ file,
+ "import {Box} from '@devup-ui/react'; export const x=;",
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ }
+ let exported = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let global = with_style_sheet(|sheet| sheet.create_css(None, false));
+ let local = with_style_sheet(|sheet| sheet.create_css(Some("private.tsx"), false));
+ let imported = serde_json::from_str(&exported).unwrap_or_else(|error| panic!("{error}"));
+ reset_build_state_internal();
+ // When
+ import_sheet_internal(imported).unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ assert_eq!(global.matches("color:red").count(), 1);
+ assert_eq!(local.matches("color:red").count(), 1);
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(None, false)),
+ global
+ );
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("private.tsx"), false)),
+ local
+ );
+ assert!(
+ !with_style_sheet(|sheet| sheet.create_css(Some("shared.tsx"), false))
+ .contains("color:red")
+ );
+ assert_eq!(
+ export_sheet_internal().unwrap_or_else(|error| panic!("{error}")),
+ exported
+ );
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/cache_reset_proof.rs b/bindings/devup-ui-wasm/src/cache_reset_proof.rs
new file mode 100644
index 000000000..40337c327
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_reset_proof.rs
@@ -0,0 +1,36 @@
+use sheet::StyleSheet;
+
+pub(super) fn validate(sheet: &StyleSheet) -> bool {
+ sheet.properties.values().all(|orders| {
+ orders.values().all(|levels| {
+ levels.iter().all(|(level, properties)| {
+ properties
+ .iter()
+ .filter(|property| property.owner_reset)
+ .all(|reset| {
+ *level == 0
+ && reset.value == "initial"
+ && reset.property.starts_with("--")
+ && reset.selector.is_none()
+ && reset.layer.is_none()
+ && !reset.typography
+ && levels.values().flatten().any(|consumer| {
+ !consumer.owner_reset
+ && !consumer.typography
+ && consumer.class_name == reset.class_name
+ && consumer.hoisted == reset.hoisted
+ && consumer.value.strip_prefix("var(").and_then(|value| {
+ value
+ .strip_suffix(')')
+ .or_else(|| value.strip_suffix(") !important"))
+ }) == Some(reset.property.as_str())
+ && sheet
+ .names
+ .get(&consumer.class_name)
+ .is_none_or(|claim| claim.descriptor.starts_with(&[1, 2]))
+ })
+ })
+ })
+ })
+ })
+}
diff --git a/bindings/devup-ui-wasm/src/cache_reset_tests.rs b/bindings/devup-ui-wasm/src/cache_reset_tests.rs
new file mode 100644
index 000000000..16634e716
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_reset_tests.rs
@@ -0,0 +1,124 @@
+use super::*;
+
+const STATIC: &str = "import {css} from '@devup-ui/react';export const x=css({color:'red'});";
+const DYNAMIC: &str =
+ "import {Box} from '@devup-ui/react';export const x=(p)=>;";
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn forged_static_reset_is_cold_when_companions_match_or_are_absent(#[case] matching: bool) {
+ // Given: a real complete snapshot with a static declaration disguised as a reset.
+ fresh();
+ let cold = output("reset.tsx", STATIC, true);
+ let mut value = snapshot();
+ let property = &mut value["properties"][""]["255"]["0"][0];
+ property["r"] = true.into();
+ property["v"] = "blue".into();
+ fresh();
+ if matching {
+ companions(&value);
+ }
+ // When: the corrupt current snapshot reaches admission.
+ assert_eq!(import(value), Ok(()));
+ // Then: no poisoned declaration survives and whole output is cold.
+ assert_eq!(with_style_sheet(|sheet| sheet.properties.len()), 0);
+ assert_eq!(output("reset.tsx", STATIC, true), cold);
+ fresh();
+}
+
+enum ResetDamage {
+ Value,
+ Selector,
+ Level,
+ Layer,
+ Typography,
+ Variable,
+ Orphan,
+}
+
+#[rstest]
+#[case(ResetDamage::Value)]
+#[case(ResetDamage::Selector)]
+#[case(ResetDamage::Level)]
+#[case(ResetDamage::Layer)]
+#[case(ResetDamage::Typography)]
+#[case(ResetDamage::Variable)]
+#[case(ResetDamage::Orphan)]
+#[serial]
+fn corrupt_owner_reset_restores_nothing(
+ #[case] damage: ResetDamage,
+ #[values(false, true)] seeded: bool,
+) {
+ // Given: independently cold output and a damaged genuine owner reset.
+ fresh();
+ if seeded {
+ seed_file_map(vec!["reset.tsx".into()]);
+ }
+ let cold = output("reset.tsx", DYNAMIC, false);
+ let mut value = snapshot();
+ let properties = value["properties"]["reset.tsx"]["255"]["0"]
+ .as_array_mut()
+ .unwrap_or_else(|| panic!("properties"));
+ let reset = properties
+ .iter_mut()
+ .find(|property| property["r"] == true)
+ .unwrap_or_else(|| panic!("owner reset"));
+ match damage {
+ ResetDamage::Value => reset["v"] = "inherit".into(),
+ ResetDamage::Selector => reset["s"] = serde_json::json!({"Selector": "&:hover"}),
+ ResetDamage::Level => {
+ let reset = reset.clone();
+ properties.retain(|property| property["r"] != true);
+ value["properties"]["reset.tsx"]["255"]["1"] = serde_json::json!([reset]);
+ }
+ ResetDamage::Layer => reset["l"] = "t".into(),
+ ResetDamage::Typography => reset["t"] = true.into(),
+ ResetDamage::Variable => reset["p"] = "--theme".into(),
+ ResetDamage::Orphan => properties.retain(|property| property["r"] == true),
+ }
+ fresh();
+ if seeded {
+ seed_file_map(vec!["reset.tsx".into()]);
+ }
+ // When: malformed reset state is admitted through the serialized API.
+ assert_eq!(import(value), Ok(()));
+ // Then: all cache state is discarded, including the reset exemption.
+ assert_eq!(with_style_sheet(|sheet| sheet.properties.len()), 0);
+ assert_eq!(output("reset.tsx", DYNAMIC, false), cold);
+ fresh();
+}
+
+#[test]
+#[serial]
+fn invalid_reset_claim_does_not_latch_a_false_collision() {
+ // Given: a live exact red claim and corrupt descriptor bytes hidden behind r=true.
+ fresh();
+ output("live.tsx", STATIC, true);
+ let before = snapshot();
+ let mut value = before.clone();
+ value["properties"][""]["255"]["0"][0]["r"] = true.into();
+ value["names"]["OLcolor-vred"]["descriptor"]
+ .as_array_mut()
+ .unwrap_or_else(|| panic!("descriptor"))
+ .push(99.into());
+ // When: the invalid snapshot overlaps a genuine live claim.
+ assert_eq!(import(value), Ok(()));
+ // Then: the live sheet remains unchanged and extraction is not poisoned by a latch.
+ assert_eq!(snapshot(), before);
+ assert!(
+ code_extract_internal(
+ "later.tsx",
+ STATIC,
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new()
+ )
+ .is_ok()
+ );
+ fresh();
+}
diff --git a/bindings/devup-ui-wasm/src/cache_restore.rs b/bindings/devup-ui-wasm/src/cache_restore.rs
new file mode 100644
index 000000000..bba9d9901
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_restore.rs
@@ -0,0 +1,245 @@
+use std::{cell::RefCell, collections::BTreeMap};
+
+use sheet::{
+ StyleSheet,
+ cache_snapshot::{CacheRestore, ClassMap, FileMap},
+};
+
+use crate::{administration::with_administration, cache_names, with_style_sheet_mut};
+
+#[path = "cache_allocator_proof.rs"]
+mod allocator;
+
+struct Authority {
+ classes: ClassMap,
+ files: FileMap,
+ before: StyleSheet,
+ before_classes: ClassMap,
+ before_files: FileMap,
+ before_sources: BTreeMap,
+ before_plan: Option>,
+ fresh_seeds: Vec>,
+}
+
+#[derive(Default)]
+enum Companion {
+ #[default]
+ Unseen,
+ Invalid,
+ Supplied(T),
+}
+
+impl Companion {
+ fn matches(&self, expected: &T) -> bool {
+ match self {
+ Self::Unseen => true,
+ Self::Invalid => false,
+ Self::Supplied(value) => value == expected,
+ }
+ }
+}
+
+impl From> for Companion {
+ fn from(value: Option) -> Self {
+ match value {
+ Some(value) => Self::Supplied(value),
+ None => Self::Invalid,
+ }
+ }
+}
+
+#[derive(Default)]
+struct Restore {
+ classes: Companion,
+ files: Companion,
+ authority: Option,
+}
+
+thread_local! {
+ static RESTORE: RefCell = RefCell::new(Restore::default());
+}
+
+pub(crate) fn absent() -> StyleSheet {
+ let mut sheet = StyleSheet::default();
+ sheet.cache_restore = CacheRestore::Rejected;
+ sheet
+}
+
+fn set_classes(classes: ClassMap) {
+ css::class_map::set_class_map(
+ classes
+ .into_iter()
+ .map(|(file, classes)| (file, classes.into_iter().collect()))
+ .collect(),
+ );
+}
+
+fn set_files(files: FileMap) {
+ css::file_map::set_file_map(files.into_iter().collect());
+}
+
+fn rollback(state: &mut Restore) {
+ if let Some(mut authority) = state.authority.take() {
+ set_classes(authority.before_classes);
+ set_files(authority.before_files);
+ css::file_map::set_original_ids(authority.before_sources);
+ css::atom_hoist::restore_atom_plan(authority.before_plan);
+ with_style_sheet_mut(|current| {
+ authority.before.theme = std::mem::take(&mut current.theme);
+ *current = authority.before;
+ });
+ for files in authority.fresh_seeds {
+ css::file_map::seed_file_numbers(&files);
+ }
+ }
+}
+
+pub(crate) fn clear() {
+ with_administration("cache_restore::clear", || {
+ RESTORE.with_borrow_mut(|state| *state = Restore::default());
+ });
+}
+
+pub(crate) fn seeded(files: &[String]) {
+ with_administration("cache_restore::seeded", || {
+ RESTORE.with_borrow_mut(|state| {
+ if let Some(authority) = &mut state.authority {
+ authority.fresh_seeds.push(files.to_vec());
+ }
+ });
+ });
+}
+
+pub(crate) fn classes(incoming: Option) {
+ with_administration("cache_restore::classes", || {
+ let incoming = Companion::from(incoming);
+ RESTORE.with_borrow_mut(|state| {
+ if state
+ .authority
+ .as_ref()
+ .is_some_and(|authority| incoming.matches(&authority.classes))
+ {
+ state.classes = Companion::Unseen;
+ return;
+ }
+ if state
+ .authority
+ .as_ref()
+ .is_some_and(|authority| !incoming.matches(&authority.classes))
+ {
+ rollback(state);
+ }
+ state.classes = incoming;
+ });
+ });
+}
+
+pub(crate) fn files(incoming: Option) {
+ with_administration("cache_restore::files", || {
+ let incoming = Companion::from(incoming);
+ RESTORE.with_borrow_mut(|state| {
+ if state
+ .authority
+ .as_ref()
+ .is_some_and(|authority| incoming.matches(&authority.files))
+ {
+ state.files = Companion::Unseen;
+ return;
+ }
+ if state
+ .authority
+ .as_ref()
+ .is_some_and(|authority| !incoming.matches(&authority.files))
+ {
+ rollback(state);
+ }
+ state.files = incoming;
+ });
+ });
+}
+
+pub(crate) fn import(mut incoming: StyleSheet) -> Result<(), String> {
+ with_administration("cache_restore::import", || {
+ let result =
+ RESTORE.with_borrow_mut(|state| match std::mem::take(&mut incoming.cache_restore) {
+ CacheRestore::Rejected => {
+ rollback(state);
+ Ok(())
+ }
+ CacheRestore::Manual => {
+ if cache_names::validate(&incoming).is_err() {
+ return Ok(());
+ }
+ with_style_sheet_mut(|current| {
+ let claims =
+ sheet::name_registry::preflight(¤t.names, incoming.names.clone())
+ .map_err(|error| error.to_string())?;
+ incoming.names = current.names.clone();
+ incoming.names.extend(claims);
+ incoming.theme = std::mem::take(&mut current.theme);
+ css::atom_hoist::restore_atom_plan(incoming.atom_plan.clone());
+ *current = incoming;
+ Ok(())
+ })
+ }
+ CacheRestore::Serialized { classes, files } => {
+ if !allocator::validate(&incoming, &classes)
+ || cache_names::validate(&incoming).is_err()
+ || !state.classes.matches(&classes)
+ || !state.files.matches(&files)
+ {
+ rollback(state);
+ return Ok(());
+ }
+ let claims = crate::with_style_sheet(|current| {
+ sheet::name_registry::preflight(¤t.names, incoming.names.clone())
+ .map_err(|error| error.to_string())
+ })?;
+ rollback(state);
+ with_style_sheet_mut(|current| {
+ incoming.names = current.names.clone();
+ incoming.names.extend(claims);
+ let before_classes = css::class_map::with_class_map(|map| {
+ map.iter()
+ .map(|(file, classes)| {
+ (
+ file.clone(),
+ classes
+ .iter()
+ .map(|(key, id)| (key.clone(), *id))
+ .collect(),
+ )
+ })
+ .collect()
+ });
+ let before_files = css::file_map::with_file_map(|map| {
+ map.iter().map(|(file, id)| (file.clone(), *id)).collect()
+ });
+ let before_sources = css::file_map::get_original_ids();
+ let before_plan = css::atom_hoist::atom_plan();
+ set_classes(classes.clone());
+ set_files(files.clone());
+ css::file_map::set_original_ids(incoming.source_ids.clone());
+ css::atom_hoist::restore_atom_plan(incoming.atom_plan.clone());
+ incoming.theme = std::mem::take(&mut current.theme);
+ let before = std::mem::replace(current, incoming);
+ state.authority = Some(Authority {
+ classes,
+ files,
+ before,
+ before_classes,
+ before_files,
+ before_sources,
+ before_plan,
+ fresh_seeds: Vec::new(),
+ });
+ state.classes = Companion::Unseen;
+ state.files = Companion::Unseen;
+ Ok(())
+ })
+ }
+ });
+ cache_names::record(&result);
+ result
+ })
+}
diff --git a/bindings/devup-ui-wasm/src/cache_seed_tests.rs b/bindings/devup-ui-wasm/src/cache_seed_tests.rs
new file mode 100644
index 000000000..7692f6d72
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_seed_tests.rs
@@ -0,0 +1,37 @@
+use super::*;
+
+fn seed_batches() {
+ seed_file_map(vec!["b.tsx".into(), "b.tsx".into()]);
+ seed_file_map(vec!["a.tsx".into(), "b.tsx".into()]);
+ seed_file_map(vec!["c.tsx".into(), "a.tsx".into(), "c.tsx".into()]);
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn rollback_keeps_seed_call_order_and_repeated_files(#[case] class_companion: bool) {
+ // Given: independently cold incremental numbering and an admitted empty cache.
+ const SOURCE: &str =
+ "import {Box} from '@devup-ui/react';export const x=(p)=>;";
+ fresh();
+ seed_batches();
+ let cold = output("a.tsx", SOURCE, false);
+ let cold_ids = css::file_map::get_original_ids();
+ let cold_files = css::file_map::get_file_map();
+ fresh();
+ let empty = snapshot();
+ assert_eq!(import(empty), Ok(()));
+ seed_batches();
+ // When: an invalid companion rolls back cached state after three fresh seed calls.
+ if class_companion {
+ cache_restore::classes(None);
+ } else {
+ cache_restore::files(None);
+ }
+ // Then: original/delivery numbering and the full emitted output match those same cold calls.
+ assert_eq!(css::file_map::get_original_ids(), cold_ids);
+ assert_eq!(css::file_map::get_file_map(), cold_files);
+ assert_eq!(output("a.tsx", SOURCE, false), cold);
+ fresh();
+}
diff --git a/bindings/devup-ui-wasm/src/cache_source_names.rs b/bindings/devup-ui-wasm/src/cache_source_names.rs
new file mode 100644
index 000000000..b5a562ffc
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_source_names.rs
@@ -0,0 +1,97 @@
+use css::content_name::ContentName;
+use sheet::name_registry::NameRegistry;
+
+pub(crate) fn validate(text: &str, names: &NameRegistry) -> Result<(), String> {
+ let mut scanned = 0;
+ for (start, _) in text.match_indices("---") {
+ if start < scanned {
+ continue;
+ }
+ let tail = &text[start..];
+ let end = tail
+ .find(|character: char| {
+ !(character.is_ascii_alphanumeric()
+ || character >= '\u{80}'
+ || matches!(character, '-' | '_'))
+ })
+ .unwrap_or(tail.len());
+ scanned = start + end;
+ let variable = &tail[..end];
+ let Some(marker) = variable.rfind(|character: char| character.is_ascii_uppercase()) else {
+ continue;
+ };
+ let (prefix, file_part) = match variable.as_bytes()[marker] {
+ b'L' | b'H' if variable[..marker].ends_with("SU") => {
+ (&variable[..marker - 1], &variable[marker - 1..])
+ }
+ b'U' if variable[..marker].ends_with('S') => {
+ return Err(format!(
+ "cached source variable `{variable}` uses an unprotected source naming contract; rebuild all caches"
+ ));
+ }
+ _ => continue,
+ };
+ let Some((file_part, _position)) = file_part.split_once('-') else {
+ return Err(format!(
+ "cached source variable `{variable}` has no source position; rebuild all caches"
+ ));
+ };
+ let name = &variable[..prefix.len() + file_part.len()];
+ let Some(claim) = names.get(name) else {
+ return Err(format!(
+ "cached source variable `{variable}` has no exact source claim; rebuild all caches with the current naming contract"
+ ));
+ };
+ if claim.descriptor != claim.content.as_bytes()
+ || ContentName::source(&claim.content).name(prefix) != name
+ {
+ return Err(format!(
+ "cached source variable `{variable}` has an invalid exact source claim; rebuild all caches with the current naming contract"
+ ));
+ }
+ }
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use css::style_origin::RealLocation;
+ use rstest::rstest;
+ use sheet::name_registry::NameClaim;
+
+ #[rstest]
+ #[case("---Sa-a", true)]
+ #[case("---Mixed-SUH-prefix-Sa-b-c", true)]
+ #[case("--theme-color", true)]
+ #[case("--v1-color-0-selector", true)]
+ #[case("plain text", true)]
+ #[case("---lowercase", true)]
+ #[case("var(---SULa-b)", true)]
+ #[case("---SULa-b-c", true)]
+ #[case("var(---SULmissing-b)", false)]
+ #[case("---SULa", false)]
+ #[case("var(---SUa-b)", false)]
+ fn cached_source_variables_require_exact_claims_without_confusing_other_namespaces(
+ #[case] text: &str,
+ #[case] protected: bool,
+ ) {
+ // Given: one exact short source claim and arbitrary cached CSS tokens.
+ let names = NameRegistry::from([(
+ "---SULa".into(),
+ NameClaim {
+ descriptor: b"a".to_vec(),
+ content: "a".into(),
+ origin: None,
+ location: RealLocation::ModuleExport {
+ file: "/real/a.tsx".into(),
+ binding: None,
+ },
+ },
+ )]);
+ // When: the production cache boundary scans both reset/value token shapes.
+ let result = validate(text, &names);
+ // Then: only source-variable tokens require source proof and a position.
+ assert_eq!(result.is_ok(), protected, "{result:?}");
+ }
+}
diff --git a/bindings/devup-ui-wasm/src/cache_special_proof.rs b/bindings/devup-ui-wasm/src/cache_special_proof.rs
new file mode 100644
index 000000000..0d7a4b63d
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_special_proof.rs
@@ -0,0 +1,152 @@
+use std::collections::{BTreeMap, BTreeSet};
+
+use css::{
+ Naming,
+ content_name::{AtomContent, ContentName},
+};
+use sheet::{StyleSheet, StyleSheetProperty};
+
+use crate::cache_descriptor::{Cursor, name_matches};
+
+type FrameSteps = Vec<(String, Vec<(String, String)>)>;
+
+fn frames(descriptor: &[u8]) -> Option {
+ let mut cursor = Cursor(descriptor);
+ if cursor.take(2)? != [1, 3] {
+ return None;
+ }
+ let count = cursor.count()?;
+ let mut steps = Vec::new();
+ for _ in 0..count {
+ let step = cursor.text()?.to_string();
+ let count = cursor.count()?;
+ let mut properties = Vec::new();
+ for _ in 0..count {
+ properties.push((cursor.text()?.to_string(), cursor.text()?.to_string()));
+ }
+ steps.push((step, properties));
+ }
+ cursor.0.is_empty().then_some(steps)
+}
+
+pub(crate) fn validate_frames(sheet: &StyleSheet) -> Result<(), String> {
+ for frames_map in sheet.keyframes.values() {
+ for (name, steps) in frames_map {
+ if let Some(claim) = sheet.names.get(name) {
+ let valid = frames(&claim.descriptor).is_some_and(|decoded| {
+ let content = ContentName::keyframes(&decoded);
+ name_matches(&content, name)
+ && decoded.into_iter().collect::>() == *steps
+ });
+ if !valid {
+ return Err(format!(
+ "cached keyframes `{name}` have a corrupt exact frame proof"
+ ));
+ }
+ }
+ }
+ }
+ Ok(())
+}
+
+fn declarations(value: &str) -> Option> {
+ let bytes = crate::cache_descriptor::hex_bytes(value.strip_prefix('t')?)?;
+ let mut cursor = Cursor(&bytes);
+ let count = cursor.count()?;
+ let mut result = BTreeSet::new();
+ for _ in 0..count {
+ result.insert((
+ cursor.byte()?,
+ cursor.text()?.to_string(),
+ cursor.text()?.to_string(),
+ ));
+ }
+ (cursor.0.is_empty() && result.len() == count).then_some(result)
+}
+
+pub(crate) fn typography(
+ property: &StyleSheetProperty,
+ group: (&BTreeMap>, u8),
+ descriptor: &[u8],
+) -> Option<()> {
+ let mut cursor = Cursor(descriptor);
+ if cursor.take(2)? != [1, 1] {
+ return None;
+ }
+ let naming = match cursor.byte()? {
+ 0 => Naming::Own,
+ 1 => Naming::Risky,
+ _ => return None,
+ };
+ if cursor.text()? != "typography" || cursor.byte()? != 1 {
+ return None;
+ }
+ let value = cursor.text()?;
+ let level = cursor.byte()?;
+ let order = cursor.byte()?;
+ if order != group.1 {
+ return None;
+ }
+ let content = AtomContent {
+ property: "typography",
+ value: Some(value),
+ naming,
+ level,
+ order,
+ selector: property.selector.as_ref(),
+ layer: property.layer.as_deref()?.strip_suffix(".t"),
+ dynamic: false,
+ }
+ .content();
+ if content.descriptor != descriptor || !name_matches(&content, &property.class_name) {
+ return None;
+ }
+ let actual = group
+ .0
+ .iter()
+ .flat_map(|(level, properties)| {
+ properties
+ .iter()
+ .filter(|other| other.class_name == property.class_name && other.typography)
+ .map(|other| (*level, other.property.clone(), other.value.clone()))
+ })
+ .collect();
+ (declarations(value)? == actual).then_some(())
+}
+
+pub(crate) fn counter_typography(
+ property: &StyleSheetProperty,
+ group: (&BTreeMap>, u8),
+ key: &str,
+) -> Option<()> {
+ let lossless = String::from_utf8(crate::cache_descriptor::hex_bytes(key)?).ok()?;
+ let tail = lossless.strip_prefix("typography-v")?;
+ let value = tail.split('-').next()?;
+ let suffix = tail.strip_prefix(value)?;
+ let level = match suffix.strip_prefix("-l") {
+ Some(level) => level.split('-').next()?.parse().ok()?,
+ None => 0,
+ };
+ let content = AtomContent {
+ property: "typography",
+ value: Some(value),
+ naming: Naming::Own,
+ level,
+ order: group.1,
+ selector: property.selector.as_ref(),
+ layer: property.layer.as_deref()?.strip_suffix(".t"),
+ dynamic: false,
+ }
+ .content();
+ let actual = group
+ .0
+ .iter()
+ .flat_map(|(level, properties)| {
+ properties
+ .iter()
+ .filter(|other| other.class_name == property.class_name && other.typography)
+ .map(|other| (*level, other.property.clone(), other.value.clone()))
+ })
+ .collect();
+ (content.lossless == lossless && declarations(value)? == actual).then_some(())
+}
diff --git a/bindings/devup-ui-wasm/src/cache_typography_coverage_tests.rs b/bindings/devup-ui-wasm/src/cache_typography_coverage_tests.rs
new file mode 100644
index 000000000..3341ec815
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_typography_coverage_tests.rs
@@ -0,0 +1,158 @@
+use super::*;
+
+#[path = "cache_typography_descriptor_tests.rs"]
+mod descriptors;
+
+fn configure(numbered: bool) {
+ fresh();
+ register_theme_internal(
+ serde_json::from_value(serde_json::json!({
+ "typography":{"body":[{"fontSize":"14px","fontFamily":"Sans"},null,{"fontSize":"18px"}]}
+ }))
+ .unwrap_or_else(|error| panic!("{error}")),
+ );
+ if numbered {
+ seed_file_map(vec!["fresh.tsx".into()]);
+ }
+}
+
+fn source(responsive: bool) -> &'static str {
+ if responsive {
+ "import {Box} from '@devup-ui/react';export const x=;"
+ } else {
+ "import {Box} from '@devup-ui/react';export const x=;"
+ }
+}
+
+#[rstest]
+#[serial]
+fn current_typography_restores_exact_state_without_fresh_theme_authority(
+ #[values(false, true)] numbered: bool,
+ #[values(false, true)] responsive: bool,
+ #[values(false, true)] matching: bool,
+) {
+ // Given: actual preset expansion, including the original-owner counter path.
+ configure(numbered);
+ let cold = output("fresh.tsx", source(responsive), false);
+ let value = snapshot();
+ assert!(cold.2.contains("font-size:18px"), "{}", cold.2);
+ if numbered {
+ assert!(cold.0.contains("a-a"), "{}", cold.0);
+ assert_eq!(
+ value["classMap"]["D9-0"]
+ .as_object()
+ .unwrap_or_else(|| panic!("counter class map"))
+ .len(),
+ 1
+ );
+ }
+ fresh();
+ let configured_theme = with_style_sheet(|sheet| sheet.theme.typography.len());
+ if matching {
+ companions(&value);
+ }
+ // When: the cache alone proves its declarations with no current preset available.
+ assert_eq!(import(value.clone()), Ok(()));
+ // Then: this is admission, not a silent cold miss, and fresh theme stays fresh.
+ assert_eq!(snapshot(), value);
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.theme.typography.len()),
+ configured_theme
+ );
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("fresh.tsx"), false)),
+ cold.2
+ );
+ register_theme_internal(
+ serde_json::from_value(serde_json::json!({
+ "typography":{"body":[{"fontSize":"14px","fontFamily":"Sans"},null,{"fontSize":"18px"}]}
+ }))
+ .unwrap_or_else(|error| panic!("{error}")),
+ );
+ assert_eq!(output("fresh.tsx", source(responsive), false), cold);
+ fresh();
+}
+
+#[rstest]
+#[serial]
+fn corrupt_typography_declarations_are_cold_not_sticky(
+ #[values(false, true)] numbered: bool,
+ #[values(false, true)] responsive: bool,
+) {
+ // Given: a real cache with one expanded declaration changed but its identity retained.
+ configure(numbered);
+ let cold = output("fresh.tsx", source(responsive), false);
+ let mut value = snapshot();
+ let mut changed = false;
+ for orders in value["properties"]
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("properties object"))
+ .values_mut()
+ {
+ for levels in orders
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("orders object"))
+ .values_mut()
+ {
+ for properties in levels
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("levels object"))
+ .values_mut()
+ {
+ for property in properties
+ .as_array_mut()
+ .unwrap_or_else(|| panic!("declaration array"))
+ {
+ if property["t"] == true && property["p"] == "font-size" {
+ property["v"] = "99px".into();
+ changed = true;
+ }
+ }
+ }
+ }
+ }
+ assert!(changed);
+ configure(numbered);
+ let before = snapshot();
+ companions(&value);
+ // When: matching companions cannot excuse the corrupt declaration proof.
+ assert_eq!(import(value), Ok(()));
+ // Then: state and complete JS/global/local CSS are independently cold; no error latched.
+ assert_eq!(snapshot(), before);
+ assert_eq!(output("fresh.tsx", source(responsive), false), cold);
+ assert_eq!(cache_names::check(), Ok(()));
+ fresh();
+}
+
+#[rstest]
+#[case("g0")]
+#[case("0g")]
+#[case("0")]
+#[serial]
+fn malformed_counter_identity_is_cold_with_matching_companions(#[case] key: &str) {
+ // Given: a real current counter typography cache with a non-hex or truncated slot key.
+ configure(true);
+ let cold = output("fresh.tsx", source(false), false);
+ let mut value = snapshot();
+ let classes = value["classMap"]["D9-0"]
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("counter class map"));
+ let previous = classes
+ .keys()
+ .next()
+ .cloned()
+ .unwrap_or_else(|| panic!("counter slot key"));
+ let slot = classes
+ .remove(&previous)
+ .unwrap_or_else(|| panic!("counter slot value"));
+ classes.insert(key.into(), slot);
+ configure(true);
+ let before = snapshot();
+ companions(&value);
+ // When: the malformed identity reaches the counter proof itself.
+ assert_eq!(import(value), Ok(()));
+ // Then: no partial adoption or false collision, with full cold output preserved.
+ assert_eq!(snapshot(), before);
+ assert_eq!(output("fresh.tsx", source(false), false), cold);
+ fresh();
+}
diff --git a/bindings/devup-ui-wasm/src/cache_typography_descriptor_tests.rs b/bindings/devup-ui-wasm/src/cache_typography_descriptor_tests.rs
new file mode 100644
index 000000000..928aaf523
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_typography_descriptor_tests.rs
@@ -0,0 +1,281 @@
+use super::*;
+use crate::cache_descriptor::Cursor;
+use css::{Naming, content_name::AtomContent};
+
+#[rstest]
+#[case(None)]
+#[case(Some("first.tsx"))]
+#[case(Some("second.tsx"))]
+#[serial]
+fn get_css_selects_global_or_numbered_file_with_main_import_control(
+ #[case] file: Option<&str>,
+ #[values(false, true)] import_main_css: bool,
+) {
+ // Given: distinct file declarations and real global typography CSS.
+ configure(false);
+ let first = output(
+ "first.tsx",
+ "import {Box}from '@devup-ui/react';export const x= ;",
+ false,
+ );
+ let second = output(
+ "second.tsx",
+ "import {Box}from '@devup-ui/react';export const x= ;",
+ false,
+ );
+ assert!(first.2.contains("color:red"), "{}", first.2);
+ assert!(second.2.contains("color:blue"), "{}", second.2);
+ assert_ne!(first.2, second.2);
+ assert!(second.1.contains("font-size:14px"), "{}", second.1);
+ let file_num = file.map(|file| {
+ with_file_map(|map| {
+ map.get_by_left(file)
+ .copied()
+ .unwrap_or_else(|| panic!("registered file: {file}"))
+ })
+ });
+ let expected = match file {
+ None => second.1,
+ Some("first.tsx") => first.2,
+ Some("second.tsx") => second.2,
+ Some(other) => panic!("unexpected fixture: {other}"),
+ };
+ // When
+ let actual = get_css_internal(file_num, import_main_css)
+ .unwrap_or_else(|error| panic!("getCss: {error}"));
+ // Then: file selection is exact; only numbered files receive the requested main import.
+ let main_import = "@import \"./devup-ui.css\";";
+ assert_eq!(
+ actual.matches(main_import).count(),
+ usize::from(file.is_some() && import_main_css)
+ );
+ assert_eq!(actual.replace(main_import, ""), expected);
+ fresh();
+}
+
+#[rstest]
+#[serial]
+fn get_css_uses_global_output_when_file_number_is_unmapped(
+ #[values(false, true)] import_main_css: bool,
+) {
+ // Given
+ configure(false);
+ let expected = output("first.tsx", source(false), false).1;
+ // When
+ let actual = get_css_internal(Some(usize::MAX), import_main_css);
+ // Then: the existing failed lookup passes None to CSS generation.
+ assert_eq!(actual, Ok(expected));
+ fresh();
+}
+
+enum Damage {
+ Header,
+ Naming,
+ Property,
+ ValueTag,
+ Order,
+ Trailing,
+}
+
+#[rstest]
+#[case(Damage::Header)]
+#[case(Damage::Naming)]
+#[case(Damage::Property)]
+#[case(Damage::ValueTag)]
+#[case(Damage::Order)]
+#[case(Damage::Trailing)]
+#[serial]
+fn malformed_typography_descriptor_is_cold_before_claim_preflight(#[case] damage: Damage) {
+ // Given: an actual content-named typography atom and its versioned proof.
+ configure(false);
+ let cold = output("fresh.tsx", source(false), false);
+ let mut value = snapshot();
+ let claim = value["names"]
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("names object"))
+ .values_mut()
+ .find(|claim| {
+ claim["descriptor"]
+ .as_array()
+ .is_some_and(|bytes| bytes.get(1) == Some(&1.into()))
+ })
+ .unwrap_or_else(|| panic!("typography claim"));
+ let mut descriptor: Vec = serde_json::from_value(claim["descriptor"].clone())
+ .unwrap_or_else(|error| panic!("descriptor: {error}"));
+ match damage {
+ Damage::Header => descriptor[1] = 3,
+ Damage::Naming => descriptor[2] = 2,
+ Damage::Property => descriptor[11] = b'x',
+ Damage::ValueTag => descriptor[21] = 0,
+ Damage::Order => {
+ let mut cursor = Cursor(&descriptor[3..]);
+ assert_eq!(cursor.text(), Some("typography"));
+ assert_eq!(cursor.byte(), Some(1));
+ assert!(cursor.text().is_some());
+ assert_eq!(cursor.byte(), Some(0));
+ let index = descriptor.len() - cursor.0.len();
+ descriptor[index] = 1;
+ }
+ Damage::Trailing => descriptor.push(99),
+ }
+ claim["descriptor"] =
+ serde_json::to_value(descriptor).unwrap_or_else(|error| panic!("descriptor JSON: {error}"));
+ configure(false);
+ let before = snapshot();
+ // When: cache admission encounters the corrupt exact typography proof.
+ assert_eq!(import(value), Ok(()));
+ // Then: the failure is a cold miss, not a real-name collision or partial adoption.
+ assert_eq!(snapshot(), before);
+ assert_eq!(output("fresh.tsx", source(false), false), cold);
+ assert_eq!(cache_names::check(), Ok(()));
+ fresh();
+}
+
+#[test]
+#[serial]
+fn current_typography_claim_collision_keeps_both_locations_and_live_state() {
+ // Given: two genuinely different preset expansions with real source locations.
+ let source =
+ "import {Box} from '@devup-ui/react';\nexport const x=;";
+ let configure = |size: &str| {
+ fresh();
+ register_theme_internal(
+ serde_json::from_value(serde_json::json!({"typography":{"body":{"fontSize":size}}}))
+ .unwrap_or_else(|error| panic!("theme fixture: {error}")),
+ );
+ };
+ configure("18px");
+ output("cached.tsx", source, true);
+ let mut incoming = snapshot();
+ let cached_claim = incoming["names"]
+ .as_object()
+ .unwrap_or_else(|| panic!("cached names object"))
+ .values()
+ .find(|claim| {
+ claim["descriptor"]
+ .as_array()
+ .is_some_and(|bytes| bytes.get(1) == Some(&1.into()))
+ })
+ .unwrap_or_else(|| panic!("cached typography claim"))
+ .clone();
+ configure("14px");
+ output("live.tsx", source, true);
+ let before = snapshot();
+ let live_name = before["names"]
+ .as_object()
+ .unwrap_or_else(|| panic!("live names object"))
+ .iter()
+ .find(|(_, claim)| {
+ claim["descriptor"]
+ .as_array()
+ .is_some_and(|bytes| bytes.get(1) == Some(&1.into()))
+ })
+ .unwrap_or_else(|| panic!("live typography claim"))
+ .0
+ .clone();
+ incoming["properties"] = serde_json::json!({});
+ incoming["names"]
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("incoming names object"))
+ .insert(live_name.clone(), cached_claim);
+ let column = source
+ .split('\n')
+ .nth(1)
+ .unwrap_or_else(|| panic!("authored second line"))
+ .find("'body'")
+ .unwrap_or_else(|| panic!("authored typography operand"))
+ + 1;
+ // When: a complete current snapshot presents conflicting exact bytes under a live name.
+ let error = import(incoming)
+ .err()
+ .unwrap_or_else(|| panic!("conflicting claims must fail"));
+ // Then: both source sites survive, no live state changes, and the genuine error stays sticky.
+ assert!(
+ error.contains(&format!("cached.tsx:2:{column}:")),
+ "{error}"
+ );
+ assert!(error.contains(&format!("live.tsx:2:{column}:")), "{error}");
+ assert!(error.contains(&format!("`{live_name}`")), "{error}");
+ assert_eq!(snapshot(), before);
+ let extraction_error = code_extract_internal(
+ "live.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .err()
+ .unwrap_or_else(|| panic!("sticky collision rejects extraction"));
+ assert_eq!(extraction_error, error);
+ assert_eq!(get_css_internal(None, false), Err(error.clone()));
+ assert_eq!(snapshot(), before);
+ assert_eq!(cache_names::check(), Err(error));
+ fresh();
+}
+
+#[rstest]
+#[case(None)]
+#[case(Some("t0"))]
+#[case(Some("tgg"))]
+#[case(Some("t0000000000000001"))]
+#[serial]
+fn risky_typography_proof_accepts_only_exact_complete_declarations(
+ #[case] malformed: Option<&str>,
+) {
+ // Given: real expanded declarations, named with Risky provenance at the pure proof boundary.
+ configure(false);
+ output("fresh.tsx", source(false), false);
+ let (mut property, mut levels, descriptor) = with_style_sheet(|sheet| {
+ let levels = &sheet.properties["fresh.tsx"][&255];
+ let property = levels
+ .values()
+ .flatten()
+ .find(|property| property.typography)
+ .unwrap_or_else(|| panic!("expanded typography property"));
+ (
+ property.clone(),
+ levels.clone(),
+ sheet.names[&property.class_name].descriptor.clone(),
+ )
+ });
+ let mut cursor = Cursor(&descriptor[3..]);
+ assert_eq!(cursor.text(), Some("typography"));
+ assert_eq!(cursor.byte(), Some(1));
+ let original = cursor
+ .text()
+ .unwrap_or_else(|| panic!("typography declaration encoding"));
+ let content = AtomContent {
+ property: "typography",
+ value: Some(malformed.unwrap_or(original)),
+ naming: Naming::Risky,
+ level: 0,
+ order: 255,
+ selector: property.selector.as_ref(),
+ layer: None,
+ dynamic: false,
+ }
+ .content();
+ let old_name = property.class_name.clone();
+ property.class_name = content.name("FLfresh_ptsx-");
+ for properties in levels.values_mut() {
+ *properties = properties
+ .iter()
+ .cloned()
+ .map(|mut other| {
+ if other.class_name == old_name {
+ other.class_name.clone_from(&property.class_name);
+ }
+ other
+ })
+ .collect();
+ }
+ // When: provenance and declaration decoding are checked against the actual expanded group.
+ let result =
+ crate::cache_special_proof::typography(&property, (&levels, 255), &content.descriptor);
+ // Then: Risky is valid, but odd/non-hex/truncated declaration encodings cannot prove it.
+ assert_eq!(result, malformed.is_none().then_some(()));
+ fresh();
+}
diff --git a/bindings/devup-ui-wasm/src/cache_v4_tests.rs b/bindings/devup-ui-wasm/src/cache_v4_tests.rs
new file mode 100644
index 000000000..29270b3bc
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_v4_tests.rs
@@ -0,0 +1,229 @@
+use super::*;
+use rstest::rstest;
+use serial_test::serial;
+#[path = "cache_admission_tests.rs"]
+mod admission;
+mod collisions;
+mod configuration;
+mod special;
+
+fn compile() -> (String, String) {
+ let output = code_extract_internal(
+ "fresh.tsx",
+ "import {Box} from '@devup-ui/react';export const View=(p)=>;",
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ (
+ output.code(),
+ with_style_sheet(|sheet| sheet.create_css(Some("fresh.tsx"), false)),
+ )
+}
+
+fn snapshot() -> serde_json::Value {
+ serde_json::from_str(&export_sheet_internal().unwrap_or_else(|error| panic!("{error}")))
+ .unwrap_or_else(|error| panic!("{error}"))
+}
+
+fn import(value: serde_json::Value) -> Result<(), String> {
+ import_sheet_internal(serde_json::from_value(value).unwrap_or_else(|_| cache_restore::absent()))
+}
+
+#[rstest]
+#[case(None)]
+#[case(Some(0))]
+#[case(Some(3))]
+#[case(Some(5))]
+#[serial]
+fn serialized_old_or_future_generation_restores_nothing(#[case] version: Option) {
+ // Given: an independently compiled cold output and a poisoned old snapshot.
+ reset_build_state_internal();
+ let cold = compile();
+ reset_build_state_internal();
+ let mut poison = StyleSheet::default();
+ poison.add_property(
+ "old-alias",
+ "color",
+ 0,
+ "magenta",
+ None,
+ None,
+ Some("fresh.tsx"),
+ );
+ import_sheet_internal(poison).unwrap_or_else(|error| panic!("{error}"));
+ let mut value: serde_json::Value =
+ serde_json::from_str(&export_sheet_internal().unwrap_or_else(|error| panic!("{error}")))
+ .unwrap_or_else(|error| panic!("{error}"));
+ let object = value
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("snapshot object"));
+ match version {
+ Some(version) => {
+ object.insert("atomNamingVersion".into(), version.into());
+ }
+ None => {
+ object.remove("atomNamingVersion");
+ }
+ }
+ reset_build_state_internal();
+ // When: serialized admission runs through the existing typed internal API.
+ let incoming = serde_json::from_value(value).unwrap_or_else(|error| panic!("{error}"));
+ import_sheet_internal(incoming).unwrap_or_else(|error| panic!("{error}"));
+ // Then: no old rule/claim survives; complete JS and CSS are the cold bytes.
+ assert_eq!(with_style_sheet(|sheet| sheet.properties.len()), 0);
+ assert_eq!(compile(), cold);
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case("names")]
+#[case("atom_plan")]
+#[case("properties")]
+#[case("css")]
+#[case("keyframes")]
+#[case("global_css_files")]
+#[case("imports")]
+#[case("font_faces")]
+#[case("sourceIds")]
+#[case("classMap")]
+#[case("fileMap")]
+#[serial]
+fn current_snapshot_requires_even_empty_restore_fields(#[case] field: &str) {
+ // Given: independent cold output and a current snapshot with one missing field.
+ reset_build_state_internal();
+ let cold = compile();
+ let mut value = snapshot();
+ value
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("snapshot object"))
+ .remove(field);
+ reset_build_state_internal();
+ // When: the partially written object reaches serialized admission.
+ assert_eq!(import(value), Ok(()));
+ // Then: no partial state survives and complete output is cold.
+ assert_eq!(with_style_sheet(|sheet| sheet.properties.len()), 0);
+ assert_eq!(compile(), cold);
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(serde_json::json!(null))]
+#[case(serde_json::json!(42))]
+#[case(serde_json::json!([]))]
+#[case(serde_json::json!({"atomNamingVersion":"4"}))]
+#[case(serde_json::json!({"atomNamingVersion":4,"properties":false}))]
+#[serial]
+fn malformed_typed_cache_objects_are_absent(#[case] value: serde_json::Value) {
+ // Given: a separately compiled cold fixture and malformed serialized data.
+ reset_build_state_internal();
+ let cold = compile();
+ reset_build_state_internal();
+ // When: the typed deserializer receives the malformed value.
+ assert_eq!(import(value), Ok(()));
+ // Then: extraction and current rewrite remain usable and byte-exact.
+ assert_eq!(compile(), cold);
+ assert_eq!(snapshot()["atomNamingVersion"], 4);
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false, false)]
+#[case(false, true)]
+#[case(true, false)]
+#[case(true, true)]
+#[serial]
+fn companion_mismatch_undoes_only_cache_restore(#[case] before: bool, #[case] class_map: bool) {
+ // Given: a poisoned authoritative snapshot and genuine fresh D9 configuration.
+ reset_build_state_internal();
+ seed_file_map(vec!["fresh.tsx".into()]);
+ compile();
+ let mut value = snapshot();
+ value["sourceIds"]["fresh.tsx"] = 777.into();
+ value["fileMap"]["fresh.tsx"] = 888.into();
+ value["classMap"] = serde_json::json!({"fresh.tsx":{"poison":999}});
+ reset_build_state_internal();
+ seed_file_map(vec!["configured.tsx".into()]);
+ let cold = compile();
+ reset_build_state_internal();
+ seed_file_map(vec!["configured.tsx".into()]);
+ let mismatch = || {
+ if class_map {
+ cache_restore::classes(Some(BTreeMap::new()));
+ } else {
+ cache_restore::files(Some(BTreeMap::new()));
+ }
+ };
+ // When: an old/missing companion disagrees before or after sheet admission.
+ if before {
+ mismatch();
+ }
+ assert_eq!(import(value), Ok(()));
+ if !before {
+ mismatch();
+ }
+ // Then: cached IDs/counters/rules disappear, but prepass configuration remains.
+ assert_eq!(
+ css::file_map::get_original_ids(),
+ BTreeMap::from([("configured.tsx".into(), 0)])
+ );
+ assert_eq!(with_style_sheet(|sheet| sheet.properties.len()), 0);
+ assert_eq!(with_class_map(HashMap::len), 0);
+ assert_eq!(compile(), cold);
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn complete_current_restore_works_in_both_companion_orders(#[case] before: bool) {
+ // Given: a real current cache including original-owner counters and sparse variables.
+ reset_build_state_internal();
+ seed_file_map(vec!["a.tsx".into(), "fresh.tsx".into()]);
+ let output = compile();
+ let value = snapshot();
+ let classes: sheet::cache_snapshot::ClassMap =
+ serde_json::from_value(value["classMap"].clone()).unwrap_or_else(|error| panic!("{error}"));
+ let files: sheet::cache_snapshot::FileMap =
+ serde_json::from_value(value["fileMap"].clone()).unwrap_or_else(|error| panic!("{error}"));
+ reset_build_state_internal();
+ // When: only the complete sheet can authorize companion state in either order.
+ if before {
+ cache_restore::classes(Some(classes.clone()));
+ cache_restore::files(Some(files.clone()));
+ }
+ assert_eq!(import(value.clone()), Ok(()));
+ if !before {
+ cache_restore::classes(Some(classes));
+ cache_restore::files(Some(files));
+ }
+ // Then: the entire naming snapshot and full JS/CSS round trip exactly.
+ assert_eq!(snapshot(), value);
+ assert_eq!(compile(), output);
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn companions_without_sheet_authority_never_allocate_poisoned_state() {
+ // Given: independent cold output and arbitrary legacy map data.
+ reset_build_state_internal();
+ let cold = compile();
+ reset_build_state_internal();
+ // When: raw companions are imported without a sheet.
+ cache_restore::classes(Some(BTreeMap::from([(
+ "fresh.tsx".into(),
+ BTreeMap::from([("poison".into(), 999)]),
+ )])));
+ cache_restore::files(Some(BTreeMap::from([("fresh.tsx".into(), 888)])));
+ // Then: neither map becomes live state and complete output remains cold.
+ assert_eq!(css::file_map::get_file_map().len(), 0);
+ assert_eq!(with_class_map(HashMap::len), 0);
+ assert_eq!(compile(), cold);
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/cache_v4_tests/collisions.rs b/bindings/devup-ui-wasm/src/cache_v4_tests/collisions.rs
new file mode 100644
index 000000000..c3132cd3e
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_v4_tests/collisions.rs
@@ -0,0 +1,107 @@
+use super::*;
+
+fn colored(file: &str, color: &str) -> Result {
+ code_extract_internal(
+ file,
+ &format!(
+ "import {{css}} from '@devup-ui/react';\nexport const x=css({{color:'{color}'}});"
+ ),
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ )
+}
+
+#[test]
+#[serial]
+fn current_exact_claim_conflict_is_atomic_located_and_sticky_until_reset() {
+ // Given: complete current blue proof and a live red atom from a real second site.
+ reset_build_state_internal();
+ colored("cached.tsx", "blue").unwrap_or_else(|error| panic!("{error}"));
+ let mut value = snapshot();
+ value["properties"] = serde_json::json!({});
+ value["names"]["OLcolor-vred"] = value["names"]["OLcolor-vblue"].clone();
+ reset_build_state_internal();
+ colored("live.tsx", "red").unwrap_or_else(|error| panic!("{error}"));
+ let before = snapshot();
+ // When: a structurally complete current claim conflicts with live exact bytes.
+ let error = import(value)
+ .err()
+ .unwrap_or_else(|| panic!("exact collision accepted"));
+ // Then: both best sites/contents are retained, and no state is partially imported.
+ for evidence in ["cached.tsx:2:27:", "live.tsx:2:27:", "`'blue'`", "`'red'`"] {
+ assert!(error.contains(evidence), "{error}");
+ }
+ assert_eq!(snapshot(), before);
+ assert_eq!(import(serde_json::json!({})), Ok(()));
+ cache_restore::classes(None);
+ cache_restore::files(None);
+ assert_eq!(colored("later.tsx", "green").err(), Some(error.clone()));
+ assert_eq!(import(before), Ok(()));
+ assert_eq!(colored("later.tsx", "green").err(), Some(error));
+ reset_build_state_internal();
+ assert!(colored("later.tsx", "green").is_ok());
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn corrupt_referenced_atom_descriptor_is_a_cold_miss_not_a_collision() {
+ // Given: real generated output with a referenced descriptor corrupted in storage.
+ reset_build_state_internal();
+ let cold = compile();
+ let mut value = snapshot();
+ let names = value["names"]
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("claims"));
+ let claim = names
+ .values_mut()
+ .find(|claim| {
+ claim["descriptor"]
+ .as_array()
+ .is_some_and(|bytes| bytes.first() == Some(&1.into()))
+ })
+ .unwrap_or_else(|| panic!("atom claim"));
+ claim["descriptor"]
+ .as_array_mut()
+ .unwrap_or_else(|| panic!("descriptor bytes"))
+ .push(99.into());
+ reset_build_state_internal();
+ // When: structural proof validation precedes any state mutation.
+ assert_eq!(import(value), Ok(()));
+ // Then: nothing is adopted and full output is independently cold.
+ assert_eq!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ assert_eq!(compile(), cold);
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn manual_in_memory_names_remain_trusted_without_serialized_generation() {
+ // Given: a freshly constructed manual sheet, not generation-less JSON.
+ reset_build_state_internal();
+ let mut sheet = StyleSheet::default();
+ sheet.add_property(
+ "manual-card",
+ "color",
+ 0,
+ "red",
+ None,
+ None,
+ Some("card.tsx"),
+ );
+ // When: the existing in-memory API imports explicit construction.
+ assert_eq!(import_sheet_internal(sheet), Ok(()));
+ // Then: the authored manual class is still usable in emitted CSS.
+ assert!(
+ with_style_sheet(|sheet| sheet.create_css(Some("card.tsx"), false)).contains(concat!(
+ ".manual-card{",
+ "color:red",
+ "}"
+ ))
+ );
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/cache_v4_tests/configuration.rs b/bindings/devup-ui-wasm/src/cache_v4_tests/configuration.rs
new file mode 100644
index 000000000..03f4bc0f5
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_v4_tests/configuration.rs
@@ -0,0 +1,54 @@
+use super::*;
+
+#[test]
+#[serial]
+fn late_companion_mismatch_preserves_fresh_configuration_and_prepass() {
+ // Given: restored cache followed by legitimate fresh names-only and routing inputs.
+ reset_build_state_internal();
+ compile();
+ let value = snapshot();
+ reset_build_state_internal();
+ assert_eq!(import(value), Ok(()));
+ set_prefix(Some("du-FLa-".into()));
+ set_naming_root(Some("/checkout".into()), Some("/checkout".into()));
+ set_debug(true);
+ register_theme_internal(
+ serde_json::from_value(serde_json::json!({"colors":{"default":{"primary":"#123456"}}}))
+ .unwrap_or_else(|error| panic!("{error}")),
+ );
+ register_shorthands_internal(BTreeMap::from([(
+ "insetX".into(),
+ vec!["left".into(), "right".into()],
+ )]));
+ let theme_css = with_style_sheet(|sheet| sheet.theme.to_css());
+ import_canonical_map_internal(HashMap::from([("child.tsx".into(), "bucket.tsx".into())]));
+ import_file_routes_internal(HashMap::from([(
+ "child.tsx".into(),
+ std::collections::HashSet::from([1, 2]),
+ )]));
+ set_atom_hoist(Some(2));
+ seed_file_map(vec!["child.tsx".into()]);
+ // When: malformed companion data invalidates only authoritative cached state.
+ cache_restore::classes(None);
+ // Then: fresh prefix/root/debug/canonical/routes/hoist/D9 inputs remain in effect.
+ assert_eq!(get_prefix(), Some("du-FLa-".into()));
+ assert_eq!(css::naming_root::key("/checkout/src/a.tsx"), "src/a.tsx");
+ assert!(is_debug());
+ assert_eq!(css::file_map::canonical("child.tsx"), "bucket.tsx");
+ assert_eq!(css::file_map::original_id("child.tsx"), Some(0));
+ assert_eq!(
+ css::file_map::get_file_map().get_by_left("bucket.tsx"),
+ Some(&0)
+ );
+ assert!(css::atom_hoist::is_hoisted_bucket("child.tsx"));
+ assert_eq!(with_style_sheet(|sheet| sheet.properties.len()), 0);
+ assert_eq!(with_style_sheet(|sheet| sheet.theme.to_css()), theme_css);
+ assert_eq!(
+ css::get_custom_shorthand_names(),
+ vec!["insetX".to_string()]
+ );
+ register_shorthands_internal(BTreeMap::new());
+ register_theme_internal(sheet::theme::Theme::default());
+ set_debug(false);
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/cache_v4_tests/special.rs b/bindings/devup-ui-wasm/src/cache_v4_tests/special.rs
new file mode 100644
index 000000000..eba06a229
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/cache_v4_tests/special.rs
@@ -0,0 +1,140 @@
+use super::*;
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn current_typography_and_keyframe_proofs_round_trip_or_go_cold(#[case] corrupt: bool) {
+ // Given: real responsive typography and keyframes with deliberately nonlexical step order.
+ reset_build_state_internal();
+ register_theme_internal(
+ serde_json::from_value(serde_json::json!({
+ "typography":{"body":[{"fontSize":"14px","fontFamily":"Sans"},null,{"fontSize":"18px"}]}
+ }))
+ .unwrap_or_else(|error| panic!("{error}")),
+ );
+ let source = "import {Box,keyframes} from '@devup-ui/react';export const k=keyframes({to:{opacity:1},from:{opacity:0}});export const x=;";
+ let output = code_extract_internal(
+ "special.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let cold_css = with_style_sheet(|sheet| sheet.create_css(Some("special.tsx"), false));
+ let mut value = snapshot();
+ assert!(with_style_sheet(|sheet| sheet
+ .properties
+ .values()
+ .flat_map(|orders| orders.values())
+ .flat_map(|levels| levels.values())
+ .flatten()
+ .any(|property| property.typography)));
+ if corrupt {
+ let claim = value["names"]
+ .as_object_mut()
+ .unwrap_or_else(|| panic!("claims"))
+ .iter_mut()
+ .find(|(name, _)| name.starts_with('K'))
+ .unwrap_or_else(|| panic!("keyframes proof"))
+ .1;
+ claim["descriptor"]
+ .as_array_mut()
+ .unwrap_or_else(|| panic!("descriptor bytes"))
+ .push(99.into());
+ }
+ reset_build_state_internal();
+ // When: complete current data is admitted without changing fresh theme configuration.
+ assert_eq!(import(value), Ok(()));
+ // Then: valid cached output is preserved, but corrupted proof adopts nothing.
+ if corrupt {
+ assert_eq!(with_style_sheet(|sheet| sheet.properties.len()), 0);
+ } else {
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("special.tsx"), false)),
+ cold_css
+ );
+ }
+ let fresh = code_extract_internal(
+ "special.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ assert_eq!(fresh.code(), output.code());
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("special.tsx"), false)),
+ cold_css
+ );
+ register_theme_internal(sheet::theme::Theme::default());
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn validated_companions_do_not_block_a_later_complete_snapshot(#[case] before: bool) {
+ // Given: a previously admitted cache whose companions have been validated.
+ reset_build_state_internal();
+ seed_file_map(vec!["fresh.tsx".into()]);
+ compile();
+ let initial = snapshot();
+ reset_build_state_internal();
+ assert_eq!(import(initial.clone()), Ok(()));
+ cache_restore::classes(Some(
+ serde_json::from_value(initial["classMap"].clone())
+ .unwrap_or_else(|error| panic!("{error}")),
+ ));
+ cache_restore::files(Some(
+ serde_json::from_value(initial["fileMap"].clone())
+ .unwrap_or_else(|error| panic!("{error}")),
+ ));
+ seed_file_map(vec!["later.tsx".into()]);
+ code_extract_internal(
+ "later.tsx",
+ "import {Box} from '@devup-ui/react';export const x= ;",
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let later = snapshot();
+ let classes = || {
+ Some(
+ serde_json::from_value(later["classMap"].clone())
+ .unwrap_or_else(|error| panic!("{error}")),
+ )
+ };
+ let files = || {
+ Some(
+ serde_json::from_value(later["fileMap"].clone())
+ .unwrap_or_else(|error| panic!("{error}")),
+ )
+ };
+ // When: existing APIs restore the next complete snapshot in either order.
+ if before {
+ cache_restore::classes(classes());
+ cache_restore::files(files());
+ }
+ assert_eq!(import(later.clone()), Ok(()));
+ if !before {
+ cache_restore::classes(classes());
+ cache_restore::files(files());
+ }
+ // Then: no old companion observation vetoes or partially restores the new snapshot.
+ assert_eq!(snapshot(), later);
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/compact_source_tests.rs b/bindings/devup-ui-wasm/src/compact_source_tests.rs
new file mode 100644
index 000000000..38179e308
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/compact_source_tests.rs
@@ -0,0 +1,143 @@
+use super::*;
+use rstest::rstest;
+use serial_test::serial;
+
+mod cache;
+
+fn compile(file: &str, source: &str) -> Result {
+ code_extract_internal(
+ file,
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ )
+}
+
+fn fresh() {
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ register_theme_internal(sheet::theme::Theme::default());
+}
+
+#[test]
+#[serial]
+fn full_output_repeats_when_100_kb_unnumbered_source_runs_in_fresh_builds() {
+ // Given: 100 KB of comments before multiple dynamic assignments.
+ let source = format!(
+ "{}import {{Box}} from '@devup-ui/react';\nexport const View=(p)=>;",
+ "// filler\n".repeat(10_240)
+ );
+ let run = || {
+ fresh();
+ let output = compile("/real/large.tsx", &source).unwrap_or_else(|error| panic!("{error}"));
+ let css = with_style_sheet(|sheet| sheet.create_css(None, false));
+ let names = with_style_sheet(|sheet| sheet.names.clone());
+ (
+ output.code,
+ output.css,
+ output.css_file,
+ output.map,
+ output.updated_base_style,
+ output.dependencies,
+ css,
+ names,
+ )
+ };
+ // When: the same received source runs twice without retained compiler state.
+ let first = run();
+ let second = run();
+ // Then: complete generated output and claims repeat, with bounded emitted names.
+ assert_eq!(first, second);
+ let variables: Vec<_> = first
+ .0
+ .split('"')
+ .filter(|text| text.starts_with("---SUH"))
+ .collect();
+ assert_eq!(variables.len(), 2);
+ assert!(variables.iter().all(|name| name.len() <= 50));
+ assert!(variables.iter().all(|name| first.6.contains(name)));
+ let source_claims: Vec<_> = first
+ .7
+ .values()
+ .filter(|claim| claim.content == source)
+ .collect();
+ assert_eq!(source_claims.len(), 1);
+ assert_eq!(source_claims[0].descriptor, source.as_bytes());
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case("/real/a.tsx", "/real/b.tsx")]
+#[case("/real/b.tsx", "/real/a.tsx")]
+#[serial]
+fn generated_output_ignores_filenames_when_received_source_is_identical(
+ #[case] first: &str,
+ #[case] second: &str,
+) {
+ // Given: an unnumbered source with distinct source-ordered responsive roles.
+ let source = "import {Box} from '@devup-ui/react';\nexport const View=(p)=>;";
+ fresh();
+ let before = compile(first, source)
+ .unwrap_or_else(|error| panic!("{error}"))
+ .code();
+ let css_before = with_style_sheet(|sheet| sheet.create_css(None, false));
+ // When: another real filename supplies exactly the same received source.
+ let after = compile(second, source)
+ .unwrap_or_else(|error| panic!("{error}"))
+ .code();
+ // Then: names and whole compiled code agree, and no duplicate CSS is emitted.
+ assert_eq!(after, before);
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(None, false)),
+ css_before
+ );
+ let variables: std::collections::BTreeSet<_> = after
+ .split('"')
+ .filter(|text| text.starts_with("---SUH"))
+ .collect();
+ assert_eq!(variables.len(), 3);
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case("")]
+#[case("Mixed-SUH-prefix-")]
+#[serial]
+fn full_generated_output_agrees_when_received_source_uses_lf_crlf_or_bom(#[case] prefix: &str) {
+ // Given: equivalent received text with runtime branches and responsive roles.
+ let lf = "import {Box} from '@devup-ui/react';\nexport const View=(p)=>;\n";
+ let sources = [
+ lf.to_string(),
+ lf.replace('\n', "\r\n"),
+ format!("\u{feff}{lf}"),
+ format!("\u{feff}{}", lf.replace('\n', "\r\n")),
+ ];
+ // When: each normalization variant runs through the actual native binding path.
+ let outputs: Vec<_> = sources
+ .iter()
+ .map(|source| {
+ fresh();
+ css::set_prefix(Some(prefix.into()));
+ let output =
+ compile("/real/normalized.tsx", source).unwrap_or_else(|error| panic!("{error}"));
+ (
+ output.code,
+ output.css,
+ output.css_file,
+ output.map,
+ output.updated_base_style,
+ output.dependencies,
+ with_style_sheet(|sheet| sheet.create_css(None, false)),
+ )
+ })
+ .collect();
+ // Then: full generated JS/CSS/output metadata are byte-identical, not just names.
+ for output in &outputs[1..] {
+ assert_eq!(output, &outputs[0]);
+ }
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/compact_source_tests/cache.rs b/bindings/devup-ui-wasm/src/compact_source_tests/cache.rs
new file mode 100644
index 000000000..f9beb2f23
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/compact_source_tests/cache.rs
@@ -0,0 +1,197 @@
+use super::*;
+
+const SOURCE: &str =
+ "import {Box} from '@devup-ui/react';\nexport const View=(p)=>;";
+
+#[rstest]
+#[case("'---SULmissing-b'")]
+#[case("'var(---SUHaaaaaaaaaaaaaaaa-a)'")]
+#[serial]
+fn literal_css_content_roundtrips_when_it_resembles_a_generated_source_variable(
+ #[case] content: &str,
+) {
+ // Given: a literal CSS string, not a generated variable consumer or reset.
+ fresh();
+ let literal = serde_json::to_string(content).unwrap_or_else(|error| panic!("{error}"));
+ let source = format!(
+ "import {{css}} from '@devup-ui/react';export const cls=css({{content:{literal}}});"
+ );
+ compile("/real/content.ts", &source).unwrap_or_else(|error| panic!("{error}"));
+ let snapshot = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let cached = serde_json::from_str(&snapshot).unwrap_or_else(|error| panic!("{error}"));
+ fresh();
+ // When: actual sheet cache import validates generated source claims.
+ let restored = import_sheet_internal(cached);
+ // Then: source-like text in literal content remains a valid static declaration.
+ assert_eq!(restored, Ok(()));
+ assert_eq!(
+ export_sheet_internal().unwrap_or_else(|error| panic!("{error}")),
+ snapshot
+ );
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case("")]
+#[case("Mixed---SUH-한😀-")]
+#[serial]
+fn source_claims_roundtrip_when_cached_sheet_is_imported_into_a_fresh_build(#[case] prefix: &str) {
+ // Given: a complete cache with exact full-source claims.
+ fresh();
+ css::set_prefix(Some(prefix.into()));
+ let before = compile("/real/a.tsx", SOURCE)
+ .unwrap_or_else(|error| panic!("{error}"))
+ .code();
+ let css_before = with_style_sheet(|sheet| sheet.create_css(None, false));
+ let snapshot = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let imported: StyleSheet =
+ serde_json::from_str(&snapshot).unwrap_or_else(|error| panic!("{error}"));
+ let source_claims: BTreeMap<_, _> = imported
+ .names
+ .iter()
+ .filter(|(name, _)| name.starts_with("---"))
+ .map(|(name, claim)| (name.clone(), claim.clone()))
+ .collect();
+ assert_eq!(source_claims.len(), 1);
+ fresh();
+ css::set_prefix(Some(prefix.into()));
+ // When: the cache is restored and identical text comes from another real file.
+ import_sheet_internal(imported).unwrap_or_else(|error| panic!("{error}"));
+ let after = compile("/real/z.tsx", SOURCE)
+ .unwrap_or_else(|error| panic!("{error}"))
+ .code();
+ // Then: names, complete JS/CSS and exact source claims remain unchanged.
+ assert_eq!(after, before);
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(None, false)),
+ css_before
+ );
+ with_style_sheet(|sheet| {
+ for (name, claim) in source_claims {
+ assert_eq!(sheet.names[&name], claim);
+ }
+ });
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn missing_source_claim_metadata_restores_nothing_and_matches_cold_output(
+ #[case] retain_reset: bool,
+) {
+ // Given: all atom claims exist, but the exact source claim is missing.
+ fresh();
+ let cold = compile("/real/a.tsx", SOURCE).unwrap_or_else(|error| panic!("{error}"));
+ let cold_css = with_style_sheet(|sheet| sheet.create_css(None, false));
+ let snapshot = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let mut incomplete: StyleSheet =
+ serde_json::from_str(&snapshot).unwrap_or_else(|error| panic!("{error}"));
+ incomplete
+ .names
+ .retain(|name, _| !name.starts_with("---SUH"));
+ if !retain_reset {
+ for orders in incomplete.properties.values_mut() {
+ for levels in orders.values_mut() {
+ for properties in levels.values_mut() {
+ properties.retain(|property| !property.owner_reset);
+ }
+ }
+ }
+ }
+ fresh();
+ // When: a current snapshot lacks source proof but retains valid atom proof.
+ assert_eq!(import_sheet_internal(incomplete), Ok(()));
+ // Then: no cached source/atom alias survives and complete output is cold.
+ assert_eq!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ assert_eq!(css::file_map::get_original_ids(), BTreeMap::new());
+ let next = compile("/real/a.tsx", SOURCE).unwrap_or_else(|error| panic!("{error}"));
+ assert_eq!(next.code(), cold.code());
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(None, false)),
+ cold_css
+ );
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn unequal_cached_source_claims_report_both_real_files_before_replacing_the_sheet() {
+ // Given: an existing exact source claim and a different full source under its name.
+ fresh();
+ compile("/real/first.tsx", SOURCE).unwrap_or_else(|error| panic!("{error}"));
+ let snapshot = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let cached: StyleSheet =
+ serde_json::from_str(&snapshot).unwrap_or_else(|error| panic!("{error}"));
+ let (name, mut claim) = cached
+ .names
+ .into_iter()
+ .find(|(name, _)| name.starts_with("---SUH"))
+ .unwrap_or_else(|| panic!("source claim"));
+ let other = format!("{SOURCE}\n// another received source");
+ claim.content.clone_from(&other);
+ claim.descriptor = other.as_bytes().to_vec();
+ let css::style_origin::RealLocation::Exact(origin) = &mut claim.location else {
+ panic!("real witness")
+ };
+ origin.file = "/real/second.tsx".into();
+ claim.origin = Some(origin.clone());
+ let mut incoming = StyleSheet::default();
+ incoming.names.insert(name, claim);
+ // When: importing overlapping claims without any atom descriptor difference.
+ let error = import_sheet_internal(incoming)
+ .err()
+ .unwrap_or_else(|| panic!("unequal exact source"));
+ // Then: both real witnesses/full sources are printed and the old sheet survives.
+ assert!(error.contains("/real/first.tsx:2:"), "{error}");
+ assert!(error.contains("/real/second.tsx:2:"), "{error}");
+ assert!(error.contains(SOURCE), "{error}");
+ assert!(error.contains(&other), "{error}");
+ assert_eq!(
+ export_sheet_internal().unwrap_or_else(|error| panic!("{error}")),
+ snapshot
+ );
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn corrupt_source_proof_restores_nothing_even_when_the_named_entry_exists(
+ #[case] corrupt_content: bool,
+) {
+ // Given: a source variable whose claim exists but has inconsistent proof bytes.
+ fresh();
+ let cold = compile("/real/a.tsx", SOURCE).unwrap_or_else(|error| panic!("{error}"));
+ let cold_css = with_style_sheet(|sheet| sheet.create_css(None, false));
+ let snapshot = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let mut corrupt: StyleSheet =
+ serde_json::from_str(&snapshot).unwrap_or_else(|error| panic!("{error}"));
+ let claim = corrupt
+ .names
+ .iter_mut()
+ .find(|(name, _)| name.starts_with("---SUH"))
+ .unwrap_or_else(|| panic!("source claim"))
+ .1;
+ if corrupt_content {
+ claim.content.push_str("\n// forged source");
+ claim.descriptor = claim.content.as_bytes().to_vec();
+ } else {
+ claim.descriptor.push(0);
+ }
+ fresh();
+ // When: source proof and content-only naming disagree at admission.
+ assert_eq!(import_sheet_internal(corrupt), Ok(()));
+ // Then: existence alone cannot adopt the original alias or any cached atom.
+ assert_eq!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ assert_eq!(css::file_map::get_original_ids(), BTreeMap::new());
+ let next = compile("/real/a.tsx", SOURCE).unwrap_or_else(|error| panic!("{error}"));
+ assert_eq!(next.code(), cold.code());
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(None, false)),
+ cold_css
+ );
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/content_location_contract_tests.rs b/bindings/devup-ui-wasm/src/content_location_contract_tests.rs
new file mode 100644
index 000000000..532721788
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/content_location_contract_tests.rs
@@ -0,0 +1,265 @@
+use super::*;
+use css::{content_hash::FingerprintBits, style_origin::RealLocation};
+use serial_test::serial;
+use std::fmt::Write;
+
+fn extract(file: &str, source: &str) -> extractor::ExtractOutput {
+ extractor::extract(
+ file,
+ source,
+ extractor::ExtractOption {
+ single_css: true,
+ ..Default::default()
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error}"))
+}
+
+#[test]
+#[serial]
+fn pair_keeps_distinct_content_names_when_native_calls_share_one_boa_position() {
+ // Given
+ reset_build_state_internal();
+ let source = "import {style} from '@devup-ui/react';\nfunction pair(a,b){return [a,b];}\nexport const pairResult=pair(style({color:'red'}),style({color:'blue'}));";
+ // When
+ let output = code_extract_internal(
+ "pair.css.ts",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ assert!(output.code().contains("OLcolor-vred"), "{}", output.code());
+ assert!(output.code().contains("OLcolor-vblue"), "{}", output.code());
+ with_style_sheet(|sheet| assert_eq!(sheet.names.len(), 2));
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn resolver_direct_and_opaque_factories_keep_identical_names_when_environment_order_reverses() {
+ // Given
+ let source = "import {style} from '@devup-ui/react';import {make} from './factory';export const cls=make({fontFamily:'abcdefghijklmnopqrstuvwx'});";
+ let bodies = [
+ "import {style} from '@devup-ui/react';export const make=(rule)=>style(rule);",
+ "import {style} from '@devup-ui/react';export const make=(rule)=>((factory)=>factory)(style)(rule);",
+ ];
+ // When
+ let mut outputs = Vec::new();
+ for order in [[0, 1], [1, 0]] {
+ reset_build_state_internal();
+ let mut results = BTreeMap::new();
+ for index in order {
+ let resolver = move |specifier: &str, _: &str| {
+ (specifier == "./factory").then(|| ResolvedModule {
+ path: "factory.ts".into(),
+ code: bodies[index].into(),
+ })
+ };
+ let output = code_extract_with_modules_internal(
+ "resolver.css.ts",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ &resolver,
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ results.insert(index, output.code().clone());
+ }
+ outputs.push(results);
+ }
+ // Then
+ assert_eq!(outputs[0], outputs[1]);
+ assert_eq!(outputs[0][&0], outputs[0][&1]);
+ with_style_sheet(|sheet| assert_eq!(sheet.names.len(), 1));
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn opaque_factory_preserves_this_arguments_and_direct_eval_when_called_through_a_helper() {
+ // Given
+ reset_build_state_internal();
+ let source = "import {style} from '@devup-ui/react';const adapt=(factory)=>function(value){if(this.tag!==value||arguments.length!==1)throw new Error('changed call');return factory({fontFamily:eval('value')});};const make=adapt(style);export const cls=make.call({tag:'abcdefghijklmnopqrstuvwx'},'abcdefghijklmnopqrstuvwx');";
+ // When
+ let output = code_extract_internal(
+ "semantics.css.ts",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ assert!(
+ !output.code().contains("__devup_style_origin"),
+ "{}",
+ output.code()
+ );
+ with_style_sheet(|sheet| {
+ assert!(
+ sheet
+ .names
+ .values()
+ .any(|claim| claim.content.contains("abcdefghijklmnopqrstuvwx")),
+ "{:?}",
+ sheet.names
+ );
+ });
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn forced_fingerprint_collision_reports_each_real_location_tier_when_width_is_an_input() {
+ // Given
+ let bits = FingerprintBits::new(1).unwrap_or_else(|| panic!("valid width"));
+ let modes = [("style", "OH"), ("keyframes", "KH")]
+ .into_iter()
+ .flat_map(|(factory, domain)| {
+ ["exact", "call", "export"].map(|mode| (factory, domain, mode))
+ });
+ // When / Then
+ for (factory, domain, mode) in modes {
+ reset_build_state_internal();
+ let mut source = format!(
+ "import {{{factory}}} from '@devup-ui/react';\nconst identity=(factory)=>factory;const native=identity({factory});\n"
+ );
+ for index in 0..3 {
+ let declaration = format!("{{fontFamily:'abcdefghijklmnopqrstuvwx{index}'}}");
+ let rule = if factory == "keyframes" {
+ format!("{{from:{declaration}}}")
+ } else {
+ declaration
+ };
+ let call = match mode {
+ "exact" => format!("{factory}({rule})"),
+ "call" => format!("identity({factory})({rule})"),
+ "export" => format!(
+ "eval({})",
+ serde_json::to_string(&format!("native({rule})"))
+ .unwrap_or_else(|error| panic!("{error}"))
+ ),
+ _ => unreachable!(),
+ };
+ writeln!(
+ source,
+ "const local{index}={call};export {{local{index} as actual{index}}};"
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ }
+ let output = extract("tiers.css.ts", &source);
+ let sheet = StyleSheet::default();
+ let error = sheet
+ .preflight_styles_with_bits(&output.styles, ("tiers.css.ts", true), bits)
+ .err()
+ .unwrap_or_else(|| panic!("three distinct atoms must collide in one bit"));
+ assert_eq!(sheet.names.len(), 0);
+ assert_ne!(error.first.descriptor, error.second.descriptor);
+ assert!(error.name.starts_with(domain), "{error}");
+ let text = error.to_string();
+ for claim in [&error.first, &error.second] {
+ assert!(text.contains(&claim.content), "{text}");
+ match (&claim.location, mode) {
+ (RealLocation::Exact(origin), "exact")
+ | (RealLocation::ProducedByCall(origin), "call") => {
+ let offset = source
+ .find(&origin.expression)
+ .unwrap_or_else(|| panic!("not authored: {origin:?}"));
+ let before = &source[..offset];
+ assert_eq!(origin.file, "tiers.css.ts");
+ assert_eq!(
+ origin.line,
+ before.bytes().filter(|byte| *byte == b'\n').count() + 1
+ );
+ assert_eq!(
+ origin.column,
+ before
+ .rsplit('\n')
+ .next()
+ .unwrap_or_default()
+ .chars()
+ .count()
+ + 1
+ );
+ }
+ (
+ RealLocation::ModuleExport {
+ file,
+ binding: Some(binding),
+ },
+ "export",
+ ) => {
+ assert_eq!(file, "tiers.css.ts");
+ assert!(binding.starts_with("actual"), "{binding}");
+ assert!(source.contains(&format!("as {binding}")), "{binding}");
+ assert!(
+ text.contains("position unavailable (produced inside eval or a callback)"),
+ "{text}"
+ );
+ }
+ _ => panic!("wrong tier for {mode}: {claim:?}"),
+ }
+ }
+ assert!(text.contains("change one declaration"), "{text}");
+ if mode == "call" {
+ assert!(
+ text.contains("produced by the call at tiers.css.ts:"),
+ "{text}"
+ );
+ }
+ }
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn cached_opaque_claims_still_reject_exact_collisions_when_no_witness_exists() {
+ // Given
+ reset_build_state_internal();
+ let bits = FingerprintBits::new(1).unwrap_or_else(|| panic!("valid width"));
+ let mut sheet = StyleSheet::default();
+ let mut collision = None;
+ // When
+ for index in 0..3 {
+ let file = format!("cached{index}.css.ts");
+ let source = format!(
+ "import {{style}} from '@devup-ui/react';const native=style;export const actual=eval(\"native({{fontFamily:'abcdefghijklmnopqrstuvwx{index}'}})\");"
+ );
+ let output = extract(&file, &source);
+ match sheet.preflight_styles_with_bits(&output.styles, (&file, true), bits) {
+ Ok(claims) => {
+ sheet.names.extend(claims);
+ let json = serde_json::to_string(&sheet.export_snapshot())
+ .unwrap_or_else(|error| panic!("{error}"));
+ sheet = serde_json::from_str(&json).unwrap_or_else(|error| panic!("{error}"));
+ }
+ Err(error) => {
+ collision = Some(error);
+ break;
+ }
+ }
+ }
+ // Then
+ let error = collision.unwrap_or_else(|| panic!("one-bit collision was not detected"));
+ for claim in [&error.first, &error.second] {
+ assert_eq!(claim.origin, None);
+ assert!(
+ matches!(&claim.location, RealLocation::ModuleExport { binding: Some(binding), .. } if binding == "actual"),
+ "{claim:?}"
+ );
+ }
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/content_name_tests.rs b/bindings/devup-ui-wasm/src/content_name_tests.rs
new file mode 100644
index 000000000..a0338ad7f
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/content_name_tests.rs
@@ -0,0 +1,257 @@
+use super::*;
+use serial_test::serial;
+
+fn compile(file: &str, source: &str) -> Result {
+ code_extract_internal(
+ file,
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ )
+}
+
+#[test]
+#[serial]
+fn public_extraction_uses_fixed_short_and_first80_descriptor_goldens() {
+ // Given
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ let source = "import {css} from '@devup-ui/react'; export const x=css({color:'red',fontFamily:'abcdefghijklmnopqrstuvwx'});";
+ let mut bytes = vec![1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 11];
+ bytes.extend_from_slice(b"font-family");
+ bytes.extend_from_slice(&[1, 0, 0, 0, 0, 0, 0, 0, 24]);
+ bytes.extend_from_slice(b"abcdefghijklmnopqrstuvwx");
+ bytes.extend_from_slice(&[0, 255, 0, 0]);
+ // When
+ let output = compile("golden.tsx", source).unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ let expected = format!("OH{}", css::content_hash::fingerprint(&bytes));
+ assert!(output.code().contains("OLcolor-vred"), "{}", output.code());
+ assert!(output.code().contains(&expected), "{}", output.code());
+ with_style_sheet(|sheet| assert_eq!(sheet.names[&expected].descriptor, bytes));
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn restored_forced_collision_reports_both_actual_sites_before_global_css_removal() {
+ // Given
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ compile(
+ "a.tsx",
+ "import {css} from '@devup-ui/react';\nexport const x=css({color:'blue'});",
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ compile(
+ "b.tsx",
+ "import {globalCss} from '@devup-ui/react'; globalCss({body:{padding:'11px'}});",
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let serialized = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let mut forced: StyleSheet =
+ serde_json::from_str(&serialized).unwrap_or_else(|error| panic!("{error}"));
+ let original = forced.names["OLcolor-vblue"].clone();
+ forced.names.insert("OLcolor-vred".into(), original);
+ import_sheet_internal(forced).unwrap_or_else(|error| panic!("{error}"));
+ let before = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let css_before = with_style_sheet(|sheet| sheet.create_css(None, false));
+ // When
+ let error = compile(
+ "b.tsx",
+ "import {css} from '@devup-ui/react';\nexport const x=css({color:'red'});",
+ )
+ .err()
+ .unwrap_or_else(|| panic!("forced collision was accepted"));
+ // Then
+ assert!(error.contains("a.tsx:2:27:"), "{error}");
+ assert!(error.contains("b.tsx:2:27:"), "{error}");
+ assert!(error.contains("`'blue'`"), "{error}");
+ assert!(error.contains("`'red'`"), "{error}");
+ assert_eq!(
+ export_sheet_internal().unwrap_or_else(|error| panic!("{error}")),
+ before
+ );
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(None, false)),
+ css_before
+ );
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn sheet_import_merges_exact_claims_and_rejects_unequal_claims_atomically() {
+ // Given
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ compile(
+ "b.tsx",
+ "import {css} from '@devup-ui/react'; export const x=css({color:'red'});",
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let before = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let original: StyleSheet =
+ serde_json::from_str(&before).unwrap_or_else(|error| panic!("{error}"));
+ let mut incoming = StyleSheet::default();
+ incoming.names = original.names;
+ incoming
+ .names
+ .get_mut("OLcolor-vred")
+ .unwrap_or_else(|| panic!("missing claim"))
+ .descriptor
+ .push(99);
+ // When
+ let rejected = import_sheet_internal(incoming);
+ // Then
+ assert!(rejected.is_err());
+ assert_eq!(
+ export_sheet_internal().unwrap_or_else(|error| panic!("{error}")),
+ before
+ );
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn missing_cached_atom_claim_restores_no_state_and_produces_cold_bytes() {
+ // Given
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ let cold = compile(
+ "source.tsx",
+ "import {css} from '@devup-ui/react';export const x=css({color:'red'});",
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let cold_css = with_style_sheet(|sheet| sheet.create_css(None, false));
+ let snapshot = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let mut incomplete: StyleSheet =
+ serde_json::from_str(&snapshot).unwrap_or_else(|error| panic!("{error}"));
+ incomplete.names.clear();
+ reset_build_state_internal();
+ // When
+ assert_eq!(import_sheet_internal(incomplete), Ok(()));
+ // Then
+ assert_eq!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ let next = compile(
+ "source.tsx",
+ "import {css} from '@devup-ui/react';export const x=css({color:'red'});",
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ assert_eq!(next.code(), cold.code());
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(None, false)),
+ cold_css
+ );
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn evaluated_helper_composition_and_keyframes_keep_factory_call_sites() {
+ // Given
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ let source = "import {style,keyframes} from '@devup-ui/react';\nconst make=()=>style({color:'red'});\nconst base=make();\nexport const combined=style([base,{padding:'4px'}]);\nexport const fade=keyframes({from:{opacity:0},to:{opacity:1}});";
+ // When
+ let output = compile("actual.css.ts", source).unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ assert!(
+ !output.code().contains("__devup_style_origin"),
+ "{}",
+ output.code()
+ );
+ assert!(!output.code().contains("__origin"), "{}", output.code());
+ with_style_sheet(|sheet| {
+ assert!(
+ sheet
+ .names
+ .values()
+ .any(|claim| claim
+ .origin
+ .as_ref()
+ .is_some_and(|origin| origin.file == "actual.css.ts"
+ && origin.line == 2
+ && origin.expression == "style({color:'red'})"))
+ );
+ assert!(
+ sheet
+ .names
+ .values()
+ .any(|claim| claim
+ .origin
+ .as_ref()
+ .is_some_and(|origin| origin.file == "actual.css.ts"
+ && origin.line == 5
+ && origin.expression == "keyframes({from:{opacity:0},to:{opacity:1}})"))
+ );
+ assert!(sheet.names.values().all(|claim| claim.origin.is_some()));
+ });
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn imported_stylesheet_helpers_keep_the_actual_foreign_factory_site() {
+ // Given
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ let source = "import {style} from '@devup-ui/react';import {make} from './helper';export const result=make();";
+ let resolver = |specifier: &str, _: &str| {
+ (specifier == "./helper").then(|| ResolvedModule {
+ path: "helper.ts".into(),
+ code: "import {style} from '@devup-ui/react';\nexport const make=()=>style({color:'red'});".into(),
+ })
+ };
+ // When
+ let output = code_extract_with_modules_internal(
+ "root.css.ts",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ &resolver,
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ assert!(!output.code().contains("__origin"), "{}", output.code());
+ with_style_sheet(|sheet| {
+ assert!(
+ sheet
+ .names
+ .values()
+ .any(|claim| claim
+ .origin
+ .as_ref()
+ .is_some_and(|origin| origin.file == "helper.ts"
+ && origin.line == 2
+ && origin.expression == "style({color:'red'})"))
+ );
+ });
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn fingerprint_claim_has_a_real_producer_when_factory_flows_through_a_function() {
+ // Given
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ let source = "import {style} from '@devup-ui/react';const identity=(factory)=>factory;export const cls=identity(style)({fontFamily:'abcdefghijklmnopqrstuvwx'});";
+ // When
+ compile("higher-order.css.ts", source).unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ with_style_sheet(|sheet| {
+ assert!(sheet.names.values().all(|claim| matches!(&claim.location,
+ css::style_origin::RealLocation::ProducedByCall(origin) if origin.file == "higher-order.css.ts"
+ && origin.expression == "identity(style)({fontFamily:'abcdefghijklmnopqrstuvwx'})")), "{:?}", sheet.names);
+ });
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/exact_class_tests.rs b/bindings/devup-ui-wasm/src/exact_class_tests.rs
new file mode 100644
index 000000000..d2a988145
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/exact_class_tests.rs
@@ -0,0 +1,96 @@
+use super::*;
+use exact_test_support::{authority, exact, fixture, mutate_authority};
+use rstest::rstest;
+use serial_test::serial;
+
+#[test]
+#[serial]
+fn exact_success_keeps_authoritative_mutations_and_sequential_sheet_entries() {
+ // Given
+ fixture();
+ // When
+ let result = exact(|| {
+ mutate_authority();
+ with_style_sheet(|sheet| assert!(sheet.css.contains_key("tentative.tsx")));
+ with_style_sheet_mut(|sheet| sheet.add_import("tentative.tsx", "url('committed.css')"));
+ css::class_map::reset_class_map();
+ set_class_map(HashMap::from([(
+ "committed".into(),
+ HashMap::from([("kept".into(), 88)]),
+ )]));
+ Ok(())
+ });
+ // Then
+ assert_eq!(result, Ok(()));
+ assert_eq!(
+ css::class_map::get_class_map(),
+ HashMap::from([("committed".into(), HashMap::from([("kept".into(), 88)]))])
+ );
+ assert!(with_style_sheet(
+ |sheet| sheet.imports["tentative.tsx"].contains("url('committed.css')")
+ ));
+ assert!(css::file_map::original_id("tentative.tsx").is_some());
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn abort_restores_full_class_map_after_owner_reset_or_replacement(#[case] replace: bool) {
+ // Given
+ fixture();
+ let before = authority();
+ // When
+ let result: Result<(), String> = exact(|| {
+ if replace {
+ set_class_map(HashMap::from([("replacement".into(), HashMap::new())]));
+ } else {
+ css::class_map::reset_class_map();
+ }
+ let inner = css::class_map::Attempt::begin();
+ let _name = css::keyframes_to_keyframes_name("new after replacement", None);
+ inner.commit();
+ Err("abort".into())
+ });
+ // Then
+ assert_eq!(result, Err("abort".into()));
+ assert_eq!(authority(), before);
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn abort_reverses_both_no_site_requests_after_inner_commit() {
+ use css::allocation_input::{LegacyDeclaration, LegacyInput, LegacyVariable, capture_context};
+ // Given
+ fixture();
+ let before = authority();
+ // When
+ let result: Result<(), String> = exact(|| {
+ let inner = css::class_map::Attempt::begin();
+ let class = LegacyInput::Declaration(LegacyDeclaration {
+ property: "width".into(),
+ level: 0,
+ value: None,
+ selector: None,
+ order: None,
+ });
+ let class_context = capture_context(&class, None, css::CounterOwner::Inactive);
+ let class = css::counter_names::allocate_name(&class, &class_context);
+ let variable = LegacyInput::Variable(LegacyVariable {
+ property: "width".into(),
+ level: 0,
+ selector: Some(format!(".{}", class.name)),
+ });
+ let context = capture_context(&variable, None, css::CounterOwner::Inactive);
+ let _variable = css::counter_names::allocate_name(&variable, &context);
+ assert_eq!(css::class_map::get_class_map()[""].len(), 2);
+ inner.commit();
+ Err("output error".into())
+ });
+ // Then
+ assert_eq!(result, Err("output error".into()));
+ assert_eq!(authority(), before);
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/exact_nested_tests.rs b/bindings/devup-ui-wasm/src/exact_nested_tests.rs
new file mode 100644
index 000000000..3abd03297
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/exact_nested_tests.rs
@@ -0,0 +1,222 @@
+use super::*;
+use exact_test_support::{authority, compile, exact, fixture, mutate_authority};
+use rstest::rstest;
+use serial_test::serial;
+use std::panic::{AssertUnwindSafe, catch_unwind};
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn real_inner_parser_error_restores_local_authority_when_outer_finishes(#[case] commit: bool) {
+ // Given
+ fixture();
+ let source = "import {Box} from '@devup-ui/react';const x= result,
+ Err(payload) => std::panic::resume_unwind(payload),
+ };
+ assert_eq!(inner.err(), Some(expected));
+ assert_eq!(authority(), local);
+ committed = Some(local);
+ if commit {
+ Ok(())
+ } else {
+ Err("outer abort".into())
+ }
+ });
+ // Then
+ assert_eq!(
+ result,
+ if commit {
+ Ok(())
+ } else {
+ Err("outer abort".into())
+ }
+ );
+ assert_eq!(
+ authority(),
+ if commit {
+ committed.unwrap_or_else(|| panic!("committed authority capture missing"))
+ } else {
+ before
+ }
+ );
+ assert_eq!(cache_names::check(), Ok(()));
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn real_inner_resolver_unwind_preserves_original_panic_when_outer_finishes(#[case] commit: bool) {
+ // Given
+ fixture();
+ let before = authority();
+ let mut committed = None;
+ let resolver =
+ |_: &str, _: &str| -> Option { panic!("nested resolver unwind") };
+ // When
+ let result = exact(|| {
+ mutate_authority();
+ let local = authority();
+ let panic = match catch_unwind(AssertUnwindSafe(|| {
+ code_extract_with_modules_internal(
+ "inner.tsx",
+ "import {Box} from '@devup-ui/react';import {COLOR} from './tokens';export const x=;",
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ &resolver,
+ )
+ })) {
+ Err(payload) => payload,
+ Ok(_) => panic!("nested resolver did not panic"),
+ };
+ assert_eq!(
+ panic.downcast_ref::<&str>(),
+ Some(&"nested resolver unwind")
+ );
+ assert_eq!(authority(), local);
+ committed = Some(local);
+ if commit {
+ Ok(())
+ } else {
+ Err("outer abort".into())
+ }
+ });
+ // Then
+ assert_eq!(
+ result,
+ if commit {
+ Ok(())
+ } else {
+ Err("outer abort".into())
+ }
+ );
+ assert_eq!(
+ authority(),
+ if commit {
+ committed.unwrap_or_else(|| panic!("committed authority capture missing"))
+ } else {
+ before
+ }
+ );
+ assert_eq!(cache_names::check(), Ok(()));
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn real_inner_compile_success_is_reversed_when_outer_aborts() {
+ // Given
+ fixture();
+ seed_file_map(vec!["inner.tsx".into()]);
+ let before = authority();
+ // When
+ let result: Result<(), String> = exact(|| {
+ let output = compile(
+ "inner.tsx",
+ "import {Box} from '@devup-ui/react';export const x= ;",
+ )
+ .unwrap_or_else(|error| panic!("inner compile failed: {error}"));
+ assert!(
+ output
+ .css()
+ .unwrap_or_else(|| panic!("inner CSS missing"))
+ .contains("width:12px")
+ );
+ assert_ne!(authority(), before);
+ Err("outer abort".into())
+ });
+ // Then
+ assert_eq!(result, Err("outer abort".into()));
+ assert_eq!(authority(), before);
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn native_outer_resolver_can_call_real_nested_compiler(#[case] commit: bool) {
+ // Given
+ fixture();
+ let before = authority();
+ let calls = std::rc::Rc::new(std::cell::Cell::new(0usize));
+ let resolver_calls = std::rc::Rc::clone(&calls);
+ let resolver = move |_: &str, _: &str| {
+ resolver_calls.set(
+ resolver_calls
+ .get()
+ .checked_add(1)
+ .unwrap_or_else(|| panic!("resolver call counter overflow")),
+ );
+ let local = authority();
+ let error = compile(
+ "nested-broken.tsx",
+ "import {Box} from '@devup-ui/react';const x=;"
+ } else {
+ "import {Box,css} from '@devup-ui/react';import {COLOR} from './tokens';export const x=;export const y=css(runtime());"
+ };
+ // When: both outer and inner now enter the production common boundary.
+ let result = code_extract_with_modules_internal(
+ "outer.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ &resolver,
+ );
+ // Then
+ assert!(calls.get() > 0);
+ assert_eq!(result.is_ok(), commit);
+ if commit {
+ assert!(
+ result
+ .unwrap_or_else(|error| panic!("outer compile failed: {error}"))
+ .css()
+ .unwrap_or_else(|| panic!("outer CSS missing"))
+ .contains("color:red")
+ );
+ assert_ne!(authority(), before);
+ } else {
+ assert!(
+ result
+ .err()
+ .unwrap_or_else(|| panic!("outer invalid fixture unexpectedly compiled"))
+ .contains("build time")
+ );
+ assert_eq!(authority(), before);
+ }
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/exact_plan_tests.rs b/bindings/devup-ui-wasm/src/exact_plan_tests.rs
new file mode 100644
index 000000000..6c52a3cae
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/exact_plan_tests.rs
@@ -0,0 +1,47 @@
+use super::*;
+use exact_test_support::{authority, compile, fixture};
+use rstest::rstest;
+use serial_test::serial;
+use std::collections::{BTreeSet, HashSet};
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn real_parser_failure_restores_unfrozen_plan_when_routes_are_configured(#[case] eligible: bool) {
+ // Given: configuration precedes capture and the real common extraction entry.
+ fixture();
+ set_atom_hoist(Some(2));
+ import_file_routes_internal(HashMap::from([(
+ "plan.tsx".into(),
+ if eligible {
+ HashSet::from([1, 2])
+ } else {
+ HashSet::from([1])
+ },
+ )]));
+ assert_eq!(css::atom_hoist::atom_plan(), None);
+ let before = authority();
+ // When: extraction freezes a new plan before the parser fails.
+ let error = compile(
+ "broken-plan.tsx",
+ "import {Box} from '@devup-ui/react';const x= = exact(|| {
+ extract_without_source_map(
+ "raw.tsx",
+ source,
+ ExtractOption {
+ package: "@devup-ui/react".into(),
+ css_dir: "df".into(),
+ single_css: false,
+ import_main_css: false,
+ import_aliases: HashMap::new(),
+ },
+ )
+ .unwrap_or_else(|error| panic!("private allocation control failed: {error}"));
+ let mut allocated = css::class_map::get_class_map();
+ let private = allocated
+ .remove(&format!("D9-{original}"))
+ .unwrap_or_else(|| panic!("private allocation control namespace missing"));
+ assert_eq!(private.values().copied().collect::>(), vec![0]);
+ assert_eq!(allocated, classes);
+ Err("control abort".into())
+ });
+ assert_eq!(control, Err("control abort".into()));
+ assert_eq!(authority(), before);
+ // When
+ let error = code_extract_internal(
+ "raw.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .err()
+ .unwrap_or_else(|| panic!("Output collision fixture unexpectedly compiled"));
+ // Then
+ assert_eq!(authority(), before);
+ assert_eq!(cache_names::check(), Err(error.clone()));
+ assert_eq!(get_css_internal(None, false), Err(error.clone()));
+ assert_eq!(compile("later.tsx", "const x=1;").err(), Some(error));
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case("prepare error")]
+#[case("output error")]
+#[serial]
+fn simulated_failure_restores_full_live_records_after_inner_commit(#[case] failure: &str) {
+ // Given
+ fixture();
+ configure_fresh_plan();
+ assert_eq!(css::atom_hoist::atom_plan(), None);
+ let before = authority();
+ // When
+ let result: Result<(), String> = exact(|| {
+ mutate_authority();
+ assert_eq!(
+ css::atom_hoist::atom_plan(),
+ Some(std::collections::BTreeSet::from(["tentative.tsx".into()]))
+ );
+ Err(failure.into())
+ });
+ // Then
+ assert_eq!(result, Err(failure.into()));
+ assert_eq!(authority(), before);
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn unwind_restores_full_live_records_after_sheet_cleanup_and_update() {
+ // Given
+ fixture();
+ configure_fresh_plan();
+ assert_eq!(css::atom_hoist::atom_plan(), None);
+ let before = authority();
+ // When
+ let panic = catch_unwind(AssertUnwindSafe(|| {
+ exact::<()>(|| {
+ mutate_authority();
+ assert_eq!(
+ css::atom_hoist::atom_plan(),
+ Some(std::collections::BTreeSet::from(["tentative.tsx".into()]))
+ );
+ panic!("emission unwind");
+ })
+ }));
+ // Then
+ assert!(panic.is_err());
+ assert_eq!(authority(), before);
+ assert_eq!(cache_names::check(), Ok(()));
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn resolver_unwind_restores_real_common_extraction_boundary() {
+ // Given
+ fixture();
+ let before = authority();
+ let resolver = |_: &str, _: &str| -> Option { panic!("resolver unwind") };
+ // When
+ let panic = catch_unwind(AssertUnwindSafe(|| {
+ code_extract_with_modules_internal(
+ "root.tsx",
+ "import {Box} from '@devup-ui/react';import {COLOR} from './tokens';export const x=;",
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ &resolver,
+ )
+ }));
+ // Then
+ assert!(panic.is_err());
+ assert_eq!(authority(), before);
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn evaluated_visitor_retry_commits_output_without_registering_unseeded_original() {
+ // Given
+ reset_build_state_internal();
+ set_debug(false);
+ // When
+ let output = compile("retry.tsx", "import {css} from '@devup-ui/react';function a(){return 'red'}const b=()=>a();export const c=css({color:b(),padding:'16px'});")
+ .unwrap_or_else(|error| panic!("evaluated visitor retry failed: {error}"));
+ // Then
+ assert!(
+ output
+ .css()
+ .unwrap_or_else(|| panic!("retry CSS missing"))
+ .contains("color:red")
+ );
+ assert!(with_style_sheet(|sheet| sheet
+ .names
+ .values()
+ .any(|claim| claim.content.contains("red"))));
+ assert_eq!(css::file_map::original_id("retry.tsx"), None);
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/exact_test_support.rs b/bindings/devup-ui-wasm/src/exact_test_support.rs
new file mode 100644
index 000000000..c9e2f7e34
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/exact_test_support.rs
@@ -0,0 +1,171 @@
+use super::*;
+use extraction_rollback::{ExtractionRollback, SheetData};
+use std::collections::BTreeSet;
+
+/// Cleans only poison deliberately created by a sheet-lock panic test.
+pub(super) struct SheetPoisonCleanup(());
+
+impl SheetPoisonCleanup {
+ pub(super) fn new() -> Self {
+ assert!(
+ !GLOBAL_STYLE_SHEET.is_poisoned(),
+ "unexpected prior sheet poison"
+ );
+ Self(())
+ }
+}
+
+impl Drop for SheetPoisonCleanup {
+ fn drop(&mut self) {
+ // The enclosing test scope outlives the intentionally unwound sheet guard.
+ GLOBAL_STYLE_SHEET.clear_poison();
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub(super) struct Authority {
+ classes: HashMap>,
+ files: bimap::BiHashMap,
+ originals: BTreeMap,
+ plan: Option>,
+ sheet: SheetData,
+ theme: String,
+}
+
+pub(super) fn authority() -> Authority {
+ with_admission(|| Authority {
+ classes: css::class_map::get_class_map(),
+ files: css::file_map::get_file_map(),
+ originals: css::file_map::get_original_ids(),
+ plan: css::atom_hoist::atom_plan(),
+ sheet: with_style_sheet(SheetData::capture),
+ theme: with_style_sheet(|sheet| sheet.theme.to_css()),
+ })
+}
+
+pub(super) fn compile(file: &str, source: &str) -> Result {
+ code_extract_internal(
+ file,
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+}
+
+pub(super) fn fixture() {
+ reset_build_state_internal();
+ set_debug(false);
+ let mut theme = sheet::theme::Theme::default();
+ let mut colors = sheet::theme::ColorTheme::default();
+ colors.add_color("retained", "#123456");
+ theme.add_color_theme("default", colors);
+ register_theme_internal(theme);
+ code_extract_internal(
+ "retained.tsx",
+ "import {css} from '@devup-ui/react';export const x=css({color:'blue'});",
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("retained fixture extraction failed: {error}"));
+ set_class_map(HashMap::from([
+ ("empty".into(), HashMap::new()),
+ (
+ "retained".into(),
+ HashMap::from([("non-length".into(), 37)]),
+ ),
+ ]));
+ set_file_map(std::iter::once(("retained.tsx".into(), 29)).collect());
+ css::file_map::set_original_ids(BTreeMap::from([("retained.tsx".into(), 42)]));
+ css::atom_hoist::restore_atom_plan(None);
+ with_style_sheet_mut(|sheet| {
+ sheet.atom_plan = Some(BTreeSet::from(["retained.tsx".into()]));
+ sheet.source_ids.insert("sheet-only".into(), 91);
+ sheet.add_css("raw.tsx", "body{margin:1px}");
+ sheet.add_import("raw.tsx", "url('retained.css')");
+ sheet.add_font_face(
+ "raw.tsx",
+ &BTreeMap::from([("font-family".into(), "retained".into())]),
+ );
+ sheet.add_keyframes(
+ "ordered",
+ BTreeMap::from([(
+ "from".into(),
+ vec![
+ ("z-index".into(), "2".into()),
+ ("opacity".into(), "0".into()),
+ ("opacity".into(), "1".into()),
+ ],
+ )]),
+ Some("raw.tsx"),
+ );
+ let props = sheet
+ .properties
+ .values_mut()
+ .flat_map(BTreeMap::values_mut)
+ .flat_map(BTreeMap::values_mut)
+ .next()
+ .unwrap_or_else(|| panic!("retained fixture properties missing"));
+ let mut distinct = props
+ .iter()
+ .next()
+ .unwrap_or_else(|| panic!("retained fixture property record missing"))
+ .clone();
+ distinct.class_name = "same-ord-distinct-record".into();
+ props.insert(distinct);
+ });
+}
+
+pub(super) fn exact(build: impl FnOnce() -> Result) -> Result {
+ with_admission(|| {
+ cache_names::check()?;
+ let rollback = ExtractionRollback::capture();
+ let result = css::exact_attempt::with_exclusive_attempt(build);
+ if result.is_ok() {
+ rollback.commit();
+ }
+ result
+ })
+}
+
+pub(super) fn mutate_authority() {
+ let _file_num = css::file_map::get_file_num_by_filename("tentative.tsx");
+ css::file_map::get_or_insert_original_id("tentative.tsx")
+ .unwrap_or_else(|error| panic!("tentative original registration failed: {error}"));
+ css::atom_hoist::freeze_atom_plan();
+ let inner = css::class_map::Attempt::begin();
+ css::class_map::with_class_map_mut(|map| {
+ map.remove("empty");
+ map.insert(
+ "retained".into(),
+ HashMap::from([("replacement".into(), 99)]),
+ );
+ map.insert("new-empty".into(), HashMap::new());
+ });
+ let _name = css::keyframes_to_keyframes_name("inserted", None);
+ inner.commit();
+ with_style_sheet_mut(|sheet| {
+ sheet.names.clear();
+ sheet.properties.clear();
+ sheet.keyframes.clear();
+ sheet.rm_global_css("raw.tsx", false);
+ sheet.add_css("tentative.tsx", "body{margin:2px}");
+ sheet.atom_plan = None;
+ sheet.source_ids.clear();
+ });
+}
+
+pub(super) fn configure_fresh_plan() {
+ set_atom_hoist(Some(1));
+ import_file_routes_internal(HashMap::from([(
+ "tentative.tsx".into(),
+ std::collections::HashSet::from([1]),
+ )]));
+}
diff --git a/bindings/devup-ui-wasm/src/extraction_rollback.rs b/bindings/devup-ui-wasm/src/extraction_rollback.rs
new file mode 100644
index 000000000..e5d65bd04
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/extraction_rollback.rs
@@ -0,0 +1,23 @@
+use crate::{with_style_sheet, with_style_sheet_mut};
+pub(crate) use sheet::live_checkpoint::LiveCheckpoint as SheetData;
+
+/// Outside CSS exact ownership and inside admission, so CSS abort runs first.
+pub(crate) struct ExtractionRollback(Option);
+
+impl ExtractionRollback {
+ pub(crate) fn capture() -> Self {
+ Self(Some(with_style_sheet(SheetData::capture)))
+ }
+
+ pub(crate) fn commit(mut self) {
+ self.0 = None;
+ }
+}
+
+impl Drop for ExtractionRollback {
+ fn drop(&mut self) {
+ if let Some(before) = self.0.take() {
+ with_style_sheet_mut(|sheet| before.restore(sheet));
+ }
+ }
+}
diff --git a/bindings/devup-ui-wasm/src/lib.rs b/bindings/devup-ui-wasm/src/lib.rs
index 0a2347ac2..6fed49d19 100644
--- a/bindings/devup-ui-wasm/src/lib.rs
+++ b/bindings/devup-ui-wasm/src/lib.rs
@@ -1,7 +1,7 @@
-use css::class_map::{set_class_map, with_class_map};
-use css::file_map::{
- canonical, is_global, set_canonical_map, set_file_map, with_canonical_map, with_file_map,
-};
+use css::class_map::with_class_map;
+use css::file_map::{canonical, is_global, set_canonical_map, with_canonical_map, with_file_map};
+#[cfg(test)]
+use css::{class_map::set_class_map, file_map::set_file_map};
use extractor::extract_style::extract_style_value::ExtractStyleValue;
use extractor::{
ExtractOption, ImportAlias, ModuleResolver, ResolvedModule, extract, extract_with_modules,
@@ -14,6 +14,76 @@ use std::collections::{BTreeMap, HashMap};
use std::fmt::Display;
use std::sync::{LazyLock, Mutex};
use wasm_bindgen::prelude::*;
+mod administration;
+#[cfg(test)]
+mod admission_contract_tests;
+#[cfg(all(test, not(target_arch = "wasm32")))]
+mod admission_exclusion_tests;
+#[cfg(test)]
+mod admission_guard_tests;
+#[cfg(test)]
+mod exact_class_tests;
+#[cfg(test)]
+mod exact_nested_tests;
+#[cfg(test)]
+mod exact_plan_tests;
+#[cfg(test)]
+mod exact_rollback_tests;
+#[cfg(test)]
+mod exact_test_support;
+mod extraction_rollback;
+mod sheet_entry;
+use administration::with_administration;
+use css::admission::with_admission;
+#[cfg(test)]
+mod cache6_adoption_tests;
+#[cfg(test)]
+mod cache6_configuration_tests;
+#[cfg(test)]
+mod cache6_damage_tests;
+#[cfg(test)]
+mod cache6_nested_tests;
+#[cfg(test)]
+mod cache6_protocol_tests;
+#[doc(hidden)]
+pub mod cache6_restore;
+#[cfg(test)]
+mod cache6_rollback_tests;
+#[cfg(test)]
+mod cache6_seed_tests;
+mod cache6_session;
+#[cfg(test)]
+mod cache6_test_support;
+mod cache_atom_proof;
+mod cache_descriptor;
+mod cache_names;
+mod cache_restore;
+mod cache_source_names;
+mod cache_special_proof;
+#[cfg(test)]
+mod cache_v4_tests;
+#[cfg(test)]
+mod compact_source_tests;
+#[cfg(test)]
+mod content_location_contract_tests;
+#[cfg(test)]
+mod content_name_tests;
+#[cfg(test)]
+mod naming_root_tests;
+#[cfg(test)]
+mod original_counter_tests;
+#[cfg(test)]
+mod prefix_cache_tests;
+#[doc(hidden)]
+pub mod prospective_output;
+#[cfg(test)]
+mod prospective_output_tests;
+#[cfg(test)]
+mod scoped_cache_tests;
+#[cfg(test)]
+mod scoped_cascade_tests;
+#[cfg(test)]
+mod static_theme_tests;
static GLOBAL_STYLE_SHEET: LazyLock> =
LazyLock::new(|| Mutex::new(StyleSheet::default()));
@@ -32,20 +102,26 @@ fn with_style_sheet(f: F) -> R
where
F: FnOnce(&StyleSheet) -> R,
{
- let guard = GLOBAL_STYLE_SHEET
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner);
- f(&guard)
+ with_admission(|| {
+ let _entry = sheet_entry::SheetEntry::enter();
+ let guard = GLOBAL_STYLE_SHEET
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ f(&guard)
+ })
}
fn with_style_sheet_mut(f: F) -> R
where
F: FnOnce(&mut StyleSheet) -> R,
{
- let mut guard = GLOBAL_STYLE_SHEET
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner);
- f(&mut guard)
+ with_admission(|| {
+ let _entry = sheet_entry::SheetEntry::enter();
+ let mut guard = GLOBAL_STYLE_SHEET
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ f(&mut guard)
+ })
}
#[cfg(not(tarpaulin_include))]
@@ -74,7 +150,7 @@ impl Output {
css_file: Option,
import_main_css: bool,
dependencies: Vec,
- ) -> Self {
+ ) -> Result {
// Use the bucket identity (single-importer collapse) so the sheet's CSS
// naming + property bucket + emitted chunk match the canonical class names
// the extractor already baked into `code`. Identity when no map is loaded.
@@ -83,14 +159,22 @@ impl Output {
let canonical_filename = canonical(&filename);
let global = single_css || is_global(&filename);
with_style_sheet_mut(|sheet| {
+ sheet
+ .preflight_styles(&styles, &canonical_filename, global)
+ .map_err(|error| {
+ let message = error.to_string();
+ cache_names::record(&Err(message.clone()));
+ message
+ })?;
// globalCss (@font-face / global selectors) is per-SOURCE-file, never
// collapsed. rm_global_css MUST use the RAW filename so a collapsed
// member (sharing the bucket-root's canonical) never wipes the root's
// globalCss. Atom property bucketing still uses canonical_filename.
let default_collected = sheet.rm_global_css(&filename, global);
- let (collected, updated_base_style) =
- sheet.update_styles(&styles, &canonical_filename, global);
- Self {
+ let (collected, updated_base_style) = sheet
+ .update_styles(&styles, &canonical_filename, global)
+ .map_err(|error| error.to_string())?;
+ Ok(Self {
code,
map,
css_file,
@@ -110,7 +194,7 @@ impl Output {
))
}
},
- }
+ })
})
}
@@ -157,7 +241,7 @@ impl Output {
#[wasm_bindgen(js_name = "setDebug")]
pub fn set_debug(debug: bool) {
- css::debug::set_debug(debug);
+ with_administration("set_debug", || css::debug::set_debug(debug));
}
#[wasm_bindgen(js_name = "isDebug")]
@@ -208,7 +292,18 @@ pub fn is_debug() -> bool {
/// ```
#[wasm_bindgen(js_name = "setPrefix")]
pub fn set_prefix(prefix: Option) {
- css::set_prefix(prefix);
+ with_administration("set_prefix", || css::set_prefix(prefix));
+}
+
+/// Set the build's names-only project root before extraction in every compiler.
+/// This never changes resolver IDs, extraction keys, buckets or D9 numbering.
+///
+/// Supply the existing ID basis when relative extraction IDs are not root-relative.
+#[wasm_bindgen(js_name = "setNamingRoot")]
+pub fn set_naming_root(root: Option, relative_base: Option) {
+ with_administration("set_naming_root", || {
+ css::naming_root::set_context(root, relative_base);
+ });
}
#[wasm_bindgen(js_name = "getPrefix")]
@@ -218,21 +313,22 @@ pub fn get_prefix() -> Option {
}
/// Internal function to import a `StyleSheet` (testable without `JsValue`)
-pub fn import_sheet_internal(sheet: StyleSheet) {
- with_style_sheet_mut(|global_sheet| *global_sheet = sheet);
+pub fn import_sheet_internal(sheet: StyleSheet) -> Result<(), String> {
+ with_administration("import_sheet_internal", || cache_restore::import(sheet))
}
#[wasm_bindgen(js_name = "importSheet")]
#[cfg(not(tarpaulin_include))]
pub fn import_sheet(sheet_object: JsValue) -> Result<(), JsValue> {
- let sheet: StyleSheet = serde_wasm_bindgen::from_value(sheet_object).map_err(js_error)?;
- import_sheet_internal(sheet);
- Ok(())
+ let sheet: StyleSheet =
+ serde_wasm_bindgen::from_value(sheet_object).unwrap_or_else(|_| cache_restore::absent());
+ import_sheet_internal(sheet).map_err(js_error)
}
/// Internal function to export `StyleSheet` as JSON string (testable without `JsValue`)
pub fn export_sheet_internal() -> Result {
- with_style_sheet(serde_json::to_string).map_err(|e| e.to_string())
+ with_style_sheet(|sheet| serde_json::to_string(&sheet.export_snapshot()))
+ .map_err(|e| e.to_string())
}
#[wasm_bindgen(js_name = "exportSheet")]
@@ -243,13 +339,20 @@ pub fn export_sheet() -> Result {
/// Internal function to export class map as JSON string (testable without `JsValue`)
pub fn export_class_map_internal() -> Result {
- with_class_map(serde_json::to_string).map_err(|e| e.to_string())
+ with_class_map(|map| {
+ let sorted: BTreeMap<&String, BTreeMap<&String, &usize>> = map
+ .iter()
+ .map(|(file, classes)| (file, classes.iter().collect()))
+ .collect();
+ serde_json::to_string(&sorted)
+ })
+ .map_err(|e| e.to_string())
}
#[wasm_bindgen(js_name = "importClassMap")]
#[cfg(not(tarpaulin_include))]
pub fn import_class_map(sheet_object: JsValue) -> Result<(), JsValue> {
- set_class_map(serde_wasm_bindgen::from_value(sheet_object).map_err(js_error)?);
+ cache_restore::classes(serde_wasm_bindgen::from_value(sheet_object).ok());
Ok(())
}
@@ -261,13 +364,17 @@ pub fn export_class_map() -> Result {
/// Internal function to export file map as JSON string (testable without `JsValue`)
pub fn export_file_map_internal() -> Result {
- with_file_map(serde_json::to_string).map_err(|e| e.to_string())
+ with_file_map(|map| {
+ let sorted: BTreeMap<&String, &usize> = map.iter().collect();
+ serde_json::to_string(&sorted)
+ })
+ .map_err(|e| e.to_string())
}
#[wasm_bindgen(js_name = "importFileMap")]
#[cfg(not(tarpaulin_include))]
pub fn import_file_map(sheet_object: JsValue) -> Result<(), JsValue> {
- set_file_map(serde_wasm_bindgen::from_value(sheet_object).map_err(js_error)?);
+ cache_restore::files(serde_wasm_bindgen::from_value(sheet_object).ok());
Ok(())
}
@@ -279,18 +386,22 @@ pub fn export_file_map() -> Result {
/// Internal function to import the canonical (bucket) map (testable without `JsValue`)
pub fn import_canonical_map_internal(map: HashMap) {
- set_canonical_map(map);
+ with_administration("import_canonical_map_internal", || set_canonical_map(map));
}
/// Internal function to export the canonical map as JSON string (testable without `JsValue`)
pub fn export_canonical_map_internal() -> Result {
- with_canonical_map(serde_json::to_string).map_err(|e| e.to_string())
+ with_canonical_map(|map| {
+ let sorted: BTreeMap<&String, &String> = map.iter().collect();
+ serde_json::to_string(&sorted)
+ })
+ .map_err(|e| e.to_string())
}
#[wasm_bindgen(js_name = "importCanonicalMap")]
#[cfg(not(tarpaulin_include))]
pub fn import_canonical_map(map_object: JsValue) -> Result<(), JsValue> {
- set_canonical_map(serde_wasm_bindgen::from_value(map_object).map_err(js_error)?);
+ import_canonical_map_internal(serde_wasm_bindgen::from_value(map_object).map_err(js_error)?);
Ok(())
}
@@ -300,24 +411,66 @@ pub fn export_canonical_map() -> Result {
export_canonical_map_internal().map_err(js_error)
}
+/// Number every file in `files` now, in path order, keeping numbers files
+/// already hold, so class prefixes depend on the paths and not on the order
+/// workers reach files in.
+#[wasm_bindgen(js_name = "seedFileMap")]
+pub fn seed_file_map(files: Vec) {
+ with_administration("seed_file_map", || {
+ cache_restore::seeded(&files);
+ cache6_restore::record_seed(&files);
+ css::file_map::seed_file_numbers(&files);
+ });
+}
+
+/// Forget everything one build left in the engine.
+///
+/// That is names, numbers, styles, buckets, routes, the prefix, atom hoisting
+/// and the module resolver, so the next build starts from its own options
+/// alone. Theme, shorthands and debug mode are set by every build, and stay.
+pub fn reset_build_state_internal() {
+ with_administration("reset_build_state_internal", || {
+ cache_names::clear();
+ cache_restore::clear();
+ cache6_restore::forget();
+ css::class_map::reset_class_map();
+ css::file_map::reset_file_map();
+ css::file_map::reset_canonical_map();
+ css::file_routes::set_file_routes(HashMap::new());
+ css::atom_hoist::set_atom_hoist(None);
+ css::atom_hoist::restore_atom_plan(None);
+ css::set_prefix(None);
+ css::naming_root::set_root(None);
+ with_style_sheet_mut(|sheet| *sheet = StyleSheet::default());
+ set_module_resolver_internal(None);
+ });
+}
+
+#[wasm_bindgen(js_name = "resetBuildState")]
+pub fn reset_build_state() {
+ reset_build_state_internal();
+}
/// Set the atom-level hoist threshold.
///
-/// When set to `Some(n)`, a style atom whose content is used by `>= n` distinct
-/// routes is emitted into the shared global `devup-ui.css` (shipped once) instead
-/// of duplicated into each per-route chunk. `None` (the default) disables atom
-/// hoisting entirely (identity behavior).
+/// When set to `Some(n)`, buckets with predeclared reach of `>= n` routes emit
+/// their atoms into shared CSS. Eligibility freezes before the first source;
+/// unknown or late reach stays local until `resetBuildState`.
+/// `None` (the default) preserves ordinary per-file naming and placement.
///
-/// MUST be called BEFORE `codeExtract` so atoms receive global (shared) class
-/// names; enabling it afterwards leaves per-file names and nothing hoists.
+/// Set this before `codeExtract` so extraction and emission share one plan.
/// Pair with `importFileRoutes` to provide the file -> routes mapping.
#[wasm_bindgen(js_name = "setAtomHoist")]
pub fn set_atom_hoist(threshold: Option) {
- css::atom_hoist::set_atom_hoist(threshold);
+ with_administration("set_atom_hoist", || {
+ css::atom_hoist::set_atom_hoist(threshold);
+ });
}
/// Internal function to import the file -> routes map (testable without `JsValue`)
pub fn import_file_routes_internal(map: HashMap>) {
- css::file_routes::set_file_routes(map);
+ with_administration("import_file_routes_internal", || {
+ css::file_routes::set_file_routes(map);
+ });
}
/// Import the file -> set-of-route-ids mapping used to decide atom hoisting.
@@ -328,9 +481,7 @@ pub fn import_file_routes_internal(map: HashMap Result<(), JsValue> {
- css::file_routes::set_file_routes(
- serde_wasm_bindgen::from_value(map_object).map_err(js_error)?,
- );
+ import_file_routes_internal(serde_wasm_bindgen::from_value(map_object).map_err(js_error)?);
Ok(())
}
@@ -426,38 +577,47 @@ fn code_extract_internal_impl(
source_map: SourceMapMode,
resolver: Option<&ModuleResolver>,
) -> Result {
- let option = ExtractOption {
- package: package.to_string(),
- css_dir,
- single_css,
- import_main_css: import_main_css_in_code,
- import_aliases,
- };
- let extracted = match (resolver, source_map) {
- (Some(resolver), mode) => extract_with_modules(
- filename,
- code,
- option,
- matches!(mode, SourceMapMode::Generate),
- resolver,
- ),
- (None, SourceMapMode::Generate) => extract(filename, code, option),
- (None, SourceMapMode::Skip) => extract_without_source_map(filename, code, option),
- };
-
- match extracted {
- Ok(output) => Ok(Output::new(
- output.code,
- output.styles,
- output.map,
- single_css,
- filename.to_string(),
- output.css_file,
- import_main_css_in_css,
- output.dependencies,
- )),
- Err(error) => Err(error.to_string()),
- }
+ with_admission(|| {
+ cache_names::check()?;
+ let rollback = extraction_rollback::ExtractionRollback::capture();
+ let result = css::exact_attempt::with_exclusive_attempt(|| {
+ let option = ExtractOption {
+ package: package.to_string(),
+ css_dir,
+ single_css,
+ import_main_css: import_main_css_in_code,
+ import_aliases,
+ };
+ let extracted = match (resolver, source_map) {
+ (Some(resolver), mode) => extract_with_modules(
+ filename,
+ code,
+ option,
+ matches!(mode, SourceMapMode::Generate),
+ resolver,
+ ),
+ (None, SourceMapMode::Generate) => extract(filename, code, option),
+ (None, SourceMapMode::Skip) => extract_without_source_map(filename, code, option),
+ };
+ match extracted {
+ Ok(output) => Output::new(
+ output.code,
+ output.styles,
+ output.map,
+ single_css,
+ filename.to_string(),
+ output.css_file,
+ import_main_css_in_css,
+ output.dependencies,
+ ),
+ Err(error) => Err(error.to_string()),
+ }
+ });
+ if result.is_ok() {
+ rollback.commit();
+ }
+ result
+ })
}
/// Set how the imports of the files being extracted are resolved.
@@ -467,7 +627,13 @@ fn code_extract_internal_impl(
#[wasm_bindgen(js_name = "setModuleResolver")]
#[cfg(not(tarpaulin_include))]
pub fn set_module_resolver(resolver: Option) {
- MODULE_RESOLVER.with_borrow_mut(|current| *current = resolver);
+ set_module_resolver_internal(resolver);
+}
+
+fn set_module_resolver_internal(resolver: Option) {
+ with_administration("set_module_resolver", || {
+ MODULE_RESOLVER.with_borrow_mut(|current| *current = resolver);
+ });
}
#[cfg(not(tarpaulin_include))]
@@ -504,25 +670,29 @@ fn code_extract_js(
import_aliases: JsValue,
source_map: SourceMapMode,
) -> Result {
- let import_aliases = import_aliases_from_js(import_aliases)?;
- let resolver = MODULE_RESOLVER.with_borrow(Clone::clone).map(|resolver| {
- move |specifier: &str, importer: &str| call_module_resolver(&resolver, specifier, importer)
- });
- code_extract_internal_impl(
- filename,
- code,
- package,
- css_dir,
- single_css,
- import_main_css_in_code,
- import_main_css_in_css,
- import_aliases,
- source_map,
- resolver
- .as_ref()
- .map(|resolver| resolver as &ModuleResolver),
- )
- .map_err(js_error)
+ with_admission(|| {
+ let import_aliases = import_aliases_from_js(import_aliases)?;
+ let resolver = MODULE_RESOLVER.with_borrow(Clone::clone).map(|resolver| {
+ move |specifier: &str, importer: &str| {
+ call_module_resolver(&resolver, specifier, importer)
+ }
+ });
+ code_extract_internal_impl(
+ filename,
+ code,
+ package,
+ css_dir,
+ single_css,
+ import_main_css_in_code,
+ import_main_css_in_css,
+ import_aliases,
+ source_map,
+ resolver
+ .as_ref()
+ .map(|resolver| resolver as &ModuleResolver),
+ )
+ .map_err(js_error)
+ })
}
#[cfg(not(tarpaulin_include))]
@@ -597,7 +767,9 @@ pub fn code_extract_without_source_map(
/// Internal function to register theme (testable without `JsValue`)
pub fn register_theme_internal(theme: sheet::theme::Theme) {
- with_style_sheet_mut(|sheet| sheet.set_theme(theme));
+ with_administration("register_theme_internal", || {
+ with_style_sheet_mut(|sheet| sheet.set_theme(theme));
+ });
}
#[wasm_bindgen(js_name = "registerTheme")]
@@ -611,7 +783,9 @@ pub fn register_theme(theme_object: JsValue) -> Result<(), JsValue> {
/// Internal function to register custom style-property shorthands.
pub fn register_shorthands_internal(shorthands: BTreeMap>) {
- css::set_custom_shorthands(shorthands);
+ with_administration("register_shorthands_internal", || {
+ css::set_custom_shorthands(shorthands);
+ });
}
#[wasm_bindgen(js_name = "registerShorthands")]
@@ -631,18 +805,26 @@ pub fn get_default_theme() -> Result, JsValue> {
#[wasm_bindgen(js_name = "getCss")]
#[cfg(not(tarpaulin_include))]
pub fn get_css(file_num: Option, import_main_css: bool) -> Result {
- Ok(with_style_sheet(|sheet| {
- if let Some(file_num) = file_num {
- with_file_map(|map| {
- sheet.create_css(
- map.get_by_right(&file_num).map(String::as_str),
- import_main_css,
- )
- })
- } else {
- sheet.create_css(None, import_main_css)
- }
- }))
+ get_css_internal(file_num, import_main_css).map_err(js_error)
+}
+
+/// Internal CSS retrieval using the same sticky-error and file-selection contract as getCss.
+pub fn get_css_internal(file_num: Option, import_main_css: bool) -> Result {
+ with_admission(|| {
+ cache_names::check()?;
+ Ok(with_style_sheet(|sheet| {
+ if let Some(file_num) = file_num {
+ with_file_map(|map| {
+ sheet.create_css(
+ map.get_by_right(&file_num).map(String::as_str),
+ import_main_css,
+ )
+ })
+ } else {
+ sheet.create_css(None, import_main_css)
+ }
+ }))
+ })
}
#[wasm_bindgen(js_name = "getThemeInterface")]
@@ -674,6 +856,15 @@ pub fn has_devup_ui_wasm(filename: &str, code: &str, package: &str) -> bool {
has_devup_ui(filename, code, package)
}
+#[cfg(test)]
+mod atom_tests;
+
+#[cfg(test)]
+mod cache_protocol_tests;
+
+#[cfg(test)]
+mod atom_hoist_measurement;
+
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
@@ -707,16 +898,30 @@ mod tests {
reset_file_routes();
register_theme_internal(sheet::theme::Theme::default());
- // a.tsx -> route 0, b.tsx -> route 1. bg:red is in BOTH (routes {0,1}, count 2 => HOIST).
- // width:11px only in a (route {0}, private). width:22px only in b (private).
+ css::atom_hoist::restore_atom_plan(None);
let mut fr = HashMap::new();
fr.insert("a.tsx".to_string(), HashSet::from([0u32]));
fr.insert("b.tsx".to_string(), HashSet::from([1u32]));
+ fr.insert("shared-a.tsx".to_string(), HashSet::from([0u32, 1]));
+ fr.insert("shared-b.tsx".to_string(), HashSet::from([0u32, 1]));
set_file_routes(fr);
set_atom_hoist(Some(2));
- let srca = r#"import { Box } from "@devup-ui/react"; const x = ;"#;
- let srcb = r#"import { Box } from "@devup-ui/react"; const x = ;"#;
+ for file in ["shared-a.tsx", "shared-b.tsx"] {
+ code_extract_internal(
+ file,
+ r#"import { Box } from "@devup-ui/react"; const x = ;"#,
+ "@devup-ui/react",
+ "df".to_string(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap();
+ }
+ let srca = r#"import { Box } from "@devup-ui/react"; const x = ;"#;
+ let srcb = r#"import { Box } from "@devup-ui/react"; const x = ;"#;
code_extract_internal(
"a.tsx",
srca,
@@ -771,337 +976,28 @@ mod tests {
set_atom_hoist(None);
reset_file_routes();
+ css::atom_hoist::restore_atom_plan(None);
}
- /// Env-gated artifact emitter for split-native measurement. Writes REAL
- /// devup CSS output (header, `@layer`, naming, dedup all authentic) for three
- /// delivery models across several workloads, so an external script can
- /// measure gzip/brotli + multi-route session + incremental-invalidation
- /// bytes. Set `DEVUP_EMIT_MEASURE=1` to run; no-op (and zero cost) otherwise
- /// so the normal test suite stays clean.
+ /// Export real before/after sheets and code for external gzip measurement.
+ /// `DEVUP_EMIT_MEASURE=1` enables structured stdout without shared temp files.
#[test]
#[serial]
- #[allow(clippy::items_after_statements, clippy::format_push_string)]
fn emit_split_measurement_artifacts() {
- use css::atom_hoist::set_atom_hoist;
- use css::class_map::reset_class_map;
- use css::file_map::reset_file_map;
- use css::file_routes::{reset_file_routes, set_file_routes};
- use std::collections::{HashMap, HashSet};
- use std::fs;
-
- if std::env::var("DEVUP_EMIT_MEASURE").is_err() {
- return;
- }
-
- let props = [
- "w",
- "h",
- "p",
- "m",
- "minW",
- "minH",
- "maxW",
- "maxH",
- "fontSize",
- "lineHeight",
- "borderRadius",
- "gap",
- ];
- let atom = |key: &str, px: usize| format!(" ");
- let build = |els: &[String]| {
- format!(
- "import {{ Box }} from \"@devup-ui/react\"; const x = <>{}>;",
- els.join("")
- )
- };
- let reset = || {
- {
- let mut s = GLOBAL_STYLE_SHEET.lock().unwrap();
- *s = StyleSheet::default();
- }
- reset_class_map();
- reset_file_map();
- reset_file_routes();
- register_theme_internal(sheet::theme::Theme::default());
- };
-
- let out = std::env::temp_dir().join("devup-split-measure");
- let _ = fs::remove_dir_all(&out);
- fs::create_dir_all(&out).unwrap();
-
- // (name, routes, universal atoms, private atoms/route)
- let workloads = [
- ("shared_heavy", 8usize, 80usize, 25usize),
- ("balanced", 8usize, 50usize, 50usize),
- ("disjoint", 8usize, 20usize, 60usize),
- ];
- let mut manifest = String::from("[");
- for (wi, (name, n, u, p)) in workloads.iter().enumerate() {
- let (n, u, p) = (*n, *u, *p);
- let universal: Vec = (0..u)
- .map(|i| atom(props[i % props.len()], 100_000 + i))
- .collect();
- let make_priv = |r: usize| -> Vec {
- (0..p)
- .map(|i| {
- atom(
- props[i % props.len()],
- 1_000_000 + wi * 1_000_000 + r * p + i,
- )
- })
- .collect()
- };
- let sources: Vec = (0..n)
- .map(|r| {
- let mut e = universal.clone();
- e.extend(make_priv(r));
- build(&e)
- })
- .collect();
- let run = |single: bool| {
- reset();
- for (r, src) in sources.iter().enumerate() {
- code_extract_internal(
- &format!("r{r}.tsx"),
- src,
- "@devup-ui/react",
- "df".to_string(),
- single,
- false,
- false,
- HashMap::new(),
- )
- .unwrap();
- }
- };
-
- // single-css: one shared file with every atom.
- run(true);
- fs::write(
- out.join(format!("{name}_single.css")),
- with_style_sheet(|s| s.create_css(None, false)),
- )
- .unwrap();
-
- // per-file: shared base (theme/base only) + one full chunk per route.
- run(false);
- fs::write(
- out.join(format!("{name}_perfile_base.css")),
- with_style_sheet(|s| s.create_css(None, false)),
- )
- .unwrap();
- for r in 0..n {
- fs::write(
- out.join(format!("{name}_perfile_r{r}.css")),
- with_style_sheet(|s| s.create_css(Some(&format!("r{r}.tsx")), false)),
- )
- .unwrap();
- }
-
- // atom-B: hoisted shared base (universal atoms) + per-route delta.
- // CRITICAL: atom_hoist must be enabled BEFORE extraction so atoms get
- // GLOBAL names (shared identity across files). Enabling it only at
- // create_css time leaves per-file names, so the same universal atom
- // looks like N distinct atoms (one per file) and never hoists.
- reset();
- let mut fr = HashMap::new();
- for r in 0..n {
- fr.insert(format!("r{r}.tsx"), HashSet::from([r as u32]));
- }
- set_file_routes(fr);
- set_atom_hoist(Some(n));
- for (r, src) in sources.iter().enumerate() {
- code_extract_internal(
- &format!("r{r}.tsx"),
- src,
- "@devup-ui/react",
- "df".to_string(),
- false,
- false,
- false,
- HashMap::new(),
- )
- .unwrap();
- }
- fs::write(
- out.join(format!("{name}_atomb_base.css")),
- with_style_sheet(|s| s.create_css(None, false)),
- )
- .unwrap();
- for r in 0..n {
- fs::write(
- out.join(format!("{name}_atomb_r{r}.css")),
- with_style_sheet(|s| s.create_css(Some(&format!("r{r}.tsx")), false)),
- )
- .unwrap();
- }
- set_atom_hoist(None);
- reset_file_routes();
-
- manifest.push_str(&format!(
- "{}{{\"name\":\"{name}\",\"n\":{n},\"u\":{u},\"p\":{p}}}",
- if wi > 0 { "," } else { "" }
- ));
+ if std::env::var_os("DEVUP_EMIT_MEASURE").is_some() {
+ let artifacts =
+ super::atom_hoist_measurement::build_predeclared_measurement_artifacts()
+ .unwrap_or_else(|error| panic!("{error}"));
+ println!("{artifacts}");
}
- manifest.push(']');
- fs::write(out.join("manifest.json"), manifest).unwrap();
- reset();
- set_atom_hoist(None);
- println!("[EMIT] artifacts -> {}", out.display());
}
- /// SPLIT-NATIVE LOCK: atom-level route-aware hoisting (global-named
- /// shared-base + per-route delta) is a STRICT upgrade over the per-file mode
- /// on the metrics that split actually competes on -- multi-route SESSION
- /// bytes and incremental-deploy INVALIDATION bytes -- NOT on fresh-single-
- /// route bytes (where per-file already hits the theoretical floor).
- ///
- /// This test supersedes an earlier "no win" lock that was built on a
- /// measurement bug: enabling atom_hoist AFTER extraction left per-file class
- /// names, so the same universal atom looked like N distinct atoms and never
- /// hoisted -- making atom-B byte-identical to per-file (a no-op, not a
- /// truth). The fix, asserted here, is that atom_hoist MUST be enabled BEFORE
- /// extraction so atoms get GLOBAL (shared) names.
+ /// Compare usable sheets with predeclared shared-module reach and count
+ /// deployment invalidations after changing a common upstream declaration.
#[test]
#[serial]
- // byte sizes are tiny so ratios are exact; doc prose names models literally
- #[allow(clippy::cast_precision_loss, clippy::doc_markdown)]
fn atom_b_beats_per_file_on_session_and_invalidation() {
- use css::atom_hoist::set_atom_hoist;
- use css::class_map::reset_class_map;
- use css::file_map::reset_file_map;
- use css::file_routes::{reset_file_routes, set_file_routes};
- use std::collections::{HashMap, HashSet};
-
- // Realistic design-system workload: many shared primitives, fewer
- // route-private atoms. Routes are disjoint on private atoms.
- const ROUTES: usize = 8;
- const UNIVERSAL: usize = 80;
- const PRIVATE: usize = 25;
-
- let props = ["w", "h", "p", "m", "minW", "minH", "maxW", "maxH"];
- let atom = |key: &str, px: usize| format!(" ");
- let build_source = |elements: &[String]| -> String {
- let body = elements.join("");
- format!("import {{ Box }} from \"@devup-ui/react\"; const x = <>{body}>;")
- };
- let reset_engine = || {
- {
- let mut s = GLOBAL_STYLE_SHEET.lock().unwrap();
- *s = StyleSheet::default();
- }
- reset_class_map();
- reset_file_map();
- reset_file_routes();
- register_theme_internal(sheet::theme::Theme::default());
- };
-
- let universal_atoms: Vec = (0..UNIVERSAL)
- .map(|i| atom(props[i % props.len()], 100_000 + i))
- .collect();
- let make_private = |route: usize| -> Vec {
- (0..PRIVATE)
- .map(|i| atom(props[i % props.len()], 1_000_000 + route * PRIVATE + i))
- .collect()
- };
- let sources: Vec = (0..ROUTES)
- .map(|r| {
- let mut e = universal_atoms.clone();
- e.extend(make_private(r));
- build_source(&e)
- })
- .collect();
- let extract_all = |single_css: bool| {
- for (r, src) in sources.iter().enumerate() {
- code_extract_internal(
- &format!("r{r}.tsx"),
- src,
- "@devup-ui/react",
- "df".to_string(),
- single_css,
- false,
- false,
- HashMap::new(),
- )
- .unwrap();
- }
- };
-
- // ---- per-file: atom_hoist OFF, multi-css. Each chunk carries all of
- // its route's atoms (universals duplicated into every chunk). ----
- reset_engine();
- extract_all(false);
- let pf_base = with_style_sheet(|s| s.create_css(None, false)).len();
- let pf_chunks: Vec = (0..ROUTES)
- .map(|r| with_style_sheet(|s| s.create_css(Some(&format!("r{r}.tsx")), false)).len())
- .collect();
-
- // ---- atom-B: enable hoist + routes BEFORE extraction so atoms get
- // GLOBAL names; universals (used by all ROUTES) hoist into the base,
- // privates stay in their per-route delta. ----
- reset_engine();
- let mut fr = HashMap::new();
- for r in 0..ROUTES {
- fr.insert(format!("r{r}.tsx"), HashSet::from([r as u32]));
- }
- set_file_routes(fr);
- set_atom_hoist(Some(ROUTES));
- extract_all(false);
- let ab_base = with_style_sheet(|s| s.create_css(None, false)).len();
- let ab_deltas: Vec = (0..ROUTES)
- .map(|r| with_style_sheet(|s| s.create_css(Some(&format!("r{r}.tsx")), false)).len())
- .collect();
- set_atom_hoist(None);
- reset_file_routes();
- reset_engine();
-
- // Session = visit every route once (base cached after the first route).
- let pf_session = pf_base + pf_chunks.iter().sum::();
- let ab_session = ab_base + ab_deltas.iter().sum::();
- // Invalidation = one route's styles change; returning user re-downloads
- // only the file(s) whose hash changed.
- let pf_invalidation = pf_chunks[0];
- let ab_invalidation = ab_deltas[0];
-
- let session_margin = (pf_session as f64 - ab_session as f64) / pf_session as f64 * 100.0;
- let invalidation_margin =
- (pf_invalidation as f64 - ab_invalidation as f64) / pf_invalidation as f64 * 100.0;
- println!(
- "[SPLIT] base: per-file={pf_base}B atom-B={ab_base}B | chunk: per-file={}B atom-B-delta={}B",
- pf_chunks[0], ab_deltas[0]
- );
- println!(
- "[SPLIT] session: per-file={pf_session}B atom-B={ab_session}B ({session_margin:.1}% smaller) | invalidation: per-file={pf_invalidation}B atom-B={ab_invalidation}B ({invalidation_margin:.1}% smaller)"
- );
-
- // Regression guard against the no-op-hoist bug: hoisting MUST have moved
- // the universal atoms into the base, so the base is large and the delta
- // is much smaller than a full per-file chunk.
- assert!(
- ab_base > pf_base + 500,
- "hoist no-op: atom-B base ({ab_base}B) should hold the universal atoms, \
- but is barely larger than the empty per-file base ({pf_base}B). \
- atom_hoist was likely enabled AFTER extraction."
- );
- assert!(
- (ab_deltas[0] as f64) < (pf_chunks[0] as f64) * 0.6,
- "hoist no-op: atom-B delta ({}B) should be far smaller than the full \
- per-file chunk ({}B) once universals are hoisted out",
- ab_deltas[0],
- pf_chunks[0]
- );
- // The split-native wins this whole investigation hinges on.
- assert!(
- session_margin >= 15.0,
- "atom-B should beat per-file on multi-route session bytes by >=15% \
- (got {session_margin:.1}%)"
- );
- assert!(
- invalidation_margin >= 30.0,
- "atom-B should beat per-file on incremental-deploy invalidation by \
- >=30% (got {invalidation_margin:.1}%)"
- );
+ super::atom_hoist_measurement::assert_predeclared_shared_savings().unwrap();
}
#[test]
@@ -1548,7 +1444,8 @@ mod tests {
Some("devup-ui-0.css".to_string()),
false,
Vec::new(),
- );
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
// Test getters
assert_ne!(output.code(), "");
@@ -1575,7 +1472,8 @@ mod tests {
None,
false,
Vec::new(),
- );
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
// Test updated_base_style getter
let _ = output.updated_base_style();
@@ -1638,7 +1536,8 @@ mod tests {
Some("devup-ui.css".to_string()),
true, // import_main_css = true
Vec::new(),
- );
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
assert!(output.css().is_some());
}
@@ -1677,7 +1576,8 @@ mod tests {
None,
false,
Vec::new(),
- );
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
// The updated_base_style should be true because global CSS was removed
assert!(output.updated_base_style());
@@ -1873,7 +1773,7 @@ mod tests {
custom_sheet.add_property("custom.tsx", "color", 0, "red", None, Some(0), None);
// Import the custom sheet
- import_sheet_internal(custom_sheet);
+ import_sheet_internal(custom_sheet).unwrap_or_else(|error| panic!("{error}"));
// Verify the sheet was imported by exporting it
let result = export_sheet_internal();
@@ -1929,6 +1829,60 @@ mod tests {
assert!(json_str.starts_with('{') || json_str.starts_with("[]"));
}
+ #[test]
+ #[serial]
+ fn test_exported_maps_are_canonical_json() {
+ css::class_map::reset_class_map();
+ css::file_map::reset_file_map();
+ css::file_map::reset_canonical_map();
+ css::class_map::set_class_map(HashMap::from([
+ (
+ "b.tsx".to_string(),
+ HashMap::from([("z".to_string(), 1), ("a".to_string(), 0)]),
+ ),
+ ("a.tsx".to_string(), HashMap::from([("k".to_string(), 0)])),
+ ]));
+ assert_eq!(
+ export_class_map_internal().unwrap(),
+ r#"{"a.tsx":{"k":0},"b.tsx":{"a":0,"z":1}}"#
+ );
+ seed_file_map(vec!["b.tsx".to_string(), "a.tsx".to_string()]);
+ assert_eq!(
+ export_file_map_internal().unwrap(),
+ r#"{"a.tsx":0,"b.tsx":1}"#
+ );
+ import_canonical_map_internal(HashMap::from([
+ ("y".to_string(), "b".to_string()),
+ ("x".to_string(), "a".to_string()),
+ ]));
+ assert_eq!(
+ export_canonical_map_internal().unwrap(),
+ r#"{"x":"a","y":"b"}"#
+ );
+ reset_build_state();
+ assert_eq!(export_class_map_internal().unwrap(), "{}");
+ assert_eq!(export_file_map_internal().unwrap(), "{}");
+ assert_eq!(export_canonical_map_internal().unwrap(), "{}");
+ assert_eq!(get_prefix(), None);
+ }
+
+ #[test]
+ #[serial]
+ fn test_numbers_do_not_depend_on_the_order_files_are_seen() {
+ let files = ["src/b.tsx", "src/a.tsx", "src/c.tsx"];
+ let mut maps = Vec::new();
+ for order in [[0, 1, 2], [2, 1, 0], [1, 2, 0]] {
+ reset_build_state_internal();
+ seed_file_map(files.iter().map(ToString::to_string).collect());
+ for index in order {
+ let _ = css::file_map::get_file_num_by_filename(files[index]);
+ }
+ maps.push(export_file_map_internal().unwrap());
+ }
+ assert_eq!(maps[0], r#"{"src/a.tsx":0,"src/b.tsx":1,"src/c.tsx":2}"#);
+ assert_eq!(maps[0], maps[1]);
+ assert_eq!(maps[0], maps[2]);
+ }
#[test]
#[serial]
fn test_code_extract_internal_success() {
diff --git a/bindings/devup-ui-wasm/src/naming_root_tests.rs b/bindings/devup-ui-wasm/src/naming_root_tests.rs
new file mode 100644
index 000000000..b6b0b73ab
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/naming_root_tests.rs
@@ -0,0 +1,256 @@
+use super::*;
+use rstest::rstest;
+use serial_test::serial;
+
+const SOURCE: &str = "import {Box} from '@devup-ui/react'; export const x=;";
+
+fn compile(file: &str) -> Output {
+ code_extract_internal(
+ file,
+ SOURCE,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"))
+}
+
+fn snapshot() -> StyleSheet {
+ serde_json::from_str(&export_sheet_internal().unwrap_or_else(|error| panic!("{error}")))
+ .unwrap_or_else(|error| panic!("{error}"))
+}
+
+#[test]
+#[serial]
+fn configured_prefix_is_not_misread_as_a_generated_scope_in_shared_css() {
+ // Given
+ reset_build_state_internal();
+ set_prefix(Some("du-FLa-".into()));
+ code_extract_internal(
+ "x.tsx",
+ SOURCE,
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let incoming = snapshot();
+ // When
+ let imported = import_sheet_internal(incoming);
+ // Then
+ assert_eq!(imported, Ok(()));
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case("du-FLa-")]
+#[case("FHprefix-")]
+#[serial]
+fn configured_prefix_does_not_require_a_fallback_claim_for_a_d9_scope(#[case] prefix: &str) {
+ reset_build_state_internal();
+ set_prefix(Some(prefix.into()));
+ seed_file_map(vec!["x.tsx".into()]);
+ let styles = std::iter::once(ExtractStyleValue::Static(
+ extractor::extract_style::extract_static_style::ExtractStaticStyle::new(
+ "color", "red", 0, None,
+ )
+ .with_naming(css::Naming::Risky),
+ ))
+ .collect();
+ with_style_sheet_mut(|sheet| sheet.update_styles(&styles, "x.tsx", false))
+ .unwrap_or_else(|error| panic!("{error}"));
+ let incoming = snapshot();
+
+ let restored = import_sheet_internal(incoming);
+
+ assert_eq!(restored, Ok(()));
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case("C:/repo/a", "C:\\repo\\a\\src\\x.tsx", "src/x.tsx")]
+#[case("/repo/a", "/repo/a/src/../x.tsx", "x.tsx")]
+#[case("/repo/a", "/repo/shared/x.tsx", "../shared/x.tsx")]
+#[case("C:/repo/a", "D:\\shared\\x.tsx", "d:/shared/x.tsx")]
+#[case("/repo/a", "../shared/x.tsx", "../shared/x.tsx")]
+#[case("//server/share/a", "//server/other/x.tsx", "//server/other/x.tsx")]
+#[serial]
+fn naming_keys_are_relative_when_a_compatible_root_exists(
+ #[case] root: &str,
+ #[case] file: &str,
+ #[case] expected: &str,
+) {
+ // Given
+ reset_build_state_internal();
+ set_naming_root(Some(root.into()), None);
+ // When
+ let key = css::naming_root::key(file);
+ // Then
+ assert_eq!(key, expected);
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn no_root_and_reset_preserve_the_existing_raw_key_contract() {
+ // Given
+ set_naming_root(Some("C:/repo".into()), None);
+ reset_build_state_internal();
+ // When
+ let key = css::naming_root::key("C:\\repo\\x.tsx");
+ // Then
+ assert_eq!(key, "C:\\repo\\x.tsx");
+}
+
+#[test]
+#[serial]
+fn checkout_roots_do_not_change_full_output_when_only_naming_context_differs() {
+ // Given: no D9 IDs, identical source and root-relative file/sheet identities.
+ let mut outputs = Vec::new();
+ // When: two independent builds use the new channel, not rewritten extraction IDs.
+ for root in ["/checkout/one", "/different/place/two"] {
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ set_naming_root(Some(root.into()), None);
+ let file = format!("{root}/src/x.tsx");
+ let output = compile(&file);
+ let sheet = snapshot();
+ assert!(sheet.properties.contains_key(&file));
+ let claims = sheet
+ .names
+ .iter()
+ .filter(|(name, _)| name.starts_with('F') && !name.contains('-'))
+ .collect::>();
+ assert_eq!(claims.len(), 1);
+ assert_eq!(claims[0].1.content, "src/x.tsx");
+ assert_eq!(claims[0].1.descriptor, b"src/x.tsx");
+ outputs.push((output.code(), sheet.create_css(Some(&file), false)));
+ }
+ // Then: every emitted JS and CSS byte matches; delivery keys remain absolute.
+ assert_eq!(outputs[0], outputs[1]);
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn cwd_relative_ids_keep_checkout_stable_names_when_compiler_context_differs() {
+ // Given: Webpack keeps cwd-relative IDs even when compiler.context is elsewhere.
+ let mut outputs = Vec::new();
+ // When
+ for checkout in ["one", "two"] {
+ reset_build_state_internal();
+ set_naming_root(
+ Some(format!("/checkouts/{checkout}")),
+ Some("/runner".into()),
+ );
+ let file = format!("../checkouts/{checkout}/src/x.tsx");
+ let output = compile(&file);
+ outputs.push((
+ output.code(),
+ with_style_sheet(|sheet| {
+ assert!(sheet.properties.contains_key(&file));
+ sheet.create_css(Some(&file), false)
+ }),
+ ));
+ }
+ // Then: resolver/delivery IDs stay raw, but every CSS/JS byte is checkout-stable.
+ assert_eq!(outputs[0], outputs[1]);
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn scope_collision_reports_full_relative_keys_and_both_real_locations() {
+ // Given: a narrow width forces two different sheet keys to the same prefix.
+ reset_build_state_internal();
+ set_naming_root(Some("/checkout".into()), None);
+ let bits = css::content_hash::FingerprintBits::new(1).unwrap_or_else(|| panic!("valid width"));
+ let first_name =
+ css::content_name::ContentName::scope("src/first.tsx").name_with_bits("", bits);
+ let second = (0..64)
+ .map(|index| format!("src/second{index}.tsx"))
+ .find(|key| {
+ css::content_name::ContentName::scope(key).name_with_bits("", bits) == first_name
+ })
+ .unwrap_or_else(|| panic!("narrow scope collision fixture"));
+ let second_file = format!("/checkout/{second}");
+ let source = "import {Box} from '@devup-ui/react';export const x= ;";
+ let styles =
+ extractor::extract_without_source_map(&second_file, source, ExtractOption::default())
+ .unwrap_or_else(|error| panic!("{error}"))
+ .styles;
+ let mut sheet = StyleSheet::default();
+ let first_styles = extractor::extract_without_source_map(
+ "/checkout/src/first.tsx",
+ source,
+ ExtractOption::default(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"))
+ .styles;
+ sheet.names = sheet
+ .preflight_styles_with_bits(&first_styles, ("/checkout/src/first.tsx", false), bits)
+ .unwrap_or_else(|error| panic!("{error}"));
+ // When
+ let error = sheet
+ .preflight_styles_with_bits(&styles, (&second_file, false), bits)
+ .err()
+ .unwrap_or_else(|| panic!("scope collision accepted"))
+ .to_string();
+ // Then
+ for value in [
+ "src/first.tsx",
+ &second,
+ "/checkout/src/first.tsx:1:",
+ &format!("{second_file}:1:"),
+ ] {
+ assert!(error.contains(value), "{error}");
+ }
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn cache_rejects_missing_or_corrupt_scope_claims_before_any_extraction(#[case] corrupt: bool) {
+ // Given
+ reset_build_state_internal();
+ set_naming_root(Some("/checkout".into()), None);
+ let cold = compile("/checkout/src/x.tsx");
+ let cold_css = with_style_sheet(|sheet| sheet.create_css(Some("/checkout/src/x.tsx"), false));
+ let mut incoming = snapshot();
+ let scope = incoming
+ .names
+ .keys()
+ .find(|name| name.starts_with('F'))
+ .unwrap_or_else(|| panic!("missing scope"))
+ .clone();
+ if corrupt {
+ incoming
+ .names
+ .get_mut(&scope)
+ .unwrap_or_else(|| panic!("missing scope"))
+ .descriptor
+ .push(1);
+ } else {
+ incoming.names.remove(&scope);
+ }
+ reset_build_state_internal();
+ set_naming_root(Some("/checkout".into()), None);
+ // When
+ assert_eq!(import_sheet_internal(incoming), Ok(()));
+ // Then: corrupt scope proof adopts no aliases; the names-only root stays configured.
+ assert_eq!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ assert_eq!(compile("/checkout/src/x.tsx").code(), cold.code());
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("/checkout/src/x.tsx"), false)),
+ cold_css
+ );
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/original_counter_tests.rs b/bindings/devup-ui-wasm/src/original_counter_tests.rs
new file mode 100644
index 000000000..689c27acf
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/original_counter_tests.rs
@@ -0,0 +1,229 @@
+use super::*;
+use rstest::rstest;
+use serial_test::serial;
+mod cache;
+mod controls;
+mod environments;
+
+fn setup() {
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ seed_file_map(vec!["a.tsx".into(), "child.tsx".into()]);
+ set_canonical_map(HashMap::from([("child.tsx".into(), "a.tsx".into())]));
+}
+
+fn compile(file: &str, source: &str) -> Output {
+ code_extract_internal(
+ file,
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"))
+}
+
+fn delivered() -> String {
+ with_style_sheet(|sheet| sheet.create_css(Some("a.tsx"), false))
+}
+
+#[rstest]
+#[case("import {Box} from '@devup-ui/react';export const x= ;")]
+#[case("import {css} from '@devup-ui/react';export const x=css({color:'red'});")]
+#[case(
+ "import * as stylex from '@stylexjs/stylex';export const x=stylex.create({red:{color:'red'}});"
+)]
+#[serial]
+fn single_atom_uses_each_original_first_slot(#[case] source: &str) {
+ // Given
+ setup();
+ // When
+ let outputs: Vec<_> = ["child.tsx", "a.tsx"]
+ .into_iter()
+ .map(|file| compile(file, source))
+ .collect();
+ // Then
+ for (output, name) in outputs.iter().zip(["b-a", "a-a"]) {
+ assert!(output.code().contains(name), "{}", output.code());
+ assert!(delivered().contains(&format!(".{name}{{color:red}}")));
+ }
+ css::class_map::with_class_map(|map| {
+ assert_eq!(map.len(), 2);
+ assert!(map.values().all(|slots| slots.len() == 1));
+ });
+ with_style_sheet(|sheet| assert_eq!(sheet.names.len(), 0));
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn collapsed_literals_keep_original_first_slots_when_source_order_reverses(#[case] reverse: bool) {
+ // Given: two numbered originals deliver into the same canonical sheet.
+ setup();
+ let mut inputs = [("a.tsx", "red", "a"), ("child.tsx", "blue", "b")];
+ if reverse {
+ inputs.reverse();
+ }
+ // When: each literal is compiled through the real binding pipeline.
+ for (file, color, owner) in inputs {
+ let source = format!(
+ "import {{Box}} from '@devup-ui/react';export const x=;"
+ );
+ let output = compile(file, &source);
+ // Then: names, canonical delivery, allocator and registry agree.
+ assert!(
+ output
+ .code()
+ .contains(&format!("className=\"{owner}-a {owner}-b\"")),
+ "{}",
+ output.code()
+ );
+ assert!(output.code().contains("df/devup-ui-0.css"));
+ assert_eq!(output.css_file(), Some("df/devup-ui-0.css".into()));
+ let css = delivered();
+ assert!(
+ css.contains(&format!(".{owner}-a{{color:{color}}}")),
+ "{css}"
+ );
+ assert!(
+ css.contains(&format!(".{owner}-b{{padding:16px}}")),
+ "{css}"
+ );
+ }
+ css::class_map::with_class_map(|map| {
+ assert_eq!(map.len(), 2);
+ for id in ["D9-0", "D9-1"] {
+ let slots = map
+ .get(id)
+ .unwrap_or_else(|| panic!("missing {id}: {map:?}"));
+ assert_eq!(slots.len(), 2);
+ assert_eq!(
+ slots
+ .values()
+ .copied()
+ .collect::>(),
+ [0, 1].into_iter().collect()
+ );
+ }
+ });
+ with_style_sheet(|sheet| {
+ assert_eq!(sheet.names.len(), 0);
+ assert_eq!(sheet.properties.len(), 1);
+ });
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn equal_content_survives_deferred_union_when_original_scopes_have_ended() {
+ // Given: equal declarations captured in independent raw-source scopes.
+ setup();
+ let source = "import {Box} from '@devup-ui/react';export const x= ;";
+ let outputs: Vec<_> = ["a.tsx", "child.tsx"]
+ .into_iter()
+ .map(|file| {
+ extract_without_source_map(
+ file,
+ source,
+ ExtractOption {
+ css_dir: "df".into(),
+ ..Default::default()
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error}"))
+ })
+ .collect();
+ let union: FxHashSet<_> = outputs
+ .iter()
+ .flat_map(|output| output.styles.iter().cloned())
+ .collect();
+ // When: the union is emitted only after both extraction scopes have ended.
+ let mut sheet = StyleSheet::default();
+ sheet
+ .update_styles(&union, "a.tsx", false)
+ .unwrap_or_else(|error| panic!("{error}"));
+ // Then: neither owner's reference was discarded and emission uses existing slots.
+ assert_eq!(union.len(), 2);
+ let css = sheet.create_css(Some("a.tsx"), false);
+ for (output, name) in outputs.iter().zip(["a-a", "b-a"]) {
+ assert!(output.code.contains(name), "{}", output.code);
+ assert!(css.contains(&format!(".{name}{{color:red}}")), "{css}");
+ }
+ assert_eq!(sheet.names.len(), 0);
+ css::class_map::with_class_map(|map| {
+ assert_eq!(map.len(), 2);
+ assert!(map.values().all(|slots| slots.len() == 1));
+ });
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn dynamic_site_owner_names_the_class_and_delivered_reset_without_registry_claims() {
+ // Given
+ setup();
+ let source = "import {Box} from '@devup-ui/react';export const x=(tone)=>;";
+ // When
+ let output = compile("child.tsx", source);
+ // Then
+ assert!(output.code().contains("b-a"), "{}", output.code());
+ let css = delivered();
+ let variable = css::Site {
+ file: css::sparse_site::SourceFile::D9(1),
+ at: source
+ .find("tone}/>")
+ .unwrap_or_else(|| panic!("missing assignment")),
+ role: 0,
+ }
+ .variable_name("");
+ assert!(output.code().contains(&variable), "{}", output.code());
+ assert!(
+ css.contains(&format!(".b-a{{{variable}:initial;color:var({variable})}}")),
+ "{css}"
+ );
+ css::class_map::with_class_map(|map| {
+ assert_eq!(map.len(), 1);
+ assert_eq!(map["D9-1"].len(), 1);
+ });
+ with_style_sheet(|sheet| assert_eq!(sheet.names.len(), 0));
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn conditional_typography_constructor_keeps_each_original_counter() {
+ // Given
+ setup();
+ register_theme_internal(
+ serde_json::from_value(serde_json::json!({"typography":{"body":{"fontSize":"14px"}}}))
+ .unwrap_or_else(|error| panic!("{error}")),
+ );
+ let source =
+ "import {Box} from '@devup-ui/react';export const x=;";
+ // When
+ let outputs: Vec<_> = ["a.tsx", "child.tsx"]
+ .into_iter()
+ .map(|file| compile(file, source))
+ .collect();
+ // Then
+ for (output, name) in outputs.iter().zip(["a-a", "b-a"]) {
+ assert!(output.code().contains(name), "{}", output.code());
+ assert!(
+ delivered().contains(&format!(".{name}:hover{{font-size:14px}}")),
+ "{}",
+ delivered()
+ );
+ }
+ css::class_map::with_class_map(|map| {
+ assert_eq!(map.len(), 2);
+ assert!(map.values().all(|slots| slots.len() == 1));
+ });
+ with_style_sheet(|sheet| assert_eq!(sheet.names.len(), 0));
+ reset_build_state_internal();
+ register_theme_internal(sheet::theme::Theme::default());
+}
diff --git a/bindings/devup-ui-wasm/src/original_counter_tests/cache.rs b/bindings/devup-ui-wasm/src/original_counter_tests/cache.rs
new file mode 100644
index 000000000..33dfd613f
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/original_counter_tests/cache.rs
@@ -0,0 +1,57 @@
+use super::*;
+
+#[rstest]
+#[case("")]
+#[case("du-FLa-")]
+#[serial]
+fn cache_restores_original_counters_without_extra_slots_or_dangling_references(
+ #[case] prefix: &str,
+) {
+ // Given
+ setup();
+ set_prefix(Some(prefix.into()));
+ let sources = [
+ (
+ "a.tsx",
+ "import {Box} from '@devup-ui/react';export const x=;",
+ ),
+ (
+ "child.tsx",
+ "import {Box} from '@devup-ui/react';export const x=;",
+ ),
+ ];
+ let before = sources.map(|(file, source)| compile(file, source).code());
+ let sheet_json = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let classes = export_class_map_internal().unwrap_or_else(|error| panic!("{error}"));
+ let files = css::file_map::get_file_map();
+ let canonical_map = css::file_map::get_canonical_map();
+ let ids = css::file_map::get_original_ids();
+ let css_before = delivered();
+ reset_build_state_internal();
+ set_prefix(Some(prefix.into()));
+ set_file_map(files.clone());
+ set_canonical_map(canonical_map.clone());
+ set_class_map(serde_json::from_str(&classes).unwrap_or_else(|error| panic!("{error}")));
+ import_sheet_internal(
+ serde_json::from_str(&sheet_json).unwrap_or_else(|error| panic!("{error}")),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ // When
+ let after: Vec<_> = sources
+ .iter()
+ .rev()
+ .map(|(file, source)| compile(file, source).code())
+ .collect();
+ // Then
+ assert_eq!(after, before.into_iter().rev().collect::>());
+ assert_eq!(delivered(), css_before);
+ assert_eq!(
+ export_class_map_internal().unwrap_or_else(|error| panic!("{error}")),
+ classes
+ );
+ assert_eq!(css::file_map::get_file_map(), files);
+ assert_eq!(css::file_map::get_canonical_map(), canonical_map);
+ assert_eq!(css::file_map::get_original_ids(), ids);
+ with_style_sheet(|sheet| assert_eq!(sheet.names.len(), 0));
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/original_counter_tests/controls.rs b/bindings/devup-ui-wasm/src/original_counter_tests/controls.rs
new file mode 100644
index 000000000..62d20f59a
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/original_counter_tests/controls.rs
@@ -0,0 +1,159 @@
+use super::*;
+use extractor::extract_style::{ExtractStyleProperty, extract_static_style::ExtractStaticStyle};
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn a_numbered_root_never_lends_its_counter_when_the_member_is_unnumbered(#[case] reverse: bool) {
+ // Given
+ reset_build_state_internal();
+ seed_file_map(vec!["a.tsx".into()]);
+ set_canonical_map(HashMap::from([("child.tsx".into(), "a.tsx".into())]));
+ let mut files = ["a.tsx", "child.tsx"];
+ if reverse {
+ files.reverse();
+ }
+ // When
+ for file in files {
+ let output = compile(
+ file,
+ "import {Box} from '@devup-ui/react';export const x= ;",
+ );
+ let name = if file == "a.tsx" {
+ "a-a"
+ } else {
+ "a-OLcolor-vred"
+ };
+ // Then
+ assert!(output.code().contains(name), "{}", output.code());
+ assert!(delivered().contains(&format!(".{name}{{color:red}}")));
+ }
+ css::class_map::with_class_map(|map| {
+ assert_eq!(map.len(), 1);
+ assert_eq!(map["D9-0"].len(), 1);
+ });
+ with_style_sheet(|sheet| assert_eq!(sheet.names.len(), 1));
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false, false)]
+#[case(true, false)]
+#[case(false, true)]
+#[serial]
+fn shared_delivery_stays_content_named_when_single_or_hoisted(
+ #[case] single: bool,
+ #[case] hoisted: bool,
+) {
+ // Given
+ setup();
+ if hoisted {
+ css::atom_hoist::set_atom_hoist(Some(1));
+ css::atom_hoist::restore_atom_plan(Some(std::iter::once("a.tsx".into()).collect()));
+ }
+ let order = if single || hoisted { 255 } else { 0 };
+ let source = format!(
+ "import {{Box,globalCss}} from '@devup-ui/react';globalCss({{body:{{color:'blue'}}}});export const x=;"
+ );
+ // When
+ let output = code_extract_internal(
+ "child.tsx",
+ &source,
+ "@devup-ui/react",
+ "df".into(),
+ single,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ assert!(output.code().contains("OLcolor-vred"), "{}", output.code());
+ let css = with_style_sheet(|sheet| sheet.create_css(None, false));
+ assert!(css.contains("color:red"), "{css}");
+ assert!(css.contains(concat!("body{", "color:blue", "}")), "{css}");
+ css::class_map::with_class_map(|map| assert_eq!(map.len(), 0));
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn diagnostics_and_naming_transitions_preserve_semantic_identity() {
+ // Given
+ setup();
+ let source = "import {Box} from '@devup-ui/react';export const x= ;";
+ let mut styles: Vec<_> = ["a.tsx", "child.tsx"]
+ .into_iter()
+ .map(|file| {
+ extract_without_source_map(file, source, ExtractOption::default())
+ .unwrap_or_else(|error| panic!("{error}"))
+ .styles
+ .into_iter()
+ .next()
+ .unwrap_or_else(|| panic!("missing style"))
+ })
+ .collect();
+ // When
+ for style in &mut styles {
+ match style {
+ ExtractStyleValue::Static(style) => {
+ style.origin = css::style_origin::Origin::from_location(
+ css::style_origin::RealLocation::ModuleExport {
+ file: "unrelated-diagnostic.css.ts".into(),
+ binding: None,
+ },
+ );
+ }
+ other => panic!("expected literal: {other:?}"),
+ }
+ }
+ // Then
+ for (style, name) in styles.iter().zip(["a-a", "b-a"]) {
+ assert_eq!(
+ style
+ .extract(Some("a.tsx"))
+ .unwrap_or_else(|| panic!("missing class"))
+ .to_string(),
+ name
+ );
+ }
+ assert_ne!(styles[0], styles[1]);
+ for style in &mut styles {
+ style.join_naming(css::Naming::Risky);
+ }
+ assert_eq!(styles[0], styles[1]);
+ assert_eq!(styles.iter().cloned().collect::>().len(), 1);
+ let literal = ExtractStaticStyle::new("color", "red", 0, None);
+ assert_eq!(literal.extract(Some("child.tsx")).to_string(), "b-a");
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn counter_only_atoms_do_not_claim_an_unused_unnumbered_canonical_scope() {
+ // Given
+ reset_build_state_internal();
+ seed_file_map(vec!["child.tsx".into()]);
+ set_canonical_map(HashMap::from([(
+ "child.tsx".into(),
+ "unseeded-root.tsx".into(),
+ )]));
+ // When
+ let output = compile(
+ "child.tsx",
+ "import {Box} from '@devup-ui/react';export const x= ;",
+ );
+ // Then
+ assert!(output.code().contains("a-a"));
+ with_style_sheet(|sheet| {
+ assert_eq!(sheet.names.len(), 0);
+ assert!(
+ sheet
+ .create_css(Some("unseeded-root.tsx"), false)
+ .contains(concat!(".a-a{", "color:red", "}"))
+ );
+ });
+ css::class_map::with_class_map(|map| assert_eq!(map["D9-0"].len(), 1));
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/original_counter_tests/environments.rs b/bindings/devup-ui-wasm/src/original_counter_tests/environments.rs
new file mode 100644
index 000000000..87c5a7f59
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/original_counter_tests/environments.rs
@@ -0,0 +1,255 @@
+use super::*;
+
+#[test]
+#[serial]
+fn imported_outer_render_condition_keeps_css_and_original_counter_classes() {
+ // Given: D9-1 owns both literal atoms despite canonical delivery through D9-0.
+ let source = "import {Box,css} from '@devup-ui/react';import {enabled} from './tokens';export const witness=css({color:enabled?'red':'red'});export const x=;export const y=enabled&& ;";
+ let mut witnesses = Vec::new();
+ for enabled in [Some(true), Some(false), None] {
+ setup();
+ let calls = std::rc::Rc::new(std::cell::Cell::new(0));
+ let provider_calls = std::rc::Rc::clone(&calls);
+ let resolver = move |specifier: &str, importer: &str| {
+ assert_eq!(specifier, "./tokens");
+ assert_eq!(importer, "child.tsx");
+ provider_calls.set(provider_calls.get() + 1);
+ enabled.map(|value| ResolvedModule {
+ path: "tokens.ts".into(),
+ code: format!("export const enabled={value};"),
+ })
+ };
+ // When: the same source is extracted with each distinct provider outcome.
+ let output = code_extract_with_modules_internal(
+ "child.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ &resolver,
+ )
+ .unwrap_or_else(|error| panic!("enabled={enabled:?}: {error}"));
+ // Then: style resolution changes, but the runtime render guard and atoms do not.
+ assert!(calls.get() > 0, "provider was not called: {enabled:?}");
+ let code = output.code();
+ let controller = code
+ .split("export const y = ")
+ .nth(1)
+ .and_then(|expression| expression.split_once("&&"))
+ .map_or_else(
+ || panic!("missing render condition: {code}"),
+ |(condition, _)| condition.trim(),
+ );
+ assert_eq!(controller, "enabled", "{code}");
+ assert!(
+ code.lines().any(|line| line.starts_with("import ")
+ && line.contains("enabled")
+ && line.contains("./tokens")),
+ "runtime import was removed: {code}"
+ );
+ let witness = code
+ .split("export const witness = ")
+ .nth(1)
+ .and_then(|expression| expression.split("export const x = ").next())
+ .unwrap_or_else(|| panic!("missing style-resolution witness: {code}"));
+ assert_eq!(witness.contains("enabled"), enabled.is_none(), "{code}");
+ let classes: Vec<_> = code
+ .split("className=")
+ .skip(1)
+ .map(|field| {
+ field
+ .strip_prefix('"')
+ .and_then(|literal| literal.split_once('"'))
+ .map_or_else(
+ || panic!("nonliteral className: {code}"),
+ |(name, _)| name.to_owned(),
+ )
+ })
+ .collect();
+ assert_eq!(classes, ["b-a", "b-b"], "{code}");
+ let css = delivered();
+ assert!(css.contains(".b-a{padding:16px}"), "{css}");
+ assert!(css.contains(".b-b{height:12px}"), "{css}");
+ assert!(
+ css.contains(concat!(".a-RLcolor-vred{", "color:red", "}")),
+ "{css}"
+ );
+ assert_eq!(output.css(), Some(css.clone()));
+ assert_eq!(output.css_file(), Some("df/devup-ui-0.css".into()));
+ css::class_map::with_class_map(|map| {
+ assert_eq!(map.len(), 1);
+ let slots = map
+ .get("D9-1")
+ .unwrap_or_else(|| panic!("missing original owner: {map:?}"));
+ assert_eq!(slots.len(), 2);
+ assert_eq!(
+ slots
+ .values()
+ .copied()
+ .collect::>(),
+ [0, 1].into_iter().collect()
+ );
+ });
+ with_style_sheet(|sheet| {
+ assert_eq!(sheet.names.len(), 1);
+ assert!(sheet.names.contains_key("a-RLcolor-vred"));
+ });
+ println!(
+ "enabled={enabled:?}; classes={classes:?}; full_css={css:?}; controller={controller}"
+ );
+ witnesses.push((css, classes));
+ reset_build_state_internal();
+ }
+ for pair in witnesses.windows(2) {
+ assert_eq!(pair[0], pair[1]);
+ }
+}
+
+#[rstest]
+#[case(
+ "export const tone='red';export const enabled=true;export const styles={color:'green',m:2};"
+)]
+#[case(
+ "export const tone='blue';export const enabled=false;export const styles={bg:'yellow',w:'8px'};"
+)]
+#[serial]
+fn risky_import_values_presence_and_shape_leave_local_padding_slot_unchanged(
+ #[case] module: &'static str,
+) {
+ // Given
+ setup();
+ let resolver = |specifier: &str, _: &str| {
+ (specifier == "./tokens").then(|| ResolvedModule {
+ path: "tokens.ts".into(),
+ code: module.into(),
+ })
+ };
+ let source = "import {Box} from '@devup-ui/react';import {tone,enabled,styles} from './tokens';export const x=;export const y=enabled&& ;";
+ // When
+ let output = code_extract_with_modules_internal(
+ "child.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ &resolver,
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ assert!(output.code().contains("b-a"), "{}", output.code());
+ assert!(
+ delivered().contains(".b-a{padding:16px}"),
+ "{}",
+ delivered()
+ );
+ css::class_map::with_class_map(|map| {
+ assert_eq!(map.len(), 1);
+ assert_eq!(map["D9-1"].len(), 2);
+ });
+ with_style_sheet(|sheet| {
+ assert!(
+ sheet
+ .names
+ .keys()
+ .any(|name| name.contains("-RL") || name.contains("-RH"))
+ );
+ assert!(!sheet.names.contains_key("b-a"));
+ });
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn nested_stylesheet_scope_restores_consumer_owner_without_copying_producer_rules(
+ #[case] producer_first: bool,
+) {
+ // Given
+ reset_build_state_internal();
+ seed_file_map(vec!["a.css.ts".into(), "child.css.ts".into()]);
+ set_canonical_map(HashMap::from([("child.css.ts".into(), "a.css.ts".into())]));
+ let producer = "import {style} from '@devup-ui/react';export const red=style({color:'red'});";
+ let consumer = "import {style} from '@devup-ui/react';import {red} from './a.css';export const selected=style({selectors:{[`.${red} &`]:{padding:'4px'}}});export const local=style({margin:'8px'});";
+ let resolver = |specifier: &str, _: &str| {
+ (specifier == "./a.css").then(|| ResolvedModule {
+ path: "a.css.ts".into(),
+ code: producer.into(),
+ })
+ };
+ let run_producer = || {
+ code_extract_with_modules_internal(
+ "a.css.ts",
+ producer,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ &resolver,
+ )
+ .unwrap_or_else(|error| panic!("{error}"))
+ };
+ if producer_first {
+ run_producer();
+ }
+ // When
+ let output = code_extract_with_modules_internal(
+ "child.css.ts",
+ consumer,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ &resolver,
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let before_producer = with_style_sheet(|sheet| sheet.create_css(Some("a.css.ts"), false));
+ if !producer_first {
+ run_producer();
+ }
+ // Then
+ let code = output.code();
+ let selected = code
+ .lines()
+ .find_map(|line| {
+ line.strip_prefix("export const selected = \"")
+ .and_then(|line| line.strip_suffix("\";"))
+ })
+ .unwrap_or_else(|| panic!("missing selected export: {code}"));
+ assert!(selected.starts_with("a-RH"), "{code}");
+ assert!(code.contains("a-RLmargin-v8px"), "{code}");
+ if !producer_first {
+ assert!(!before_producer.contains("color:red"), "{before_producer}");
+ }
+ let css = with_style_sheet(|sheet| sheet.create_css(Some("a.css.ts"), false));
+ assert!(css.contains(concat!(".a-a{", "color:red", "}")), "{css}");
+ assert!(
+ css.contains(&format!(".a-a .{selected}{{padding:4px}}")),
+ "{css}"
+ );
+ assert!(
+ css.contains(concat!(".a-RLmargin-v8px{", "margin:8px", "}")),
+ "{css}"
+ );
+ assert_eq!(css.matches("color:red").count(), 1);
+ css::class_map::with_class_map(|map| {
+ assert_eq!(map.len(), 1);
+ assert_eq!(map["D9-0"].len(), 1);
+ });
+ with_style_sheet(|sheet| {
+ assert_eq!(sheet.names.len(), 2);
+ assert!(sheet.names.contains_key(selected));
+ assert!(sheet.names.contains_key("a-RLmargin-v8px"));
+ });
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/prefix_cache_tests.rs b/bindings/devup-ui-wasm/src/prefix_cache_tests.rs
new file mode 100644
index 000000000..ffa6dbaa6
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/prefix_cache_tests.rs
@@ -0,0 +1,215 @@
+use super::*;
+use rstest::rstest;
+use serial_test::serial;
+
+#[rstest]
+#[case("OL")]
+#[case("RL")]
+#[case("KL")]
+#[case("OH")]
+#[serial]
+fn seeded_counter_cache_round_trips_when_prefix_resembles_content(#[case] prefix: &str) {
+ // Given: actual safe-own counter output under an unrestricted configured prefix.
+ reset_build_state_internal();
+ set_prefix(Some(prefix.into()));
+ seed_file_map(vec!["x.tsx".into()]);
+ let output = code_extract_internal(
+ "x.tsx",
+ "import {Box} from '@devup-ui/react';export const x= ;",
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let incoming: StyleSheet =
+ serde_json::from_str(&export_sheet_internal().unwrap_or_else(|error| panic!("{error}")))
+ .unwrap_or_else(|error| panic!("{error}"));
+ let class = format!("{prefix}a-a");
+ assert!(output.code().contains(&class));
+ assert!(!incoming.names.contains_key(&class));
+ let before = incoming.create_css(Some("x.tsx"), false);
+ reset_build_state_internal();
+ set_prefix(Some(prefix.into()));
+ // When
+ let imported = import_sheet_internal(incoming);
+ // Then: cached CSS remains usable instead of being mistaken for an unclaimed atom.
+ assert_eq!(imported, Ok(()));
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("x.tsx"), false)),
+ before
+ );
+ reset_build_state_internal();
+}
+
+fn compiled_content(single: bool) -> Output {
+ code_extract_internal(
+ "x.tsx",
+ "import {Box,keyframes} from '@devup-ui/react';export const k=keyframes({from:{opacity:0},to:{opacity:1}});export const x= ;",
+ "@devup-ui/react",
+ "df".into(),
+ single,
+ false,
+ false,
+ HashMap::new(),
+ ).unwrap_or_else(|error| panic!("{error}"))
+}
+
+fn exported_content(prefix: &str, single: bool) -> StyleSheet {
+ reset_build_state_internal();
+ set_prefix(Some(prefix.into()));
+ compiled_content(single);
+ serde_json::from_str(&export_sheet_internal().unwrap_or_else(|error| panic!("{error}")))
+ .unwrap_or_else(|error| panic!("{error}"))
+}
+
+#[rstest]
+#[case("OL")]
+#[case("RL")]
+#[case("KL")]
+#[case("OH")]
+#[serial]
+fn generated_cache_round_trips_when_prefix_resembles_content(
+ #[case] prefix: &str,
+ #[values(false, true)] single: bool,
+) {
+ // Given: genuine short/hashed content atoms and global keyframes, not legacy counters.
+ let incoming = exported_content(prefix, single);
+ let namespace = if single {
+ prefix.into()
+ } else {
+ format!("{prefix}FLx_ptsx-")
+ };
+ assert!(
+ incoming
+ .names
+ .contains_key(&format!("{namespace}OLcolor-vred"))
+ );
+ assert!(
+ incoming
+ .names
+ .keys()
+ .any(|name| name.starts_with(&format!("{namespace}OH")))
+ );
+ assert_eq!(
+ incoming
+ .keyframes
+ .values()
+ .map(BTreeMap::len)
+ .sum::(),
+ 1
+ );
+ let before = incoming.create_css(None, false);
+ let claims = incoming.names.clone();
+ reset_build_state_internal();
+ set_prefix(Some(prefix.into()));
+ // When
+ let imported = import_sheet_internal(incoming);
+ // Then: the real imported CSS and exact claims survive the prefix boundary.
+ assert_eq!(imported, Ok(()));
+ with_style_sheet(|sheet| {
+ assert_eq!(sheet.create_css(None, false), before);
+ assert_eq!(sheet.names, claims);
+ });
+ reset_build_state_internal();
+}
+
+enum ClaimDamage {
+ MissingAtom,
+ MissingScope,
+ CorruptScope,
+}
+
+#[rstest]
+#[case("OL")]
+#[case("RL")]
+#[case("KL")]
+#[case("OH")]
+#[serial]
+fn genuine_generated_cache_rejects_damaged_claims_when_prefix_resembles_content(
+ #[case] prefix: &str,
+ #[values(
+ ClaimDamage::MissingAtom,
+ ClaimDamage::MissingScope,
+ ClaimDamage::CorruptScope
+ )]
+ damage: ClaimDamage,
+) {
+ // Given: only one required exact claim is removed or corrupted in real scoped output.
+ let mut incoming = exported_content(prefix, false);
+ let cold_css = incoming.create_css(Some("x.tsx"), false);
+ let cold_code = compiled_content(false).code();
+ let scope = format!("{prefix}FLx_ptsx");
+ match damage {
+ ClaimDamage::MissingAtom => {
+ incoming
+ .names
+ .remove(&format!("{scope}-OLcolor-vred"))
+ .unwrap_or_else(|| panic!("missing atom claim fixture"));
+ }
+ ClaimDamage::MissingScope => {
+ incoming
+ .names
+ .remove(&scope)
+ .unwrap_or_else(|| panic!("missing scope claim fixture"));
+ }
+ ClaimDamage::CorruptScope => {
+ incoming
+ .names
+ .get_mut(&scope)
+ .unwrap_or_else(|| panic!("missing scope claim fixture"))
+ .descriptor
+ .push(1);
+ }
+ }
+ reset_build_state_internal();
+ set_prefix(Some(prefix.into()));
+ // When
+ let imported = import_sheet_internal(incoming);
+ // Then: recognizing a user prefix must not adopt any damaged generated namespace.
+ assert_eq!(imported, Ok(()));
+ assert_eq!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ assert_eq!(compiled_content(false).code(), cold_code);
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("x.tsx"), false)),
+ cold_css
+ );
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case("OL")]
+#[case("RL")]
+#[case("KL")]
+#[case("OH")]
+#[serial]
+fn previous_global_namespace_is_rejected_in_per_file_cache_with_content_shaped_prefix(
+ #[case] prefix: &str,
+) {
+ // Given: valid global atoms and claims placed in an old nonzero-order per-file bucket.
+ let mut incoming = exported_content(prefix, true);
+ let cold = exported_content(prefix, false);
+ let cold_css = cold.create_css(Some("x.tsx"), false);
+ let cold_code = compiled_content(false).code();
+ let orders = incoming
+ .properties
+ .remove("")
+ .unwrap_or_else(|| panic!("missing single CSS bucket fixture"));
+ incoming.properties.insert("x.tsx".into(), orders);
+ reset_build_state_internal();
+ set_prefix(Some(prefix.into()));
+ // When
+ let imported = import_sheet_internal(incoming);
+ // Then: stripping the configured prefix cannot allow previous cross-sheet aliases back in.
+ assert_eq!(imported, Ok(()));
+ assert_eq!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ assert_eq!(with_style_sheet(|sheet| sheet.properties.len()), 0);
+ assert_eq!(compiled_content(false).code(), cold_code);
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("x.tsx"), false)),
+ cold_css
+ );
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/prospective_output.rs b/bindings/devup-ui-wasm/src/prospective_output.rs
new file mode 100644
index 000000000..04fa2aa42
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/prospective_output.rs
@@ -0,0 +1,50 @@
+//! Pure Output materialization from the kernel's borrowed prospective sheet.
+use crate::Output;
+use sheet::{StyleSheet, counter_kernel::UpdateEffects};
+
+/// Owned compilation metadata; contains no naming authority.
+pub struct OutputMetadata {
+ pub code: String,
+ pub map: Option,
+ pub css_file: Option,
+ pub dependencies: Vec,
+}
+/// Actual kernel finish inputs; no second update is performed.
+pub struct ProspectiveSheet<'a> {
+ pub sheet: &'a StyleSheet,
+ pub effects: UpdateEffects,
+}
+/// Existing raw-source routing options used by Output rendering.
+pub struct OutputRoute<'a> {
+ pub raw_source: &'a str,
+ pub single_css: bool,
+ pub import_main_css: bool,
+}
+impl Output {
+ /// Render actual prepared effects without allocating names or cleaning twice.
+ #[must_use]
+ pub fn from_prospective(
+ metadata: OutputMetadata,
+ prepared: ProspectiveSheet<'_>,
+ route: OutputRoute<'_>,
+ ) -> Self {
+ let global = route.single_css || css::file_map::is_global(route.raw_source);
+ let canonical = css::file_map::canonical(route.raw_source);
+ let effects = prepared.effects;
+ Self {
+ code: metadata.code,
+ map: metadata.map,
+ css_file: metadata.css_file,
+ dependencies: metadata.dependencies,
+ updated_base_style: effects.updated_base_style || effects.default_collected,
+ css: if effects.collected || effects.default_collected {
+ Some(prepared.sheet.create_css(
+ if global { None } else { Some(&canonical) },
+ route.import_main_css,
+ ))
+ } else {
+ None
+ },
+ }
+ }
+}
diff --git a/bindings/devup-ui-wasm/src/prospective_output_tests.rs b/bindings/devup-ui-wasm/src/prospective_output_tests.rs
new file mode 100644
index 000000000..02320c58e
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/prospective_output_tests.rs
@@ -0,0 +1,223 @@
+use super::cache6_test_support::*;
+
+#[test]
+#[serial]
+fn prospective_output_updates_base_when_first_order_zero_static_finish_has_no_cleanup() {
+ // Given
+ let _guard = Guard::new();
+ let items = with_original(
+ FixtureRequest {
+ filename: "first",
+ source: "abcdef",
+ },
+ || {
+ let mut declaration = ExtractStaticStyle::new("color", "red", 0, None);
+ declaration.style_order = Some(0);
+ FxHashSet::from_iter([ExtractStyleValue::Static(declaration)])
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ // When
+ let output = with_style_sheet_mut(|sheet| {
+ CounterSheet::new(sheet).with_attempt(|attempt| {
+ attempt
+ .prepare(
+ &items,
+ UpdateRequest {
+ raw_source: "first",
+ single_css: false,
+ },
+ )?
+ .finish(|sheet, effects| {
+ // Then: effects are produced by the real prepare/finish, not fabricated.
+ assert!(effects.updated_base_style);
+ assert!(!effects.default_collected);
+ Ok::<_, String>(Output::from_prospective(
+ metadata(),
+ ProspectiveSheet { sheet, effects },
+ OutputRoute {
+ raw_source: "first",
+ single_css: false,
+ import_main_css: false,
+ },
+ ))
+ })
+ })
+ })
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ assert!(output.updated_base_style());
+}
+
+#[rstest]
+#[case(false, false)]
+#[case(false, true)]
+#[case(true, false)]
+#[case(true, true)]
+#[serial]
+fn prospective_output_preserves_metadata_and_routing_when_real_kernel_inserts(
+ #[case] global: bool,
+ #[case] import_main: bool,
+) {
+ // Given
+ let _guard = Guard::new();
+ import_canonical_map_internal(HashMap::from([("raw".into(), "root".into())]));
+ let items = ir("raw", 0);
+ // When
+ let output = update(
+ &items,
+ OutputRoute {
+ raw_source: "raw",
+ single_css: global,
+ import_main_css: import_main,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ // Then
+ assert_eq!(output.code(), metadata().code);
+ assert_eq!(output.map(), Some("fixture-map".into()));
+ assert_eq!(output.css_file(), Some("fixture.css".into()));
+ assert_eq!(output.dependencies(), vec!["tokens.ts".to_string()]);
+ assert_eq!(
+ output.css(),
+ Some(with_style_sheet(|sheet| sheet.create_css(
+ if global { None } else { Some("root") },
+ import_main
+ )))
+ );
+ assert!(
+ output
+ .css()
+ .unwrap_or_else(|| panic!("css"))
+ .contains("color:red")
+ );
+}
+
+#[test]
+#[serial]
+fn prospective_output_has_no_css_when_real_update_inserts_nothing() {
+ // Given
+ let _guard = Guard::new();
+ let items = ir("a", 0);
+ update(
+ &items,
+ OutputRoute {
+ raw_source: "a",
+ single_css: false,
+ import_main_css: false,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ let maps_before = maps();
+ // When
+ let output = update(
+ &items,
+ OutputRoute {
+ raw_source: "a",
+ single_css: false,
+ import_main_css: false,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ // Then
+ assert_eq!(output.css(), None);
+ assert!(!output.updated_base_style());
+ assert_eq!(maps(), maps_before);
+}
+
+#[rstest]
+#[case(false)]
+#[case(true)]
+#[serial]
+fn cleanup_only_output_updates_base_when_real_guard_removes_global_or_at_consumer(
+ #[case] at: bool,
+) {
+ // Given
+ let _guard = Guard::new();
+ let selector = if at {
+ css::style_selector::StyleSelector::At {
+ kind: css::style_selector::AtRuleKind::Media,
+ query: "print".into(),
+ selector: Some("body".into()),
+ outer: vec![],
+ file: Some("raw".into()),
+ }
+ } else {
+ css::style_selector::StyleSelector::Global("body".into(), "raw".into())
+ };
+ let items = with_original(
+ FixtureRequest {
+ filename: "raw",
+ source: "abcdef",
+ },
+ || {
+ FxHashSet::from_iter([ExtractStyleValue::Dynamic(ExtractDynamicStyle::new(
+ "color",
+ 0,
+ "tone",
+ Some(selector),
+ ))])
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ update(
+ &items,
+ OutputRoute {
+ raw_source: "raw",
+ single_css: false,
+ import_main_css: false,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ let before = maps();
+ // When
+ let output = update(
+ &Default::default(),
+ OutputRoute {
+ raw_source: "raw",
+ single_css: false,
+ import_main_css: false,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ // Then
+ assert!(output.updated_base_style());
+ assert!(output.css().is_some());
+ assert_eq!(maps(), before);
+ with_style_sheet(|sheet| {
+ let survivors = &sheet.properties["raw"][&255][&0];
+ assert_eq!(survivors.len(), 1);
+ assert!(
+ survivors
+ .iter()
+ .all(|record| record.owner_reset && record.selector.is_none())
+ );
+ });
+}
+
+#[test]
+#[serial]
+fn canonical_global_route_renders_shared_css_when_single_css_option_is_false() {
+ // Given
+ let _guard = Guard::new();
+ import_canonical_map_internal(HashMap::from([(
+ "raw".into(),
+ css::file_map::GLOBAL_BUCKET.into(),
+ )]));
+ let items = ir("raw", 0);
+ // When
+ let output = update(
+ &items,
+ OutputRoute {
+ raw_source: "raw",
+ single_css: false,
+ import_main_css: true,
+ },
+ )
+ .unwrap_or_else(|error| panic!("{error:?}"));
+ // Then
+ assert_eq!(
+ output.css(),
+ Some(with_style_sheet(|sheet| sheet.create_css(None, true)))
+ );
+ assert!(with_style_sheet(|sheet| sheet.properties.contains_key("")));
+}
diff --git a/bindings/devup-ui-wasm/src/scoped_cache_tests.rs b/bindings/devup-ui-wasm/src/scoped_cache_tests.rs
new file mode 100644
index 000000000..db402fa9b
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/scoped_cache_tests.rs
@@ -0,0 +1,278 @@
+use super::*;
+use rstest::rstest;
+use serial_test::serial;
+
+#[test]
+#[serial]
+fn previous_per_file_content_names_cannot_restore_cross_sheet_aliases() {
+ reset_build_state_internal();
+ let source = "import {Box} from '@devup-ui/react';export const x= ;";
+ let cold = code_extract_internal(
+ "x.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let cold_css = with_style_sheet(|sheet| sheet.create_css(Some("x.tsx"), false));
+ let exported = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let mut previous: StyleSheet =
+ serde_json::from_str(&exported).unwrap_or_else(|error| panic!("{error}"));
+ let prefix = "FLx_ptsx-";
+ for levels in previous
+ .properties
+ .get_mut("x.tsx")
+ .unwrap_or_else(|| panic!("per-file fixture"))
+ .values_mut()
+ {
+ for properties in levels.values_mut() {
+ *properties = properties
+ .iter()
+ .cloned()
+ .map(|mut property| {
+ property.class_name = property
+ .class_name
+ .strip_prefix(prefix)
+ .unwrap_or_else(|| panic!("scoped fixture"))
+ .to_string();
+ property
+ })
+ .collect();
+ }
+ }
+ previous.names = previous
+ .names
+ .into_iter()
+ .filter(|(name, _)| name != "FLx_ptsx")
+ .map(|(name, claim)| {
+ (
+ name.strip_prefix(prefix).unwrap_or(&name).to_string(),
+ claim,
+ )
+ })
+ .collect();
+ reset_build_state_internal();
+
+ let imported = import_sheet_internal(previous);
+
+ assert_eq!(imported, Ok(()));
+ assert_eq!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ let fresh = code_extract_internal(
+ "x.tsx",
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ assert_eq!(fresh.code(), cold.code());
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("x.tsx"), false)),
+ cold_css
+ );
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case("", 255)]
+#[case("du-FLa-", 1)]
+#[case("FHprefix-", 255)]
+#[serial]
+fn previous_hashed_per_file_names_are_rejected_even_with_exact_atom_claims(
+ #[case] prefix: &str,
+ #[case] order: u8,
+) {
+ // Given: real exported long-content atoms, stripped of only their sheet namespace.
+ reset_build_state_internal();
+ set_prefix(Some(prefix.into()));
+ let source = format!(
+ "import {{Box}} from '@devup-ui/react';export const x=;"
+ );
+ let cold = code_extract_internal(
+ "x.tsx",
+ &source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let cold_css = with_style_sheet(|sheet| sheet.create_css(Some("x.tsx"), false));
+ let mut incoming: StyleSheet =
+ serde_json::from_str(&export_sheet_internal().unwrap_or_else(|error| panic!("{error}")))
+ .unwrap_or_else(|error| panic!("{error}"));
+ let scope = format!("{prefix}FLx_ptsx");
+ let scoped_prefix = format!("{scope}-");
+ for properties in incoming
+ .properties
+ .get_mut("x.tsx")
+ .unwrap_or_else(|| panic!("missing per-file bucket fixture"))
+ .get_mut(&order)
+ .unwrap_or_else(|| panic!("missing style order fixture"))
+ .values_mut()
+ {
+ *properties = properties
+ .iter()
+ .cloned()
+ .map(|mut property| {
+ property.class_name = format!(
+ "{prefix}{}",
+ property
+ .class_name
+ .strip_prefix(&scoped_prefix)
+ .unwrap_or_else(|| panic!("missing scoped name fixture"))
+ );
+ property
+ })
+ .collect();
+ }
+ incoming.names = incoming
+ .names
+ .into_iter()
+ .filter(|(name, _)| name != &scope)
+ .map(|(name, claim)| match name.strip_prefix(&scoped_prefix) {
+ Some(atom) => (format!("{prefix}{atom}"), claim),
+ None => (name, claim),
+ })
+ .collect();
+ reset_build_state_internal();
+ set_prefix(Some(prefix.into()));
+ // When
+ assert_eq!(import_sheet_internal(incoming), Ok(()));
+ // Then: an exact atom claim cannot admit an old unscoped namespace.
+ assert_eq!(with_style_sheet(|sheet| sheet.names.len()), 0);
+ assert_eq!(
+ code_extract_internal(
+ "x.tsx",
+ &source,
+ "@devup-ui/react",
+ "df".into(),
+ false,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"))
+ .code(),
+ cold.code()
+ );
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("x.tsx"), false)),
+ cold_css
+ );
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false, false, 0)]
+#[case(true, false, 255)]
+#[case(false, true, 255)]
+#[case(false, false, 255)]
+#[serial]
+fn actual_exported_shared_and_scoped_names_round_trip(
+ #[case] single: bool,
+ #[case] hoist: bool,
+ #[case] order: u8,
+) {
+ // Given: actual global base, single CSS, hoisted, or current per-file output.
+ reset_build_state_internal();
+ set_prefix(Some("du-FLa-".into()));
+ if hoist {
+ set_atom_hoist(Some(2));
+ import_file_routes_internal(HashMap::from([(
+ "x.tsx".into(),
+ std::collections::HashSet::from([0, 1]),
+ )]));
+ }
+ let source = format!(
+ "import {{Box,keyframes}} from '@devup-ui/react';export const k=keyframes({{from:{{opacity:0}},to:{{opacity:1}}}});export const x=;"
+ );
+ code_extract_internal(
+ "x.tsx",
+ &source,
+ "@devup-ui/react",
+ "df".into(),
+ single,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ let exported = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let incoming: StyleSheet =
+ serde_json::from_str(&exported).unwrap_or_else(|error| panic!("{error}"));
+ let property = incoming
+ .properties
+ .values()
+ .flat_map(|orders| orders.values())
+ .flat_map(|levels| levels.values())
+ .flatten()
+ .next()
+ .unwrap_or_else(|| panic!("missing exported property fixture"));
+ let expected = if order == 0 {
+ "du-FLa-OLcolor-vred-o0"
+ } else if single || hoist {
+ "du-FLa-OLcolor-vred"
+ } else {
+ "du-FLa-FLx_ptsx-OLcolor-vred"
+ };
+ assert_eq!(property.class_name, expected);
+ assert_eq!(property.hoisted, hoist);
+ assert_eq!(
+ incoming
+ .keyframes
+ .values()
+ .map(BTreeMap::len)
+ .sum::(),
+ 1
+ );
+ assert!(
+ incoming
+ .keyframes
+ .values()
+ .flat_map(|frames| frames.keys())
+ .all(|name| name.starts_with("du-FLa-K"))
+ );
+ let before = incoming.create_css(None, false);
+ reset_build_state_internal();
+ set_prefix(Some("du-FLa-".into()));
+ // When: import runs without relying on the producer's active hoist configuration.
+ let result = import_sheet_internal(incoming);
+ // Then
+ assert_eq!(result, Ok(()));
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(None, false)),
+ before
+ );
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case("a-a")]
+#[case("Rcolor-vred")]
+#[case("manual-card")]
+#[serial]
+fn generationless_serialized_counter_and_manual_names_are_cold(#[case] name: &str) {
+ // Given: legacy JSON with a real per-file declaration, no generation marker or names registry.
+ reset_build_state_internal();
+ let json = serde_json::json!({
+ "properties": {"x.tsx": {"255": {"0": [{"c": name, "p": "color", "v": "red", "s": null}]}}}
+ });
+ let incoming: StyleSheet =
+ serde_json::from_value(json).unwrap_or_else(|error| panic!("{error}"));
+ // When
+ let result = import_sheet_internal(incoming);
+ // Then: serialized legacy names are never silently promoted to current state.
+ assert_eq!(result, Ok(()));
+ assert_eq!(with_style_sheet(|sheet| sheet.properties.len()), 0);
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/scoped_cascade_tests.rs b/bindings/devup-ui-wasm/src/scoped_cascade_tests.rs
new file mode 100644
index 000000000..911abb60f
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/scoped_cascade_tests.rs
@@ -0,0 +1,211 @@
+use super::*;
+use rstest::rstest;
+use serial_test::serial;
+
+fn compile(file: &str, source: &str, single: bool) -> Output {
+ code_extract_internal(
+ file,
+ source,
+ "@devup-ui/react",
+ "df".into(),
+ single,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"))
+}
+
+#[rstest]
+#[case(false, false)]
+#[case(false, true)]
+#[case(true, false)]
+#[case(true, true)]
+#[serial]
+fn independent_sheets_cannot_override_responsive_atoms_when_base_content_matches(
+ #[case] seeded: bool,
+ #[case] reverse: bool,
+) {
+ // Given: two delivered sheets share base content, but only A has a desktop override.
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ if seeded {
+ seed_file_map(vec!["a.tsx".into(), "b.tsx".into()]);
+ }
+ let inputs = [
+ (
+ "a.tsx",
+ "import {Box} from '@devup-ui/react'; export const x=;",
+ ),
+ (
+ "b.tsx",
+ "import {Box} from '@devup-ui/react'; export const x= ;",
+ ),
+ ];
+ let order = if reverse { [1, 0] } else { [0, 1] };
+ // When: extraction arrives in either order without changing placement.
+ let mut outputs = BTreeMap::new();
+ for index in order {
+ outputs.insert(
+ inputs[index].0,
+ compile(inputs[index].0, inputs[index].1, false),
+ );
+ }
+ // Then: B's base selectors cannot match A's classes; each rule remains in its exact bucket.
+ with_style_sheet(|sheet| {
+ let base = |file: &str| {
+ sheet.properties[file][&255][&0]
+ .iter()
+ .find(|property| property.property == "flex-direction")
+ .unwrap_or_else(|| panic!("missing base rule"))
+ .class_name
+ .clone()
+ };
+ let a = base("a.tsx");
+ let b = base("b.tsx");
+ assert_ne!(a, b);
+ assert!(outputs["a.tsx"].code().contains(&a));
+ assert!(outputs["b.tsx"].code().contains(&b));
+ assert!(
+ !sheet
+ .create_css(Some("b.tsx"), false)
+ .contains(&format!(".{a}{{"))
+ );
+ assert_eq!(
+ sheet.properties["a.tsx"][&255]
+ .keys()
+ .copied()
+ .collect::>(),
+ vec![0, 1, 3]
+ );
+ assert_eq!(
+ sheet.properties["b.tsx"][&255]
+ .keys()
+ .copied()
+ .collect::>(),
+ vec![0]
+ );
+ assert_eq!(sheet.properties.get(""), None);
+ });
+ reset_build_state_internal();
+}
+
+#[rstest]
+#[case(false, false)]
+#[case(false, true)]
+#[case(true, false)]
+#[case(true, true)]
+#[serial]
+fn shared_delivery_keeps_existing_rule_placement_when_atoms_are_unprefixed(
+ #[case] hoist: bool,
+ #[case] reverse: bool,
+) {
+ // Given: single CSS or a frozen atom-hoist plan, not moved per-file rules.
+ reset_build_state_internal();
+ css::debug::set_debug(false);
+ if hoist {
+ set_atom_hoist(Some(2));
+ import_file_routes_internal(HashMap::from([
+ ("a.tsx".into(), std::collections::HashSet::from([0, 1])),
+ ("b.tsx".into(), std::collections::HashSet::from([0, 1])),
+ ]));
+ }
+ let inputs = [
+ (
+ "a.tsx",
+ "import {Box} from '@devup-ui/react'; export const x=;",
+ ),
+ (
+ "b.tsx",
+ "import {Box} from '@devup-ui/react'; export const x= ;",
+ ),
+ ];
+ // When
+ for index in if reverse { [1, 0] } else { [0, 1] } {
+ compile(inputs[index].0, inputs[index].1, !hoist);
+ }
+ // Then: the shared sheet retains base-before-responsive order; raw buckets do not change.
+ with_style_sheet(|sheet| {
+ let css = sheet.create_css(None, false);
+ assert!(
+ css.find("flex-direction:column")
+ .unwrap_or_else(|| panic!("missing base"))
+ < css
+ .find("@media")
+ .unwrap_or_else(|| panic!("missing responsive rule"))
+ );
+ assert_eq!(css.matches("flex-direction:column").count(), 1);
+ assert!(css.contains("flex-direction:row"));
+ let expected = if hoist {
+ vec!["a.tsx", "b.tsx"]
+ } else {
+ vec![""]
+ };
+ assert_eq!(
+ sheet
+ .properties
+ .keys()
+ .map(String::as_str)
+ .collect::>(),
+ expected
+ );
+ for orders in sheet.properties.values() {
+ for property in orders[&255].values().flatten() {
+ assert!(property.class_name.starts_with('O'));
+ assert_eq!(property.hoisted, hoist);
+ }
+ }
+ });
+ reset_build_state_internal();
+}
+
+#[test]
+#[serial]
+fn collapsed_bucket_retains_its_identity_prefix_and_cache_claims() {
+ // Given: A and its child share a canonical bucket, B remains independently delivered.
+ reset_build_state_internal();
+ import_canonical_map_internal(HashMap::from([("child.tsx".into(), "a.tsx".into())]));
+ compile(
+ "child.tsx",
+ "import {Box} from '@devup-ui/react'; export const x=;",
+ false,
+ );
+ compile(
+ "b.tsx",
+ "import {Box} from '@devup-ui/react'; export const x= ;",
+ false,
+ );
+ let before = export_sheet_internal().unwrap_or_else(|error| panic!("{error}"));
+ let css_before = with_style_sheet(|sheet| sheet.create_css(Some("a.tsx"), false));
+ // When: exact name claims round-trip through the existing cache protocol.
+ import_sheet_internal(serde_json::from_str(&before).unwrap_or_else(|error| panic!("{error}")))
+ .unwrap_or_else(|error| panic!("{error}"));
+ // Then
+ assert_eq!(
+ with_style_sheet(|sheet| sheet.create_css(Some("a.tsx"), false)),
+ css_before
+ );
+ with_style_sheet(|sheet| {
+ assert_eq!(
+ sheet
+ .properties
+ .keys()
+ .map(String::as_str)
+ .collect::>(),
+ vec!["a.tsx", "b.tsx"]
+ );
+ let a = sheet.properties["a.tsx"][&255][&0]
+ .iter()
+ .next()
+ .unwrap_or_else(|| panic!("missing A"));
+ let b = sheet.properties["b.tsx"][&255][&0]
+ .iter()
+ .next()
+ .unwrap_or_else(|| panic!("missing B"));
+ assert_ne!(a.class_name, b.class_name);
+ assert!(a.class_name.starts_with("FLa_ptsx-"));
+ assert!(sheet.names.contains_key(&a.class_name));
+ assert_eq!(sheet.names["FLa_ptsx"].content, "a.tsx");
+ });
+ reset_build_state_internal();
+}
diff --git a/bindings/devup-ui-wasm/src/sheet_entry.rs b/bindings/devup-ui-wasm/src/sheet_entry.rs
new file mode 100644
index 000000000..82a263499
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/sheet_entry.rs
@@ -0,0 +1,24 @@
+use std::cell::Cell;
+
+thread_local! {
+ static HELD: Cell = const { Cell::new(false) };
+}
+
+/// Marks only the live sheet data lock, not the enclosing admission scope.
+pub(crate) struct SheetEntry;
+
+impl SheetEntry {
+ pub(crate) fn enter() -> Self {
+ HELD.with(|held| {
+ assert!(!held.get(), "style sheet: recursive lock-held entry");
+ held.set(true);
+ });
+ Self
+ }
+}
+
+impl Drop for SheetEntry {
+ fn drop(&mut self) {
+ HELD.with(|held| held.set(false));
+ }
+}
diff --git a/bindings/devup-ui-wasm/src/static_theme_tests.rs b/bindings/devup-ui-wasm/src/static_theme_tests.rs
new file mode 100644
index 000000000..68314f15c
--- /dev/null
+++ b/bindings/devup-ui-wasm/src/static_theme_tests.rs
@@ -0,0 +1,60 @@
+use super::*;
+use rstest::rstest;
+use serial_test::serial;
+
+#[rstest]
+#[case("px='$gutter'", vec![0, 2])]
+#[case("px={'$gutter'}", vec![0, 2])]
+#[case("px={['$gutter']}", vec![0])]
+#[case("_hover={{px:['$gutter']}}", vec![0])]
+#[case("selectors={{'&:focus':{px:['$gutter']}}}", vec![0])]
+#[serial]
+fn theme_arrays_remain_static_when_array_indices_define_the_responsive_levels(
+ #[case] props: &str,
+ #[case] expected: Vec,
+) {
+ // Given: a responsive token expanded only by the established root-token path.
+ reset_build_state_internal();
+ register_theme_internal(
+ serde_json::from_value(
+ serde_json::json!({"length":{"default":{"gutter":["8px",null,"16px"]}}}),
+ )
+ .unwrap_or_else(|error| panic!("{error}")),
+ );
+ let source = format!("import {{Box}} from '@devup-ui/react'; export const x= ;");
+ // When
+ let output = code_extract_internal(
+ "theme.tsx",
+ &source,
+ "@devup-ui/react",
+ "df".into(),
+ true,
+ false,
+ false,
+ HashMap::new(),
+ )
+ .unwrap_or_else(|error| panic!("{error}"));
+ // Then: class-only tokens have no tuple/inline spreads, and array tokens stay at index zero.
+ for marker in [
+ "__devupAssignment",
+ "__devupLevel",
+ "__devupValue",
+ "style=",
+ ] {
+ assert!(!output.code().contains(marker), "{}", output.code());
+ }
+ with_style_sheet(|sheet| {
+ let levels = sheet.properties[""][&255]
+ .iter()
+ .filter(|(_, properties)| {
+ properties
+ .iter()
+ .any(|property| property.property == "padding-left")
+ })
+ .map(|(level, _)| *level)
+ .collect::>();
+ assert_eq!(levels, expected);
+ });
+ reset_build_state_internal();
+ register_theme_internal(sheet::theme::Theme::default());
+}
diff --git a/bindings/devup-ui-wasm/tests/content-names.mjs b/bindings/devup-ui-wasm/tests/content-names.mjs
new file mode 100644
index 000000000..58b65a3a0
--- /dev/null
+++ b/bindings/devup-ui-wasm/tests/content-names.mjs
@@ -0,0 +1,162 @@
+import assert from 'node:assert/strict'
+import { createHash } from 'node:crypto'
+import { createRequire } from 'node:module'
+
+const require = createRequire(import.meta.url)
+const wasm = require('../pkg/index.js')
+const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789_'
+const size = (value) => {
+ const bytes = Buffer.alloc(8)
+ bytes.writeBigUInt64BE(BigInt(value))
+ return bytes
+}
+const text = (value) =>
+ Buffer.concat([size(Buffer.byteLength(value)), Buffer.from(value)])
+const atom = (property, value) =>
+ Buffer.concat([
+ Buffer.from([1, 1, 0]),
+ text(property),
+ Buffer.from([1]),
+ text(value),
+ Buffer.from([0, 255, 0, 0]),
+ ])
+const fingerprint = (descriptor) => {
+ const first80 = createHash('sha256')
+ .update(descriptor)
+ .digest()
+ .subarray(0, 10)
+ let value = BigInt(`0x${first80.toString('hex')}`)
+ let result = ''
+ for (let digit = 0; digit < 16; digit++) {
+ result = alphabet[Number(value % 37n)] + result
+ value /= 37n
+ }
+ assert.equal(value, 0n)
+ return result
+}
+const compile = (file, code) =>
+ wasm.codeExtract(file, code, '@devup-ui/react', 'df', true, false, false, {})
+
+wasm.resetBuildState()
+wasm.setDebug(false)
+if (process.argv[2] === 'goldens') {
+ const source =
+ "import {css,keyframes} from '@devup-ui/react'; export const x=css({color:'red',fontFamily:'abcdefghijklmnopqrstuvwx'});export const k=keyframes({from:{opacity:0},to:{opacity:1}});"
+ const output = compile('golden.tsx', source)
+ const family =
+ 'OH' + fingerprint(atom('font-family', 'abcdefghijklmnopqrstuvwx'))
+ assert.ok(output.code.includes('OLcolor-vred'))
+ assert.ok(output.code.includes(family))
+ const frames = Buffer.concat([
+ Buffer.from([1, 3]),
+ size(2),
+ text('from'),
+ size(1),
+ text('opacity'),
+ text('0'),
+ text('to'),
+ size(1),
+ text('opacity'),
+ text('1'),
+ ])
+ const animation = 'KH' + fingerprint(frames)
+ assert.ok(output.code.includes(animation))
+ assert.ok(wasm.getCss(null, false).includes('@keyframes ' + animation))
+ const sheet = JSON.parse(wasm.exportSheet())
+ assert.deepEqual(sheet.names[family].descriptor, [
+ ...atom('font-family', 'abcdefghijklmnopqrstuvwx'),
+ ])
+ assert.deepEqual(sheet.names[animation].descriptor, [...frames])
+ output.free()
+} else if (process.argv[2] === 'compact-sources') {
+ const source =
+ '// filler\n'.repeat(10_240) +
+ "import {Box} from '@devup-ui/react';\nexport const View=(p)=>;\n"
+ const root = '---SUH' + fingerprint(Buffer.from(source))
+ const run = (received) => {
+ wasm.resetBuildState()
+ wasm.setDebug(false)
+ wasm.registerTheme({})
+ const output = compile('/real/large.tsx', received)
+ const variables = output.code.match(/---SUH[a-z0-9_]{16}-[a-z0-9_]+/gu)
+ assert.equal(variables.length, 2)
+ assert.ok(variables.every((name) => name.startsWith(root + '-')))
+ assert.ok(variables.every((name) => name.length <= 38))
+ const css = wasm.getCss(null, false)
+ assert.ok(variables.every((name) => css.includes(name)))
+ const sheet = JSON.parse(wasm.exportSheet())
+ assert.equal(sheet.names[root].content, source)
+ assert.deepEqual(sheet.names[root].descriptor, [...Buffer.from(source)])
+ const result = {
+ code: output.code,
+ css: output.css,
+ map: output.map,
+ cssFile: output.cssFile,
+ updatedBaseStyle: output.updatedBaseStyle,
+ dependencies: output.dependencies,
+ completeCss: css,
+ }
+ output.free()
+ wasm.resetBuildState()
+ wasm.importSheet(sheet)
+ const restored = compile('/real/other.tsx', source)
+ assert.equal(restored.code, result.code)
+ assert.equal(wasm.getCss(null, false), css)
+ restored.free()
+ return result
+ }
+ const baseline = run(source)
+ for (const received of [
+ source,
+ source.replaceAll('\n', '\r\n'),
+ '\uFEFF' + source,
+ '\uFEFF' + source.replaceAll('\n', '\r\n'),
+ ]) {
+ assert.deepEqual(run(received), baseline)
+ }
+} else if (process.argv[2] === 'cache-conflict') {
+ compile(
+ 'a.tsx',
+ "import {css} from '@devup-ui/react';\nexport const x=css({color:'blue'});",
+ ).free()
+ compile(
+ 'b.tsx',
+ "import {globalCss} from '@devup-ui/react'; globalCss({body:{padding:'11px'}});",
+ ).free()
+ const sheet = JSON.parse(wasm.exportSheet())
+ sheet.names['OLcolor-vred'] = sheet.names['OLcolor-vblue']
+ wasm.importSheet(sheet)
+ const before = wasm.exportSheet()
+ const css = wasm.getCss(null, false)
+ assert.ok(css.includes('padding:11px'))
+ let collision
+ assert.throws(
+ () =>
+ compile(
+ 'b.tsx',
+ "import {css} from '@devup-ui/react';\nexport const x=css({color:'red'});",
+ ),
+ (error) => {
+ assert.ok(error instanceof Error)
+ assert.ok(error.message.includes('a.tsx:2:27:'))
+ assert.ok(error.message.includes('b.tsx:2:27:'))
+ assert.ok(error.message.includes('OLcolor-vred'))
+ collision = error.message
+ return true
+ },
+ )
+ assert.equal(wasm.exportSheet(), before)
+ assert.throws(
+ () => wasm.getCss(null, false),
+ (error) => {
+ assert.ok(error instanceof Error)
+ assert.ok(error.message.includes('a.tsx:2:27:'))
+ assert.ok(error.message.includes('b.tsx:2:27:'))
+ assert.equal(error.message, collision)
+ return true
+ },
+ )
+} else {
+ assert.fail('unknown fixture mode')
+}
+wasm.resetBuildState()
diff --git a/bindings/devup-ui-wasm/tests/dynamic-scaled-values-additional-fixtures.mjs b/bindings/devup-ui-wasm/tests/dynamic-scaled-values-additional-fixtures.mjs
new file mode 100644
index 000000000..e4d861a0e
--- /dev/null
+++ b/bindings/devup-ui-wasm/tests/dynamic-scaled-values-additional-fixtures.mjs
@@ -0,0 +1,70 @@
+export const shorthands = {
+ mixedScale: ['padding', 'opacity', 'animation-duration'],
+}
+const absent = [
+ 'null',
+ 'undefined',
+ 'false',
+ 'true',
+ "''",
+ 'NaN',
+ 'Infinity',
+ '-Infinity',
+]
+export const additionalFixtures = [
+ ...[
+ ['proven-number', '0.5', ':number'],
+ ['numeric-capable-string', "'0.5'", ':string'],
+ ['unknown', '0.5', ''],
+ ].map(([name, value, annotation]) => ({
+ name: `mixedScale-${name}`,
+ dynamic: '',
+ literal: '',
+ value,
+ annotation,
+ props: ['paddingTop', 'opacity', 'animationDuration'],
+ expected: ['2px', '0.5', '0.0005s'],
+ })),
+]
+export const isolatedSuites = []
+for (const [prop, computed, anchor, expected] of [
+ ['p', 'paddingTop', 'anchor', '7px'],
+ ['opacity', 'opacity', 'opacityAnchor', '0.37'],
+]) {
+ const isolated = []
+ for (const value of absent) {
+ for (const annotation of ['', ':number']) {
+ const fixture = {
+ name: `absent-plain-${prop}-${annotation ? 'number' : 'unknown'}-${value}`,
+ dynamic: ` `,
+ literal: ' ',
+ value,
+ annotation,
+ props: [computed],
+ expected: [prop === 'p' ? '0px' : '1'],
+ }
+ additionalFixtures.push(fixture)
+ isolated.push({ ...fixture, name: `${fixture.name}-noOtherRule` })
+ if (prop === 'opacity')
+ additionalFixtures.push({
+ ...fixture,
+ name: `absent-prior-opacity-${annotation ? 'number' : 'unknown'}-${value}`,
+ dynamic: ``,
+ literal: ` `,
+ expected: [expected],
+ })
+ }
+ }
+ isolatedSuites.push({
+ name: `noOtherRule-${prop}`,
+ fixtures: isolated,
+ source: `import {Box} from '@devup-ui/react';${isolated
+ .map(
+ (fixture, index) =>
+ `function dynamic${index}(n${fixture.annotation}){return ${fixture.dynamic}}globalThis.pairs.push([dynamic${index}(${fixture.value}),${fixture.literal}]);`,
+ )
+ .join('\n')}`,
+ importedSource: "import {Box} from '@devup-ui/react';",
+ baselineCss: '',
+ })
+}
diff --git a/bindings/devup-ui-wasm/tests/dynamic-scaled-values-fixtures.mjs b/bindings/devup-ui-wasm/tests/dynamic-scaled-values-fixtures.mjs
new file mode 100644
index 000000000..328273be1
--- /dev/null
+++ b/bindings/devup-ui-wasm/tests/dynamic-scaled-values-fixtures.mjs
@@ -0,0 +1,245 @@
+import { additionalFixtures } from './dynamic-scaled-values-additional-fixtures.mjs'
+
+const components = [
+ 'Box',
+ 'Flex',
+ 'Grid',
+ 'VStack',
+ 'Center',
+ 'Text',
+ 'Image',
+ 'Input',
+ 'Button',
+]
+export const fixtures = []
+const add = (
+ name,
+ dynamic,
+ literal,
+ value,
+ annotation = '',
+ props = ['paddingTop'],
+ hover = false,
+) => {
+ fixtures.push({ name, dynamic, literal, value, annotation, props, hover })
+}
+for (const component of components) {
+ add(
+ `${component}-number`,
+ `<${component} p={n}/>`,
+ `<${component} p={4}/>`,
+ '4',
+ ':number',
+ )
+}
+for (const [prop, computed] of Object.entries({
+ p: 'paddingTop',
+ m: 'marginTop',
+ w: 'width',
+ h: 'height',
+ minW: 'minWidth',
+ maxH: 'maxHeight',
+ px: 'paddingLeft',
+ py: 'paddingTop',
+ mx: 'marginLeft',
+ my: 'marginTop',
+ gap: 'gap',
+ rowGap: 'rowGap',
+ columnGap: 'columnGap',
+ borderRadius: 'borderTopLeftRadius',
+ fontSize: 'fontSize',
+ letterSpacing: 'letterSpacing',
+ boxSize: 'width',
+})) {
+ add(
+ `${prop}-number`,
+ ` `,
+ ` `,
+ '4',
+ ':number',
+ [computed],
+ )
+}
+for (const [value, annotation, label] of [
+ ['4', '', 'unknown-number'],
+ ['4', ':string|number', 'union-number'],
+ ["'4'", ':string|number', 'union-string'],
+ ["'4'", ':string', 'string-number'],
+ ["'-2'", ':string', 'negative-string'],
+ ["'0.5'", ':string', 'fraction-string'],
+ ["' 4 '", ':string', 'spaced-string'],
+ ["'10px'", ':string', 'length-string'],
+]) {
+ add(label, '', ``, value, annotation, [
+ 'marginTop',
+ ])
+}
+add('width-100', '', '', '100', ':number', ['width'])
+add('auto', '', " ", "'auto'", ":'auto'|'10px'", [
+ 'width',
+])
+add('template-unit', '', " ", '10')
+add('template-number', '', " ", "''")
+add('template-escape', '', '', "''")
+add('operator-number', '', '', '2')
+add(
+ 'time-number',
+ '',
+ '',
+ '200',
+ ':number',
+ ['transitionDuration'],
+)
+add(
+ 'time-string',
+ '',
+ " ",
+ "'200'",
+ ':string',
+ ['transitionDuration'],
+)
+add(
+ 'unitless-number',
+ '',
+ '',
+ '0.5',
+ ':number',
+ ['opacity'],
+)
+add('percentage-string', '', " ", "'50%'", ':string', [
+ 'width',
+])
+add(
+ 'calc-string',
+ '',
+ " ",
+ "'calc(2 * 4px)'",
+ ':string',
+)
+add('responsive', "", "", '4')
+add(
+ 'hover',
+ '',
+ '',
+ '4',
+ ':number',
+ ['paddingTop'],
+ true,
+)
+add(
+ 'responsive-hover',
+ "",
+ "",
+ '4',
+ '',
+ ['paddingTop'],
+ true,
+)
+add('jsx-runtime', 'jsx(Box,{p:n})', 'jsx(Box,{p:4})', '4', ':number')
+add(
+ 'selector-array',
+ '',
+ '',
+ '4',
+ '',
+ ['paddingTop'],
+ true,
+)
+add(
+ 'selector-child',
+ " span':{p:n}}}> ",
+ " span':{p:4}}}> ",
+ '4',
+ ':number',
+ ['paddingTop'],
+)
+for (const value of ['0', '-0', 'false', 'null', 'undefined', "''", 'NaN']) {
+ add(
+ `and-${value}`,
+ '',
+ ``,
+ value,
+ )
+ add(
+ `runtime-and-${value}`,
+ 'jsx(Box,{className:"anchor",p:n&&readRight()})',
+ `jsx(Box,{className:"anchor",p:${value}})`,
+ value,
+ )
+ add(
+ `css-and-${value}`,
+ '',
+ `
`,
+ value,
+ )
+}
+for (const value of [
+ 'undefined',
+ 'null',
+ 'false',
+ 'true',
+ "''",
+ 'NaN',
+ 'Infinity',
+ '-Infinity',
+]) {
+ add(
+ `absent-padding-${value}`,
+ '',
+ ' ',
+ value,
+ )
+ fixtures.at(-1).expected = ['7px']
+ add(
+ `absent-number-${value}`,
+ '',
+ ' ',
+ value,
+ ':number',
+ )
+ fixtures.at(-1).expected = ['7px']
+ add(
+ `absent-color-${value}`,
+ '',
+ ' ',
+ value,
+ '',
+ ['color'],
+ )
+ add(
+ `absent-lower-${value}`,
+ '',
+ '',
+ value,
+ )
+ add(
+ `absent-responsive-${value}`,
+ '',
+ '',
+ value,
+ )
+ add(
+ `absent-selector-${value}`,
+ '',
+ '',
+ value,
+ '',
+ ['paddingTop'],
+ true,
+ )
+ add(
+ `absent-runtime-${value}`,
+ 'jsx(Box,{className:"anchor",p:n})',
+ 'jsx(Box,{className:"anchor"})',
+ value,
+ )
+}
+fixtures.push(...additionalFixtures)
+export const source = `import {${components.join(',')},css} from '@devup-ui/react';import {jsx}from 'react/jsx-runtime';
+${fixtures.map((fixture, index) => `function dynamic${index}(n${fixture.annotation}){return ${fixture.dynamic}}globalThis.pairs.push([dynamic${index}(${fixture.value}),${fixture.literal}]);`).join('\n')}`
+export const importedSource =
+ "import {Box}from '@devup-ui/react';import {size}from './types';globalThis.pairs.push([,