# Desktop preview consumer proof

Base: [f7b81e1](https://github.com/pingdotgg/t3code/commit/f7b81e1d1115bc7284b39161d3cd6c7f3184eb1c)
Head: [f0f768a](https://github.com/pingdotgg/t3code/commit/f0f768a126a7709f6325f0932c2edf068ece4b32)

These are manual runtime recordings, not test results. Both sides use built Linux Electron under headless-desktop, a synthetic changing page, fresh isolated databases (T3CODE_DEV_SEED=0), the same scripts, light app theme, 1920x1080 display, and three 640px columns. The left column is the actual native preview. Center and right are ordinary HTTP web clients in plain Chromium BrowserWindows with no desktop preload, paired to the desktop backend. They use the remote viewer/WebSocket path over loopback; this does not exercise a physical network or relay.

The free synthetic ACP driver keeps one session active so the real server exposes its preview MCP tools. The driver invokes preview_open, preview_recording_start and preview_recording_stop through that session's actual authenticated MCP endpoint. No paid agent or private data is involved. A pending synthetic prompt is deliberate. The changing page increments every 100ms.

## Rebuild and run

Install dependencies with vp i in separate worktrees at the two commits. In each, run T3CODE_DEV_SEED=0 vp run build:desktop, then node apps/desktop/node_modules/electron/install.js. Do not seed from another database.

Run python3 instrument-bundle.py <checkout> once after building each side. This identical instrumentation adds two read-only exports (tab map and relay lookup) and an optional probe-response sink to the built bundle. The sink is inactive during viewer recordings. During diagnostics it only isolates the added raw CDP sessions from the server's Playwright connection. No capture ownership or relay routing decision is replaced.

Run node site.mjs once. Start each desktop from its own checkout, with its own XDG_CONFIG_HOME and T3CODE_HOME:

```sh
T3CODE_DEV_SEED=0 XDG_CONFIG_HOME="$PWD/.electron-proof" T3CODE_HOME="$PWD/.t3-probe" T3CODE_PORT=19356 headless-desktop apps/desktop/node_modules/electron/dist/electron --no-sandbox --disable-gpu --remote-debugging-port=19381 --inspect=19382 apps/desktop/dist-electron/main.cjs
```

For base use server port 19355, debug port 19371, inspector 19372. Keep both terminal sessions. Discover each X display with node inspect.mjs <inspector-port> process.env.DISPLAY. Driver ffmpeg uses the desktop's own XAUTHORITY. Set EVIDENCE_CHECKOUT to an installed checkout so lib.mjs can resolve ws. Scripts and their private runtime outputs must share one directory.

```sh
node proof-run.mjs after <head-checkout> 19381 19382 19356 <head-display>
node proof-run.mjs before <base-checkout> 19371 19372 19355 <base-display>
node recording-repeat.mjs after <head-checkout> 19381 19382 19356 <head-display>
node recording-repeat.mjs before <base-checkout> 19371 19372 19355 <base-display>
node viewer-close.mjs after <head-checkout> 19381 19382 19356 <head-display>
node viewer-close.mjs before <base-checkout> 19371 19372 19355 <base-display>
node edge.mjs after 19382 <head-display>
node edge.mjs before 19372 <base-display>
```

Each run writes to a new timestamped directory. recording-repeat and viewer-close reuse the same synthetic thread but navigate/reload the same single preview tab, recreate the same two viewer windows, and reset observation counts. There is no Browser toggle reconnect on either side. Viewer B visibly disappears about 5 seconds into viewer-close; A stays visible another 7 seconds, then visibly disappears. Recording-stop waits for the real recording start to complete, records for 4 seconds, stops it, and leaves the viewers open for 6 seconds. Its actual tool responses are in the sanitized transcripts. Native counters in the footer are observed commands/acks/frames, not expected values.

## Native lifecycle recording

observe.js wraps the actual Electron debugger to record sends, resolved native replies and incoming native frames, without changing commands. edge.mjs attaches real page sessions to the existing attached tab's actual CdpRelay. An idle session plus active A/B demonstrate frame routing. Stopping B demonstrates that A still receives frames. A is restarted, then detached without Page.stopScreencast, isolating the last-detach case that an ordinary viewer may obscure by sending an explicit stop before detach. Head sends/acks a native stop, frame counts stop, owner count is zero, painting is false, and pending holds are zero. Base keeps emitting frames despite zero owners. The native desktop page remains visible and continues changing independently of screencast delivery.

The next probe starts a real native stream, then sends an invalid-format replacement to Chromium. Chromium genuinely rejects it; head retains the running owner, while base loses it and disables painting. A fresh invalid start is also rejected and cleaned up. Direct native stops between independent probes are deliberate resets, recorded in the logs; do not count those as app-issued fixes.

Finally, a wrapper deliberately holds one real native start at the debugger boundary, detaches its session, then releases that start. This is controlled scheduling, not a claim that the timing happened naturally. Head issues another native stop after the late start resolves and native frames remain stable; base's native frame count keeps growing with no owner. The side-by-side runtime observations and full native logs show the difference. This uses the real built host, relay and Chromium, without fake WebContents, mocked replies, or unit tests.

Resource evidence is scoped to native screencast stop acknowledgement, no subsequent native frames, empty screencast ownership, no pending holds, and painting disabled. It does not claim a heap or GPU memory measurement. The active synthetic agent may independently retain focus emulation.

Private credentials, local artifact paths and machine identifiers are omitted. Do not publish newly generated mcp-*-private.json or raw metadata/transcripts without redaction. Stop each owned desktop with node stop.mjs <inspector-port>, stop the synthetic page server, and remove disposable state afterwards.

AI-assisted; verified with the tests and tools listed above.
