livewire
Drive the real GUI against the real engine via the standalone dev HTTP bridge (`npm run dev:bridge`). Use whenever "does the wiring test actually hold up against real code?" matters — smoke verificati
Files1
View on GitHubSKILL.md.claude/skills/livewire/SKILL.md
GitHubReal Chromium ↔ real HTTP bridge ↔ real engine. No mocks, no fixtures. The
GUI runs in a regular Chrome tab; every invoke() / listen() goes over
the bridge to the bundled endstate.exe binary. The same DevTools you'd
use on a webapp (network panel, console, evaluate, snapshots) work on the
real production code path.
Input: short description of what to do. Examples:
/livewire smoke the backup subscribe flow/livewire drive setup with dry-run and confirm no UAC fires/livewire hot-iterate on the subscription banner — change copy, see it live
Modes (pick before driving)
- Smoke — verify a flow works end-to-end against the real engine. Order: curl envelope checks first (cheap, proves protocol), then UI drive. Section Smoke below.
- Dev — iterate on the GUI with the real engine wired up. Vite HMR hot-reloads frontend changes; the bridge persists. Section Dev below.
- Debug — reproduce a real-engine bug. Drive to the failure, inspect state via evaluate, scrub network/console panels. Section Debug below.
All three share the same launch + watcher + hazard handling.
Pre-flight (always)
- Check sibling engine version. The bridge speaks to whatever
endstate.exeis bundled insrc-tauri/binaries/. If a recent engine command is needed, pull the sibling and force a rebuild:
Verify the binary has what you need:git -C ../endstate fetch --tags git -C ../endstate checkout v<X.Y.Z> rm -f src-tauri/binaries/endstate-x86_64-pc-windows-msvc.exe node scripts/rebuild-engine.cjs./src-tauri/binaries/endstate-x86_64-pc-windows-msvc.exe <cmd> --help - Direct envelope check (smoke mode only — cheap ground truth, no
Tauri):
./src-tauri/binaries/endstate-x86_64-pc-windows-msvc.exe backup subscribe --jsonTreat those bytes as the assertion target.
Launch the bridge
- Clear orphan listeners on 1420 + 9876 first. Earlier crashed runs
often leave Vite still listening on 1420 (npm doesn't reap it) or
stranded sockets on 9876. Without this check, the launch fails
silently: Vite errors with "Port 1420 is already in use" and exits
via
beforeDevCommand, OR the Tauri bridge fails to bind and keeps running without it (no crash signal, no success signal — watcher waits forever).
Thefor P in $(netstat -ano | grep "LISTENING" | grep -E ":(1420|9876) " | awk '{print $NF}' | sort -u); do taskkill //F //PID "$P" 2>/dev/null || true done sleep 1 netstat -ano | grep "LISTENING" | grep -E ":(1420|9876) " || echo "ports clear"awk '{print $NF}'is load-bearing —$5isLISTENING, not the PID. On Git Bash,//F //PID(double-slash) prevents Msys from mangling the args into paths. - Run
SKIP_ENGINE_BUILD=1 npm run dev:bridgein the background. This starts Vite (1420) + the standaloneendstate-dev-bridgebinary (9876) — a non-Tauri process that links none of the native GUI stack. (npm run tauri:dev:browseris now an alias fordev:bridge.) First run compiles the bridge crate (fast; usually already built).ENDSTATE_ROOTdefaults to the sibling../endstaterepo; override via env if needed. - Arm a single-shot watcher that emits when ANY of these signals
land:
Run that inuntil grep -qE "Browser bridge listening|Browser bridge failed to bind|error\[|panicked|exited" /tmp/dev-bridge.log; do sleep 2 donerun_in_background:truewith a 5-minute timeout. The bind-failure literal isBrowser bridge failed to bind port 9876— match the prefix so it works across OS-error wording.
STABILITY — the dev bridge no longer runs inside Tauri. The
intermittent ~12% 0xc0000374 heap corruption (see
memory/project_tauri_dev_server_crash.md) lived in the native GUI
layer (tao/wry/webview2-com) that the in-process bridge dragged in.
The standalone endstate-dev-bridge binary links none of it
(cargo tree -p endstate-dev-bridge shows no tao/wry/webview2-com), so
that crash surface is structurally gone. If the bridge process dies now,
it's a real bug in our (safe) Rust or the engine subprocess — investigate
it, don't write it off as "the known Tauri flake."
Response policy:
- In smoke mode, curl is enough for envelope-shape verification. Do the curl pass first so you have protocol proof regardless of whether the UI drive lands.
- In dev mode, if the bridge dies mid-iteration, relaunch — Vite HMR and engine/keychain state persist. But also capture the stderr: a standalone-bridge crash is now signal, not noise.
Tool choice — chrome-devtools MCP is primary
First check ToolSearch with +chrome devtools to see if the
mcp__plugin_chrome-devtools-mcp_* tools are loaded. Use them by default
(real DevTools artifacts: list_network_requests for the network panel,
list_console_messages for console, take_snapshot for uid-based a11y
trees, evaluate_script for arbitrary JS, get_network_request for
request/response bodies). If chrome-devtools isn't connected this
session, use mcp__plugin_playwright_playwright__* as fallback and note
that in the report. Don't mix the two in one session. (See
[feedback-chrome-devtools-over-playwright].)
Open the GUI in Chromium (separate from Tauri's webview)
Don't drive the Tauri webview window itself — chrome-devtools MCP can't attach to WebView2. Both webviews talk to the same bridge, so a separate Chromium is fine and gives you full DevTools access.mcp__plugin_chrome-devtools-mcp_chrome-devtools__new_page(url="http://127.0.0.1:1420")
Smoke mode
The verification flow. Order matters — curl first, drive after.
- Curl
engine_is_runningthrough the bridge:
Expectcurl -sS -X POST http://127.0.0.1:9876/api/invoke \ -H 'Content-Type: application/json' \ -d '{"cmd":"engine_is_running","args":{}}'{"ok":true,"data":false}. - Curl the engine command itself. Dispatch goes through
endstate_exec—exeis__bundled__in bundled mode,argsare the engine subcommand flags. The response embeds the engine envelope indata.stdoutas a JSON string:
Compare to step 2's ground-truth bytes — they should match.curl -sS -X POST http://127.0.0.1:9876/api/invoke \ -H 'Content-Type: application/json' \ -d '{"cmd":"endstate_exec","args":{"exe":"__bundled__","args":["backup","--json","subscribe"]}}' \ | python -c "import sys,json; print(json.dumps(json.loads(json.load(sys.stdin)['data']['stdout']), indent=2))" - Drive Chromium through the flow. Use the command palette
(
Ctrl+K → "Go to <pane>") — sidebar is hidden on landing/save/setup (intent pages). Take a snapshot, click the target, then immediately checklist_network_requests({resourceTypes: ["fetch"]})for the POST(s) to127.0.0.1:9876/api/invoke— that's proof the click round-tripped through the real bridge. - Capture screenshot for the report:
take_screenshot({filePath: ".claude/scratch/livewire-<step>.png", fullPage: false})
Dev mode
Hot-iterate on the GUI while everything stays live.
- Vite HMR is on. Edit
src/**/*.tsx, save, and the running Chromium tab updates immediately. The bridge connection persists. You can keep the Tauri dev process running across many edits. - No reload needed for most changes. If a change does require a full
reload (route changes, mount-time effects), use
navigate_page({type:"reload"}). - Inspect/seed local state with
evaluate_script. Examples:
Hit// Read all endstate-related localStorage () => Object.fromEntries( Object.entries(localStorage) .filter(([k]) => k.includes('endstate')) .map(([k, v]) => [k, JSON.parse(v)]) ) // Force advanced UI mode () => { localStorage.setItem('web:endstate-ui-mode', 'advanced'); localStorage.setItem('web:endstate-sidebar-visible', 'true'); } // Flip dryRunEnabled to test the apply path () => { const k = 'web:endstate-gui-settings'; const s = JSON.parse(localStorage.getItem(k)); s.dryRunEnabled = false; localStorage.setItem(k, JSON.stringify(s)); }navigate_page({type:"reload"})after seeding state if the app reads settings at mount. - Watch the network panel.
list_network_requests({resourceTypes: ["fetch"]})shows every bridge call as you interact. Filter to9876mentally; everything else is HMR / asset traffic. - Console panel for errors.
list_console_messages({types: ["error", "warn"]})surfaces React errors, prop-type warnings, etc., the same as opening DevTools in a normal browser.
Debug mode
When you're reproducing a real-engine bug:
- Diff direct-invoke vs bridge-invoke. Step 2 (binary directly) and step 8 (binary through bridge) should produce identical envelopes. If they don't, the bridge is the culprit (rare).
- Look at the engine's own debug dump if the bug involves apply /
capture / verify. The engine writes per-run logs to
C:\Users\<user>\AppData\Local\Endstate\debug\—ts-parsed-envelope-<cmd>-<ts>.jsonhas the structured envelope including counters and thedryRunflag. Useful for confirming the CLI received the args you think it did. - Use
get_network_request(reqid)to inspect a specific bridge request body — that's how you confirm a flag like--dry-runwas actually sent (the request body contains the full args array).
Teardown
- Stop the background
dev:bridgetask. NoteTaskStopdoes NOT reap the detached child processes — kill the orphan listeners on 1420 + 9876 with the step-3 command, and if acargo/endstate-dev-bridgeprocess sticks,taskkill //F //IM endstate-dev-bridge.exe. - If the sibling engine was checked out to a specific tag, switch it
back:
git -C ../endstate switch <previous-branch>.
Report (smoke mode)
End with a tight summary:
- What was verified (bridge round-trip; envelope shape; click triggered
network POST(s) to
:9876; final DOM state; any engine flags confirmed via the debug dump) - What was not verified, with the reason (e.g. "Tauri host crashed before click — see memory/project_tauri_dev_server_crash.md")
- Screenshot paths
Anti-patterns
- Don't mock anything. This skill exists because the wiring e2e already covers mocks. If your assertions need mocks, write a Playwright spec instead.
- Don't drive the Tauri webview window. chrome-devtools MCP can't attach to WebView2. Use a separate Chromium against the same Vite URL.
- Don't loop-retry on Tauri crashes beyond one restart — the crash is intermittent, not transient. Report the gap.
- Don't
cdin Bash on this Windows box — PowerShell cwd resets fight it. Use absolute paths withgit -C/yadm -Cstyle flags. - Don't conflate dev and smoke. In dev you're iterating and tolerate flakiness; in smoke you're producing a verifiable report. Pick the mode up front so the report writes itself.