From ad86ad002f295a599154865016d4757d7260994e Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Mon, 7 Sep 2026 19:02:20 -0400 Subject: [PATCH] TodoMVC emits its stylesheet: asset-valued via the upstream asset: spelling The Pages demo rendered unstyled: the app bundle shipped its CSS as an asset and the frame minted a blob: URL for it, but the app never emitted the because stream-dom-dioxus had no way to spell an asset handle in a string attribute value. polymorph-stream-dom#19 added one; bump the pin and emit link { rel: stylesheet, href: asset: }. A unit test pins the const to manifest.json; frame-network-dead now ignores the blob: load and asserts the stylesheet applied. --- Cargo.lock | 6 +++--- Cargo.toml | 4 ++-- apps/todomvc/src/lib.rs | 48 ++++++++++++++++++++++++++++------------- deno.json | 2 +- deno.lock | 25 ++++++++++----------- docs/design.md | 2 +- e2e/run.ts | 20 +++++++++++------ 7 files changed, 67 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1d7688d..3387cea6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2195,7 +2195,7 @@ dependencies = [ [[package]] name = "stream-dom-dioxus" version = "0.1.0" -source = "git+https://github.com/polymorph-components/polymorph-stream-dom?rev=197492374936b3b4ce5af55f2bac50fa6b551597#197492374936b3b4ce5af55f2bac50fa6b551597" +source = "git+https://github.com/polymorph-components/polymorph-stream-dom?rev=e35dc357137a3563aa891662a626b3310a7078c4#e35dc357137a3563aa891662a626b3310a7078c4" dependencies = [ "dioxus-core", "dioxus-core-types", @@ -2208,7 +2208,7 @@ dependencies = [ [[package]] name = "stream-dom-guest" version = "0.1.0" -source = "git+https://github.com/polymorph-components/polymorph-stream-dom?rev=197492374936b3b4ce5af55f2bac50fa6b551597#197492374936b3b4ce5af55f2bac50fa6b551597" +source = "git+https://github.com/polymorph-components/polymorph-stream-dom?rev=e35dc357137a3563aa891662a626b3310a7078c4#e35dc357137a3563aa891662a626b3310a7078c4" dependencies = [ "prost", "rustc-hash", @@ -2219,7 +2219,7 @@ dependencies = [ [[package]] name = "stream-dom-proto" version = "0.1.0" -source = "git+https://github.com/polymorph-components/polymorph-stream-dom?rev=197492374936b3b4ce5af55f2bac50fa6b551597#197492374936b3b4ce5af55f2bac50fa6b551597" +source = "git+https://github.com/polymorph-components/polymorph-stream-dom?rev=e35dc357137a3563aa891662a626b3310a7078c4#e35dc357137a3563aa891662a626b3310a7078c4" dependencies = [ "prost", "prost-build", diff --git a/Cargo.toml b/Cargo.toml index 270ce462..3f67575e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,8 +73,8 @@ dioxus = { version = "=0.7.10", default-features = false, features = ["macro", " # docs/design.md "Pins, with reasons": unpublished, moving; policy object # and asset handles landed in #15. Full 40-char sha of origin/main at the # time this skeleton was cut. -stream-dom-dioxus = { git = "https://github.com/polymorph-components/polymorph-stream-dom", rev = "197492374936b3b4ce5af55f2bac50fa6b551597" } -stream-dom-guest = { git = "https://github.com/polymorph-components/polymorph-stream-dom", rev = "197492374936b3b4ce5af55f2bac50fa6b551597" } +stream-dom-dioxus = { git = "https://github.com/polymorph-components/polymorph-stream-dom", rev = "e35dc357137a3563aa891662a626b3310a7078c4" } +stream-dom-guest = { git = "https://github.com/polymorph-components/polymorph-stream-dom", rev = "e35dc357137a3563aa891662a626b3310a7078c4" } # The store's name derivation (docs/design.md "Storage", internal.wit # `storage`): every object in the user's Drive is named # `hex(HMAC-SHA256(name-key, tree || commit))`, so two devices of a group diff --git a/apps/todomvc/src/lib.rs b/apps/todomvc/src/lib.rs index dac82525..cb49f775 100644 --- a/apps/todomvc/src/lib.rs +++ b/apps/todomvc/src/lib.rs @@ -205,6 +205,12 @@ enum FilterState { Completed, } +/// The stylesheet's asset handle, `asset:` followed by `manifest.json`'s +/// `handle` for `todomvc-app.css` — the spelling upstream's writer gives an +/// asset-valued attribute (polymorph-stream-dom#19, `writer.rs` `asset_handle`). The +/// `tests` module below asserts these stay in agreement. +const STYLESHEET: &str = "asset:0f827d119b7bec30534b1767e8ab8ee0f2890c98f93baa1159dcf1a46f10bc17"; + pub fn app() -> Element { // The snapshot. Owned by the `tasks` service; this is a cached view of it. let items = use_signal(Vec::::new); @@ -249,17 +255,8 @@ pub fn app() -> Element { }; rsx! { - // CONTRACT: the stylesheet link is deliberately absent. The app should - // emit `link { rel: "stylesheet", href: }` using - // `stream_dom_guest::Batch::set_attribute_asset`, but Dioxus attribute - // values are strings and `stream-dom-dioxus`'s writer has no - // convention for spelling an asset handle as one: its `set_attribute` - // (crates/stream-dom-dioxus/src/writer.rs:540) reduces every value - // through `serialize` to text and only ever calls - // `Batch::set_attribute` / `set_property` — the string `asset` does - // not appear anywhere in that file. So there is no way for this crate - // to reach `set_attribute_asset` today; the change belongs in - // `writer.rs:540`, upstream. M1's gate is functional, not pretty. + link { rel: "stylesheet", href: STYLESHEET } + section { class: "todoapp", TodoHeader { items } section { class: "main", @@ -485,15 +482,18 @@ fn ListFooter( /// handles"). Nothing else checks that at build time, so this does. #[cfg(test)] mod tests { + use super::STYLESHEET; use sha2::{Digest, Sha256}; + fn manifest() -> serde_json::Value { + let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); + serde_json::from_str(&std::fs::read_to_string(dir.join("manifest.json")).unwrap()).unwrap() + } + #[test] fn manifest_asset_handles_match_the_bytes() { let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); - let manifest: serde_json::Value = - serde_json::from_str(&std::fs::read_to_string(dir.join("manifest.json")).unwrap()) - .unwrap(); - + let manifest = manifest(); let assets = manifest["assets"].as_array().unwrap(); assert!(!assets.is_empty(), "manifest declares no assets"); @@ -508,4 +508,22 @@ mod tests { ); } } + + /// The app's `STYLESHEET` const must name the same asset the manifest + /// declares for `todomvc-app.css`, in the `asset:` spelling + /// upstream's writer expects (polymorph-stream-dom#19). + #[test] + fn stylesheet_const_matches_the_manifest_handle() { + let manifest = manifest(); + let assets = manifest["assets"].as_array().unwrap(); + let css = assets + .iter() + .find(|a| a["path"].as_str() == Some("todomvc-app.css")) + .expect("manifest declares no todomvc-app.css asset"); + let expected = format!("asset:{}", css["handle"].as_str().unwrap()); + assert_eq!( + STYLESHEET, expected, + "STYLESHEET is stale against manifest.json" + ); + } } diff --git a/deno.json b/deno.json index 19319750..493248fa 100644 --- a/deno.json +++ b/deno.json @@ -16,7 +16,7 @@ "@remote-dom/core": "npm:@remote-dom/core@^1.11.1", "@remote-dom/core/receivers": "npm:@remote-dom/core@^1.11.1/receivers", "@remote-dom/core/elements": "npm:@remote-dom/core@^1.11.1/elements", - "@polymorph/stream-dom-receiver/": "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/", + "@polymorph/stream-dom-receiver/": "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/", "@std/assert": "jsr:@std/assert@^1", "playwright": "npm:playwright@1.62.1", "@std/fs": "jsr:@std/fs@^1", diff --git a/deno.lock b/deno.lock index 43472851..9c23cf28 100644 --- a/deno.lock +++ b/deno.lock @@ -390,18 +390,19 @@ } }, "remote": { - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/dispatch.ts": "114107ed7fbd3d535cd194ae5cb425956c6591062eb1ddd6f42f6f0ea0a0c0cf", - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/driver.ts": "9f99c10ad9979035c6630d146696fe5fb5528c30377585639b450396bb94097a", - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/events.ts": "9f7d431393a2ba300059cf2aa9c32698d19ea8b1115944896a2af8f5564ac9cb", - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/frames.ts": "940abc92b71c797b41f892652126785281330485fbddb7a025030e17c6599a3e", - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/mod.ts": "57e7e8f809f03784ca8159d7791fed609fdabf9a11569155917737b8827ec5d9", - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/mount.ts": "99189376c82a21395b17705aa7bc4f1e87f8248a9dd6f6e9630dafb8ac63b7fd", - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/native.ts": "26fa83f874ca63e66cb71fe100704d66490d538a1911ac140629376ffffd4128", - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/policy.ts": "8daece4b13b514e0a09c821a57505837a001df9bd8d7f62a7331c607d527030e", - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/proto.ts": "79c7dfd8541fc51624818f686dfe266f3e1bfb33acca8ddae819fe9fa4299d2a", - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/receiver.ts": "47ce8ea37b14d6bce2c2bf83fbc7c1b3c4a092f51c5265fac62f60138edcaa7f", - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/remote.ts": "7491aafd059f2382e06b0df766ec8243eba748ef9d7f4834b1938096a36ba776", - "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/197492374936b3b4ce5af55f2bac50fa6b551597/receiver/src/templates.ts": "052e1d75bcaa5c9e20e3a0ad826960cd87a8c60e5a84af19163b69816c1b7e1c" + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/dispatch.ts": "114107ed7fbd3d535cd194ae5cb425956c6591062eb1ddd6f42f6f0ea0a0c0cf", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/driver.ts": "9adafb4423e0635864ed0565f184de570c14b3f946d5f2edb377b167efcadf8f", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/events.ts": "9f7d431393a2ba300059cf2aa9c32698d19ea8b1115944896a2af8f5564ac9cb", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/frames.ts": "940abc92b71c797b41f892652126785281330485fbddb7a025030e17c6599a3e", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/mod.ts": "1df264446aba40de8bf01960c4fe4cc983b080c00a56240a9ce9e452a03ebc0e", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/mount.ts": "99189376c82a21395b17705aa7bc4f1e87f8248a9dd6f6e9630dafb8ac63b7fd", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/native.ts": "26fa83f874ca63e66cb71fe100704d66490d538a1911ac140629376ffffd4128", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/policy-desktop.ts": "bff494415a66095d6eced2eff7b979658268c1f331c92422d7fda4e6df9d6814", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/policy.ts": "8daece4b13b514e0a09c821a57505837a001df9bd8d7f62a7331c607d527030e", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/proto.ts": "79c7dfd8541fc51624818f686dfe266f3e1bfb33acca8ddae819fe9fa4299d2a", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/receiver.ts": "47ce8ea37b14d6bce2c2bf83fbc7c1b3c4a092f51c5265fac62f60138edcaa7f", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/remote.ts": "7491aafd059f2382e06b0df766ec8243eba748ef9d7f4834b1938096a36ba776", + "https://raw.githubusercontent.com/polymorph-components/polymorph-stream-dom/e35dc357137a3563aa891662a626b3310a7078c4/receiver/src/templates.ts": "052e1d75bcaa5c9e20e3a0ad826960cd87a8c60e5a84af19163b69816c1b7e1c" }, "workspace": { "dependencies": [ diff --git a/docs/design.md b/docs/design.md index 35ec9bff..ca4e2e43 100644 --- a/docs/design.md +++ b/docs/design.md @@ -422,7 +422,7 @@ native tests, so browser gates are mandatory for every visor change. | `wit-bindgen` | `=0.60.0`, workspace-wide | must equal stream-dom's pin: `StreamReader` (a wit-bindgen runtime type) crosses the delegation from our world's `run` into `stream_dom_dioxus::driver::run`. Different wit-bindgen versions *can* coexist in one component (the `wasip3_task_set` weak-symbol ABI exists for exactly that), but not across a shared runtime type. Bumps follow stream-dom's. `generate!` never sets `async: true`: that lowers sync WIT functions (resource constructors) async, which the canonical ABI forbids and only the translator catches; WIT's own `async func` annotations are the source of truth | | `@polyengine/*` | 0.6.7, one version across the graph | first release where an async export may park on a guest waker (#292) without the 0.6.6 lift regression (#312); brand symbols are per-version, so a partial upgrade fails at `instanceof` | | `dioxus` | `=0.7.10` | dioxus-core state is shared with `stream-dom-dioxus`; skew breaks the build | -| polymorph-stream-dom | git rev (see Cargo.toml / deno.json) | unpublished, moving; policy object and asset handles landed in #15 | +| polymorph-stream-dom | git rev (see Cargo.toml / deno.json) | unpublished, moving; policy object and asset handles landed in #15; the Dioxus `asset:` attribute spelling landed in #19 | | subduction | git `sansio` rev | above | | keyhive | git rev `a509a2d` | `keyhive_core` / `keyhive_crypto` / `beekem`, unreleased and moving. The sealed plaintext is keyhive's own `Envelope` and the read-back walk is keyhive's own `try_causal_decrypt`, so a rev bump is a wire-format change for every stored blob: its own PR | | `@polymorph/*` | 0.6.1 (webcrypto, websocket), 0.6.2 (webrtc-datachannels) | the cuts current at the polyengine 0.6.7 pin; taken within the `^0.6` range | diff --git a/e2e/run.ts b/e2e/run.ts index 6f158dd8..30d40c65 100644 --- a/e2e/run.ts +++ b/e2e/run.ts @@ -1189,15 +1189,23 @@ const scenarios: Scenario[] = [ await launchTodoMvc(page); await page.frameLocator("#app-zone iframe").locator("input").first() .waitFor({ timeout: 30_000 }); - // The app emits no `` in M1 (apps/todomvc/src/lib.rs's CONTRACT - // note), and the frame's CSP has no connect-src at all: there is - // nothing the frame could fetch even if it named something. + // The app's one fetch is its stylesheet, a `blob:` URL the frame + // minted from the bundle (web/frame.ts's `resolveAsset`) — Playwright + // reports it as a request but it is not network. The frame's CSP has + // no connect-src at all, so nothing else can leave. + const notBlob = fromFrame.filter((url) => !url.startsWith("blob:")); check( - fromFrame.length === 0, - `the app frame made ${fromFrame.length} request(s): ${ - fromFrame.join(", ") + notBlob.length === 0, + `the app frame made ${notBlob.length} non-blob request(s): ${ + notBlob.join(", ") }`, ); + + // The stylesheet did apply: `.todoapp { background: #fff }` from + // apps/todomvc/assets/todomvc-app.css. + const background = await todoFrame(page).locator("section.todoapp") + .evaluate((el) => getComputedStyle(el).backgroundColor); + eq(background, "rgb(255, 255, 255)", "the stylesheet did not apply"); }, },