# Proof audit, pass 1 — current mobile unsent-draft implementation

## Findings first

**Proof is insufficient before captures exist.** This is a fixture plan for [#25360](https://git.cafe/pingdotgg/t3code/pull/25360), branch [fix/issue-15974](https://git.cafe/pingdotgg/t3code/tree/fix/issue-15974), head [d7a0e4276e](https://git.cafe/pingdotgg/t3code/commit/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943), against merge-base [5b15cfc1e8](https://git.cafe/pingdotgg/t3code/commit/5b15cfc1e8f37f33b1a7e25d02248da7d999f062). It addresses [#15974](https://git.cafe/pingdotgg/t3code/issues/15974). The supplied worktree was clean, matched that head, and its actual `origin/main...head` merge-base matched the brief. All six changed files and their relevant callers were read. No worktree edits, test execution, builds, devices, servers, leases, browser sessions, forge mutations, pushes, or comments occurred. Unit-test status below means a concrete checked-in assertion was inspected, **not a claimed successful execution**. The lead must retain focused test/CI results separately.

1. **The old implementation's matrix is not proof of this revision.** The decision now lives in the stack-owned [NewTaskFlowProvider](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/new-task-flow-provider.tsx#L222), and the new per-route `unsentDraftChecked` flag replaces a screen-local async continuation. Resumption directly changes `draftKey`; there is no new `openDraft` action or layout. The old legacy queued-editor, single-project automatic picker selection, promise-cancellation, and uncaught-read-rejection descriptions cannot be copied forward unchanged.
2. **Tests reduce the capture set substantially.** [Draft selector tests](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/state/use-composer-drafts.test.ts#L1927) assert ordering by creation stamp, whitespace/settings-only exclusion, attachment eligibility, same-environment/project isolation, existing-thread exclusion, excluded keys, and no-candidate null. [Flow tests](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/new-task-flow-state.test.ts#L158) assert the loaded gate, route-project versus fallback guard, picker settlement, once-checked gate, draft/branch/clone/share exclusions, worktree-path-only eligibility, and draft keys named by covered routes before their flows write. These are unit-test rows, not separate emulator obligations. They do not render the composer or execute the provider effect and real navigation together.
3. **Two integration risks need labelled harness recordings.** The current-content check reads the live snapshot inside the [effect](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/new-task-flow-provider.tsx#L399); the new tests do not prove typing/attaching during hydration wins end to end. Also, collecting bound keys is unit tested, but retaining route A while route B binds and rendering the correct composer on Back needs a stacked-route recording. Ordinary New thread and published single-detail links use [workspace detail selection](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/workspace/workspace-detail-selection.ts#L85), which replaces/reuses the workspace page instead of pushing a second draft. Repeating the normal button cannot prove stack exclusion.
4. **Fresh backend state is not fresh composer state.** Drafts live on the device in [document storage](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/state/use-composer-drafts.ts#L710), `composer-drafts/drafts.json`. Server RPC can seed projects; it cannot populate or gate device-local drafts. Clean the disposable client sandbox per side, pair to that side's fresh `T3CODE_DEV_SEED=0` server, and seed content through the UI. Retain actual draft/environment/project identities in a manifest. No real user data or credentials belong in uploaded evidence.
5. **No confirmed implementation defect is asserted by this planning pass.** The remaining blocker is the missing evidence named in R01–R06 and H01–H02, not an inferred source regression. The read-only review does not refresh moving forge metadata: its subject is the exact supplied commit and merge-base, not latest PR state.

## Derivation: changed components, callers, and render paths

Only two components change, and neither receives new JSX, CSS, theme, viewport, or platform branches.

- [RootStackLayout](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/Stack.tsx#L503) has one direct caller, the root navigator's `layout` function. It always wraps the root children in one `NewTaskFlowProvider`. It finds the last draft Thread anywhere on the root stack and computes active by whether that route is also the last Thread. A picker/settings overlay above it still serves that draft; a live Thread above it makes it inactive. With no draft route it passes no route/flow key and `active=false`. With a draft it now passes the **whole** `NewTaskDraftRouteParams` object instead of just normalized `draftId`. One draft, several drafts, a covered draft, popped/promoted routes, and reused route keys with changed draft identity are the relevant stack shapes. Root navigation, hardware commands, connection/status, Android tabs, responsive workspace layout, settings filtering, and children are rendered identically.
- [NewTaskFlowProvider](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/new-task-flow-provider.tsx#L222) has exactly one production caller: `RootStackLayout`. Inputs are `children`, `active` (false suppresses draft creation/resume and publishes null active-task context), `flowKey` (absent → inactive key), and the full [route shape](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/thread-route-state.ts#L1). Its render is always the same two context providers around `children`. It has no direct empty/loading/error/theme/width/platform render. Its context changes which existing composer/pickers/settings are rendered and populated.
- Every production `useNewTaskFlow` consumer was traced: [NewTaskDraftScreen](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx#L145), [NewTaskRouteScreen/project picker](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/NewTaskRouteScreen.tsx#L150), [environment and branch picker screens](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx#L185), and [NewTaskThreadSettingsRouteScreen](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/ThreadSettingsSheet.tsx#L1566). The provider's `ActiveNewTaskContext` also controls the currently active pending/Unsent identity. The draft view is reached through [ThreadRouteScreen's draft arm](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/ThreadRouteScreen.tsx#L220) → [NewTaskDraftRouteScreen](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/NewTaskDraftRouteScreen.tsx#L93) → `NewTaskDraftScreen`; live Thread routes use the other arm. Missing model renders a simple text composer; selected project null renders no draft surface; normal model renders `ThreadScreenBinding` with the chosen draft key/text/attachments/settings.

### Every entry family into these paths

| Caller / entry | Route or state it supplies | Fixture class |
|---|---|---|
| [Shared layout startNewTask](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/layout/AdaptiveWorkspaceLayout.tsx#L570): home compose/FAB, sidebar/empty detail/native iOS compose, thread header, wide Android rail | New request; [destination helper](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/threads/new-task-project-selection.ts#L121) chooses current project, then remembered project when preference is on/default; otherwise no project IDs. It does **not** blindly select the first project. | R01, R02, R03. Visible Android compose is the representative minimum; no need to capture every equivalent button. |
| [Home/project/sidebar actions](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/home/HomeRouteScreen.tsx#L80) | Explicit environment/project/title/request; New thread on branch adds branch/worktreePath | R02/R05; null branch belongs to U06 rather than branch exclusion. |
| [Unsent rows](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/home/usePendingTaskListActions.ts#L22) | Exact `draftId` and its environment/project | R04. Queued rows open live Thread and bypass the draft path. |
| Prompt links, PR Ask/review prompts, failed-creation recovery | Prefill/recover a specific draft, then exact-key route | Same explicit-draft guard/binding as R04/U08. No extra visual permutation from title/requestId. |
| [Add existing folder/create/clone](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/projects/AddProjectScreen.tsx#L675) | Existing/create: targeted plain route; tracked clone adds `cloning="1"`; prompt form can also carry exact draft ID | R02/U06. No active-clone lifecycle capture is necessary for this selection diff. |
| [IncomingShareProvider](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/sharing/IncomingShareProvider.tsx#L256), native share and destination picker | Real generated incomingShareId; destination IDs may arrive after selection; reserved/imported/unavailable states stay in existing share code | U06/S05. Do not fabricate a share ID and claim native-share proof. |
| [Launcher quick action](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/shortcuts/appShortcuts.ts#L90) | `/new` project picker, then selected project route; recent-thread shortcuts are live routes | R03 equivalent; optional launcher recording, not a separate minimum. |
| [Command palette](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/keyboard/CommandPalette.tsx#L269), [hardware fallback](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/features/keyboard/HardwareKeyboardCommandProvider.tsx#L157) | Generic new-task command or explicitly targeted active-project/project result; fallback draft is `{}` | R02/R03 equivalence. Native entry availability is S07. |
| [Published draft links](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/lib/appLinking.ts#L29) | `t3code-dev://new/draft?...` params are wrapped into a root Thread draft | Real route replay for R02/R05 and fresh draft seeding. It selects one workspace detail, not two stacked drafts. |
| Missing/unavailable live-thread redirect | Targeted plain draft in that environment if a project exists; otherwise request-only draft | R02/R03 equivalence. |
| Context pickers/settings opened over a draft | Keep its route/flow alive; mutate project/environment/workspace/model/runtime/interaction/expanded-provider state | R03 retarget continuation; S03 optional settings/geometry check. |

### Complete decision inventory introduced or touched by the diff

1. Normalize array-valued `draftId` to first item in the provider (previously done by Stack). Other string-array route values are normalized in `unsentDraftResumeDue`; branch is string/null. Undefined route, null selected project, null active draft key, already checked, or drafts not loaded → no resume. A new persistent empty key is still created when active with a selected project and no explicit draft ID/key; this creation can precede hydration.
2. Explicit draft ID, incoming share ID, nonempty branch, or first cloning value exactly `"1"` → no automatic resume. Title/requestId/worktreePath alone do not exclude. Null/empty branch and cloning absent/other value are plain for this gate. The exclusion tests use normal scalar IDs; first-item array handling and degenerate empty-string inputs are source-inspected, not extra tested cases.
3. With both route environment and project IDs, selected project must match both. With no complete pair, the settled flow `projectKey` must equal the selected scoped project. The provider's environment/first-project fallback must not consume the one-shot attempt for a different route project.
4. Effect is inert when inactive, explicit draft route, or no selected project. After a key exists and the gate becomes due, re-read **current** composer snapshot. Non-whitespace text OR any attachment keeps that key. Whitespace/settings alone do not protect it. Otherwise search the loaded draft atom.
5. Search only stamped `new-task:` drafts in that exact environment AND project, descending `project.createdAt`, not last edit/list order. Exclude all flow-bound keys and all draft IDs explicitly named by draft routes on the stack, including routes whose flow has not written. The current fresh key is among flow-bound keys. Select first eligible text/attachment candidate; no candidate leaves current key unchanged. Equal timestamps have no specified extra tie-break; use distinct creation stamps in evidence.
6. Consume `unsentDraftChecked` whether content wins, a candidate is selected, or none exists. This is per flow, survives pickers/settings/covered routes, and is pruned when that draft route leaves or becomes live. The resume effect contains no await/cancellation continuation. A new route identity/new request creates a fresh flow and can resume again.
7. Existing explicit binding remains `none` / `pending` / `missing` / `open`: wait for hydration or the stamped environment's project delivery; missing/invalid key or gone project returns to picker; open uses the stamp; existing binding wins. No new loading or error UI is introduced. Hydration failure is caught by the unchanged [loader](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/src/state/use-composer-drafts.ts#L1022), logs a warning, leaves loaded=false, and permits a later load caller to retry.
8. Resumption switches context key; it neither sends, copies, merges, nor deletes the chosen draft. Selected text, attachments, saved model/runtime/interaction/workspace derive from that key. Settings-only candidate exclusion does not mean settings on a contentful chosen draft are discarded.

### Inherited layout combinations (inventory, not a Cartesian capture demand)

| Render family | Discriminators / interactions | Treatment |
|---|---|---|
| Draft unavailable / preparing | No project → null; explicit pending draft → no selected project; missing draft/project → picker; nonempty branch → Switching branch until checkout, dialog/back on error | U08 plus R03/R05. There is no newly added draft-loading spinner. |
| Empty / text / attachment composer | Empty; short; multiline long; 0/1/many attachments (image thumbnail vs file chip); editor keyboard open/closed; saved model/settings; pending upload/blocked send | R01, H01, H02; candidate eligibility U01–U03; optional S03 for long/mixed state. No provider turn needed. |
| Model / project identity | No model → saved-on-device text editor; model → full binding; normal project/group title (single-line 250px cap) vs scratch No project hero (no workspace controls) | S01/S02/S03; shared draft binding, inherited layout. |
| Workspace / branch | Git known/unknown/non-Git; local/worktree mode; branch label missing/current/default/selected; worktree path icon; branch list empty/loading; start-from-origin/PR reference | R05 selected branch, optional saved settings S03; other labels unchanged. |
| Available space / platform / theme | Hero hidden below 180px available height; header/bottom insets; narrow/wide/split/tablet geometry; Android icon overlays/headers vs iOS native header; iOS interactive vs Android on-drag keyboard dismissal; light/dark | No new condition here. Minimum Pixel portrait/light only; optional S07 iOS/light or dark repeat. |
| Connections / pickers / settings | Zero/one/many project scopes, grouped repo scopes, environment counts 0/1/>1; loading/offline/error; branch count/search/selection; provider/model group selection/expansion, runtime/interaction; permissions | R03 picker/retarget, U04 isolation; inherited statuses S05/S07. |
| Clone/share/import/submit | Clone pending, active/cancel/retry/remove/done; share loading/unavailable/importing/cancel/retry; context import; submitting; composer/picker/back locks; setup reasons and dialogs | Exclusion predicate U06. Full lifecycle unchanged, source-only S05; not demanded as extra captures. |

## Fixture seeding and isolation

Use the pooled Pixel 9 and this checkout's dev client via `vp run mobile:install android <device-id>`, then Metro from the pinned side. Each side gets a fresh backend with `T3CODE_DEV_SEED=0` and an explicit isolated home. Confirm actual native dimensions, OS, bundle SHA/Metro path, native fingerprint, app theme, environment UUID, project UUIDs, and each fixture's draft ID/creation stamp in a non-secret manifest. Baseline is the **merge-base**, not a moving main. Do not modify this read-only worktree to switch revisions; the lead/capturer supplies a separate baseline checkout/bundle.

A reused lease can retain device drafts and connections. Use a fresh client sandbox or clean only disposable proof data with the capturer's authorised workflow; backend reset alone is insufficient. Pair only to proof servers. Do not upload a pairing URL/token. For the core baseline/head comparison, use identical title/text/theme/keyboard state/route semantics. Environment UUIDs can differ between fresh sides, but each side must stamp all its drafts to its own actual environment and project.

Create distinct small Git fixture repositories P1 and P2 under the capturer's timestamped scratch run, with an initial commit and `proof-seed` branch. Add them through Add project → existing folder, or the authenticated typed projects mutation RPC using the [project.create contract](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/packages/contracts/src/project.ts#L185):

```json
{"type":"project.create","commandId":"<unique>","projectId":"<uuid>","title":"Proof P1","workspaceRoot":"<scratch-run>/p1","createWorkspaceRootIfMissing":true,"defaultModelSelection":null}
```

This is a typed WebSocket/RPC payload, not an invented HTTP JSON endpoint. Establish one available model for the full-composer rows; otherwise state explicitly that the no-model fallback was captured. No agent send is needed.

Create draft content through the real editor. To create a **second independent** draft in one project when plain New thread would now resume the first, replay a supported nonempty-branch route with a fresh requestId:

```text
t3code-dev://new/draft?environmentId=<E>&projectId=<P>&branch=proof-seed&requestId=<unique>
```

Let checkout finish, type a unique marker, Back. Make A older, B newer, then optionally edit A last through its Unsent row. A normal plain project route omits branch, cloning, incomingShareId and draftId. For explicit selection tap the **actual** older A Unsent row. A public route replay is real application routing; label it as route replay rather than a captured originating clone/branch workflow. Distinct draft creation times/order must be documented; do not infer them from edits or list display.

## Fixture matrix and per-row status

`MIN` is the required pass-1 handoff set. All its interaction/time paths are **recording**; settled stills supplement recordings and do not replace them. `unit-test` means the named checked-in assertion handles the input distinction and no extra capture is required. `source-only` means unchanged rendering/applicability, not runtime-verified proof. Optional extension rows do not block the scoped minimum. All captures are pending.

| ID | Named fixture and required data | Diff effect / expected observable result | Real-app seed or harness | Minimum capture / proof | Status |
|---|---|---|---|---|---|
| R01 | Reported bug: P1, one persisted short-text unsent draft `P1-RESUME`, no other route holding it | Changed: Back → plain New thread restores exact content on head; baseline opens blank. Repeat keeps content. Relaunch/re-entry checks persisted path, not memory alone. | Real editor + Back + visible compose/target P1. Explicit branch route can mint draft consistently on both sides. Preserve app data for relaunch. | **MIN recording**, Pixel 9 native portrait × light × baseline AND head. Show seed/Back/entry/result; capture settled matched before/after stills with keyboard in same state. One head repeat and cold re-entry may share clip. | PENDING — real app |
| R02 | Target is P2, fallback/first project P1 has `P1-DECOY`, P2 has `P2-ONLY` | Changed: P1 fallback must not consume attempt or appear as restored text in P2; final composer/project show P2 marker. | Real project action or published targeted plain draft link with fresh requestId; confirm which project is provider fallback in manifest. Target P1 in a separate entry to confirm distinguishable data. | **MIN recording**, Pixel portrait × light × head. Route/selection transcript supports visual identity. Guard is also unit tested. | PENDING — real route replay/UI |
| R03 | No-target draft with two project scopes, then choose P2; later retarget current content | Changed: no implicit P1 restore before explicit selection; picker → P2 resumes P2. After first attempt, edit `CURRENT-KEPT`, select P1; current content remains rather than P1 decoy taking over. | Disable Use last project for new threads (or use request-only `/new/draft`); actual project picker. Keep P1/P2 separate repositories so grouping does not collapse them. | **MIN recording**, Pixel portrait × light × head; picker, target, retarget and result in one indexed clip. One-scope no-target routes also pick now (U09). | PENDING — real app |
| R04 | Explicit older A Unsent row with newer B in same P1 | Touched binding control: exact A opens with its content despite resumable B; no automatic swap. | Seed A/B through independent branch routes; tap actual A Unsent row. | **MIN recording**, Pixel portrait × light × head; include tapped row and settled A still. | PENDING — real app |
| R05 | Nonempty branch route `proof-seed`, P1 has eligible B | Touched guard control: checkout path settles on requested branch with fresh empty composer, B never replaces it. | Real New thread on branch if a stopped fixture thread exists; otherwise supported branch link above, labelled route replay. | **MIN recording**, Pixel portrait × light × head; include entry/preparation if shown, final branch and empty composer. | PENDING — real app/route replay |
| R06 | Stacked draft routes: A holds newest `A-HELD`; an older free B exists; plain C pushed above A | Changed: C resumes B, never A; edit C without affecting A; Back restores A and its project/content. Bound keys include explicit routes before flow writes. | **Labelled client-navigation harness**: seed older free B first, then newer held A via UI; open A; use rootNavigationRef + real StackActions.push of a Thread draft with plain target params and fresh requestId. Log route keys/flow keys/bindings. A normal compose/deep link replaces detail and is not this fixture. Same-turn two-route reset can extend pre-write case; unit test U07 covers that narrower boundary. | **MIN recording**, Pixel portrait × light × head + stack/binding transcript. Only navigation insertion is injected; real provider/editor handle result. | PENDING — harness, not prepared |
| H01 | Persisted `OLDER-SAVED`; storage read held pending; user types `CURRENT-BEFORE-LOAD` in fresh composer | Changed current-content guard: releasing read keeps current key/text; older saved draft remains separate. | **Labelled storage-gate harness** in capturer-owned temporary bundle, before initial app load. Gate only composer drafts read/resolve, not JS thread or shell/network; type through real editor, then release once. Log read-start, current key/text, loaded=false, release, loaded=true, final binding. | **MIN recording**, Pixel portrait × light × head + gate events. Cold launch without a read barrier is insufficient. | PENDING — harness, not prepared |
| H02 | Same storage gate; current composer has exactly empty text and one real image/file attachment; older saved text eligible | Changed current-content guard: attachment keeps current key/thumbnail or chip after release; no old text swap. | Same harness as H01, new run. Use actual media/file picker, remove any inserted inline-reference text while retaining attachment; verify empty text in non-secret transcript before release. If OS picker timing cannot coexist with pending read, leave row pending rather than silently injecting a fake attachment. | **MIN recording**, Pixel portrait × light × head + gate/attachment manifest. Share H01 harness setup, separate run. | PENDING — harness, not prepared |
| U01 | Many candidates: older text A, newer text B, newer settings-only and whitespace drafts | Changed selector: B by createdAt, no last-edit promotion, contentless candidates ignored | In-memory fixture already in draft tests; real branch-route creation optional | **unit-test**: `resumes a project's newest unsent...` at UTDRAFT; last-edit non-effect follows no updatedAt sort field, not a separate asserted update history. No capture required. | UNIT-TEST — assertions inspected, not rerun |
| U02 | Newest attachment-only candidate, text exactly empty | Changed selector: attachment is eligible and beats older text | Existing test uses a typed file attachment; optional real image-only restoration in S03 | **unit-test**, same draft selector test; visual file/image rendering is inherited | UNIT-TEST — inspected, not rerun |
| U03 | Zero candidates, ineligible-only candidates, or all candidates excluded | Changed selector returns null; provider retains fresh/current key, no foreign content | Existing selector without free draft asserts null; zero-length input/ineligible-only are same return path, not separately asserted input fixtures | **unit-test** for no-eligible/null and ineligible filtering; empty composer represented in R05. No extra capture required. | UNIT-TEST — branch covered; variants source-derived |
| U04 | Other environment with colliding project ID; other project; existing-thread draft; current and bound drafts excluded | Changed selector isolates exact environment/project and new-task kind | Existing synthetic selector fixture with real typed IDs, exclusion Set | **unit-test**: `resumes only an unbound new-task draft...`; no second proof server required | UNIT-TEST — inspected, not rerun |
| U05 | Loaded=false vs true; flow already checked vs unchecked | Changed readiness/once gate | Existing flow `due` assertions; current content read/effect wiring remains H01/H02 | **unit-test**: `waits for drafts and the route's project...`; no extra H0 empty-until-release recording | UNIT-TEST — inspected, not rerun |
| U06 | Explicit draft, branch, clone=1, incoming share; worktreePath alone | Changed route guard excludes first four, permits path-only | Existing flow test; real clone/share seeding not needed for a guard-only unit row | **unit-test**: `keeps a fresh draft for draft, branch, clone, and share routes`; R04/R05 additionally prove integration | UNIT-TEST — inspected, not rerun |
| U07 | Covered explicit draft whose flow has not written; current fresh key; covered bound flows; pop/prune | Changed held-key collection plus inherited per-route lifecycle | New test expects both covered route ID and fresh flow key; existing flow tests retain covered A and drop popped/promoted flows. Selector exclusion proven U04 | **unit-test**; R06 is the nonredundant provider/navigation/composer integration | UNIT-TEST — inspected, not rerun |
| U08 | Explicit route binding none/open/pending/missing; slow stamped environment, missing draft/project, already bound flow | Touched normalized ID plumbing; binding semantics unchanged | Existing `resolveRouteDraft` and `stampedEnvironmentProjectsLoading` tests | **unit-test**; R04 covers valid row wiring. No extra hydration/error UI capture | UNIT-TEST — inspected, not rerun |
| U09 | Incomplete/no route IDs; implicit fallback vs explicit settled picker project; one or many scopes | Changed resume gate only after flow.projectKey matches; current ordinary one-scope flow still opens picker | New `resumes for the project a picker route settles on` + existing project-selection/destination tests; real R03 | **unit-test** for settlement and destination policy; no separate automatic-single-project capture | UNIT-TEST — inspected, not rerun |
| S01 | No route/inactive; selected project null; key not yet created | Provider no resume; inactive context null; no new placeholder | Normal Home/live Thread, project-less state; transient key creation follows next effect | **source-only**, inherited/unrendered transition; no additional capture. R06 plus U05/U08 exercise relevant edges | SOURCE-ONLY — no runtime claim |
| S02 | Model-less environment or scratch project | Selected draft still binds; model-less text editor or scratch No project hero, workspace controls hidden | Real config without available model; scratch via picker No project and server openScratch; no fake model status | Optional **recording**, same Pixel/light; these existing layout alternatives do not gate logic-only minimum | OPTIONAL — not captured |
| S03 | Resumed long multiline content + 2 attachments + saved runtime/model/workspace; keyboard shown/hidden | Binding supplies full saved draft; existing content/height/chip/settings layout | UI-seed separate contentful draft via branch route; real attachments with bytes; open settings to verify meaningful nondefault persisted option | Optional **recording** + settled stills. Distinct 0/1/many attachment cardinalities are not separate selector captures; U02 proves eligibility | OPTIONAL — not captured |
| S04 | Array/scalar route values, empty branch/null, clone other than 1, request/title/worktree path only | First value normalization; exclusions exactly as code, no new layout | Supported links generally supply scalar strings; repeated-query array/degenerate empty-string injection is a labelled route harness if needed | **source-only** normalization; worktree-only is asserted U06. No claim that every normalization edge has a test | SOURCE-ONLY — not runtime verified |
| S05 | Offline/error/loading env; clone/share failures/pending; import/submit/permission locks | No new status/error render; existing composer/context branches remain | Normal connection states or native share/clone workflow; storage read failure requires fault harness, not server outage | **source-only**, inherited state inventory. Existing read/decode failure storage tests inspected; no extra capture demanded | SOURCE-ONLY — no new lifecycle claim |
| S06 | Leave during storage load; switch project before gate release; later new route identity | No async continuation to cancel; effect observes active route/selection at render; per-flow one-shot persists | Optional extension to H01 gate: navigate Home then release, or choose new project then release; log current route/key | Optional **recording** if investigating lifecycle; old cancellation-ref recipe is obsolete | OPTIONAL — not captured |
| S07 | Dark, narrow/landscape/split/tablet, iOS; alternate native keyboard/palette/launcher entries | No changed geometry/theme/native code; shared JS resume gate | Optional iPhone portrait/light R01 smoke. iOS Cmd+N needs real chord input; iPad Cmd+K palette; Android native module has neither new-task nor palette chord. Phone JS palette opener is harness only | Optional **recording** for interactive entries; no broad theme×width×platform cross-product in minimum | SOURCE APPLICABILITY / OPTIONAL |

## Minimum capture package and realistic timebox

**Required combinations:** Pixel 9 at its actual portrait dimensions × light × R01 on merge-base and head; same device/theme × R02, R03, R04, R05, R06, H01, H02 on head. Eight named rows, nine side/row combinations. No mandatory dark repeat, iOS pass, second server for colliding IDs, genuine native share/clone lifecycle, offline queued editor, or exhaustive attachment ordering film. Those would spend time duplicating unit proofs or inherited paths. All nine combinations are recordings because navigation, restart, typing, stack changes or hydration timing are the evidence. Extract matched R01 before/after stills; other settled stills are convenience, not acceptance proof.

| Work batch | Target minutes | Outcome |
|---|---|---|
| Install/pair correct bundles, fresh proof data, seed P1/P2 and A/B | 20 | Clean side manifests; prepared core fixtures |
| R01 matched baseline/head, head restart/re-entry | 15 | Reported bug proven first |
| R02/R03 route-project, picker, current-content retarget | 10 | Actual project/context wiring |
| R04/R05 exact Unsent and branch controls | 10 | Most credible exclusion controls |
| R06 navigation-driver setup + stacked run | 15 | Stack exclusion/Back with real provider |
| Storage gate setup + H01/H02 | 30 | Live text/attachment protection through loaded transition |
| Index/upload/verify/teardown | 15 | Public evidence package and clean lease/processes |
| Buffer | 5 | Total 120 minutes |

These times are a planning allocation, not a promise that an unprepared Hermes/storage harness can be implemented in 30 minutes. The capturer should run R01 first, then the simple real-app controls, before investing in harness work. If the gate/driver cannot be built within the budget, upload completed captures and explicitly leave H01/H02/R06 pending. Do not replace them with a fast app relaunch, server network delay, still image, or normal compose action and call them passed. No harness was written during this read-only audit.

For R06 the driver must actually dispatch a **push**, not `selectWorkspaceDetail`/a normal single-detail link. Prefer a small capturer-owned debug harness that calls the existing `rootNavigationRef` and React Navigation actions in a separate temporary source copy; mark the bundle as instrumented and record only the injected action. For H01/H02 place a single barrier around the device composer storage read before its first invocation; the app attachment worker begins hydration early, so installing the barrier only after opening New thread misses the race. Release via an explicit driver event. Keep loaded=false while the editor is usable; preserve normal merge/effect code. A unit-test file's mocked `File.text` barrier is a pattern, not an existing app RPC/debug endpoint. No available public RPC can seed these controlled timing states.

Evidence index fields: row ID; exact side SHA and bundle source; device/OS/native dimensions; theme/keyboard state; route/control used; project/environment/draft markers and creation order; raw clip URL plus timestamps of action/result; still URLs; for harness rows, injected seam and gate/route/binding events. Mark pass only after visual comparison and transcript agree. Preserve raw logs/evidence until upload succeeds. Do not upload local auth or reusable dev tokens.

## Reconciliation with all 27 old rows

The [old matrix](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7d5e762145419d63/proof-matrix-25360-pass1.md) concerns a different commit and is used only for ideas. Nothing is grandfathered as captured at this head.

| Old ID | Current disposition |
|---|---|
| B0 | U03 unit-test selector/null path; blank integration also visible in R05; no separate capture. |
| B1 | R01 mandatory matched recording; drop mandatory dark expansion for this logic-only diff. |
| B2 | Ordering/settings-only is U01 unit-test; long/mixed layout is optional S03. |
| B3 | Attachment eligibility is U02 unit-test; optional actual restored image layout S03. |
| B4 | Fold persisted restart into R01; a restart is still not a controlled read race. |
| B5 | Project isolation U04 unit-test; R02 shows route identity integration. No extra empty-P2 film. |
| B6 | R02 mandatory route-project/fallback recording; U05/flow guard assertion supports it. |
| B7 | U04 unit-test environment collision; second backend no longer required for capture minimum. |
| B8 | **Old expectation no longer applies:** ordinary no-target one-scope selection opens picker now, rather than automatically selecting the only scope. U09/current selection tests and R03 cover it. |
| B9 | R03 mandatory actual picker entry and chosen-project resume. |
| B10 | S02 optional scratch layout; same selection gate, no special changed scratch branch. |
| B11 | Retarget preservation folded into R03; one-shot gate U05. No separate environment-switch film required. |
| B12 | U06 unit-test worktreePath-only; null/empty branch normalization S04 source-only. |
| B13 | S07 optional launcher entry; picker/result represented by R03. |
| H0 | Loaded gate U05 unit-test; no separate empty→resume race clip demanded. H01/H02 record actual gated content wins. |
| H1 | H01 mandatory labelled real-editor/storage-gate recording. |
| H2 | H02 mandatory attachment-current guard under same gate, separate run. |
| H3 | Old async cancellation-ref scenario no longer applies. Current active/flow lifecycle is U07 plus R06; optional leave-before-release S06 only if needed. |
| H4 | Old claim of uncaught rejection in new continuation no longer applies: current effect does not await; unchanged loader catches/logs and keeps loaded=false. Storage read/decode tests exist. S05/source-only, not a new capture blocker. |
| C1 | R04 mandatory exact older Unsent row plus U06/U08. |
| C2 | **Not applicable:** pendingTaskId is absent from current route/screen contract; queued-list items open live Thread. Do not create an obsolete legacy editor fixture. |
| C3 | U06 unit-test share guard; full native share delivery/import lifecycle is unchanged and not minimum capture. |
| C4 | R05 mandatory branch control; U06 also proves guard. |
| C5 | U06 unit-test clone guard; real clone-with-competing-draft setup not minimum capture. |
| K1 | S07 optional real iOS Cmd+N when hardware input is available. Shared JS does not prove native event delivery, but no native code changes require it here. |
| K2 | S07 optional labelled phone palette harness or real iPad; not minimum capture. |
| K3 | Still not applicable to Android native chords: [native implementation](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/modules/t3-native-controls/android/src/main/java/expo/modules/t3nativecontrols/T3KeyboardCommandsModule.kt#L37) handles copy-reference and cycle-host only. [iOS native implementation](https://github.com/pingdotgg/t3code/blob/d7a0e4276ebe6dd2b9e7dbc0539f5dfb91d18943/apps/mobile/modules/t3-native-controls/ios/T3KeyboardCommandsModule.swift#L35) exposes Cmd+N and iPad palette. |

## Scope, verification, and friction

Reviewed all six changed files against the pinned base, current provider/stack binding lifecycle and pruning, all direct flow consumers, mobile entry families, draft storage/hydration/creation/retarget helpers, selection/deep-link/navigation seams, and the earlier matrix. Web/desktop JSX, contracts, server, provider adapters, agent MCP paths, and scheduled tasks do not change. Mobile drafts are client-local; local/remote/tunnel permutations add no new candidate-selection render branch. Android and iOS share this changed JS; native presentation remains an explicitly optional smoke, not silently claimed verified. No tests were rerun because this pass was source-derived and the supplied worktree must remain untouched; no test pass or current CI state is claimed.

The worktree remains unchanged; the lead-owned parent scratch directory is preserved. Only this audit's own scratch subdirectory is removed after the matrix upload and integrity check. No processes/devices/leases were started. No commits or WIP branch exist.

FRICTION:
- tool | The web reader could not open the supplied old Markdown artifact; a direct curl download into this audit's scratch directory succeeded | Direct artifact download avoids the web reader's unsupported response handling.

VERDICT: INSUFFICIENT R01, R02, R03, R04, R05, R06, H01, H02 recordings absent
