# Proof audit pass 1 — capture contract

PR [#24641](https://git.cafe/pingdotgg/t3code/pull/24641), branch `takeover/pr-19687`, head [271e7b6f11](https://github.com/pingdotgg/t3code/commit/271e7b6f110b5817e70bba16c46a91b2475ccf09), compared with merge-base [3eb836664e](https://github.com/pingdotgg/t3code/commit/3eb836664eb1a5d0102055fe1e4a7fc5c1d8a9c7). Read-only source audit in the supplied worktree. This is a fixture/capture specification, not a visual-verification result. No captures from this round exist. Historical PR images, matrices and recordings are unaccepted leads until a later evidence audit verifies their fixture, revision and actual actions.

## Findings first

1. No concrete implementation defect found within this narrow proof audit. The sole changed production branch removes the source native-reference requirement while preserving source-thread identity, app-thread ownership and provider-instance association. Both known production callers consume it.
2. The affected result is cross-provider eligibility, not new markup. Web changes the driver/continuation lock, initial Favorites tab, search candidates, provider sidebar disabled states/order, selectable keyboard targets, short-name preference, and the selection-handler guards. Mobile changes the available provider/model groups and filter choices, with a pending-selection/Save step. Showing an enabled icon or a selected row alone does not prove selection or handoff.
3. Exact precedence matters: a **resolved provider session** returns its negotiated capability before the no-session active-run guard. Do not claim every active run blocks handoff. A bound session is looked up by ID even if its status says stopped/error; an unbound fallback ignores stopped/error records. Normal detach removes the session from the returned projection. Record the effective client projection, not just SQL input.
4. Missing mobile evidence remains material. A narrow web viewport is not Android or iOS. This round must include the requested Pixel 9 and iPhone 17 Pro through the shared pool; device-panel-off is not a blocker while the pool works.
5. Saved-history delivery needs a real send plus a target request record containing a prior-history sentinel. A composer draft change is insufficient. A mock transport can prove the pipeline and history delivery; it cannot prove an external model understood history.

## Source-derived call and render paths

Changed production file: [threadWorkflows.ts](https://github.com/pingdotgg/t3code/blob/271e7b6f110b5817e70bba16c46a91b2475ccf09/packages/client-runtime/src/state/threadWorkflows.ts#L67). The other changed file is its test. Neither renders UI. Repository search finds two production calls:

- Web/desktop content: [ChatView.tsx](https://github.com/pingdotgg/t3code/blob/271e7b6f110b5817e70bba16c46a91b2475ccf09/apps/web/src/components/ChatView.tsx#L759) → `modelPickerLockedProvider` → `ChatComposer` → `useComposerProviderModel` → `ComposerControlsBar` → `ComposerModelPicker` → `ProviderModelPicker` → `ModelPickerContent` → sidebar/model rows. Independently, `useComposerSelection` consumes the same boolean for model disabled reasons and pointer/keyboard selection guards. All picker selection routes converge there. `modelPicker.toggle` opens this picker via the composer handle. Settings configure providers, model visibility, favorites and keybindings; they are fixture inputs, not another call to this helper. CommandPalette reads picker shortcut context but has no separate call or independent provider-switch implementation. Local drafts bypass the started-thread lock. Desktop wraps this web content; Electron shell/default desktop shortcut claims require a native desktop capture and are outside the requested web/mobile minimum.
- Mobile: [thread-provider-switching.ts](https://github.com/pingdotgg/t3code/blob/271e7b6f110b5817e70bba16c46a91b2475ccf09/apps/mobile/src/state/thread-provider-switching.ts#L17) → `useThreadComposerState` → `ThreadRouteScreen` → `ThreadScreenBinding`/`ThreadDetailScreen` → `ThreadComposer.canSwitchProvider` → `lockedProviderInstanceId` passed to `buildModelOptions` and `ThreadSettingsSheet` session. Model control opens `ThreadSettingsSheet`; an unavailable-model warning also opens it. The sheet re-reads the catalog for Manage hidden with the same lock. It filters/searches/groups models, sets a pending model, and Save commits it to composer draft settings. `NewTaskDraftScreen` separately supplies true directly and does not call this helper. Scheduled-task/new-task settings reuse the sheet without this changed policy.

Predicate equivalence classes, in order:

1. Projection null/undefined: helper false; mobile wrapper instead allows only a shell with no start markers (`latestRun`, `latestUserMessageAt`, `runtime` all null). Web derives its existing lock from the shell/draft.
2. Resolved session: capability true/false, regardless of source native ref; unchanged.
3. No resolved session + any preparing/starting/running/waiting run: false; unchanged. Queued is not active.
4. No session/active run + v1 import or zero runs: true; unchanged, no source association needed.
5. Otherwise require a provider thread whose id equals activeProviderThreadId, appThreadId equals thread.id, and providerInstanceId equals thread.modelSelection.instanceId. Missing/mismatched association remains false. Matching association + non-null native ref remains true. Matching association + null native ref changes false → true. Completed/failed/interrupted/cancelled/rolled_back/queued-only histories enter this same branch; their surrounding timeline differs but the picker policy is equivalent.

## Capture dimensions and notation

Minimum dimensions are chosen pairwise because no styles, breakpoints, native layout code, model data contracts or themes changed. Do not take a full Cartesian product of every policy control with every theme.

- **W** = web 1440×1000 CSS px, light, local dev origin.
- **N** = web 390×844 CSS px, dark, same origin; includes collapsed/resting and focused composer.
- **A** = native Android **Pixel 9**, portrait, dark, default device logical viewport; record exact resolution/density and software keyboard in metadata.
- **I** = native iOS **iPhone 17 Pro**, portrait, light, default device logical viewport; record exact point/pixel sizes and software keyboard in metadata.
- **Core** = W, N, A, I. Compare baseline merge-base and pinned head against identical fixtures. Additional W-dark/N-light and opposite native themes are optional because styling is unchanged; label them if supplied.
- F01 requires a baseline/head pair on all Core dimensions. F02–F06 guard controls can be captured on the head only; their unchanged expectations are established from source. V01–V03 require paired visual states for their affected layouts, with the head interaction recorded. K01/M01 route interactions can share the F01 baseline and use head recordings.
- `recording` means video of actual actions/transitions; stills cannot close that row. Extract labelled before/after frames for visual comparison in addition to the video. Several rows can be chapters of one recording if each chapter resets the fixture and has an unambiguous timestamp and effective state record.
- `TO_CAPTURE` = required and presently missing. `MAPPED` = code-equivalent or unchanged path, not additional mandatory media. `TO_CAPTURE/HARNESS` = requires an explicitly labelled client/projection fixture.

## Fixture matrix

| Row / named fixture | Data and relevant branches | Diff effect / expected baseline → head | Minimum viewport × theme × row; media | Seed and drive through the app | Status |
| --- | --- | --- | --- | --- | --- |
| F01 — detached completed source, no native ref | Started app thread; ≥1 completed run with user/assistant history sentinel `T3_24641_HISTORY`; no returned provider sessions; no active run; matching active source provider thread; nativeThreadRef null. Source S and ready target T use different drivers/continuation identities; one selectable model each; no favorites. | **Directly affected:** target disabled/filtered → target reachable and selectable. On the head, after selection show target in composer, send, and completed target turn. The baseline must remain on S and cannot send through the blocked target picker. Target transport contains prior sentinel. | **Core × F01**, before and after, **recording**. W/N cover pointer/sidebar/search and composer expansion. A/I cover native open → target → Save → composer → send. Still pairs: open picker/sheet and resulting composer. | B0 + B1 below. Use the real thread route `/<environmentId>/<threadId>` on web; select same project/thread in mobile. Complete source and detach via RPC, then stopped-server native-ref projection fixture if source cannot naturally omit its ID. Capture resulting RPC/client state. Real UI seeded fixture; disclose null-ref injection. Use app send and target mock transport for delivery, not a dispatch driver impersonating UI send. | **TO_CAPTURE** |
| F02 — same source, native ref present | F01 except nativeThreadRef is valid. | Unchanged true → true. Establish that detach itself does not generally prohibit switching. | **W × F02**, **recording**, may be first chapter of F01 before null-ref seeding. No additional native capture; native success/failure states covered by F01/F04/F05. | Preserve event-backed source snapshot before nulling ref. Actual detach then picker target selection. | **TO_CAPTURE** |
| F03 — attached session allows handoff | F01 plus a ready session bound by providerSessionId; capability true, native ref null. | Unchanged true → true; verifies capability path remains authoritative. | **W, A, I × F03**, **recording**, can be combined with detach chapter in F01 if effective capability and bound session are recorded. | Prefer actual ready adapter-negotiated session. If negotiated true cannot be obtained, label projection harness and do not claim adapter negotiation. Select target; Save on mobile. Reset source before detach. | **TO_CAPTURE** |
| F04 — attached session denies handoff | Same as F03, capability false. Favorite T exists; target search token unique. | Unchanged false → false. Web target sidebar disabled and target absent from search/Favorites; mobile target group absent, filter/search/Favorites cannot bypass lock. | **W, A, I × F04**, **recording** of attempted pointer/filter/search route; web numbered shortcut attempt can share K01. | B2 capability-false replay/injection bound to active provider thread. Label **client-policy harness** unless a real adapter negotiates false. Keep identical source/history/catalog to F03, change only capability. Prove effective client session/capability. | **TO_CAPTURE/HARNESS** |
| F05 — active work with no session yet | F01 + a held preparing run, no provider sessions, matching source, null native ref. Also map starting/running/waiting statuses to this guard. | Unchanged false → false; no brief unlock while work awaits its session. | **W, A, I × F05**, **recording**. Show pending state and locked target, attempt search; cancel/release held work and show eligible state returns. Other active status values need no duplicate visual capture if exact helper inputs are checked separately. | B3 event-backed deferred preparation held at a deterministic milestone. Capture before restart/shutdown recovery cancels it. If impossible through public app/RPC + controlled provider, replay projection and label harness. Do not silently disable startup recovery. | **TO_CAPTURE** (or labelled harness) |
| F06 — detached but source association missing | Started non-import thread, completed run, no session/active run; providerThreads empty or active id missing/null. Equivalent mismatches: wrong id/appThreadId/providerInstanceId; extra unrelated provider thread cannot satisfy it. | Unchanged false → false, prevents broad unlock with no usable source association. | **W × F06**, **recording** of disabled target and absent search result; native same false outcome already exercised F04/F05. | No normal app command deliberately corrupts ownership. B2 **projection harness** removes/mismatches source after preserving shell start markers; log payload. Never use this to claim backend accepts corrupt state. One missing-source visual is enough; mismatch variants are code-equivalent. | **TO_CAPTURE/HARNESS** |
| F07 — empty / imported / unsent draft | No session/active run; runs empty, or historyOrigin v1_import even with runs; source association can be absent. Separately a local/new mobile draft bypasses this helper. Empty timeline vs imported history are existing layouts. | Unchanged allowed. No new UI branch; reuses unlocked model catalog states in F01. | None beyond F01; code inventory only. Optional initial source-creation chapter can show empty-thread selection. | Create unsent thread/new task normally; for imported history use existing import flow or labelled projection fixture. Do not require source association for these exceptions. | **MAPPED — unchanged** |
| F08 — started shell, projection loading | Shell start markers populated, selected provider S known; thread detail projection initially absent, then resolves to F01. Separate empty shell has no lock. | Initial lock unchanged; after F01 resolves, baseline remains locked while head unlocks. No transient ability before projection known on mobile. | **W, A, I × F08**, **recording** of load/reconnect with picker/sheet state before/after projection. Can share F01 reopening chapter. | Gate the thread-detail RPC/stream delivery with a driver latch, then release; preserve shell. If deterministic interception is required, label **transport-delay harness**. Capture effective shell/projection and connection generation. Do not use an arbitrary sleep as a fixture. | **TO_CAPTURE/HARNESS** |
| F09 — failed/interrupted or historyless terminal/queued source | F01 association/ref/session conditions with latest failed/interrupted run, or cancelled/rolled_back/queued-only history. Error/resume/queue affordances differ elsewhere. | **Affected eligibility**, same unlocked picker as F01. Server handoff sources are completed/failed/interrupted; cancelled-only/queued-only may legitimately have no eligible history to transfer. | None extra for picker policy; F01 covers changed visual outcome. For any claims about failed/interrupted history transfer, add a recording and target sentinel trace for that exact state. | Use source mock failure/interruption or queued-run APIs. No artificial completed-only restriction. Do not assert history-loss merely because no eligible source run exists. | **MAPPED — equivalent eligibility; no blanket transfer claim** |
| V01 — Favorites, search hit and empty results | F01 plus one target favorite, one source favorite; uniquely searchable target model. Record nonempty Favorites, query hit, query miss/empty state and clearing query. | **Affected:** unlock changes default tab when favorites exist; target appears in Favorites/search. Search hides web sidebar; miss renders empty result. Mobile filters/provider list reflect the broader catalog. | **W, A, I × V01**, **recording**; repeat blocked target query/Favorites in F04, not another full theme matrix. | Set favorite through actual star/long-press UI or settings RPC in B0. Reopen picker/sheet, explicitly choose Favorites, select T. Reset S before alternate route. On mobile Save, then verify composer label. | **TO_CAPTURE** |
| V02 — provider lists, long labels and secondary models | F01 with two instances of one driver in different continuation groups plus cross-driver T; ≥12 target models for scroll, long model/provider labels and a shortName differing from full name; one legacy and one hidden model. | **Affected reachability:** formerly locked providers/groups become available. Web preferShortName changes with lock; sidebar/list ordering/filtering and scroll are visible. Mobile non-primary provider sections may start collapsed; counts and expansion change with unlocked groups. | **N × V02** and **A × V02**, **recording**; use I F01/V01 for native iOS sheet. Do not require wide/tablet/fold layouts because no responsive code changed. | B0 synthetic catalog/custom models/settings; public settings for hidden/favorite. Scroll to target, expand collapsed provider/legacy sections and select. On mobile show provider filter → narrowed list. Manage hidden must reuse lock: show hidden target only under management, not selectable as ordinary visible model. Long catalogs are seeded real app; if fake catalog injection needed, label harness. | **TO_CAPTURE** |
| V03 — source unavailable, healthy alternate | F01 but source catalog entry remains present with same driver/instance and unavailable/unauthenticated status; T installed/authenticated/enabled for project. Keep matching saved source association. | **Affected:** unlock can permit fallback/recovery. Web may change from unavailable/setup control to healthy model control. Mobile keeps fallback/unavailable model label plus entry to settings and now lists T. | **W × V03**, **A × V03**, **recording**. Same mobile shared catalog derivation on iOS; F01/F04 supply iOS native integration. | Change source settings/status in isolated environment after completing/detaching it. Keep S metadata so baseline still has a meaningful lock; do not remove provider id and accidentally exercise a pre-existing unknown-driver fallback. Tap mobile unavailable warning entry, choose T, Save and verify draft. | **TO_CAPTURE** |
| V04 — target unavailable / catalog pending / setup and error | F01 plus target disabled for project, not installed, unauthenticated, or no selectable models. Web setup/update notice branch; mobile omits unusable groups or retains an unavailable current fallback. Unknown config yields catalog-loading state. Refresh/Save can produce existing error alert. | Helper may unlock but does **not** make unusable targets usable. No styles or readiness rules changed. F03/F04 govern handoff capability, independent of target readiness. | No extra media in minimum; record alongside V03 if claiming unavailable-target behavior. No need to manufacture every unrelated model/provider error dialog for this one-predicate diff. | Existing provider/project/model settings; mobile pull-to-refresh; if pending selected model becomes absent/unavailable before Save, existing canCommitPendingModel alert must keep it unapplied. Label any RPC error/catalog harness. | **MAPPED — existing independent gates** |
| K01 — web keyboard routes | F01; effective modelPicker.toggle, previousProvider/nextProvider, jump.1 bindings; unique searchable target; optional favorite. | **Affected:** enabled candidates and provider navigation must agree with pointer path; disabled controls must not be bypassed. | **W × K01**, **recording**: shortcut opens → next/previous provider → query and Enter select; separate reset → numbered jump selects T. Under F04/F05 query/jump must not choose T. | Configure a supported web jump binding in Settings if desktop-only default is unavailable; record effective binding and visible jump label. This proves opt-in web binding only, not Electron shortcuts. No unrelated command-palette provider workflow exists in source. | **TO_CAPTURE** |
| M01 — native pending, Cancel, Save and return | F01; pending target selection, then Cancel leaves S; reopen, select T, Save updates composer; reopen and see applied selection; choose S before send to prove reverse draft selection, then choose T and send. Compact icon-only and expanded text composer, software keyboard up and dismissed. | **Affected native interaction:** visible pending choice must not be mistaken for applied draft or server provider change. Native Android header differs from iOS bottom controls/search toolbar. | **A, I × M01**, **recording**, merge into F01/V01 per platform. Actual target send/history trace required F01; reverse draft choice needs no additional backend run. | Actual native model control/settings sheet. Save is conditional on pending choice on Android and Done/Save on iOS. Record cancel/back/dismiss outcome; only explicit Save counts as commit. Use fresh draft reset before each independent attempt. | **TO_CAPTURE** |

All minimum rows are missing in this round. Required row ids: **F01–F06, F08, V01–V03, K01, M01**. F07/F09/V04 are explicit source-mapped scope decisions, not fabricated visual passes. One clip may close several rows with per-row timestamps, but no row is closed by a still when marked recording.

## Seeding and evidence recipe

**B0 — isolated real app/catalog.** Capturer owns its server and Metro, isolated T3 home, synthetic repository/project/thread and provider instances S/T. Never use live userdata for writes. Keep all drivers/logs/captures in a new timestamped run directory under the assigned capturer scratch. Use mock providers copied together with their adjacent JSON dependencies from repository fixtures such as [codexCollabMockPeer.mjs](https://github.com/pingdotgg/t3code/blob/271e7b6f110b5817e70bba16c46a91b2475ccf09/apps/server/src/provider/testFixtures/codexCollabMockPeer.mjs), configured only in isolated provider settings; it supports recording `thread/inject_items` requests. Do not edit repo-owned fixture code. Public app Settings/RPC can configure enabled providers, models, favorites, hidden models and keybindings. Keep T installed/authenticated/available and project-enabled in successful fixtures. No external subscription/model execution is required.

**B1 — actual completion and detach, null-ref seed.** Create and complete source turn through app commands. Snapshot projection. Dispatch `orchestration.dispatchCommand` with payload `{type: "provider-session.detach", commandId, threadId, providerSessionId, reason: "client-requested"}` for the actual source session; await the persisted detach/returned projection. No public command sets nativeThreadRef null. Prefer a genuine supported provider flow that naturally omits it. Otherwise stop only the owned server, use `node apps/server/scripts/t3-sqlite-state.ts` against isolated home after inspecting table schema, alter only the active source provider-thread `payload_json.nativeThreadRef` to null, restart, and verify `orchestration.getThreadProjection({threadId})` and effective client state. Label **seeded real-app null-ref fixture**, not naturally reproduced provider negotiation. Original events and actual source turn/history remain intact; direct projection seeds alone cannot establish backend history correctness. Record the actual send and transport requests after restart. Never bypass startup recovery silently.

**B2 — explicit client-policy harness.** Missing associations and capability flips may not be reachable via ordinary settings. Replay/inject the typed thread projection at the same RPC/stream boundary the real app reads, retaining all other fixture data. Include exact JSON, source association/session binding, capabilities, sessions and runs actually received by the client, plus harness mechanism in manifest. WebSocket replay must cover stream updates as well as initial RPC; otherwise a subsequent real event may overwrite it. Use a labelled harness for native too if necessary. Do not claim genuine adapter capability negotiation or backend business behavior from these rows.

**B3 — controlled active milestone.** Use actual message.dispatch with controlled worktree preparation/provider startup held before session attachment. Await a concrete milestone/Deferred/event; release it intentionally. Preparing/starting/running/waiting without a resolved session are equivalent for the predicate. An event-backed preparing/no-session capture is sufficient for the visual lock guard; it must not be described as a running provider turn. If using injected projection instead, label it. Capture before shutdown recovery mutates the state.

**Mobile setup — capturer only.** Consult [test-t3-mobile](https://github.com/pingdotgg/t3code/blob/271e7b6f110b5817e70bba16c46a91b2475ccf09/.agents/skills/test-t3-mobile/SKILL.md) with the task's current runbook override: `vp run mobile:install`, not the stale old worktree install command. Apply installed device-pool instructions when T3 Device tools are off. Prepare cache with `vp run mobile:install android --prepare` / `ios --prepare` **before** leasing. Connect `agent-device` to the ambient pool proxy in a unique session, then request **Pixel 9** / **iPhone 17 Pro** exactly as named in the brief. A skill's example phone name is not permission to substitute a different phone. Install using `vp run mobile:install <platform> --session <session>` from the session's directory; start/reuse the capturer's Metro and isolated backend on device-reachable interfaces/origins. Pair via the test-t3-mobile helper. Native build/cache miss is an install step. Keep software keyboards enabled; record actual viewport/device/OS/theme. Release each owned pool session after evidence upload. This audit did not lease or start any device/server.

**Manifest per clip.** Include row IDs/timestamps; before/after source SHA and actual bundle revision; URL/route without pairing credentials; viewport/theme/platform; fixture provenance (app command, stopped-server seed, or harness); effective thread/session/run/source-provider fields; selected composer draft before/after; actual send command/result when required; exact sentinel in target transport; file hash. Scrub credentials and pairing links. In final presentation embed labelled before/after images and link videos, not only filenames.

**Exclusions supported by the diff.** No provider adapter, server, wire schema, permissions or transport change. All driver kinds use this client predicate; names/icons do not require a six-provider Cartesian capture set. Use cross-driver S/T plus V02 continuation-group variant to exercise both locks. Local web plus remote pooled mobile exercises distinct connection surfaces without requiring every relay/tunnel permutation. No new RPC/MCP/scheduled-task feature. Desktop web content is covered; native shell/default shortcut verification is explicitly outside the minimum and must remain unclaimed. Tablet/fold, older iOS alternate search toolbar, multi-model unsent drafts, runtime options, attachments, long chat timelines, approval dialogs, and unrelated general Settings screens have unchanged markup and do not require new media for this predicate fix.

## Review coverage and cleanup

Checked complete two-file diff, exact head/merge-base, both production callers, shared helper resolution/precedence, web filtering/selection/shortcuts, native catalog/sheet/pending commit, detach command and server history-source policy. Read PR body/current metadata and historical final row matrix only as context; did not independently view or accept historic clips. No tests run: this pass derives media fixtures, not test/merge readiness. No app/client/server/device launched; no checkout/PR edits, comments or pushes. Capturer owns later runtime verification. No subagents created.

FRICTION:

tool | Python urllib verification download received CDN HTTP 403; curl downloaded the published matrix successfully. | Use curl for ghdrop artifact retrieval.

VERDICT: INSUFFICIENT F01–F06, F08, V01–V03, K01, M01
