# v3.19.5 — `open_visualization` reuses the running server; snapshot clobber fixed

Fifth entry in the 3.19.x reliability series. This one fixes two
`cortex-visualize` defects: every `open_visualization` call spawned a brand-new
server on a fresh ephemeral port with a cold graph rebuild, and a vestigial
skeleton-snapshot write silently destroyed the shared full graph snapshot.

## What it fixes

### 1. New server + new port + cold rebuild on every call

Calling `open_visualization` twice left two (or more) servers running on
different ephemeral ports (observed: `:56746`, `:57167`), each paying the full
cold-start graph build. Two root causes:

1. **No instance record.** Nothing persisted which pid/port was already
   serving, so every launch path started from scratch.
2. **Double-spawn race.** The handler ran `launch_server` unconditionally
   *after* the bootstrap had already started a server, so a single tool call
   could itself spawn two processes.

### 2. Skeleton snapshot clobbered the full snapshot

`http_standalone_graph.py` wrote a "skeleton" snapshot intended for an
`/api/graph.bin` route that does not exist. Its only observable effects were
overwriting the shared full snapshot (36,931 → 31 nodes) and flipping the
complete-snapshot counts to `None` — which then forced cold rebuilds on the
next launch.

## The fix

- **New `mcp_server/server/viz_instance.py`** — an instance registry at
  `~/.cache/cortex/viz-server.json` (`{pid, port, started_at}`):
  - `reusable_instance()` probes the registered server and reuses it when it
    is alive, responding, and *source-current* (`is_current` compares the
    newest source mtime, excluding `__pycache__`, against the instance start).
  - `kill_and_wait()` stops stale instances deterministically
    (SIGTERM → wait → SIGKILL), reaping the instance's own zombie children
    via `waitpid(WNOHANG)` so the wait cannot stall.
- `http_standalone.py` registers the actually-bound port right after bind.
- `http_launcher.py` consults the registry before killing/spawning, and
  `_kill_port` now waits for process exit instead of fire-and-forget.
- `visualize_bootstrap.py` prints `ok reused pid=N url=...` on the reuse path
  and reports the ACTUAL bound port.
- `open_visualization` handler parses the bootstrap's `url=` line, probes it,
  and **skips `launch_server`** when the bootstrap already produced a live
  server — eliminating the double-spawn race. Dead `_prepare_layout` removed.
- The skeleton-snapshot write was **removed** outright (it fed a nonexistent
  route).

## Verification

- 30 viz tests pass (13 new in `tests_py/server/test_viz_instance.py`;
  handler tests made hermetic by stubbing `_find_dev_source`).
- `tests_py/server/` — 63 passed; ruff clean.
- Smoke test: run 1 spawned and registered pid 71693 on port 3458; run 2
  reused the same pid. Full snapshot intact at 45,332 nodes afterwards.
