# Proof audit, pass 1

PR [#32330](https://git.cafe/pingdotgg/t3code/pull/32330), issue [#32313](https://git.cafe/pingdotgg/t3code/issues/32313). Reviewed [5551099eb1](https://git.cafe/pingdotgg/t3code/commit/5551099eb1093eca432730a888e94a774e87c8e0) against local origin/main [e5b0718a8a](https://git.cafe/pingdotgg/t3code/commit/e5b0718a8a297823920d1bf3a7d5cbacd57263ea). Read-only audit: no source edits, pushes, forge comments, server launches or device leases.

## Findings

1. **Missing integrated protocol proof (R1/R2).** The PR body links no captures or runtime transcript. Its four new tests execute the real drain but mock startTurn, shell/connection inputs, attachment preparation and composer persistence. loseLaunchReply makes startTurn return a connection error and manually inserts the thread into the shell atom. No server commits anything in that test. The assertions prove the client decision, equal command/message IDs and local cleanup; they do not demonstrate committed server state, actual lost reply, reconnect or absence of a second server message/run.
2. **The claimed device limitation is too broad.** Timing an airplane-mode tap is unreliable, but a proxy can forward the real launch, observe the successful response, suppress it and close the downstream socket. The server has already finished accepting the launch by that point. A new connection is then passed through. This can drive either a pooled Android emulator or iOS simulator; no platform-specific production edit is needed. A dev-only hook after the real startTurn result, returning a connection failure once, is another option, but must be identified as injected failure and must not fake a server commit. Neither mechanism exists in this diff; feasibility is code-based, not an executed device claim.
3. **No render components changed.** This is a client state/protocol correction, not a spacing/color/layout correction. AGENTS.md calls for a failing regression test for state/protocol bugs. A real-delivery/real-server before/after runtime transcript is the proportionate missing integration evidence. A full screenshot grid is unnecessary. Existing tests should remain the primary regression proof, with the transcript establishing the boundary they mock.
4. **Tests independently checked at the requested head:** the two mobile delivery files pass 45/45. Three targeted existing server launch tests pass: replay of a server-allocated launch, retry after only thread creation was accepted, and scheduling an accepted preparing message once across retries. Those server tests cover receipt replay and partial-launch completion separately; they are not a mobile end-to-end repro. The PR body's baseline failure is reported evidence; this audit did not rerun the new test against main. check:changed was not rerun in this read-only proof audit.

## Caller and render-path inventory

The changed production file is [threadOutboxDelivery.ts](https://git.cafe/pingdotgg/t3code/blob/5551099eb1093eca432730a888e94a774e87c8e0/apps/mobile/src/state/threadOutboxDelivery.ts), not a React component. Its other changed file is its permissions test. There are zero changed component props, JSX branches, styles or platform branches.

- Production lifetime caller: App module -> installThreadOutboxDelivery -> createThreadOutboxDelivery. Delivery input atom changes and retry timers call drain; the returned drain/dispose methods are also used by tests. recoverEditedCreationAfterDelivery is called by the successful-but-edited launch path and the drain's creation/remove path; its only other callers are tests. The new map is in memory, keyed by messageId, with launch revision and workspace; queue disappearance prunes it.
- Entry: NewTaskDraftScreen.handleStart persists a creation in the outbox, navigates to the submitted thread and clears the new-task draft. Home, sidebar, new-task navigation, incoming prompt/share entry flows converge on this creation path. Existing-thread sends use the separate sendQueuedMessage path. ThreadRouteScreen's work-locally recovery can call startTurn directly and does not use this new map. Settings/command palette/MCP do not invoke the mobile creation drain.
- Thread selection: outbox -> selectThreadCreationDetail/use-thread-selection -> ThreadRouteScreen (ordinary thread, fullscreen, inspector/workspace embedding) -> ThreadScreenBinding -> ThreadScreenHost/Outlet -> ThreadDetailScreen -> ThreadFeed and composer. NewTaskDraftScreen also uses ThreadScreenBinding for the preceding draft surface. While an unacknowledged creation remains queued, detailRef is null even if the shell already lists the real thread. The local initial-message anchor is retained, the creation presentation is preparing, and follow-up Send is blocked. Successful replay records delivered, removes the queue item, enables detail subscription and transitions to the server message/run. The creation anchor is omitted once the same message ID appears in the projection.
- Feed inputs: creation present/absent, delivery outcome null/delivered/failed, queuedMessages zero/one/many, dispatching message ID, server message ID present/absent, detail loading/ready/unavailable, connection tone, worktree setup and terminal run status. Pending user rows show Pending until acknowledged; creation rows do not show the existing-message Edit action. Empty/unavailable feed without queued messages is a different existing branch. None of those render branches changed.
- List: outbox -> usePendingNewTasks/buildPendingNewTasks -> HomeRouteScreen/HomeScreen and ThreadNavigationSidebar -> ThreadListV2PendingRow. A queued creation is an Unsent row with Sends on reconnect; successful cleanup removes it. Hidden-thread keys filter it. A rejected creation can become a separate Draft row. A zero-to-one change adds the Unsent section; multiple pending/draft rows use existing sorting/dividers. The title derives from text (fallback New thread, truncation after 72 characters); a recovered attachment-only draft uses singular/plural attachment titles.
- List layout inputs: pending versus draft badge/delete label, screen versus sidebar pane, selected/unselected, project icon present/absent, project title fallback, branch null/present, environment label null/present, machine icon presence, pending/trailing dividers, title one/two lines, width/full-swipe width, font scale and Android title measurement. Thread layout inputs: compact/split/column/fullscreen/inspector, iOS/Android keyboard and safe-area treatment, light/dark appearance. These existing axes do not affect the new revision predicate and are factored out of the required proof rather than multiplied into an unrelated screenshot suite.
- Composer payload layout: empty versus populated draft; short/multiline text; context chips; zero/one/multiple attachments; image/video media versus files, pending/uploaded states; attachment-only input; restored draft/error panels and send caps. The fix avoids calling the merge path for an unchanged launch; it does not alter how any of these are rendered.
- Interactions: New task/Send; navigate to pending or live thread; type an independent draft during retry; reconnect; retry backoff; change permissions; PR preparation; edit/save/cancel existing queued edits where available; delete pending task via swipe/context menu plus confirmation; error recovery/Edit task; attachment preview/removal. An open editor holds the queue and revision-changing edits retain the old hand-back semantics. Restart destroys the map and retains the documented old hand-back limitation. Stills cannot prove any delivery/interaction sequence below.
- Surfaces/providers: iOS and Android share this service. Web/desktop delivery is unchanged. Local/remote/tunnel transport all reach the same server launch RPC; the repro is a remote-mobile transport failure. Provider choice does not branch in the new code; use the free Replay provider for runtime evidence rather than repeat paid-provider runs. No new contract, permission scope, MCP capability or user setting is introduced.

## Fixture matrix

Common seed: disposable paired environment and project rooted in a disposable Git repository; a unique environment/thread/message/command ID per case; one creation with text “My dictated idea”, no attachments, local workspace, valid provider selection and operation access, live shell and connected transport. R1/R2 intentionally retain the same IDs on resend. Routes are t3code-dev://new with the selected project and t3code-dev://threads/<environmentId>/<threadId>; normal app navigation can supply the project selection. Injecting local queued data requires the real enqueue/update manager through a dev driver, not writing fake screenshots.

Status: **MISSING** means required additional integration evidence; **UNIT** means an identified existing test is appropriate proof at this layer; **EXEMPT** means an unchanged presentation/control variant, not a demand for a capture. Every temporal/interactive row is marked **recording** if UI proof is chosen; an ordered runtime transcript can replace video for this state/protocol fix.

| Row | Named fixture and required data | Diff effect / visible state | How to seed in the real app or driver | Evidence form and pass-1 status |
|---|---|---|---|---|
| R0 | Ordinary acknowledged creation; empty thread composer; launch reply delivered | Control: local preparing prompt -> one server prompt/run, empty outbox, empty composer | New task -> select paired project -> Send; no fault | **recording** if UI; control in existing delivery suite; **EXEMPT** from additional capture |
| R1 | Lost reply, unchanged revision, empty composer; server has committed launch and initial message before disconnect | **Affected:** before, sent text returns to composer; after, same-ID resend clears outbox and composer stays empty; one server message/run | Proxy procedure below; stay on thread route after Send; preserve app process through reconnect | Ordered before/after runtime transcript, or real-app **recording** plus protocol log; **MISSING** real-server boundary proof |
| R2 | R1 with independent draft “Keep this draft separate” in thread composer during retry | **Affected:** before, newline + sent text merged into draft; after, draft byte-for-byte unchanged | R1 with proxy paused until composer draft is set by typing or real composer-draft API; release reconnect after setup | **recording** if UI; new unedited-launch test covers mocked drain; **MISSING** integrated runtime transcript |
| R3 | Edit queued text to “Edited idea” while launch in flight, then shell lists thread; revision differs | Preserved: edited content handed back/merged once, no resend, queue clears | Real manager.update while the intercepted launch is held; an already-open edit owner must release before recovery. Do not imply a creation bubble has the ordinary follow-up Edit button | **recording**; new edited-launch test covers this; **UNIT**, no separate capture required |
| R4 | App restart after commit/lost reply, persisted creation and real thread, no in-memory launch record | Preserved limitation: text handed back after reload; not fixed by this PR | R1 then terminate/relaunch client before retry; same persisted outbox, real shell catches up | **recording**; body explicitly discloses limitation and generic recovery tests cover hand-back; **EXEMPT** from fix proof |
| R5 | Unchanged launched creation parked during disconnect or non-live shell; thread may already be in cached shell; reconnect becomes live | **Affected gate:** retain pending prompt/preparing/Unsent state, do not hand back merely because cached thread exists; eventually replay. Missing config also parks/retries | Proxy holds connection/reconnect or pauses shell delivery; no server-config mock in a claimed real-app run | **recording**; gates traced in resolver, not covered by the new access test (its transport stays connected/live); **EXEMPT** from separate capture, include transition in R1/R2 |
| R6 | Unchanged launched creation, thread exists, connected/live but operation access revoked; later regranted | **Affected gate:** no replay while denied; queue stays, then one replay and cleanup; existing read-only composer treatment | Disposable connection with operation scope removed/regranted through real auth/RPC, or labelled atom harness for deterministic gate-only proof | **recording**; new access test covers predicate; **UNIT**, no separate capture required |
| R7 | PR-backed creation, valid PR reference, prepared checkout path/branch, lost reply, unchanged revision | **Affected workspace reuse:** setting-up presentation may precede launch; retry uses the launched worktree, no second PR checkout | Select PR task in real app with disposable accessible PR; proxy loss as R1. A checkout mock is a **labelled harness**, not a real checkout capture | **recording**; new PR retry test checks one checkout and equal bootstrap; **UNIT**, no separate capture required |
| R8 | Normal worktree launch; server accepted thread.create but initial-message receipt missing | **Affected replay path:** retry must finish initial message rather than silently drop it; setup/preparing -> one run | Driver can dispatch thread.create with launch command ID through real RPC, then enqueue same-ID creation and inject the failed first launch; mark constructed partial launch explicitly. A server hook pausing between commits is more faithful | **recording** if UI; existing targeted server claimed-thread/replay tests pass; **UNIT** for separate server behavior, no new capture required |
| R9 | Retry gets definitive rejection (model unavailable/policy/attachment too large/missing project); recovery persistence succeeds or fails | Newly reachable existing rejection path: failed creation/Edit task or separate recovered Draft; preserve content, keep queue if durable recovery fails | Replay attempt after altering disposable provider/policy/project via real settings/RPC; deterministic disk/persistence failure needs a **labelled harness** | **recording**; existing recovery tests cover restoration/rollback; **UNIT** for unchanged recovery machinery |
| R10 | Short text versus >72-character/multiline prompt, optional context chips, pre-existing multiline composer | Same predicate and cleanup; title wrap/truncation and text/context layout are unchanged | Type/paste data into new-task/thread composer, attach supported context; apply R1 only if demonstrating delivery | Static layout **still** allowed; any send/retry is **recording**; **EXEMPT**, no new layout branch |
| R11 | Attachment-only creation, one file, empty text | Same retry predicate; attachment-only feed and recovered Draft title (“1 attachment”) are existing layout branches | Attach one disposable file, Send through normal uploader; proxy after actual launch reply is produced | Static layout **still**; upload/retry is **recording**; **EXEMPT**, not in required text-only capture set |
| R12 | Text plus image/video media, pending/uploaded state | Same predicate; media preview/grid and pending-to-server attachment display are unchanged | Pick disposable media in app; real upload before launch; labelled attachment harness if native picker not used | **recording** for upload/delivery; static grid may use still; generic preparation tests cover revision handling; **EXEMPT** from new captures |
| R13 | Multiple files/media and an existing attachment-bearing draft; recovery exceeds send cap | Same predicate; attachment count/chips, plural title and trimming guard unchanged; edited payload must retain ownership | Attach multiple disposable files; for overflow recovery use real queue/draft manager APIs and label driver-seeded state | **recording** for edit/recovery; static overflow layout may use still; existing recovery/ownership tests; **EXEMPT** from new captures |
| R14 | Queue zero/one/many across same/other environments; hidden thread; open editor/delete confirmation; selected/unselected pending row | Map cleanup follows disappearance; Unsent section/row removal, selected styling and hidden filtering already exist; editor holds delivery | Enqueue unique-ID tasks via Send while proxy blocks; Home/sidebar, open pending task, swipe/menu delete and confirm; atom harness for unavailable edit races | **recording**; no changed render branch; **EXEMPT** from extra capture; R1/R2 must still assert final empty outbox |

## Minimum evidence and capture set

Required: keep the state regression's reported main failure and passing head result, plus **one timestamped before/after runtime artifact containing R1 and R2 as two parameterized runs**. It must execute the real createThreadOutboxDelivery, actual queue revision/removal and actual composer merge/read operations against a real isolated dev server; the transport/startTurn must not be a fake success/failure sequence. Native storage/filesystem shims, if necessary in Node, must be declared, and the artifact labelled a **non-UI runtime harness transcript**, not a mobile capture. A script that only calls launchThread twice misses the client bug and is insufficient.

The transcript must record, without credentials or private runtime metadata:

1. Baseline/head commit, synthetic fixture IDs, queued text, initial composer snapshot and revision.
2. Server receipt/projection proves thread and initial message committed; proxy intercepts success for that request and drops it. Distinguish the protocol request ID from the durable launch command ID.
3. Client observes transport failure, retains outbox, reconnects and receives live shell containing thread.
4. Before: no replay through the buggy remove path, text merges into the draft. After: replay uses original commandId/messageId and workspace; server projection still has one user message and one run; outbox becomes empty; composer equals its initial value.
5. New timestamped run directories under the task scratch root, driver command and relevant output. Do not delete earlier runs or raw logs before upload.

Required visual capture set (viewport × theme × row): **empty** for the recommended transcript route, because no render code changed. Required dynamic rows are R1/R2, not static screenshots. If the lead elects UI proof instead: **one pooled phone viewport × one theme × R1/R2**, recorded before and after; preferably iOS for the reported incident, Android is also valid for this shared JS path. Record the actual device dimensions and theme (do not invent them). Show keyboard/composer and enough transcript to identify the sent message; correlate with the server/proxy log. No new iOS/Android, light/dark, compact/tablet/fold matrix is justified by this diff. Other table rows are controls or pre-existing presentation branches, with unit coverage/exemptions explicitly stated rather than falsely called captured.

## Concrete mobile repro feasibility

This can be produced on a pooled Android emulator or iOS simulator. Prepare the native dev client with mobile:install before leasing, lease through device-pool, install into that named session, start checkout Metro and isolated backend on reachable interfaces, and pair the mobile app to an HTTP/WebSocket reverse proxy on this VM. The proxy must forward auth/ticket and ordinary HTTP routes and WebSocket upgrade; use its reachable origin consistently. Keep startup pairing tokens and bearer credentials out of logs. Do not use the live T3 home.

The wire uses Effect RPC JSON. Track outbound Request frames whose tag is orchestration.launchThread, payload commandId matches the fixture, and id identifies the pending response. Accumulate frame parts if enabled. Forward the request upstream normally. Intercept the corresponding successful Exit response (and any result-bearing frames, if present), confirm/record committed state using a separate control connection, suppress it, then terminate downstream so the mobile RPC fails instead of hanging. Drop once only. Pass new sessions through so the shell resynchronizes and the unchanged outbox retries normally. Never drop the launch request itself or cut the socket before knowing the server succeeded: that would reproduce a different bug.

This proxy requires no application source edit. A mobile dev-only hook at the startTurn boundary can also await the actual server success and then return one transport error; it is easier but is deliberately injected failure, not literal packet loss. Both options need real commit evidence. Access to the pool, VM reachability, cache availability and lease availability were not exercised in this read-only planning pass; no device blocker was established.

## Audit coverage and friction

Read complete two-file diff, delivery/recovery callers and data consumers, mobile routes/list/feed/composer state, real startTurn -> launch RPC mapping, server launch receipts and existing server replay tests. Ran only the focused tests noted above. No capture or runtime integration driver was run in this pass. No long-lived process was started; both test sessions exited. Filesystem remains clean. Disk remained above 5 GB (105 GB at final recorded check).

FRICTION:
- forge | First PR view briefly reported the pre-merge head while status had the requested head; a second view confirmed the requested head and same verification text | Revision-consistent forge read snapshots.
- tool | Some initial guessed module paths were absent; rg --files/imports resolved the actual files | Discover paths before targeted reads.

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

VERDICT: INSUFFICIENT R1/R2 real-delivery, real-server dropped-reply before/after runtime transcript (or correlated mobile recordings)
