Skip to content

refactor: one control surface, and no Tauri runtime in a headless browser - #45

Merged
pathscale merged 11 commits into
masterfrom
feat/one-control-surface
Sep 11, 2026
Merged

refactor: one control surface, and no Tauri runtime in a headless browser#45
pathscale merged 11 commits into
masterfrom
feat/one-control-surface

Conversation

@pathscale

@pathscale pathscale commented Sep 9, 2026

Copy link
Copy Markdown
Owner

The headless host now delegates document actions to the same blitz-control-protocol implementation used by the runtime. Pointer phases, keyboard phases and scrolling therefore reach the real document instead of an incomplete host-specific action match.

The headless build gate avoids building browser chrome for a binary that does not use it. A separate system-fonts feature lets rendered QA load real fonts while minimal embedders can omit font discovery. The shared website action installs Linux fonts/fontconfig, builds without GUI features, and requires ps-qa 0.7.1. Action responses have a bounded settle period while the host continues processing pending work.

WebSocket event dispatch now snapshots listeners. Previously, GetApps and CreateApp could both wait for a connecting socket; the first open listener removed itself and the live-array loop skipped the second. CreateApp then hung without sending a request. Removed listeners are still skipped and newly registered listeners wait for the next dispatch.

The Cargo package was renamed to chuzz, but CI, release metadata, and render-check scripts still selected chuzz-gui. Those callers now use the published package name. The control test also reflects the shared protocol's supported headless pointer, wheel, and scroll actions; Relaunch and Quit remain unsupported in the headless host.

Validation: the full GUI release build, full workspace test suite, workspace clippy, and release-profile font-enabled chuzz-headless build pass. Honey passes 193/196 full-suite outcomes, with the three failures isolated to the backend's empty API-key regeneration response; its dedicated truthful error path passes 18/18 and recovery-code flow passes 33/33. Native input and six driver regression scenarios pass. UI passes 273 checks across 75 fixtures; Worktables passes 112/112; Web3 passes 103/103.

Current CI failures are the expected unpublished dependency boundary: this branch requires blitz-control-protocol ^0.5, which is released by the PR immediately before it. The frontend and formatting jobs are green. Do not weaken the dependency chain to make an intermediate PR install from crates.io early.

Release order: ps-blitz #95ps-observability #21tauri-runtime-blitz #57 → this PR → UI and approved consumers. Wait for each publication; the local validation uses coordinated path overrides while those versions are unavailable.

The owner reviews the PR before any merge, publication, or deployment. The capture feature still reaches blitz-shell for diagnostics; this does not claim a completely window-library-free capture graph.

meh added 8 commits September 9, 2026 17:26
`cargo pkgid` resolves the dependency graph, so it requires a `Cargo.lock`.
This repo stopped committing one, and the command has failed ever since:

    error: a Cargo.lock must exist for this command

The step did not fail with it. `VERSION` was assigned the empty string from a
failed substitution, the job continued, and the bundle verification compared
`0.1.37` against nothing:

    ##[error]bundle reports 0.1.37 but this release is

So 0.1.37 was merged, built and signed, and never published.

`cargo metadata --no-deps` reads the workspace manifests without resolving
anything, needs no lock, and creates none. Verified both ways with the lock
moved aside: it returns 0.1.37 offline, while `pkgid` reproduces the error
above.

The empty-version guard is the other half. A release that cannot name itself
should stop rather than publish under a blank version.
`build_frontend` ran unconditionally, so `cargo build --bin chuzz-headless
--no-default-features` shelled out to `bun run build` in
`apps/chuzz/frontend` for assets that binary never links. `frontend.rs` is
the only consumer of the generated module and is already behind `gui`.

On a machine without `apps/chuzz/frontend/node_modules` the build script
panicked instead:

    error: script "layouts:local" exited with code 127
    error: script "prebuild" exited with code 127

That is every runner using the `headless-host` action, which installs the
site under test's dependencies and has no reason to install this crate's, so
the whole fleet's QA went red in the host build step.

Verified both ways with `node_modules` moved aside: the headless build now
finishes, and `--bin chuzz-gui` still fails there with the error above,
which is the proof the gate did it rather than something else.

