# Desktop Stable-switch proof

Base: [742a91209e](https://github.com/pingdotgg/t3code/commit/742a91209e242caddf504e2d81bbd42b5d9118d0)
Head: [b612ad435a](https://github.com/pingdotgg/t3code/commit/b612ad435aed664cac7cba6d8b2478eb877a0da9)

Linux x64 packaged Electron 44.10.1 apps, built separately with nightly version 0.1.9-nightly.20261114.1; run from extracted AppImages under headless-desktop (Xvfb/D-Bus), with APPIMAGE pointing at the respective real AppImage. The same driver used fresh T3CODE_HOME and XDG_CONFIG_HOME directories. Light theme, 1280x900 viewport. No live database was copied; no projects or threads were imported. First-run completion was saved through the real desktopBridge.setClientSettings IPC.

System policy at /etc/t3code/policy.json:

```json
{"schemaVersion":1,"organization":"Example Org","limits":{"updates":{"mode":"auto","allowNightly":false}}}
```

The synthetic display label was Example desktop. The local fixture registry (npm_config_registry) advertised Stable 0.1.9 with databaseMigration 94. This avoids the unrelated downgrade guard: the public Stable package advertises migration 81, but both tested commits create migration 94 databases. The local generic update feed served an actual Stable 0.1.9 AppImage built from head, with production branding and migration 94. Both sides used the identical registry metadata, Stable binary, and policy. Feed configuration is a fixture, not a replacement desktopBridge or fake update state.

Build each nightly with:

```sh
T3CODE_DEV_SEED=0 vp run dist:desktop:linux --build-version 0.1.9-nightly.20261114.1 --mock-updates --mock-update-server-port 18432 --output-dir "$OUTPUT"
```

Build the Stable fixture from head with build-version 0.1.9. Serve its latest-linux.yml and AppImage from the feed subdirectory beside feed.cjs. The head nightly was initially built without --mock-updates; its extracted resources/app-update.yml was configured with provider generic, url http://127.0.0.1:18432, and updaterCacheDirName t3code-evidence-updater. The base used the same local server via the build's generic app-update.yml. Managed policy itself had no custom feed and was exactly the policy above.

## Driver

Start feed.cjs. Set CHECKOUT, RUN_DIR, DRIVER_PORT, EXECUTABLE (extracted t3code executable), and IMAGE (the real AppImage), then run headless-desktop node drive.cjs. The driver exposes a loopback JSON command endpoint for text, eval, main-process window bounds, role-based click, screenshot, recording, and close. Set first-run completion through desktopBridge, navigate to t3code://app/#/settings/about, and set the window bounds to x0/y0/1280x900 after startup. Read window.desktopBridge.getUpdateState() without modifying it. Select the Update track combobox, then Stable. Wait for release checking to finish. Capture the dialog only after two screenshots 500ms apart match. Run confirm.py with the driver port and run output directory: it records the dialog, clicks Switch to Stable after two seconds, and records ten further seconds.

Base: IPC had enabled false, status disabled, channel nightly, the policy's switch-to-Stable message, and no channelBlockedByPolicy key. The dialog had an empty machine table and said no machine can switch and reinstall manually. After confirmation, channel latest was saved but currentVersion remained 0.1.9-nightly.20261114.1, downloadedVersion null. It fetched metadata but no AppImage.

Head: IPC carried channelBlockedByPolicy true. The dialog listed Example desktop / This app / restarts last and had neither incorrect note. Confirmation fetched the Stable AppImage, reported download 100%, and invoked quitAndInstall. The installed AppImage SHA-256 matched the served Stable artifact: 87458a264e17f56f3dc65420e06af41fa29ecce8eeb0057b9397cd97f397c87b. The original nightly AppImage path became a symlink to the installed Stable AppImage. Automatic relaunch reached the splash screen in the recording. To verify the installed version independently of the debugger, the resulting installed artifact was extracted and reopened with the same T3 home: About showed Version 0.1.9, Stable, Up to Date, and the same managed organization.

The head confirmation driver ends with an expected closed-page error when it tries to read IPC after quitAndInstall; this occurred after the actual install, not before it. The video was already saved. The installed-state verification was a separate launch of the installed artifact.

## Capture processing

Dialog stills are crops of stable full-window screenshots. Result stills crop About's organization/version/update-track rows. Videos record the same dialog-confirmation interaction; an unrelated CLI-path description is redacted after confirmation. Each ends with a labelled still of the resulting version, with head explicitly labelled as a reopened installed artifact. No application behavior or update status was edited.
