# Linux protocol launcher matrix — historical plan with fresh P2/P3 correction

**Current status:** This preserves the original proof plan. Its original no-captures status prose is historical. The P2/P3 requirements and backend-assumption correction below are current; eight fresh accepted retakes are in the corrected evidence index.

Scope: takeover [#8668](https://git.cafe/pingdotgg/t3code/pulls/8668), mirrored [#8668](https://github.com/pingdotgg/t3code/pull/8668). Original [limwa/t3code](https://github.com/limwa/t3code) branch `feature/fix/linux-protocol-launcher`, [fa3d27b](https://github.com/limwa/t3code/commit/fa3d27bf1c1fd2a7d42f3afacbb98efebedef488). Replacement `takeover/8668-linux-launcher`, [f42e44a](https://github.com/pingdotgg/t3code/commit/f42e44a1e5b3508add3ab8818e0b8e3956e39a57), against [3cc31ed](https://github.com/pingdotgg/t3code/commit/3cc31ed27c8956b36cee35a50509fb7c6869592b). Checkout: `/home/ubuntu/wt/pr-8668` only. All six changed files and complete pinned diff read. No code edits, captures, pushes, comments, watches, or delegation performed. This is an evidence plan, not a runtime PASS.

## Finding and evidence status

Source matches the requested selection in both writers: trimmed nonempty `T3CODE_DESKTOP_LAUNCHER_PATH`, then trimmed nonempty `APPIMAGE`, then `process.execPath`. Normal path obtains the value through DesktopConfig and DesktopEnvironment; early path reads process.env synchronously. Both use the same desktop-entry renderer. No concrete implementation defect found in the pinned diff.

Proof remains INCOMPLETE. The two added tests exercise override precedence with APPIMAGE present; the normal test supplies an already-normalized Option, and only the early test proves trimming. Neither new test covers the actual shared-Electron defect: override present, APPIMAGE absent. Existing fallback tests support unchanged behavior, but are not real desktop dispatch evidence. No tests were executed by this auditor. `git diff --check` against pinned commits passed.

## Selection fixture matrix

L = executable distribution launcher; use a real wrapper which forwards every argument to the ordinary built app. A = executable APPIMAGE-path fixture forwarding to that same app (not a claim of actual AppImage mount coverage). E = actual `process.execPath`. Use a launcher path containing an internal space plus leading/trailing whitespace in the environment value. Baseline ignores L entirely.

Every row applies to BOTH early and normal Linux writers. Primary runtime fixture is packaged, production scheme. Each interaction row requires **recording: timestamped transcript** under `/tmp/to-8668/headless-desktop`; each gets BEFORE/AFTER entry bytes, Exec line, default query, and dispatch observation. No captures exist yet.

| ID | Launcher env | APPIMAGE env | BEFORE target | AFTER target | Evidence / purpose |
|---|---|---|---|---|---|
| S1 | padded nonempty L | unset | E | trimmed L | Recording; actual defect. Shared Electron baseline fails to deliver to running T3; replacement does. |
| S2 | padded nonempty L | padded nonempty A | trimmed A | trimmed L | Recording; override outranks APPIMAGE; wrapper invocation distinguishes targets. |
| S3 | padded nonempty L | whitespace-only | E | trimmed L | Recording; blank APPIMAGE does not block override. |
| S4 | unset | padded nonempty A | trimmed A | trimmed A | Recording; opt-in unset AppImage control. |
| S5 | unset | unset | E | E | Recording; opt-in unset executable control. Use standalone packaged executable, not shared Electron, for successful control dispatch. |
| S6 | unset | whitespace-only | E | E | Recording; blank APPIMAGE fallback. |
| S7 | empty string | padded nonempty A | trimmed A | trimmed A | Recording; empty override AppImage control. |
| S8 | empty string | unset | E | E | Recording; empty override executable control. |
| S9 | empty string | whitespace-only | E | E | Recording; both blank fallback. |
| S10 | whitespace-only | padded nonempty A | trimmed A | trimmed A | Recording; whitespace override AppImage control. |
| S11 | whitespace-only | unset | E | E | Recording; whitespace override executable control. |
| S12 | whitespace-only | whitespace-only | E | E | Recording; trim-to-empty on both. |

Unset and empty APPIMAGE take the same optional-config/JS fallback branch; execute empty APPIMAGE as an alias fixture of S3/S6/S9/S12 or record explicit test evidence that normalization is equivalent. A nonpadded nonempty L is an alias of trimmed L, not a separate code path. Existing renderer escaping remains shared: simple paths and paths needing quotes are relevant; use S1's internal space to cover quoting. Percent, quotes, backslashes, dollar signs and backticks retain the existing renderer tests; no new escaping algorithm warrants a full runtime Cartesian product. Do not interpret launcher value as a shell command with arguments, expand `~`, or require executable validation: those are outside this spec.

## Lifecycle and surface matrix

| ID | Fixture / action | Required result | Evidence |
|---|---|---|---|
| P1 | Linux packaged production, VITE_DEV_SERVER_URL unset | `com.t3tools.T3Code.desktop`, `t3code`, Icon present; both writers select S1–S12; normal registers MIME default | Recording; main fixture for all selection rows. |
| P2 | Linux unpackaged production, VITE_DEV_SERVER_URL unset | Production filename/scheme, no packaged Icon; both writers select override/fallback; normal writes entry but returns before its own MIME registration | Recording for S1 and S5 representatives; query both schemes before app launch, after entry creation before OAuth registration, and after startup; snapshot private default files at the real normal-writer return. Observe the normal writer returning while unpackaged, zero setDefaultHandler calls, and private MIME-default file snapshots before any driver manual prerequisite. Preserve nonempty query output and distinguish desktop-file inference and separate OAuth registration from this writer. |
| P3 | Linux unpackaged development, valid VITE_DEV_SERVER_URL | `com.t3tools.T3Code.Development.desktop`, `t3code-dev`, no packaged Icon; override/fallback selection unchanged; normal Linux writer skips explicit MIME registration (whole-app no-registration is not implied) | Recording for S1 and S5 representatives, with the same pre-app/pre-OAuth/post-start queries and normal-return side-effect observations as P2; query both schemes. Use a real isolated Vite/server if starting renderer. |
| P4 | Linux packaged development, valid VITE_DEV_SERVER_URL | Development filename/scheme WITH Icon; isPackaged still permits normal MIME registration | Source-supported unusual configuration; focused fixture for S1 if claiming this combination supported. Not substitute for production P1. |
| W1 | Missing entry at startup | Early writes valid complete entry before portal identity setup; normal sees identical bytes and skips truncation | Recording on S1; capture early bytes and final bytes, `cmp` + hashes; observe normal registration milestone and no second write. |
| W2 | Stale entry referencing old executable | Early replaces stale bytes with current selection before readiness; normal preserves matching bytes | Recording on S2 or S4; seed stale file only inside private XDG. |
| W3 | Entry removed/changed after early writer, before normal read | Real normal writer independently creates/corrects entry with exactly early writer's bytes | Recording on S1 and S4; pause real app via main-process inspector at normal read, save early file, remove only that private file, continue, then `cmp` full bytes. No mocked writer or mocked isPackaged. |
| W4 | Early/normal write failure; icon/cache/default helper failure | Existing best-effort startup behavior remains; write failure does not falsely establish default; icon/cache failure may still proceed to default | Existing focused error tests/source review sufficient for unchanged branches; injected failures are optional diagnostic transcripts, not required new captures. |
| W5 | Restart after override removed | Entry returns to A/E; no sticky override remains | Recording; reuse private fixture S1→S5 or S2→S4 with app stopped between launches. |
| X1 | macOS / Windows | Early Linux entry branch absent; normal returns before writer; new config inert here | Source review, no platform screenshot required. |
| X2 | Web, mobile, provider adapters, settings/palette/keybindings/MCP, relay/tunnel | No changed renderer, contract, provider, transport, or entry point; desktop OS launch selection only | N/A; do not fabricate cross-client combinations. |
| X3 | Viewport, theme, responsive layouts | No render components or visual output changed | N/A. Viewport/theme screenshot grids would not prove Exec/default/dispatch behavior. |

P1/P2 are distinct even though both can use the production scheme. `isDevelopment` depends on VITE_DEV_SERVER_URL, not `app.isPackaged`. Plain `electron <app-directory>` is unpackaged: it cannot establish P1 automatic registration.

## Transcript contract and minima

1. Record timestamp, pinned source SHA, baseline/after bundle hashes, real Electron executable, app path, actual packaged/development flags (inspector read is acceptable), private HOME/XDG/T3 paths, launcher/APPIMAGE presence and values. Do not publish tokens, private project titles, database contents, or full unfiltered environment dumps.
2. Run all app launches, queries, and xdg-open in the SAME headless desktop/D-Bus environment with an actual generic desktop handler available (e.g. gio). Record relevant XDG_CURRENT_DESKTOP/DE; otherwise xdg-open may take a generic fallback unrelated to the desktop entry.
3. Capture complete generated desktop entry and `desktop-file-validate`; record `xdg-mime query default x-scheme-handler/t3code`, extracted `Exec=`, app readiness and original app PID. Development row queries `t3code-dev` too.
4. Timestamp `xdg-open 't3code://proof-audit-8668/s1-before'` (unique marker per run) and its exit status. This is harmless: no provider auth token, prompt import, or task action. Observe the original running T3 main process receiving that exact URL through `second-instance`, using a nonmutating main-process inspector event observer or debugger breakpoint. Record original PID still alive and secondary launch completion. Wrapper argv logs prove dispatch selection, but alone do not prove delivery to the running app. xdg-open exit 0 alone is insufficient. For baseline S1 record honest failure/non-delivery, not an invented success.
5. Capture early/final bytes before any second launch (the dispatched secondary also runs early setup). For byte consistency, observe startup writes with syscall tracing or debugger; final file alone cannot prove both writers. W3 forces the actual normal writer without changing production code; compare whole files, not just Exec. On ordinary W1 observe read + no rewrite, demonstrating preservation of portal-readable entry.
6. Preserve BEFORE/AFTER plus unset/blank controls. Minimum distinct selection classes are override→L without A, override→L with A, fallback→A, fallback→E. At least S1, S2, S4, S5, S7, S8, S10, S11 need explicit transcript runs; complete matrix also resolves S3/S6/S9/S12 and empty-APPIMAGE aliases through transcript or focused fixture normalization evidence. Required lifecycle additions: P2, P3, W1–W3, W5. P4 can be disclosed as source-reviewed only. Do not mark the full matrix PASS from four happy-path runs.

## Real-app isolated seeding recipe

The capturer should implement this outside the repository. No scratch scripts or proof assets should be committed.

1. Start from EMPTY fresh state under `/tmp/ev-8668`; the task explicitly forbids reading, copying, or writing `~/.t3/userdata`. The protocol-launch claim requires no existing projects, conversations, or user secrets. Set `T3CODE_DEV_SEED=0` and use `--no-seed` if a development server is needed.
2. For each version/row create private home/config/data/cache/T3 directories and logs. Let the real app create its own empty database. Never migrate or copy live state.
3. Export HOME to private home, XDG_CONFIG_HOME/config, XDG_DATA_HOME/data, XDG_CACHE_HOME/cache, and T3CODE_HOME/private t3 BEFORE app start. Explicit T3CODE_HOME means desktop state is its `userdata` in both development and production. XDG config separately isolates Chromium profile/single-instance lock. Keep standard XDG_DATA_DIRS for installed desktop utilities. Do not set VITE_HTTP_URL or VITE_WS_URL. Unset VITE_DEV_SERVER_URL for production, rather than setting an invalid URL.
4. Assemble two real Electron distributions outside repo from installed `apps/desktop/node_modules/electron/dist`. In each place actual built app under `resources/app`, with root package manifest main `apps/desktop/dist-electron/boot.cjs`, actual `apps/desktop/dist-electron`, built `apps/server/dist` including client assets, desktop prod-resources/resources, and all external/native runtime dependencies resolved physically or from private staging. Baseline uses `/tmp/to-8668/before-dist-final` for desktop bundle; after uses the pinned after bundle. Shared unchanged server/renderer assets are acceptable. Do not mutate repo build output or patch isPackaged. Verify main/renderer/server successfully start; missing native/external dependencies are packaging friction, not license to mock app.
5. For P1 launch distribution executable directly with its adjacent resources/app so Electron naturally sets isPackaged true. For S1 reproduce shared-Electron launching: use installed Electron with the real app directory and a wrapper preserving app-directory argument; this naturally gives isPackaged false, so use P2 to demonstrate the defect and separately P1 to prove registration. Alternatively stage a shared runtime executable with adjacent real resources/app and a distinct stable launcher, naturally packaged; disclose that packaging shape. APPIMAGE-present path fixture is a forwarding executable and must be labeled simulated APPIMAGE env, not an actual mount.
6. Invoke real app and all OS interaction inside `/tmp/to-8668/headless-desktop`; capture stdout/stderr and timestamps. Track only spawned PIDs, stop them by captured PID, and verify shutdown before reusing/copying any state. Never pkill by pattern. Readiness uses actual app/backend milestone, not arbitrary sleeps.

## FRICTION

- Advertised headless-desktop helper was missing; dispatcher recovered `/tmp/to-8668/headless-desktop` with installed Xvfb/D-Bus/keyring. xdg-utils and desktop-file-utils are now installed. Auditor verified helper exists; did not execute it or claim captures.
- Baseline desktop bundle exists in `/tmp/to-8668/before-dist-final`; after full builds were reported successful by dispatcher. Directory existence is not runtime packaging proof, nor independent provenance/hash verification.
- Normal writer deliberately skips equal early entry. A final entry snapshot cannot distinguish both writers; W3 or equivalent independent real-writer observation is needed.
- The normal Linux writer skips registration for shared Electron unpackaged launches; separate Clerk OAuth setup still calls Electron registration. Packaging and development are independent axes. Desktop registration and URL delivery require real process/profile continuity.
- Generic xdg-open fallback can mishandle quoted executable paths; record actual dispatch backend. A valid desktop file and MIME query do not establish successful URI delivery.
- No captures yet. This pass found proof gaps, not a merge-ready evidence set. No live ~/.t3 state was opened or changed by this auditor.

## Lead correction before capture

Main was merged without rebase; the launcher diff is unchanged. Capture baseline is `3cc31ed27c8956b36cee35a50509fb7c6869592b`, integrated head `f42e44a1e5b3508add3ab8818e0b8e3956e39a57`. Both desktop bundles are rebuilt against that baseline, with shared server/renderer assets from the integrated build. The original auditor seeding suggestion was removed because it contradicted the explicit task prohibition on live T3 state. Empty isolated state proves this nonvisual behavior. No capture has yet been approved.

## Fresh correction to P2/P3 fixture assumption

The original literal requirement that an unpackaged query remain empty was a faulty backend assumption, not a change to launcher scope. Installed xdg-mime defapp_fallback scans desktop-entry MimeType fields and can infer a handler without a default assignment. In addition, this built application contains an unchanged Clerk OAuth call to Electron app.setAsDefaultProtocolClient; the whole-app startup may therefore create mimeapps.list even though DesktopLinuxUrlHandler returns before its own setDefaultHandler. The correct assertion is narrowly about the real normal Linux writer: no explicit registration by that writer. Record other startup registration honestly; never infer its absence from isPackaged or conflate query inference with explicit registration. Fresh runtime correction links and exact temporal snapshots are supplied in the corrected evidence index. Earlier planning/status prose above is historical, not a claim that captures remain absent.
