# Recapture replay guide

PR [#27236](https://git.cafe/pingdotgg/t3code/pull/27236). Before [dd14848153](https://github.com/pingdotgg/t3code/commit/dd148481531a0a090780a2ade20588566db099a2); after [194948a4ea](https://github.com/pingdotgg/t3code/commit/194948a4eaa18bd56a19344c39aa230c83d8d0ca).

All content is synthetic. This guide supersedes the original guide for A1-file, M1, R3-file, M2 and R1-routes. The tarball contains both original drivers/seeds and recapture adaptations; exploratory scripts are retained as historical material, not a linear run list. No product source is modified. Never replay private logs or credentials from another run.

## Setup and lifecycle

1. Create the requested before checkout at the before SHA; run global `vp install` there. Keep the reviewed head checkout unchanged. Use a new scratch root; update the scratch paths in the scripts. Reuse the same synthetic README/proof.txt content and same absolute project root on both sides.
2. Start only one revision's servers at a time. Set `T3CODE_DEV_SEED=0`, `T3CODE_PORT_OFFSET=2778`; primary command is `vp run dev --port 16551 --home-dir <scratch>/<side>-home`. Remote: `T3CODE_DEV_SEED=0 T3CODE_PORT_OFFSET=1006 T3CODE_DEV_ALLOWED_ORIGINS=http://localhost:8511 vp run dev:server --port 14779 --home-dir <scratch>/<side>-remote-home`. Keep VITE_HTTP_URL/VITE_WS_URL unset. Startup logs are private. Confirm actual ports before continuing.
3. Start the copied controller with `CAPTURE_SIDE=base` or `head`; it listens on 19878. Execute scripts through `continue.mjs` / `command.mjs`. Await each action's completion. Do not run two recording actions concurrently: the recorder has one active recording slot. Retain the process PIDs at spawn and stop only those PIDs or verified port owners.
4. Use `01-start.js` to pair the primary. Await completed pairing before proceeding; if the pairing page remains, explicitly submit its token using the actual form. On a restart, mint a new token with `node apps/server/src/bin.ts pair --base-dir <scratch>/<side>-home`, keep its output private, and submit it as in `submit-primary.js`. Do not navigate away until hydration finishes. This avoids the race found in the inherited scripts.
5. Use `05-pair-remote.js` for authentic secondary pairing. Wait until the remote runtime is connected before `09-seed.js`; pairing registration alone is not RPC readiness. Check the command's Success result, not just the controller HTTP response. Use `10-inspect.js` to select the actual seeded remote thread; `12-fixtures.js` snapshots its catalog in private controller memory.
6. Install `13-harness.js`, `47-wire-observer.js`, `51-normalize-display.js`, then `recorder.js`. These keep the actual resolver and URL builder and observe only the final dispatch/config/wire boundaries. `prepare-web.js` completes onboarding, sets dark theme, reloads at real time, awaits hydrated chat, closes the sidebar, then freezes Date at 2026-10-30T00:00:00Z. Freezing before hydration was a rejected take.

## Web recaptures

- `capture-web.js` prewarms proof.txt and returns to rendered README before recording. A1-file visibly clicks the markdown file link, then the compact primary and Ctrl+O. The code renderer is canvas-backed, so the inherited text locator is unsuitable; loading absence and stable screenshot pairs establish readiness. The accepted A1 clips omit no samples.
- The same script records M1 separately. Right-click missing.png, hold the context menu, visibly select Preview media, hold the remote-specific toast. The wire observer records the actual AssetWorkspaceAssetNotFoundError.
- `46-local-seed.js` creates the same synthetic project/thread in the primary environment. `capture-local.js` records R3-file and M2 separately, from hydrated README. It holds each real context menu before choosing its action. Reload between clips removes the preceding toast. Close the main sidebar again after each reload; otherwise cached relative times pollute the comparison.
- `capture-M2.js` is the standalone corrected M2 chapter, identical on both revisions. Its before retake reused the same isolated before database after a restart, authentic new browser pairing and `base-M2-replay.js`; it did not reseed from live state or alter source. Before and after initially started with fresh databases.
- R3's editor availability is a config fixture. The real local shell.openInEditor RPC returns missing code CLI; successful editor installation/launch is not verified. M2 is a primary/local media request. Generic overlay wording mentions the fixture's available loopback transport; it is not a claim that either operation is remote.

## Native R1 routes

Build before with `vp run build:desktop`; run Electron postinstall from apps/desktop if needed. Use the actual matching built main module. The existing head build was already present. Run the corresponding copied desktop wrapper under `headless-desktop`, isolated XDG_CONFIG_HOME/T3CODE_HOME, T3CODE_DEV_SEED=0, T3CODE_DISABLE_AUTO_UPDATE=true, VITE_DEV_SERVER_URL=http://localhost:8511; Electron flags `--no-sandbox --disable-gpu`, CDP 19902 before / 19901 head, backend 19002 / 19001. Record and retain their PIDs.

`53-desktop-both.js` now operates only CAPTURE_SIDE, seeds through the real encrypted-catalog bridge and installs the IPC overlay. Then `55-desktop-config.js`, `60-R1-prepare.js`, `desktop-xid.py [base]`, `native.js`, `record-R1.js`. Close the main sidebar before Settings so unrelated relative timestamps cannot leak through its blurred background. Both wrappers return the same real secondary endpoint localhost:14779 for the SSH bootstrap substitution. Real descriptor/session/auth checks remain in place. The preferred/active SSH route is real catalog state; only the transport setup is substituted. Native captures use ImageMagick import -descend on the owned X window, at 1440×1000. R1 uses an identical overlay label on both revisions so the full pair is exactly identical at zero threshold.

## Recording, diffs and review

`recorder.js` awaits network idle/fonts and two identical screenshots 500 ms apart for every start and still. It records live screenshots at 5 fps. It excludes samples containing visible Loading code/files or a toast root outside the viewport; omitted wall timestamps/reasons are published in each trace. It waits 0.8 s after updating narration before executing an action, then holds the result. Each step's `at` is encoded clip time, `wallAt` is real elapsed time; omitted transitions make these differ. This is evidence capture, not a performance measurement. Both media toast holds exceed 1.5 s.

`make-diffs.py` uses exact RGB absolute differences amplified32× and a magenta max-channel>16/255 mask, without masking any pixels. `extract.py` extracts every encoded frame with ffmpeg and makes review sheets/storyboards. The frame/start archive contains all 422 accepted frames and 10 starts. Rejected takes stay in scratch and are excluded from published comparisons. `prepare-public.py` selects the accepted latest takes, creates per-clip rendering/steps/log sidecars and SHA-256 hashes, and packages public synthetic drivers/seeds. Inspect every before/after still, both diff types and video storyboards before uploading via ghdrop upload -m. Never upload homes, auth catalogs or private logs.

Stop every owned browser, Electron and server. Remove only the created before worktree. Leave the reviewed head checkout clean. The completed recapture did so; 15 GB remained free.
