Repository navigation
Load a module a page declares, view its source, and fix the seven chrome defects - #19
Merged
Merged
Conversation
added 15 commits
August 15, 2026 19:37
`run_guest` read the file and instantiated in one body, so a caller with bytes already in hand had nowhere to enter. A module fetched from a page never touches the disk. `run_guest_bytes` is that entry point and now holds the whole sequence: validate, link, `instantiate_and_start`, call the entry export, check the status, warn when a guest reports OK without mutating anything. `run_guest` reads the file and calls it. One copy rather than two, deliberately. Everything that decides whether a guest ran correctly is subtle enough to be worth having in a single place -- the note on why this is `instantiate_and_start` and not `instantiate` has to keep applying to the only copy of that call. No behaviour change: both existing callers keep the signature they had.
A page can now hand its interface to a WebAssembly guest: <script type="application/wasm" src="/demo.wasm" mount="#root"></script> The module is fetched with the page, vetted, and mounted against the parsed document. No JavaScript anywhere in the path: a page that declares a module is parsed without a script runtime, because attaching an engine to run none is dead weight. This is the other shape from `--wasm`. That replaces the document entirely -- an empty `<html><body>` the guest fills, no HTML at all. Here the document exists first, including whatever sits inside the mount, and the guest takes that element over. The fallback is the reason for most of the care here. It is what a browser without wasm shows, so every way of failing has to leave it standing: a bad selector, a 404, a server answering 200 with its index page, a module that instantiates and then reports a non-OK status. The mount is emptied only after the entry export has returned OK. Failure re-parses rather than repairs. A guest appends into the mount as it builds, so a run that fails part way leaves its own half-built tree beside the fallback with no way to tell them apart afterwards. Parsing the same bytes again is the only way to be certain the document is what the page said. Bytes are vetted before wasmi sees them, because the failure that catches is not a corrupt module. The site this exists for is behind a CDN that answers 200 with its index page for every unknown path, so a missing module arrives as `<!doctype html>` and wasmi reports byte offsets in a file that is not a module. That has to say the server returned HTML: the fix is a deploy, not a rebuild. Same origin only. The guest runs against the host ABI with the whole document reachable, which is a different trust decision from a local file named on the command line. Cross-origin needs a policy, not a default. `runtime="..."` is parsed and ignored. Nothing implements preloaded runtimes yet and the live page carries the attribute, so failing on it would reject the page it is meant to serve.
`view-source:https://example.com` fetches what it names and shows the bytes instead of rendering them. No new URL parsing. `view-source:https://x` already parses as a URL whose scheme is `view-source` and whose path is the inner address, so `request_from_input` accepts it, the address bar shows it, and history holds it like any other entry. Opening it in a new tab is the existing `open_tab` command with that address; nothing here needs to know whether it is a new tab or the current one. The source is escaped and put in a `<pre>`, and that is the whole job. Nothing may reformat, pretty-print or re-serialise it: a source view that showed a parsed and re-emitted tree would be answering a different question. For a page whose claim is "there is no script here" it would be the wrong answer, because the reader is checking the bytes rather than the tree. The test pins the properties that matter: the script tag survives with its type readable, markup is escaped rather than interpolated, and ampersands are escaped before angle brackets -- the other order turns `<` into `&lt;` and quietly corrupts every escape on the page.
The scheme existed and the only way to reach it was to type it. Cmd-U is what Chrome and Safari bind, and it opens a new tab rather than replacing the page, so the source and the page it came from stay side by side. Inert on a tab that is already showing source: the naive version opens view-source:view-source:https://..., which the address bar accepts and nothing can render.
The Inspect/Act surface has been there since the diagnostics work, and
nothing in this repository could speak to it. The wire is MCP JSON-RPC
over endpoint-libs' length-delimited framing, so "connect and ask" was
a project rather than a command, and UI work went on being reported from
log lines and exit codes instead. Two bugs reached the user that way.
chuzz-inspect tree the chrome, with every box
chuzz-inspect overlap <a> <b> do two controls collide
chuzz-inspect click <node> through hit testing, not the handler
chuzz-inspect type <text> one key at a time
`type` rather than `set-value` for anything that matters. The runtime's
set-value asks for a select-all first, and on this engine that arrives as
a literal 'a' in the field: a URL entered that way becomes
ahttps://example.com, which parses, fetches nothing, and renders the
browser's own error page. Two navigation "failures" were diagnosed from
that before the field was read back.
…rops Every control in the title bar was inert. Not the tab strip's own doing: solid-layouts 0.1.2 exempted compiled components from carrying a caller's plain-HTML props onto the root element, so `onClick`, `title` and `aria-label` passed to any `@pathscale/ui` component were dropped before they reached the DOM. Settings could not be clicked, new-tab did nothing, and no tooltip in the window had ever appeared. Reproduced outside Blitz to be sure it was not the engine: in Chromium the same bundle put no click listener and no title attribute on those buttons at all. After the bump, `'$$click' in button` is true and `title` reads "Settings" and "New tab". 0.1.3's own note puts it at 72 of the 78 failures from porting one application to a Layout-based library, each of which read as an application bug. @pathscale/ui 2.5.0 requires it, so the two move together.
Diagnostics was two runtime switches over one compiled-in plane. The second plane, tauri-runtime-blitz's `debug-control`, was not in the binary at all, so the only thing that can photograph the window did not exist in the build that needed photographing. Turning it on costs nothing on a run where nobody asks: the listener binds only when TAURI_BLITZ_DRIVER names an address. The switches now say what each layer is for rather than what it is made of. Agent control is the browsing layer, the semantic tree and pointer and keyboard input, which is what a program driving this window as a browser needs. Deep debugging is the other kind of question, why the window looks wrong: screenshots, layout and computed-style snapshots, renderer metrics, the intrusive collectors. `chuzz-inspect screenshot` reaches the second one. It releases its session on exit, because the server allows exactly one and keeps it after the socket closes: without that, the second screenshot of any browser run failed with "only one session is supported" and the fix looked like restarting the app.
A tab said loading or idle, so a page whose scripts all 404'd looked exactly like one that worked, and a page that never arrived looked the same again. Those are the states a person tells apart at a glance, and the browser knew all of them and threw them away. `PageOutcome` is ordered by severity and accumulated with `max`: the document arriving sets the floor at Ready, and each thing the page named that did not arrive can only raise it. A script that fails to fetch now says so on stderr as well, which it never did: silently swallowing that is the single most common reason a page renders and then does nothing. The names are the contract with `LoadStatus` in the frontend's types. The bottom strip reads the same value as the tab, so a strip and a dot cannot disagree about the tab they are both describing.
Five defects in the title bar, each measured in the running window
through the control socket before and after.
The tab title ran under the close button. `.tab-title` had `overflow`
and `text-overflow` and nothing to act on: a flex item's automatic
minimum is its content, so `flex-grow: 1` grew it and nothing shrank it.
Two of the tab's rules also lost to `@pathscale/ui`'s own on the same
element. `.tabs__tab` sets `padding: 0 16px` and ties with a bare
`.tab`; PostCSS expands the square button's aspect ratio into
`.button--width-square.button--sm { width: 2.25rem }`, which ties with a
bare `.button.tab-close`. Both won on source order, so the pill's right
pad stayed 16px and the close button stayed 36px wide. Prefixing both
with `.tab-shell` settles it. Measured: title 137-216, button 225-242,
clear, where they were 146-225 against 194-233.
The status pill is a dot. A `Chip` is a labelled token and brought a
chip's geometry with it, so 7px lost to its padding and line height and
rendered as a 15x22 capsule. A `Badge` is a marker, and its flavours are
exactly the five meanings, so the colours come from the library rather
than from five literals here. Its default `top-right` placement is
absolute and had to be turned off, which is what put the dot at the far
end of the pill instead of before the title.
New tab and Settings were 36px circles beside 34px tabs. 26px.
The "N" was an Avatar holding a hardcoded letter, a placeholder for an
account system this browser does not have. The component goes with it.
There was no vitest config, so the suite ran in Node with no JSX transform and the only testable thing was a pure function. Every defect in the tab strip was untestable by construction, which is a large part of why seven of them shipped at once. `vite-plugin-solid` and `jsdom` were already dev dependencies; the wiring was what was missing. jsdom has no layout engine, so an overlap cannot be measured here. The measurement lives in `chuzz-inspect`, which reads the real boxes out of a running window; these assertions stop the declarations that produced the right boxes from being deleted, and they carry the measured numbers in their comments so the next reader knows what the geometry is for. Tests compile separately. `tsconfig.test.json` is where `node` types are allowed, and the app's own compilation excludes these files, so `readFileSync` cannot reach code that ships in a browser bundle. The stylesheet is read off disk rather than imported: Vite's CSS pipeline claims `.css` before `?raw` can and hands back an empty string, which had four assertions passing against a stylesheet nobody had read.
view-source produced a document that was fetched, parsed, laid out and painted, and looked like a black rectangle. It declared no colours, so it took the engine's default black text on a transparent background, over a viewport the shell paints with the dark theme surface. That is indistinguishable from not rendering, and was reported as the source not showing. Light and explicit, like every other browser's error and source pages, and not a theme token: these are documents inside a page viewport, and nothing in a page can reach the shell's custom properties. The error page and the empty-response page had the same problem and are covered by the same change.
The browser narrated a page load to a terminal nobody had open. A page that renders and then does nothing is almost always a script or a module that never arrived, and that fact was only ever available to whoever had started the binary from a shell. Every navigation, fetch, script, module and mount now goes to a ring buffer as well, and the inspector's first section is that stream: source tag, message, colour by level, newest at the bottom. 500 lines, because this records a line per subresource of every page for the life of the process. Both a push and a pull. The event keeps an open panel current; the pull is what lets a panel opened after a load still show what happened, and what stops a dropped event leaving it permanently behind. The startup read of the log is defended. It is the one value the window does not need in order to draw, and a bridge that answered `null` for it put `null` in the store, the section read `.length` off that, and the entire interface failed to render.
The viewing area painted the theme surface and let a page's own transparent background through. That is right for a blank tab and wrong for every document: a page that declares no background of its own is written against the white canvas every engine gives it, and its default black text was landing on a near-black desk. The demo site is the case that found it. The page loaded, its module fetched and validated, the guest ran, 36 nodes were built and laid out, the fallback was replaced, and what reached the screen was black on black. Nothing in the log said anything was wrong, because nothing was. A blank tab keeps the surface and says so through the tab's own status, which the shell already reports.
Four things in the window, all reported together. The gear had never rendered. `<Icon src="icon-[mdi--cog]">` asks @iconify/tailwind4 to emit a rule carrying the glyph as a mask; that plugin reads the glyph from an installed icon set, @iconify-json/mdi is not a dependency here, and the build says `Invalid icon name` once and carries on. Nothing matching `icon-[` reaches the stylesheet at all, so the button was a correctly sized, correctly positioned, entirely empty span. Drawn inline instead: no icon set, no plugin, no mask support needed, and confirmed painting at 15px in the window. The panel seam was 5px of hairline with a chevron on it that had `pointer-events: none`, so the only thing you could hit was a band narrower than a cursor. 18px of seam with the hairline drawn rather than being the element, and the chevron takes pointer events like the rest of the button. The panel also stops short of the frame so the control has room to sit on. Sections did not close. `Collapsible` collapses by animating `grid-template-rows` from `1fr` to `0fr`, and the body kept its full height in both states: measured in the window, closing the debugging section left the one below it at y=544 both times. `display: none` on the closed state is unambiguous in both engines this has to work in. Now 544 open, 194 closed. New tab and Settings down again, 26px to 22px.
This was referenced Aug 16, 2026
anyrender f1c53ba renders a backdrop from inside the layers a page opens instead of declining it, ps-blitz a81bea5 carries that plus a screenshot painted at the document's own scale, and tauri-runtime-blitz 070ab3a follows both. All three move together: two revisions of one git source put two copies of a crate in the graph and surface as unrelated `PaintScene` traits rather than as a version error. Glass now renders in the CPU paths. Verified through this pin, with no local redirect: a page with a `backdrop-filter: blur(12px)` panel inside an `overflow: hidden` wrapper captures with hard-edged stripes outside the panel and smeared ones inside it. blitz-wasm moved its status reporting to a `Status` newtype over the raw `i32`, replacing the bare `OK` constant and the `status::name` free function. The guest runner wraps once and reads both from it.
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.
One branch, 15 commits, one version bump to 0.1.31. Supersedes #17 and #18, which are closed.
Every claim below was measured in a running window through the control socket, before and after.
Three things arrive here
A page can hand its interface to a WebAssembly module.
<script type="application/wasm" src=... mount=...>is fetched same-origin, validated, instantiated and mounted. Every failure keeps the page's fallback standing: the fallback is removed only after the entry export returns OK, and a module that fails part way causes a re-parse from the same bytes rather than a repair, because a guest appends into its mount as it builds and a half-built tree cannot be told from the fallback afterwards.view-source:and Cmd-U. The scheme shows the bytes that arrived, escaped into a<pre>, never re-serialised. Cmd-U opens it in a new tab, and is inert on a tab already showing source.The seven reported chrome defects, with the tool that made them checkable.
The tool first, because nothing else was checkable without it
The Inspect/Act surface had existed since the diagnostics work and nothing in this repository could speak to it, so UI work was being reported from log lines and exit codes.
chuzz-inspectis that client:tree,overlap a b,clickthrough real hit testing,type,screenshot.typerather thanset-valuefor anything that matters. The runtime'sset-valueasks for a select-all first, and on this engine that arrives as a literalain the field: a URL entered that way becomesahttps://example.com, which parses, fetches nothing, and renders the browser's own error page. Two navigation "failures" were diagnosed from that before the field was read back.The seven
<Avatar label="N" />4 was not a chuzz bug. solid-layouts 0.1.2 dropped every plain-HTML prop passed to a compiled component, so
onClick,titleandaria-labelnever reached the DOM: the whole title bar was inert and no button in the window had ever had a tooltip. Reproduced in Chromium to rule out the engine. 0.1.3 fixes it and @pathscale/ui 2.5.0 requires it, so the two move together.1 and 2 were the same defect. The viewing area painted the theme's dark surface and let a page's transparent background through. That is right for a blank tab and wrong for every document: a page that declares no background is written against the white canvas every engine gives it, and its default black text was landing on a near-black desk. The demo site is the case that found it, and the browser's own error and source pages had it too.
3 needed three passes, because two of the tab's rules tie with @pathscale/ui's on the same element and lose on source order.
.tabs__tabsetspadding: 0 16px; PostCSS expands the square button's aspect ratio into.button--width-square.button--sm { width: 2.25rem }. Prefixing both with.tab-shellsettles it.Two more found while verifying
The settings gear had never rendered.
icon-[mdi--cog]needs an installed icon set for @iconify/tailwind4 to read the glyph from;@iconify-json/mdiis not a dependency, the build saysInvalid icon nameonce and carries on, and zeroicon-[rules reach the stylesheet. Drawn inline instead, and photographed painting.Inspector sections did not close.
Collapsibleanimatesgrid-template-rowsfrom1frto0frand the body kept full height in both states, so all five were permanently open.Diagnostics, and the debugging panel
Two runtime layers, named for what each is for. Agent control is the browsing layer: the semantic tree, and pointer and keyboard input. Deep debugging is the other question, why the window looks wrong: screenshots, snapshots, metrics, the intrusive collectors. Both are compiled into every build and both are off until asked for. A capability that is absent rather than off cannot be turned on at the moment it is needed, which is always the moment something is already wrong.
The inspector's first section is now the browser's own narration: every navigation, fetch, script, module and mount. It went only to stderr before, where nobody watching the window could see it, and a page that renders and then does nothing is almost always a script or a module that never arrived.
Tests
There was no vitest config, so the suite ran in Node with no JSX transform and the only testable thing was a pure function. Every defect in the tab strip was untestable by construction, which is a large part of why seven shipped at once.
vite-plugin-solidandjsdomwere already dev dependencies; the wiring was missing. 15 frontend tests, 29 Rust.jsdom has no layout engine, so an overlap cannot be measured there. The measurement lives in
chuzz-inspect; these assertions stop the declarations that produced the right boxes from being deleted, and carry the measured numbers in their comments.Not in this PR
Glass. It needs ps-anyrender#9, and
tauri-runtime-blitzpins the sameps-anyrendersource, so both have to be repinned together orPaintScenebecomes two unrelated traits.