Re-target the todomvc dioxus guest onto polyengine-dioxus; fix the surface guests - #143
Merged
Conversation
THE RULING THIS EMBODIES: polyvisor keeps two app surfaces, split by what the app is written in. `polyvisor:surface` — the WebIDL mirror, issue #15's pass-through bet — stays for hand-written, Preact and componentize-js apps. `polymorph:dioxus`, the sibling renderer, is the backend for dioxus apps and for the visor. This spike's `guest-dioxus` was the PROTOTYPE of the second: its `renderer.rs` a hand-rolled `WriteMutations` over the surface, its `events.rs` a hand-rolled event converter. polyengine-dioxus generalised and superseded both, so they are deleted (285 + 249 lines, plus a second copy of TodoMVC), and the guest builds on `polyengine_dioxus::launch!` for wasm32-wasip2 — no wbg-sever, no `component new`. The page's ?guest=hand|dioxus|preact toggle now demonstrates the two-backend shape in one place. THE FRAME TRANSPORT IS THE REAL WORK. The page's default backend is the sandboxed opaque-origin iframe, and that is the trust story, so the dioxus guest had to keep it. The sibling's `mountApp` applies into the document it runs in; this adds the polyengine-dioxus analogue of visor/frame: the instance runs in the shell, mutation batches cross a MessageChannel, and the frame runs the sibling's own `DomApplier` and `EventDispatcher`, posting serialised payloads back to `handle-event`. `history` is the shell's (`fragmentHistory` — it owns the URL); `head` is supplied with a refusing interceptor, because a framed app's <head> is the frame's and whether it gets one is a later decision. FOUR DEGRADATIONS ACROSS THE FRAME, each documented at its site: prevent-default and stop-propagation are no-ops (the native listener returned before the shell saw the event); the sync `dom` queries answer none/false; eval is not granted and is asserted absent on the artifact; and resource-carrying payloads (files, data-transfer) cannot cross a structuredClone. The first of these produced a real bug the gate found: an un-prevented in-page anchor click navigated the FRAME's fragment, which still appends to the joint session history, so the shell's back button stepped through frame entries. The frame now refuses in-page anchor defaults on the guest's behalf. TWO OF THE THREE DOCUMENTED GAPS CLOSE; THE THIRD IS REOPENED BY THE FRAME. Rich event families and real bubbling come with the renderer. `onmounted`/focus closes IN the renderer and is reopened by the sync `dom` queries being unanswerable across the frame — a different cause for the same visible symptom, and the honest report is that on the default backend the auto-focus behaviour is unchanged. THE SPIKE HAD NO AUTOMATED GATES. It now has two: the existing differential harness wrapped as a Playwright test, and a real-interaction test of the dioxus guest inside the frame that also asserts the frame cannot read the shell's anchor colour. The runtime moved off a pre-async polyengine onto the sibling's checkout to carry the mutation stream. SIZE: 224,565 gzipped, from 130 KB. wasi-libc and std enter with the wasip2 target, the full renderer replaces ~530 lines of glue, and dioxus-document/dioxus-history arrive for the unconditional providers. docs/spike-todomvc/ is the justfile's build output and is now ignored alongside docs/demo/.
…tion now FOUND BY THE GATE ON ITS FIRST HONEST RUN. Commit 4ec8d2f (2026-08-21, #74 "Split the WIT contracts") moved visor/surface/surface.ts onto `polyvisor:surface/*@0.1.0` import keys; spikes/todomvc/wit/todomvc.wit kept declaring `polymorph:todomvc-spike@0.0.1`, and so did the three surface guests' generated paths and import specifiers. The interface bodies were byte-identical, which is why it looked innocuous — but the package name IS the import key, and every surface component died at import resolution. Nothing caught it because the spike's correctness lived in a page a human had to open, and for fifteen days nobody did. The differential harness, wrapped as a Playwright gate in the previous commit, was red on this before it could serve the purpose it was written for. THE FIX IS A DELETION, NOT A RENAME. Renaming the package in place would have left two byte-identical definitions of `dom`/`events`/`shell` in the tree — exactly the arrangement that produced this. The second copy was the defect. todomvc.wit now declares only the spike's two worlds and imports `polyvisor:surface@0.1.0` through wit/deps/polyvisor-surface, a repo-relative symlink to wit/surface: one definition, and the spike cannot drift from it again. (spikes/visor-dioxus vendors its deps because its dependency lives OUTSIDE the repo; here a copy is optional and a link is honest.) 64 lines of duplicated WIT leave; the guests' module paths and specifiers follow mechanically, with `generate_all` added where wit-bindgen requires it for a dependency package. No guest behaviour touched — which is what lets the harness's stepwise DOM comparison certify the change as purely mechanical. AND THE QUESTION THE GATE EXISTED TO ANSWER HAS NOW ACTUALLY BEEN ASKED. The runtime moved off a pre-async polyengine in the previous commit, and whether the old surface components survived that could not be tested while they died at import resolution. They do: three backends, fifteen scripted steps, full-DOM serialisation stepwise identical; eight trap probes identical; flush-on-trap holding. Evidenced, not assumed.
lannbot
enabled auto-merge
September 5, 2026 15:27
lannbot
pushed a commit
that referenced
this pull request
Sep 5, 2026
THE E2E SUITE WENT FROM ~20 CONSECUTIVE FIRST-ATTEMPT PASSES TO ONE PASS IN SEVEN, starting at #143, and the failures never repeated a scenario: firefox-smoke's browser process gone, solo-erase's page load timing out, cross-engine-pairing's sync never arriving, store-outage-recovery's PUTs failing after three attempts. Different scenario each time is the shape of a runner under load, not of a broken test. WHAT #143 ACTUALLY CHANGED FOR THE DEMO: nothing in its build, and one line in spikes/todomvc/rust-toolchain.toml. But all three workflows key the shared cargo cache on `**/rust-toolchain.toml`, which matches every spike's toolchain file though no spike is among the cached workspaces — so that edit rotated the key, the E2E job restored nothing ("No cache found" from that run on, "full match: true" on every run before), and each run built the engine composite cold. And because the E2E job never saves and CACHE_ON_FAILURE is off, a red main run could not seed the new key either; only the Pages build on main eventually did. The key now hashes engine/rust-toolchain.toml alone — the only toolchain file belonging to a workspace any of these jobs caches — in pages.yml, e2e.yml and soak.yml alike, since they must match. The spike workspaces Pages additionally caches carry no toolchain file of their own. A spike may change its compiler without the demo paying for it. The mechanism linking a cold build to a browser crash is plausible (disk and time on a shared runner) and not proven; the correlation is exact and the fix is right regardless.
lannbot
pushed a commit
that referenced
this pull request
Sep 5, 2026
THE SPIKE HAD A LIVE-DEMO LINK TO A DEAD SITE. Its README pointed at a former polymorph-apps Pages root that 404s, and claimed the built demo was committed under docs/spike-todomvc/ — which stopped being true when docs/ moved to CI builds, and is doubly false now that the directory is gitignored. pages.yml built only docs/demo/. So the re-targeted dioxus guest (#143) had no URL anyone could open, and a demo nobody can open is most of how the surface guests stayed broken for fifteen days. Now pages.yml runs `just build` in spikes/todomvc after the demo build and checks its key outputs exist before uploading, so a silently empty build fails the job rather than deploying a hole. It deploys to https://polymorph-components.github.io/polyvisor/spike-todomvc/ on every push to main; PRs build and stop at the artifact, as before. WHAT THE BUILD NEEDS THAT CI LACKED. The dioxus guest depends on the polyengine-dioxus renderer BY PATH, as a sibling of the repo root — the same layout setup.sh already uses for polymorph-webrtc-datachannels — so setup.sh now pins it (lannbot/polyengine-dioxus @ fdc0d52) through the existing pin_repo helper. That sibling in turn carries its own .deps/polyengine checkout plus a translator shim, which the spike's deno.json and translate step reach into; setup.sh clones and pins the former in the siblings phase (reading POLYENGINE_REV out of the sibling's justfile rather than duplicating it, so a sibling bump carries it) and builds the shim in the tools phase, mirroring the sibling's own `just deps`. wasm32-wasip2 arrives through the spike's rust-toolchain.toml with no setup change. rust-cache gains the spike's three workspaces; the shared key already hashes setup.sh, so this diff rotates it. Verified against a throwaway siblings dir and a disposable worktree, never the real checkouts: setup.sh pins the right shas, produces the shim, and is idempotent on re-run; `just build` then succeeds end to end in ~28s warm. Cold-cache CI cost is estimated at three to five minutes inside a job the engine composite already dominates. Also: guest-preact's recipe now runs `npm ci` before `npm run build` — a fresh checkout without node_modules failed `just build` regardless of CI. And the README's harness and bench links, also dead, now point at the new site.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two commits, reviewable separately.
1. Re-target the todomvc dioxus guest onto polyengine-dioxus
The ruling this embodies. polyvisor keeps two app surfaces, split by what the app is written in:
polyvisor:surface(the WebIDL mirror, #15's bet) for hand-written, Preact and componentize-js apps;polymorph:dioxusfor dioxus apps and the visor.spikes/todomvc/guest-dioxuswas the prototype of the second — itsrenderer.rsandevents.rsare deleted in favour of the renderer that generalised them, along with a second copy of TodoMVC. The page's?guest=hand|dioxus|preacttoggle now demonstrates the two-backend shape in one place.The frame transport is the real work. The page's default backend is the opaque-origin sandboxed iframe — the trust story — so the dioxus guest had to keep it. This adds the polyengine-dioxus analogue of
visor/frame: the instance runs in the shell, mutation batches cross a MessageChannel, and the frame runs the sibling's ownDomApplierandEventDispatcher.historyis the shell's (fragmentHistory);headis supplied with a refusing interceptor, since a framed app's<head>is the frame's and whether it gets one is a later decision.Four degradations across the frame, each documented at its site:
prevent-default/stop-propagationare no-ops; the syncdomqueries answer none/false; eval is not granted and is asserted absent on the artifact; resource-carrying payloads cannot cross astructuredClone. The first produced a real bug the gate found — an un-prevented in-page anchor click navigated the frame's fragment, which still appends to the joint session history, so the shell's back button stepped through frame entries. The frame now refuses in-page anchor defaults on the guest's behalf.Two of the three documented gaps close; the third is reopened by the frame. Rich events and real bubbling come with the renderer.
onmounted/focus closes in the renderer and is reopened by the syncdomqueries being unanswerable across the frame — a different cause, same visible symptom, reported rather than buried.The spike had no automated gates. It now has two: the differential harness wrapped as Playwright, and a real-interaction test of the dioxus guest inside the frame that also asserts the frame cannot read the shell's anchor colour.
Size: 224 KB gzipped, from 130 — wasi-libc/std with the wasip2 target, the full renderer replacing ~530 lines of glue, and
dioxus-document/dioxus-historyfor the unconditional providers.2. The surface guests could not instantiate for fifteen days
Found by the new gate on its first honest run. Commit
4ec8d2f(#74, 2026-08-21) movedvisor/surface/surface.tsontopolyvisor:surface/*@0.1.0import keys;spikes/todomvc/wit/todomvc.witkept declaringpolymorph:todomvc-spike@0.0.1. The interface bodies were byte-identical, which is why it looked innocuous — but the package name is the import key, and every surface component died at import resolution. Nothing caught it because the spike's correctness lived in a page a human had to open.The fix is a deletion, not a rename. Two byte-identical definitions in the tree was the arrangement that produced this; the second copy was the defect.
todomvc.witnow importspolyvisor:surface@0.1.0through a repo-relative symlink towit/surface— one definition. 64 lines of duplicated WIT leave; guest paths follow mechanically; no guest behaviour touched, which is what lets the stepwise DOM comparison certify the change as purely mechanical.And the question the gate existed to answer has now actually been asked: the old surface components do survive the runtime bump — three backends, fifteen steps, stepwise-identical DOM; eight trap probes identical. Evidenced, not assumed.
Also
docs/spike-todomvc/is the justfile's build output and is now ignored alongsidedocs/demo/.Commits are unsigned — an agent's commits should not carry a signature asserting a human author.