---
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.
