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.
Installs into .claude/skills of the current project.
Are you the author of Livewire?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/artexis10-livewire)
---
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)
1. **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`
2. **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
3. **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).
```bash
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.
4. 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.
5. **Arm a single-shot watcher** that emits when ANY of these signals
land:
```bash
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)
6. ```
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.
7. **Curl `engine_is_running`** through the bridge:
```bash
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}`.
8. **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:
```bash
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.
9. **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.
10. **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:
```ts
// 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));
}
```
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
11. 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`.
12. 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.