| name | livewire |
| description | 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 verification, UI development with live engine state, real-engine bug repro, exploratory debugging with Chrome DevTools instruments. |
Real 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.exe is bundled in src-tauri/binaries/. If a recent engine
command is needed, pull the sibling and force a rebuild:
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
Verify the binary has what you need:
./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 --json
Treat 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).
for 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"
The awk '{print $NF}' is load-bearing — $5 is LISTENING, 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:bridge in the background.
This starts Vite (1420) + the standalone endstate-dev-bridge
binary (9876) — a non-Tauri process that links none of the native
GUI stack. (npm run tauri:dev:browser is now an alias for
dev:bridge.) First run compiles the bridge crate (fast; usually
already built). ENDSTATE_ROOT defaults to the sibling ../endstate
repo; override via env if needed.
- Arm a single-shot watcher that emits when ANY of these signals
land:
until grep -qE "Browser bridge listening|Browser bridge failed to bind|error\[|panicked|exited" /tmp/dev-bridge.log; do
sleep 2
done
Run that in run_in_background:true with a 5-minute timeout. The
bind-failure literal is Browser 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)
-
mcp__plugin_chrome-devtools-mcp_chrome-devtools__new_page(url="http://127.0.0.1:1420")
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.
Smoke mode
The verification flow. Order matters — curl first, drive after.
- Curl
engine_is_running through the bridge:
curl -sS -X POST http://127.0.0.1:9876/api/invoke \
-H 'Content-Type: application/json' \
-d '{"cmd":"engine_is_running","args":{}}'
Expect {"ok":true,"data":false}.
- Curl the engine command itself. Dispatch goes through
endstate_exec
— exe is __bundled__ in bundled mode, args are the engine
subcommand flags. The response embeds the engine envelope in
data.stdout as a JSON string:
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))"
Compare to step 2's ground-truth bytes — they should match.
- 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
check list_network_requests({resourceTypes: ["fetch"]}) for the
POST(s) to 127.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:
() => Object.fromEntries(
Object.entries(localStorage)
.filter(([k]) => k.includes('endstate'))
.map(([k, v]) => [k, JSON.parse(v)])
)
() => {
localStorage.setItem('web:endstate-ui-mode', 'advanced');
localStorage.setItem('web:endstate-sidebar-visible', 'true');
}
() => {
const k = 'web:endstate-gui-settings';
const s = JSON.parse(localStorage.getItem(k));
s.dryRunEnabled = false;
localStorage.setItem(k, JSON.stringify(s));
}
Hit 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 to
9876 mentally; 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>.json has the structured envelope
including counters and the dryRun flag. 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-run was
actually sent (the request body contains the full args array).
Teardown
- Stop the background
dev:bridge task. Note TaskStop does NOT reap
the detached child processes — kill the orphan listeners on 1420 +
9876 with the step-3 command, and if a cargo/endstate-dev-bridge
process 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
cd in Bash on this Windows box — PowerShell cwd resets
fight it. Use absolute paths with git -C / yadm -C style 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.