Release: UI 2.11.10 + Solid 2.0.0-rc.4, capture viewport, and runtime script fetching - #30
Release: UI 2.11.10 + Solid 2.0.0-rc.4, capture viewport, and runtime script fetching#30pathscale wants to merge 7 commits into
Conversation
…0-rc.4 local-ui/package.json still asked for @pathscale/ui ^2.5.0, solid-js ^1.9.5 and solid-layouts ^0.1.3, from before the chrome was ported. Those are the peer ranges of a workspace member, so bun resolved them for the whole workspace and installed solid-js 1.9.14 and @pathscale/ui 2.5.0 -- the app has been building against Solid 1 while its own package.json asked for 2.0.0-rc.0. Widening the member's ranges is what makes the app's pins take effect. Also adds the @iconify-json sets. index.css @sources @pathscale/ui, which uses mdi-- tokens internally, and nothing installed them, so those icons resolved to nothing. babel-preset-solid moves to rc.2 behind a caret; the exact rc.0 pin could not take a fix. Verified: typecheck, biome, the 15 frontend tests, a production build, and the built chrome rendering its address bar, title bar and tabs. The scripts/solid-2-boundary.ts workaround stays. Its exit condition is solid-layouts-oxc consulting boundaryFor, which 0.2.2 now does -- but rsbuild-plugin-solid-layouts 0.2.1 carries its own 0.2.1 copy of the validator, and a bun override does not dedupe it. The pair goes when that plugin publishes against 0.2.2.
Nothing here uses daisyUI; the comment described the cascade problem in terms of a package this repository does not install.
A patch release: no exports added or removed against 2.11.9, and the same peer range. The caret already admitted it; this pins the lockfile to it.
`scripts/render-check.sh` has documented and exported CHUZZ_CAPTURE_WIDTH and CHUZZ_CAPTURE_HEIGHT since it was written, and the capture entry point read neither: it passed a literal 1440 by 960. Every capture was that size whatever the caller asked for, and a run at another width produced a byte-identical tree, which is how this surfaced. That matters now because captures are about to be compared against a reference browser. Two engines laying the same page out at different widths disagree on every percentage width, every centred box and every responsive breakpoint, so an unhonoured viewport turns a diff into noise that reads exactly like a rendering fault. Only CHUZZ_CAPTURE_SCALE was wired up, so the fix follows its shape.
The prefetch walks the parsed HTML and loads every script it names. A page that builds a `<script>` from JavaScript asks for a URL nobody knew about until the page was already running, and that request reached `DefaultScriptFetcher`, which serves `file:` and `data:` only. The script was dropped with `unsupported URL scheme for script: https`, which reads like a policy decision rather than the missing capability it is. Measured over a hundred-site corpus, this is the single most common engine defect: **26 sites**, a quarter of the corpus. It hides well, because scripts the parser found load perfectly, so a page fails only in the parts it assembles itself, and jQuery going undefined on seven sites looks like its own bug rather than a consequence of never having been fetched. `ScriptFetcher::fetch` is synchronous, because classic scripts execute in document order, so the network call blocks. It runs on the page's own provider and so keeps the per-origin connection cap the rest of the loads obey, and it is bounded by a timeout: a server that accepts and never answers would otherwise hang the capture, and a missing script is better than a run that never ends. Verified by re-capturing the six worst-affected sites: dropped scripts went 12, 6, 4, 2, 2, 2 to zero everywhere. Rendering barely moved, which is the honest result rather than a disappointing one. On one site the error count rose from 9 to 14 with a new kind, `unescape is not defined`: the scripts now run and reach the *next* missing global. This unblocks a layer; the missing web APIs behind it are what turn that into pixels. The test serves a script from a real socket and asserts a synchronous fetch completes a round trip from inside the runtime driving the page. Its accept loop has a deadline rather than blocking: restoring the defect makes no connection at all, and a blocking accept hangs the run instead of failing it. Confirmed by restoring the defect, which fails the test in ten seconds. Only the capture path. `browser.rs` has the same fetcher and is reached from a synchronous poll with no runtime handle to hand, so it needs a stored handle and is left for its own change.
The previous change fixed the capture path and left the window with the same defect, so the browser people actually use still dropped a quarter of the corpus's scripts while the tool measuring it did not. Measuring correctly is not the point of the exercise. The fetcher moves into `script_fetch.rs` and both paths share it, rather than the window growing a second copy of logic that must then be kept in step. The prefetch-then-fall-back shape was already duplicated between the two; it is now in one place. The deadline is per caller, and the two differ for a reason worth stating. `ScriptFetcher::fetch` is synchronous and page scripts run on the UI thread, so in the window this blocks everything, other tabs included, for as long as it waits. Five seconds there against the capture's ten: a capture is unattended and a dropped script costs it the fidelity it exists to provide, while a window has someone watching the frame. Neither number is the real answer. The real answer is an asynchronous script-loading path in the engine, which would not have to choose, and that is a change to blitz-script rather than to this. The window path is the same code the tests cover and the capture exercises, but it is not itself verified in a window: this machine has no screen access, and the control socket sees the chrome document rather than the page's sub-document. Re-captured after the refactor to confirm the tested path did not move: dropped scripts still zero on the worst-affected site.
038c913 to
12ca80d
Compare
The browser advertises two tools over the same socket. `chuzz-inspect` only ever called `blitz.agent.control`, so `blitz.diagnostics` — DOM and layout snapshots, renderer metrics, idle settlement, and the console and runtime-error streams — was unreachable from the client that exists to read the browser. That gap has a cost. A page that throws during evaluation reports it only to stdout, mixed in with the renderer's own logging, and a JS error is then indistinguishable from a paint trace. Driving honey.id here is what surfaced it: the page rendered blank, and the reason was a TypeError only visible by grepping the process output. `call` keeps its shape and routes to the agent tool, `diagnostics` is the same round trip against the other one, and both share `call_tool`. On top of that the CLI grows `console`, `metrics`, `settle`, `dom` and a verbatim `diag`. Two limits worth stating, both in the runtime rather than here: - `console` answers `streamingUnavailable`: diagnostic subscriptions are not implemented, and the runtime says so rather than pretending. - `dom` reports the chrome window. A page lives in a sub-document on its `<web-view>` mount and is still not in the tree, so page content remains reachable only by screenshot.
|
Added The browser advertises two tools over the same socket. That gap has a cost. Driving honey.id here is what surfaced it: the page rendered blank, and the only way to learn why was to grep the process stdout for
Verified against a running browser: Two limits, both upstream in the runtime rather than here, and both worth knowing:
|
|
Superseded by #33, which consolidates all chuzz work into one PR. |
This is the one to merge. Six commits, everything stacked: it now includes #27's frontend work as well as the corpus fixes. Supersedes #27, #28 and #29.
The bottom three are #27, unchanged and rebased forward. The top three are new, found by capturing a 104-site corpus headlessly and comparing against a reference browser. Sites are referred to by opaque id; the id-to-host map is local and not committed.
Capture viewport
scripts/render-check.shhas documented and exportedCHUZZ_CAPTURE_WIDTHandCHUZZ_CAPTURE_HEIGHTsince it was written, and the capture entry point read neither — it passed a literal1440, 960.Found because a re-capture at width 1507 produced a byte-identical tree dump to the 1440 run, which is not something a working viewport knob can do. It matters beyond tidiness: two engines laying the same page out at different widths disagree on every percentage width, centred box and responsive breakpoint, so an unhonoured viewport turns a rendering diff into noise that reads exactly like a rendering fault.
Scripts a page discovers while running
The prefetch walks the parsed HTML and loads every script it names. A page that builds a
<script>from JavaScript asks for a URL nobody knew about until the page was running, and that request reachedDefaultScriptFetcher, which servesfile:anddata:only. The script was dropped withunsupported URL scheme for script: https— reading like a policy decision rather than the missing capability it is.The most common engine defect in the corpus: 26 of 104 sites, a quarter.
It hides well. Scripts the parser found load perfectly, so a page fails only in the parts it assembles itself, and
jQuery/$coming out undefined on 7 and 6 sites looks like its own bug until you notice the library was never fetched.One commit fixes the capture path; the next fixes the window and moves the fetcher into
script_fetch.rsso both share it rather than keeping two copies in step.The deadlines differ per caller, deliberately
ScriptFetcher::fetchis synchronous, because classic scripts execute in document order. In the window, page scripts run on the UI thread, so this blocks everything — other tabs included — for as long as it waits. Five seconds there, ten in the capture: a capture is unattended and a dropped script costs it the fidelity it exists to provide, while a window has someone watching the frame.Neither number is the real answer. The real answer is an asynchronous script-loading path in
blitz-script, which would not have to choose. That is an engine change, not this one. This is a real tradeoff against the previous behaviour of silently dropping the script, and worth a second opinion before tagging.Verification
Re-captured the six worst-affected sites: dropped scripts 12, 6, 4, 2, 2, 2 → 0 on every one, and still 0 after the refactor.
Rendering barely moved, which is the honest result rather than a disappointing one. On one site the error count rose 9 → 14 with a new kind,
unescape is not defined: the scripts now run and reach the next missing global. This unblocks a layer; the missing web APIs behind it —fetch(4 sites),XMLHttpRequest(6),Image(4),getComputedStyle(3) — are what turn it into pixels.The test serves a script from a real socket and asserts a synchronous fetch completes a round trip from inside the runtime driving the page. Its accept loop carries a deadline rather than blocking: restoring the defect means no connection is ever made, and a blocking accept hung the run instead of failing it — which it did, until the test was fixed. Confirmed by restoring the defect: fails in 10.02s.
Green on the stacked branch:
cargo fmt --check,cargo clippy -D warnings, 34 Rust tests, and the frontendtypecheckincluding the local@chuzz/uilayouts regeneration.Not verified
The window path is the same code the tests cover and the capture exercises, but it is not itself exercised in a window: this machine has no screen access, and the control socket sees the chrome document rather than the page's sub-document. Worth a human look before tagging.