Remote handoff · Revised path · Proof gated
A remote agent can ship Weaver capture without refactoring the runtime first
Weaver's normal bootstrap already loads the manifest, storage, providers, images, QuickJS, and WidgetApp before it enters the Native SDK runner. The runner already has a headless sibling in session replay, so capture belongs there. Do not extract WidgetSession and do not add a second executable until a receipt proves either is needed. The corrected GPU frame sequence is still unverified: all three local prototype PNGs were fully opaque black, so the remote implementation starts by producing one visibly correct image.
The handoff decision
Recommended decision: add capture beside
runSessionReplay, reuse the existing Weaver bootstrap and automation command model, and ship one-shot capture before action scripts or a viewer. A persistent agent session stays deferred until valid captures establish real startup and interaction latency.
| Question | Decision | Reason |
|---|---|---|
Extract WidgetSession first | No | The current bootstrap reaches the runner with the real bundle and app already constructed. |
Add weaver-capture as another executable | Defer | Session replay already lives in the production binary behind an environment branch. Measure binary and resident cost before splitting it. |
| First agent contract | One-shot capture | A fresh process removes cross-rebuild node identity and lifetime problems. |
| Interaction model | Reuse current commands | The SDK already dispatches click, drag, wheel, key, text, context menu, resize, snapshot, and screenshot actions. |
| Recorded external inputs | Reuse session journals | Journals already carry events, effect results, fingerprints, screenshot marks, and mismatch ordinals. |
| New fixture format | Weaver providers only | CPU, memory, media, and audio provider frames cross Weaver's host pipe and are not journaled by the SDK. |
| Persistent agent session | Measure later | The only timing receipts came from invalid black captures. |
Source: Weaver runtime/src/main.zig:1239-1491; Native SDK src/app_runner/root.zig:392-417,693-878; local review and prototype inspection on 2026-08-24.
The safety fact is now narrower and better: capture can branch at the existing SDK runner after Weaver has built the real app. Source inspection and the reproduced null-backend link failure take that fact to proof level 3. A valid end-to-end PNG is the missing level 4 proof.
Start from a clean remote checkout
The uncommitted runHeadlessCapture prototype exists only in a separate local checkout. It is evidence, not an input. A remote agent must reimplement the small runner change from the contracts below and must not assume the prototype was pushed.
| Item | Known evidence state | Remote rule |
|---|---|---|
| Weaver source | e69a0997c5a883a69514674fab7dfcdbe0016114 | Record the assigned checkout's actual SHA before editing. Do not silently reset a newer branch to this evidence commit. |
| Native SDK pin | 4c5c0999c2324b8dc2016625e85571e554b2484a | Initialize the submodule, create a real branch inside it, and push its commit before updating Weaver's pointer. |
| Prototype | Uncommitted local submodule diff | Do not depend on it. The shipping change must factor shared startup instead of copying its event sequence. |
| Prototype output | Clock, text, and pomodoro PNGs were uniform black | Dimensions, PNG validity, byte count, and nonzero alpha do not prove a capture. |
| Toolchain | Zig 0.16.0; Node 24.19.0 locally | Zig must be 0.16.0. Node must satisfy the repository's >=20.11 engine. |
Source: repository commits and manifests, package.json, .gitmodules, local Git state, and Native SDK AGENTS.md.
# Fresh remote setup
git clone --recurse-submodules https://github.com/SunkenInTime/weaver.git
cd weaver
git submodule update --init --recursive
npm ci
# Record provenance before work starts
git rev-parse HEAD
git -C runtime/native-sdk rev-parse HEAD
node --version
zig version
# Create branches in both repositories before the first edit
git switch -c agent/widget-capture
git -C runtime/native-sdk switch -c agent/headless-capture
Source: repository remotes and the submodule branch declaration in .gitmodules. Branch names are examples; keep the remote executor's assigned naming convention.
host requirement Use a macOS remote for the first complete reference-capture proof. A Windows remote owns the Windows proof. A Linux-only machine can edit and run portable SDK tests, but the current Weaver runtime build accepts macOS and Windows targets only, so Linux cannot claim end-to-end completion.
Use the runner path that already exists
Remote implementation path · no session extraction
Source: Weaver bootstrap in runtime/src/main.zig:1239-1491; SDK runner, session replay, automation server, session journal, and screenshot renderer at Native SDK 4c5c0999.
The runner branch is the only new lifecycle. The CLI remains responsible for checking and bundling. Weaver remains responsible for its actual app. The SDK remains responsible for platform events, retained rendering, screenshots, snapshots, and input dispatch.
Build five proof-gated work packages
Package 0: fix and pin the backend gate
- Reproduce
zig build -Dplatform=null -Dautomation=true -Doptimize=ReleaseFastfailing on_native_sdk_appkit_render_host_run. - Gate
--render-hoston the selected Native SDK backend, not onlybuiltin.os.tag. Ifmain.zigcannot read that backend today, expose it throughweaver_build_optionsor move the render-host branch into backend-specific code. - Add a macOS null-backend build test so the link regression cannot return.
- After the fix, run the unmodified clock bundle. Reaching
UnsupportedViewKindproves the real Weaver bootstrap entered the null runner.
Exit: the null build links, the normal macOS build still accepts --render-host, and the capture environment never enters render-host mode.
Package 1: land a real Native SDK capture mode
- Add the capture branch beside
runSessionReplayinsrc/app_runner/root.zig. - Factor the null startup dispatch shared by normal null execution and capture. Do not keep two handwritten copies of app start, appearance, surface, window, and shutdown ordering.
- Enable
gpu_surfacesandimage_decodefor capture. Install macOS headless text services only in the macOS-backend build. - Drive the created GPU view with
gpu_surface_resizedand the frame events the runtime actually requests. Do not preserve the prototype's unexplained two-frame constant. - Render through
renderCanvasScreenshot, encode PNG, and reuse the existing automation snapshot generator. - Count pixels that differ from the declared clear color. Also report retained node, command, image, and font counts. Alpha alone is not a content test.
Exit: clock, styling-text, styling-images, and pomodoro produce the expected dimensions and visibly non-flat content. Styling-interaction exposes actionable semantics. Noro Shell proves registered fonts and local images. The agent or human must open the PNGs before calling this package done.
Package 2: expose weaver capture
- Add
weaver capture <directory> --out <file.png>to the closed CLI parser. - Call
bundleWidgetdirectly. Never calldevWidget, register the widget, startweaverd, open a desktop window, or connect shared renderer IPC. - Spawn the existing widget runtime with an internal capture request. Keep stdout to one JSON receipt and send human diagnostics to stderr.
- Create an explicit capture-state root that works on macOS and Windows. The runtime must not read or write the live widget store, logs, placement, or provider endpoint by accident.
- Write PNG, snapshot, and receipt files atomically. A failed command must not leave an artifact that looks complete.
Exit: a fresh agent can run one command from a source widget directory and receive the PNG, semantic snapshot, receipt, and an exit status that agrees with them.
Package 3: add short deterministic action scripts
- Accept repeatable actions or an action file in the same short-lived process that produces the final capture.
- Resolve targets by role, accessible name, and current semantic state. An ambiguous target must list the matching controls instead of choosing one.
- Translate resolved actions into the existing automation command model and dispatcher. Do not create another click, drag, wheel, key, text, or resize implementation.
- Allow explicit clock advancement. Never sleep until an animation looks settled.
- Accept a session journal as an optional recorded-input mode. Keep explicit Weaver provider fixtures for provider states that the SDK journal does not carry.
Exit: an action script starts the pomodoro, advances its clock, and captures the changed state; a slider drag changes both the snapshot and the PNG; the same script can be attached to a bug report and replayed in CI.
Package 4: decide the viewer from receipts
- Measure the valid one-shot path across the example corpus before choosing a persistent agent process.
- If process startup dominates useful work, keep a session alive internally. If it does not, keep the agent contract one-shot and reserve persistence for the human viewer.
- Build the loopback viewer as a PNG and semantic-overlay client. It must never reproduce widget layout in HTML.
- Add repository pixel gates for selected examples. Keep
weaver checkfocused on failures knowable without an expensive render until capture latency earns a broader default.
Exit: the viewer architecture cites the measured bundle, spawn, startup, render, encode, and write costs. No hidden frame cadence ships.
Make the receipt sufficient without the source
# Contract shape; fields marked number carry measured integer counts or durations
type CaptureReceipt = {
schema: "weaver.capture.v1";
status: "ok" | "error";
provenance: {
weaverCommit: string;
nativeSdkCommit: string;
bundleSha256: string;
platform: string;
};
output: {
image: string;
snapshot: string;
widthPx: number;
heightPx: number;
scale: number;
pngBytes: number;
};
renderer: {
pixels: "reference";
textMeasurement: "estimator" | "coretext";
eventsDriven: string[];
framesDriven: number;
retainedRevision: number;
commands: number;
nodes: number;
pixelsDifferentFromClear: number;
};
pending: {
timers: number;
fetches: number;
images: number;
providers: string[];
frameRequests: number;
};
timingUs: {
bundle: number;
spawn: number;
startup: number;
render: number;
encode: number;
write: number;
total: number;
};
inputs: {
clock: string;
storage: string;
providerFixtureSha256?: string;
sessionJournalSha256?: string;
};
warnings: string[];
error?: { name: string; ask: string; remedy: string };
};
Source: Weaver's agent-facing error rule, the existing automation snapshot, session journal provenance, and the black-frame failure found by the prototype.
A blank image is not automatically an error because a widget may intentionally be transparent. The receipt makes blankness explainable: clear-color difference, retained commands, semantic nodes, and pending work tell an agent whether it captured an intentional empty state, an undriven frame, or unfinished input.
Measure before setting a latency or frame budget
| Case | What it proves | Required artifact |
|---|---|---|
examples/clock | Known 240 px by 110 px static baseline | PNG, snapshot, full timing receipt |
examples/styling-text | Text measurement, wrapping, and glyph resources | Reference PNG on every host; macOS parity PNG when available |
examples/styling-images | Local decode, registration, and draw | PNG plus image pending and failure counts |
examples/styling-interaction | Semantic targeting and real input dispatch | Before and after snapshots and PNGs |
examples/pomodoro | Explicit clock advancement | Action script and changed-state receipt |
examples/noro-shell | Heavier bundle, registered font, and several images | Cold and warm stage timings |
| Provider-backed example | Named missing input and fixture behavior | Error receipt without fixture; valid capture with fixture |
Source: shipped Weaver examples and the project's rule that every limit needs a receipt.
Record cold and warm runs separately. Keep bundling separate from process startup and screenshot work. Only after this table is complete may the implementation set a latency tripwire, a maximum driven-frame count, or a policy for keeping an agent process alive.
Say which platform truth each image carries
| Mode | What it uses | What it may claim | Gate |
|---|---|---|---|
| macOS reference | Null backend, estimator text, CPU reference pixels | Deterministic agent feedback without a window or Metal presentation | Must build and run without the Metal toolchain |
| macOS parity | macOS backend, CoreText measurement, CPU reference pixels | Desktop-equivalent layout measurement, not OS glyph rasterization or compositor parity | Requires a macOS build and currently encounters the Metal toolchain dependency |
| Windows reference | Null backend and engine measurement | Only what an actual Windows capture proves | Compare against a live Windows widget before naming parity |
| Linux portable work | Native SDK portable tests | SDK behavior only | Cannot close the Weaver end-to-end gate |
Source: Native SDK macOS installHeadlessTextServices, Windows platform services, screenshot renderer, and Weaver runtime/build.zig target checks.
There is no proven Windows capture mismatch yet. The absence of a Windows headless text hook is a question, not a receipt. The remote owner must compare current Windows capture and desktop output before turning that question into a product claim.
The remaining blast radius is lifecycle, state, and build linkage
| Area | Real failure | Likelihood | Cost | Required check |
|---|---|---|---|---|
| Render-host backend gate | A null build links AppKit or a macOS build loses --render-host. | Medium | High | Build null and macOS variants; exercise render-host argument parsing. |
| SDK runner startup | Capture and normal null execution dispatch startup or shutdown in different orders. | Medium | High | One shared helper plus event-order tests. |
| GPU frame drive | The command writes a valid clear-color PNG and exits zero. | High until proven | High | Open output; compare against clear color; assert commands and nodes for visible examples. |
| Capture state root | The command reads or mutates live widget storage, placement, logs, or provider state. | Medium | High | Seed the live store with a sentinel and prove capture leaves it byte-identical. |
| CLI routing | Capture calls devWidget, registers the widget, or starts desktop services. | Medium | Medium | Spy on registry, host, window, and IPC entry points; all remain untouched. |
| Automation reuse | A second action implementation drifts from desktop input behavior. | Medium | Medium | One dispatcher test runs the same action through desktop automation and capture. |
| Production widget path | The new environment branch changes startup, idle CPU, memory, or binary size when unset. | Low | High | Desktop tests plus before and after ReleaseFast binary and process receipts. |
WidgetSession extraction | No change planned. | Cleared | None | Revisit only after a measured blocker. |
| Public TSX SDK | No authoring API change in the first package. | Cleared | None | Existing widgets compile unchanged. |
Source: reproduced null-link failure, uniform-black prototype outputs, existing CLI routing, SDK runner and automation code, and Weaver's storage bootstrap.
Stop instead of shipping a convincing lie
- Stop if a visible example matches its clear color even when the PNG parses and its dimensions are correct.
- Stop if the receipt calls alpha coverage content. Transparent and opaque clear colors both make that number misleading.
- Stop before adding a frame cap, wait timeout, latency target, or process-lifetime policy without the example-corpus receipt.
- Stop if capture needs a second input dispatcher, snapshot schema, layout engine, or renderer.
- Stop if capture reads the live storage root or connects the live provider channel without an explicit opt-in.
- Stop if the Weaver commit points at a Native SDK commit that the remote cannot fetch.
- Stop if a Linux-only run is reported as macOS or Windows end-to-end proof.
- Stop if macOS or Windows parity is inferred from source instead of compared pixels.
The remote handoff is done only with these receipts
- A fresh clone initializes the submodule and installs JavaScript dependencies with no local file copied in.
- The null backend links on macOS, and the normal macOS render-host mode still builds.
- Clock, text, image, interaction, timer, font, and provider cases produce inspectable artifacts.
- Every capture prints one receipt with provenance, pixel facts, pending work, inputs, and stage timings.
- The capture command leaves widget registration, desktop windows, host IPC, live storage, and live placement untouched.
- An action script changes both semantics and pixels through the existing dispatcher.
- The Native SDK commit is pushed before Weaver updates the submodule pointer.
- Native SDK fast gate, full relevant tests, Weaver tests, type checking, and
git diff --checkpass. - Before and after ReleaseFast binary size and ordinary widget process receipts are attached.
- The user-facing CLI change has its changelog fragment and
skills/conjure-widget/SKILL.mdteaches capture and inspection.
# Final remote verification
npm test
npm run typecheck
cd runtime
zig build test -Dplatform=null --summary all
cd native-sdk
zig build test --summary all
scripts/gate.sh fast
cd ../..
# Capture acceptance cases
node cli/bin/weaver.js capture examples/clock --out artifacts/clock.png
node cli/bin/weaver.js capture examples/styling-text --out artifacts/text.png
node cli/bin/weaver.js capture examples/styling-images --out artifacts/images.png
node cli/bin/weaver.js capture examples/pomodoro --action-file test/capture/pomodoro.actions --out artifacts/pomodoro-running.png
# Repository and submodule proof
git diff --check
git status --short
git -C runtime/native-sdk status --short
git diff --submodule=log
Source: Weaver package scripts, Native SDK agent guide, and the acceptance cases defined in this handoff. Proposed capture commands do not exist before implementation.
Raw evidence and proof level
Reproduced null-backend blocker
$ cd runtime
$ zig build -Dplatform=null -Dautomation=true -Doptimize=ReleaseFast --summary all
error: undefined symbol: _native_sdk_appkit_render_host_run
note: referenced by .../weaver-widget_zcu.o:_main.main
Build Summary: 3/6 steps succeeded (1 failed)
Reproduced 2026-08-24 on macOS with Zig 0.16.0 against Weaver 999ccfb; runtime code matches the evidence base because intervening commits changed CI files only.
Invalid prototype PNGs
# ffmpeg signal statistics for clock.png, pomodoro.png, and text.png
RGB luma: YMIN=16, YMAX=16
Chroma: UMIN=128, UMAX=128, VMIN=128, VMAX=128
Alpha: YMIN=255, YMAX=255
Result: every output is one opaque black color.
The corrected GPU event code was modified after these files were written.
Measured 2026-08-24 from the local prototype outputs in /tmp/wcap. These files are not required by the remote handoff.
Current code anchors
| Fact | Anchor |
|---|---|
| Weaver builds the real app before calling the runner. | runtime/src/main.zig:1239-1491 |
| The render-host branch keys on host OS and references the missing symbol. | runtime/src/main.zig:1242-1252 |
| The runner selects session replay before a desktop backend. | runtime/native-sdk/src/app_runner/root.zig:392-417 |
| Session replay enables null GPU surfaces and screenshots. | runtime/native-sdk/src/app_runner/root.zig:693-878 |
| Null GPU surfaces and image decode default off. | runtime/native-sdk/src/platform/null_platform.zig:220-245 |
| A GPU view is rejected when null GPU support stays off. | runtime/native-sdk/src/platform/null_platform.zig:910-914 |
| macOS can install CoreText services without a window. | runtime/native-sdk/src/platform/macos/root.zig:960-973 |
| The automation server publishes snapshots and atomic screenshots. | runtime/native-sdk/src/automation/server.zig:40-97 |
| The session journal records events, effects, fingerprints, and screenshots. | runtime/native-sdk/src/runtime/session_journal.zig |
| The screenshot uses the deterministic CPU reference renderer. | runtime/native-sdk/src/runtime/canvas_frame.zig:895-956 |
All anchors refer to Weaver commit e69a0997c5a883a69514674fab7dfcdbe0016114 and Native SDK submodule commit 4c5c0999c2324b8dc2016625e85571e554b2484a.
proof level 3 The runner architecture, existing services, black-frame failure, and link blocker are confirmed. A remote agent must produce and inspect the first correct full Weaver PNG before raising this to level 4.
The verdict
Recommended decision: build a separate headless capture worker and expose it first as
weaver capture. Keep it alive in the next slice as a revision-based JSON Lines session. Let a local live viewer consume that session, but never let the viewer become another widget renderer.
The wild version is closer than it sounds. The Native SDK already turns a live retained scene into RGBA pixels without presenting a window, writes those pixels as PNG, snapshots the widget semantics, and dispatches click, drag, wheel, and key actions through the runtime's real input route. Weaver needs to assemble those pieces around its own bundle and lifecycle.
| Capability | Current state | Finding |
|---|---|---|
| Build TSX into runnable widget code | exists | bundleWidget already checks and bundles a source directory. |
| Render retained UI without presenting | exists | renderCanvasScreenshot plans the current scene, runs the CPU reference renderer, and does not mutate presentation state. |
| Encode and publish PNG | exists | The automation command allocates two RGBA8 buffers, encodes PNG, and publishes atomically. |
| Read the semantic tree | exists | Snapshots carry roles, labels, bounds, values, actions, errors, and renderer facts. |
| Interact with a widget | exists | Automation clicks synthesize pointer down and up through dispatchPlatformEvent; other verbs cover text, keys, drag, wheel, hold, and context actions. |
| Run a Weaver bundle headlessly | integration | The runtime setup is one large desktop entry point. It must become a reusable session. |
| Expose a Weaver CLI contract | missing | The current parser has no capture or preview command. |
Source: cli/src/index.ts:100-138, runtime/src/main.zig:1239-1491, runtime/native-sdk/src/runtime/canvas_frame.zig:895-971, runtime/native-sdk/src/runtime/flow.zig:928-973, and runtime/native-sdk/src/runtime/automation_widget_dispatch.zig:50-93.
What the product should look like
One-shot capture
# Proposed CLI, names are not implemented yet
weaver capture ./clock --out ./artifacts/clock.png
# The command prints one machine-readable receipt
{"image":"./artifacts/clock.png","snapshot":"./artifacts/clock.snapshot.json","revision":1,"state":"first-committed-root"}
The default captures the first committed root. It does not guess when network work, an animation, or a provider has "settled". That removes a hidden timeout and makes the result repeatable.
The PNG should always travel with a semantic snapshot. Pixels tell an agent what the widget looks like. The snapshot tells it what can be pressed, which text belongs to which node, where each node is, and what error prevented a healthy frame. The pair is much more useful than a screenshot alone.
Interactive agent session
# Proposed long-lived protocol
weaver preview ./clock --headless --jsonl
{"id":"read","method":"snapshot"}
{"id":"press","method":"click","target":{"revision":1,"node":9433737043790503074}}
{"id":"after","method":"capture","out":"./artifacts/clock-after.png"}
One request produces one response. Every mutation returns the new revision, diagnostics, semantic snapshot path, and image path. A target from an old revision fails as StaleWidgetRevision and names both revisions.
Revision checks matter because retained node IDs can change after a rebuild. An agent must never click an old ID and receive success against a different control. The protocol should make the safe behavior automatic: read, act against that exact revision, then read the resulting revision.
Live view
An optional --serve mode can bind a loopback-only viewer. The page displays the latest PNG, draws semantic bounds as an inspection overlay, and translates a click into a session action. It does not run widget code and it does not reproduce widget layout in HTML. The browser is only a viewer and controller.
The session should push a new frame only when the retained revision changes or the caller advances the clock. A blind video cadence would waste the exact CPU profile Weaver is built to protect.
One scene, two ways to present it
Recommended capture path · the desktop and agent paths share the retained scene
Source: Weaver runtime flow in runtime/src/main.zig:1311-1470; offscreen render in runtime/native-sdk/src/runtime/canvas_frame.zig:895-956; input routing in runtime/native-sdk/src/runtime/automation_widget_dispatch.zig:50-318.
The important design choice is the seam before presentation. Capture the retained scene, not the OS window and not the GPU surface. That avoids Screen Recording permission, desktop placement, host registration, renderer-process startup, and OS compositor noise. It also works on a machine with no visible desktop session once the headless lifecycle exists.
Three implementation choices
| Choice | Speed to first image | Fidelity | Cost | Decision |
|---|---|---|---|---|
| Capture the desktop window | Fast | Highest OS fidelity | Needs a real window, permissions, placement, and a GUI session. | Reject as the agent default |
| Expose current live automation | Very fast | Same retained scene, but the widget still runs in a desktop process. | Automation build, shared artifact directory, host and lifecycle coupling. | Useful spike only |
| Headless Weaver capture session | Moderate | Same QuickJS tree and retained display list; reference-rendered pixels. | Runtime extraction, input protocol, deterministic environment choices. | Build this |
Source: current runner behavior in runtime/native-sdk/src/app_runner/root.zig:412-460 and existing offscreen test harness in runtime/native-sdk/examples/system-monitor/src/tests.zig:208-263,1086-1157.
The current -Dplatform=null runner is not the finished feature. Its built-in loop starts and shuts down immediately, so it cannot accept a later action. The reusable part is the public TestHarness, NullPlatform, screenshot renderer, snapshot generator, and automation dispatch. Weaver should own a long-lived session loop instead of bending the short null runner into one.
What the image proves, and what it does not
| Concern | Capture fidelity | Reason |
|---|---|---|
| TSX logic, hooks, retained state | High | The same QuickJS engine and Weaver tree build the scene. |
| Layout, colors, clips, images, shadows | High | The screenshot plans the live display list with the registered image and font resources. |
| Buttons, sliders, text input, scrolling | High | Commands enter through real pointer, key, and scroll events before the next capture. |
| Text measurement | Platform-dependent | A pure null platform uses its deterministic estimator. Native capture can use platform measurement. This needs an explicit parity choice. |
| Glyph rasterization | Reference pixels | The CPU renderer rasterizes the retained font resources. It is stable for agent feedback, not proof of OS text rasterization. |
| Metal, D3D, alpha handoff, desktop layering | Not exercised | Headless capture stops before the GPU presenter and OS compositor. |
| Live provider and network timing | Only with named inputs | Determinism requires fixtures or an explicit live attachment. Silence must not become invented data. |
Source: screenshot semantics in runtime/native-sdk/skill-data/automation/SKILL.md:121-152; renderer implementation in runtime/native-sdk/src/runtime/canvas_frame.zig:895-956.
This should become the standard authoring proof, not the final renderer release proof. An agent can use it to see spacing, clipping, state transitions, overflow, missing assets, and broken interactions. Metal and D3D changes still need the existing physical presenter gates.
The headless environment must be honest
The renderer is the easy part. Environment policy is where a quiet landmine would form. Capture must state every source of state in its receipt.
| Input | Default | Explicit path | Failure behavior |
|---|---|---|---|
| Clock | Record the chosen instant in output metadata. | Caller supplies a fixed instant for a reproducible test. | Invalid time input names the value and accepted format. |
| CPU, memory, audio, media | No fabricated frames. | Provider fixture file, or a later opt-in live host attachment. | CaptureProviderUnavailable names the provider and the missing input. |
| Storage | Isolated session storage. | Caller supplies a state fixture or an isolated saved session. | The capture worker never reads or writes the live widget store by accident. |
| Network | Keep declared-origin enforcement. One-shot capture does not wait for an unknown "settled" state. | A live session waits for a semantic condition, or a future recorded response fixture supplies the result. | Origin and transport errors stay visible in diagnostics and the rendered error state. |
| Timers and animation | First committed root. | The caller advances the session clock or requests a named frame. | No hidden sleep and no guessed frame deadline. |
Source: current provider guard in sdk/src/reconciler.ts:1138-1169; current live storage setup in runtime/src/main.zig:1274-1285,1392-1406; declared network and runtime errors remain in the existing bridge.
Provider fixtures are not fake fallbacks. They are declared test inputs. The output receipt should name their file hash, the clock input, the storage source, the bundle hash, the capture scale, and the renderer kind. Given only that receipt, a fresh agent should know why it sees each value.
The blast radius is narrow in rendering and real in lifecycle
The user's instinct is half right. This does not spread across the production renderer stack. It does cross the runtime bootstrap, and that code currently owns bundle loading, state, providers, images, QuickJS, UI creation, desktop placement, dev reload, and the platform run loop in one function. Extracting it carelessly could change live widgets.
| Area | Change | How it breaks | Likelihood | Cost | Check |
|---|---|---|---|---|---|
runtime/src/main.zig | Extract reusable WidgetSession setup. | Desktop startup, teardown, hot reload, or image lifetime changes during the refactor. | Medium | High | Keep the desktop sequence equivalent and rerun host, dev reload, provider, and teardown gates. |
runtime/src/capture_main.zig | New separate executable. | Headless loop captures before the UI display list exists or leaks session resources. | Medium | Medium | Capture a real bundle, decode PNG, assert manifest dimensions, then exit cleanly. |
runtime/build.zig | Build capture worker beside the widget runtime. | Capture code lands in the production widget binary and adds idle cost. | Medium if combined | High | Separate artifacts; compare production binary and process receipts before and after. |
cli/src/index.ts | Add capture and preview parsing, bundle handoff, JSON receipts. | Usage parsing regresses existing commands or capture mutates the registry by reusing devWidget. | Medium | Medium | Call bundleWidget directly; never call devWidget; extend CLI misuse tests. |
| Provider and storage adapters | Add isolated capture inputs. | A preview reads live private state, writes live state, or invents provider data. | High if unspecified | High | Default isolation tests and named missing-provider errors. |
| Agent action protocol | Wrap existing semantic actions with revision checks. | An old node ID actuates a new control after rerender. | High without revision | Medium | Reject stale revisions and return both expected and current values. |
skills/conjure-widget/SKILL.md | Teach the capture, inspect, act, recapture loop. | Agents keep relying on desktop screenshots or miss diagnostics. | Medium | Low | Run the skill against a broken and an interactive example. |
| Host and shared GPU renderer | None for headless capture. | No direct change. | Cleared | None | Capture command does not start host, register a widget, or open renderer IPC. |
| Public TSX SDK | None for the first slices. | No authoring API change. | Cleared | None | Existing widgets bundle unchanged. |
Source: CLI command split in cli/src/index.ts:100-138,397-555; runtime bootstrap in runtime/src/main.zig:1239-1491; host-free null tests and current automation paths in the pinned Native SDK.
The separate executable is not ceremony. Weaver treats idle CPU and memory as product behavior. Linking preview machinery into every desktop widget would be a self-inflicted tax even if most pages stay untouched. Shared session code belongs in a library module; capture-only protocol, PNG output, and viewer code belong only in the capture artifact.
The one fact this is safe because of
Safety fact: desktop presentation and agent capture can consume the same retained canvas revision. Capture does not need a parallel implementation of TSX, layout, styling, or interaction.
proof level 4 The fact reached "ran it". The pinned implementation says the screenshot path plans the current retained scene, uses the same software pixel path as presentation, and does not present or mutate presentation state. Its screenshot test renders the same scene twice byte-identically, routes pointer input, and proves the next PNG changes. Its automation dispatcher turns semantic clicks into pointer events through dispatchPlatformEvent.
# Weaver at e69a0997, compiled against the null platform
cd runtime
zig build test -Dplatform=null --summary all
# 71 passed, 1 skipped, 0 failed
# Pinned Native SDK at 4c5c099
cd runtime/native-sdk
zig build test --summary all
# 1,836 passed, 2 skipped, 0 failed
# desktop-canvas-frame-tests: 96 passed, 1 skipped
# desktop-canvas-widget-tests: 200 passed
Source: commands run on 2026-08-24 in this worktree. Full implementation anchors: runtime/native-sdk/src/runtime/canvas_screenshot_tests.zig:68-218 and runtime/native-sdk/src/runtime/automation_widget_dispatch.zig:50-318.
not yet proven No full Weaver bundle was launched through a finished headless capture worker because that worker does not exist. Cross-platform pixel parity, native text measurement in a windowless process, provider fixtures, and production-binary cost remain implementation gates. The code makes the direction credible. It does not make those gates disappear.
Build it in slices that each pay for themselves
Slice A: extract the session without behavior change
- Move manifest, state, provider, image, font, QuickJS, and
WidgetAppsetup intoruntime/src/widget_session.zig. - Keep desktop placement, dev reload, host endpoints, renderer diagnostics, and the platform loop in
runtime/src/main.zig. - Run the current desktop and null-platform tests before adding capture behavior.
- Compare the ReleaseFast widget binary and live process receipts before and after. Do not accept an unexplained production cost.
Slice B: ship one-shot capture
- Add a separate
weaver-captureexecutable with a null platform and isolated storage. - Evaluate the real bundle, start the real
WidgetApp, render the first committed root, write PNG and semantic JSON, then exit. - Add
weaver capture <directory> --out <file.png>to the TypeScript CLI. Reuse checking and bundling, not host registration. - Make missing providers, missing roots, image errors, and output failures name the input, ask, and remedy.
Slice C: keep the session alive
- Add JSON Lines commands for snapshot, capture, click, drag, wheel, key, text, clock advance, and shutdown.
- Require an expected revision for every target action.
- Return new image and snapshot artifacts only after the update has committed.
- Add provider and storage fixtures as explicit session inputs.
Slice D: show the live view
- Add a loopback-only viewer that displays the latest PNG and semantic overlay.
- Send interactions back to the headless session. Never duplicate layout or widget behavior in the page.
- Push on retained revision changes and explicit clock advances. Do not invent a background frame cadence.
Before this can ship
weaver capture examples/clockproduces a non-flat PNG whose dimensions match the manifest, with the chosen time recorded in the receipt.- An image and bundled-font example render with registered assets, not placeholders.
- A styling example covers clips, rounded corners, shadows, opacity, stacks, and canvas commands.
- A pomodoro capture exposes semantic button and slider nodes. Press and drag actions change both the snapshot and the next PNG.
- The pomodoro capture cannot alter its live storage document. An explicit fixture changes only the isolated capture session.
- A CPU, audio, or media widget without an input fails with the named provider. A supplied fixture renders the supplied value and records its hash.
- A stale action revision fails loudly. The error gives the requested revision and the current revision.
- The capture command never changes the registry, starts
weaverd, opens a desktop window, or connects to shared renderer IPC. - Current CLI, runtime, host lifecycle, hot reload, provider, and platform tests stay green on macOS and Windows.
- The production widget binary and running cost have before and after receipts. The capture worker's own cost is reported separately.
- A physical desktop capture remains in renderer-release testing. Headless PNGs do not certify Metal, D3D, desktop layering, or OS text rasterization.
Raw evidence
Current code anchors
| Fact | Anchor |
|---|---|
CLI has a closed command table and one desktop dev path. | cli/src/index.ts:100-138,397-555 |
| Weaver runtime startup is monolithic. | runtime/src/main.zig:1239-1491 |
| Weaver maps retained press IDs back into QuickJS callbacks. | runtime/src/main.zig:269-315 |
| Offscreen capture plans the retained scene and does not present. | runtime/native-sdk/src/runtime/canvas_frame.zig:895-956 |
| PNG publication allocates two RGBA8 surfaces and writes atomically. | runtime/native-sdk/src/runtime/flow.zig:934-973, runtime/native-sdk/src/automation/server.zig:80-99 |
| Semantic clicks use pointer down and up through the real platform event route. | runtime/native-sdk/src/runtime/automation_widget_dispatch.zig:50-93 |
| Headless offscreen capture already exists in an example test. | runtime/native-sdk/examples/system-monitor/src/tests.zig:1086-1157 |
| Non-time providers currently require a host. | sdk/src/reconciler.ts:1138-1169 |
All anchors refer to Weaver commit e69a0997c5a883a69514674fab7dfcdbe0016114 and Native SDK submodule commit 4c5c0999c2324b8dc2016625e85571e554b2484a.
Test receipts
# Weaver null-platform test receipt
Build Summary: 9/9 steps succeeded
71/72 tests passed
1 skipped
0 failed
# Pinned Native SDK test receipt
Build Summary: 316/316 steps succeeded
1,836/1,838 tests passed
2 skipped
0 failed
desktop-canvas-frame-tests: 96 passed, 1 skipped
desktop-canvas-widget-tests: 200 passed
desktop-runtime-core-tests: 195 passed
Captured 2026-08-24 on macOS with Zig 0.16.0. Warnings in expected failure-path tests did not fail either suite.
Generated 2026-08-24 · Codex for Dara · inputs: Weaver e69a0997, Native SDK 4c5c099, repository source, null-platform and full SDK test receipts · confidence: high on feasibility, medium on integration estimate · revision v1
Revised 2026-08-24 · Codex for Dara · remote handoff inputs: Weaver e69a0997, Native SDK 4c5c0999, Opus review, reproduced null-link failure, black-frame pixel inspection, repository source, and prior test receipts · confidence: high on architecture, medium on implementation order, unproven on first valid full-widget capture · revision v2