The helpers move behind the same feature so an unused import does not become
a denied warning.
`rustls` picks its provider from crate features and panics at the first
handshake when the graph enables neither `ring` nor `aws-lc-rs`, or both.
Features are additive across a graph, so which of those holds is an outcome
of resolution rather than a decision anyone made, and with no lockfile it is
not fixed at any point in time: one dependency picking up `ring` in a later
release is enough to turn every `https://` fetch and every `wss://`
connection into a panicked worker on the next runner that resolves it.

It was hit on a fresh resolution during QA work, on `rustls 0.23.43`, and
went away on re-resolution to 0.23.44. Today's graph enables `aws-lc-rs`
alone, so this is latent rather than reproducible here, which is exactly the
problem: nothing holds it there.

The failure does not look like a browser failure. The socket never opens,
Solid halts reactivity on the escaped error, and the page collapses to
unnamed nodes, so a QA run reports a broken site.

Both binaries now install a named provider before anything can reach the
network. Verified by capturing an https page end to end.
The corpus tooling was rewritten out of the repository, so nothing under
scripts/corpus is Python any more and no build step produces bytecode. The
ignore rule outlived the files it was written for.

Leaving it in place is worse than merely dead. Python is not allowed in this
tree, and an ignore rule for its bytecode is the one thing that would keep a
reintroduction out of git status, so the rule quietly works against the
convention it now has nothing to serve.
The headless browser depended on `tauri-runtime-blitz` with
`default-features = false` and `agent-control` plus `diagnostics`: a Tauri
runtime with the runtime switched off, taken purely to reach an inspection
socket. It also read the wire vocabulary through that crate's re-export
rather than depending on the protocol at all, so naming an
`AgentControlRequest` meant compiling Tauri, and on Linux that reaches GTK.

The control surface now lives in `blitz-control-protocol`, so this takes it
directly. `tauri-runtime-blitz` becomes optional and is pulled only by
`gui`, which is the build that actually opens a window.

`tauri-build` goes the same way. `build.rs` only calls it under `gui`, but
the build-dependency was unconditional, so a headless build compiled it and
its two transitives for a binary that never uses it. The same fault, one
plane down.

Measured on the headless feature set, 1810 lines of dependency tree:

    tauri-runtime-blitz: 0
    any tauri:           0

and cargo says so itself, unprompted:

    warning: patch `tauri-runtime-blitz v0.4.0` was not used in the crate graph
The package was `chuzz-gui` with `publish = false`, so the library its own
header calls "the browser, as a library, so that more than one binary can
be it" was named after one of its binaries and could not be depended on at
all. AgencyZero is about to embed it.

The package is now `chuzz` and publishable. Both binaries keep their names,
because build-app.sh, the release workflow and the Homebrew cask all
reference them.

`serve` gains the name for what it is: the headless runtime for Blitz, the
pair to `tauri-runtime-blitz`. It stays a module rather than becoming a
crate because it needs `document_loader`, `page_server`, `nav` and
`identity`, which is most of a browser, and the last attempt to package it
separately is the `qa-inspect-host` story in its own header.

The unused `chuzz-control` dependency goes: it was declared and never
imported, and the vocabulary it duplicated now comes from
`blitz-control-protocol`.
Nothing in this crate imports `dioxus_native`. It was an unconditional
dependency, and because it rejects a build that names no renderer, the
headless feature set carried `vello` to satisfy it. `vello` is wgpu, naga,
winit and AppKit, in a binary whose only rasteriser is the CPU one.

It is optional now and `gui` turns it on. Measured on the headless feature
set, with `vello` no longer named at all:

    wgpu           17 -> 0
    naga            6 -> 0
    dioxus-native   2 -> 0
    objc2-app-kit   4 -> 1
    tree          1810 -> 1378 lines

The CPU rasteriser is untouched: `ps-anyrender-vello-cpu` is still there,
which is what `capture` was always for.

`winit` survives at 6, and its path is now exact:

    winit <- ps-blitz-shell <- blitz-control-protocol <- chuzz

The protocol crate's `capture` feature takes blitz-shell for frame timings
and the deep-profiling session. Splitting the rasteriser from the
diagnostics would finish the job; it is a feature split in that crate, not
here.
@pathscale
pathscale merged commit 5590227 into master Sep 11, 2026
6 of 8 checks passed
@pathscale
pathscale deleted the feat/one-control-surface branch September 11, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant