# Proof audit, pass 1 — mobile composer capture matrix

Target: [#23986](https://git.cafe/pingdotgg/t3code/pull/23986) ([GitHub mirror](https://github.com/pingdotgg/t3code/pull/23986)), [flamboh:t3/composer-card-on-tap](https://github.com/flamboh/t3code/tree/t3/composer-card-on-tap), head [2d2443fd90](https://github.com/pingdotgg/t3code/commit/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b), merge-base [9bc0c33438](https://github.com/pingdotgg/t3code/commit/9bc0c334383a43af5390283e9b10f999acc021b8). Read-only audit of the pinned source, not the concurrently changing working tree. `origin/main` currently points later than that merge-base; its merge-base with the requested head is the supplied base.

## Findings first

1. **No capture evidence has been accepted in this pass.** This is a capture plan, not a visual pass or a merge recommendation. Existing links in the PR description are not treated as evidence for the takeover implementation. Re-run the minimum on its final head and record that SHA. No runtime defect is asserted from this source-only pass.
2. **There are two entry paths, and they prove different things.** Both live threads and New Task drafts use the changed composer through `ThreadScreenBinding` → `ThreadScreenOutlet` → `ThreadDetailScreen`. A New Task draft renders a hero instead of `ThreadFeed`; it cannot prove that “No conversation yet” remains still. That proof requires a persisted, empty, ready thread.
3. **There are two changed feed placeholder paths.** The normal empty-ready overlay and the explicit unavailable/error placeholder both receive the new inset. Loading itself has no centered placeholder. An unavailable state with queued messages bypasses the explicit placeholder. Do not substitute the outer route’s “Thread unavailable” screen, which never mounts this feed.
4. **Focus and expansion are separate state machines.** Text overflow/wrap can open a card with no focus. Android Back normally hides the IME while keeping focus. Attachments use focus/settings state for their strip versus thumbnail placement, while the label uses card expansion and row width. A still of a focused card proves none of the closing, resizing, or restoration behavior.
5. **Physical screen size is insufficient metadata.** The branch reads the inner compact row’s measured width, after outer insets and max-width limits. Android’s content max is 640 pt before the composer’s 12 pt padding on each side; the actual row can be smaller. A Pixel Fold proves width responsiveness, but it does not exercise the Apple hinge/`duo-unfolded` sidebar-picker branch: `useSidebarWorkspaceTabs` returns null on Android, and iPads also bypass that picker branch.
6. **Historical bot findings are not current blockers by themselves.** The old yielded-label/wrap code is absent at the requested head. Floating-control coverage is included in the new placeholder inset at this head. Preserve both as capture risks (F06/F12/F14/F20), not as repeated unverified bug reports.

## Fixture notation and acceptance rules

- `R` = measured inner compact-row width in React Native layout points (Android dp), not screenshot pixels, device width, or the text slot. Narrow is `0 < R < 500`; wide is `R >= 500`. Initial `R = 0` is an unmeasured mount state, not a third supported resting layout.
- `N-I` = iPhone 17 Pro portrait, narrow; `N-A` = Pixel 9 portrait, narrow. `W-I` = iPad full-width/landscape with R >= 500; `W-A` = Pixel Tablet with R >= 500. `F-A` = Pixel 9 Pro Fold, closed/open, measure both states. `S-I` = the same iPad in split view or a resized app window crossing 500. If a named viewport does not reach the specified R, adjust panes/orientation or mark it missing; never relabel it by device name.
- Default data: persisted top-level empty thread `E0`, ready detail, no worktree setup, `activeWorkStartedAt = null`, no queued messages or status, connected, operable, no drafts/attachments, voice idle, base text size 16 pt. Default model `M+`: a real installed model with visible reasoning/effort summary, preferably a long catalog label and the longest available supported reasoning label. Record the exact instance, label, summary, and mic availability; do not assume a hardcoded provider/model exists.
- `M0`: a model whose capabilities produce no reasoning summary. `MB`: a configured second instance with a visible instance badge. `D0`: an unsent New Task draft with selected project/environment/model, local workspace, and controls visible. `P1`: a populated idle thread with one short user/assistant exchange. Synthetic payloads only.
- `short` = `Hi`; `wrapped` = `one\ntwo`; `tall` = five nonempty newline-separated lines (then scroll inside the editor). For overflow, type one character at a time near the observed transition. Save the exact string and model/font/row geometry: a fixed character count is not portable. Test both nonempty whitespace/newline data and normal visible text; expansion uses raw `value.length`, while send availability uses trimmed text.
- `A` = directly affected; `C` = unchanged control state that must appear in the same recording; `H` = labelled native harness when real app seeding is unavailable. MIN = required acceptance subset; EXTRA = supplemental distinct branch, reported missing if not captured. Extras are not silently declared covered by a similar fixture.
- Every row with an interaction, asynchronous phase, animation, remeasurement, upload, or keyboard change is **recording**. Extracted endpoint stills may supplement it. A static row may be an identified frame in another recording. Capture base and final takeover head with the same seeded data/viewport/theme. For intentional new behavior, the base side should show the old behavior, not be expected to pass the new spec.
- Record full-screen composition including header, placeholder/feed, composer, and IME where relevant. Label before/after and SHA. Keep an uncut interval covering the trigger, transition, final settled state, and reverse action. Do not infer continuous stability from two stills. On Android the settings screen is a full-screen card: explicitly say its underlying composer is occluded; before/return frames are not direct proof of the hidden interval.
- No harness image counts as a real-device keyboard/folding proof. Harness captures must name mocked contexts/native metrics/data. No test-only placeholder or mic substitutions in real-app acceptance captures.

## Seeding recipes (through the real app or its RPC)

**S0 — isolated environment and project.** The lead/capturer owns server startup and device leases. Start the local `vp run dev` with fresh isolated state and seeding disabled (`--no-seed` / `T3CODE_DEV_SEED=0`) as required by the brief; never use the live userdata. Pair the native dev client to that environment using its normal UI. Add a disposable project through the app’s New Task/project flow, select the local workspace, and choose an actually available model through model settings. Do not open or consume another person's pairing link. This audit starts no server or simulator.

**S1 — persisted empty thread E0.** Create the project in the app, then use an authenticated driver for the existing app RPC `orchestration.dispatchCommand` with a `thread.create` command. Required command shape at the audited head:

```json
{
  "type": "thread.create",
  "commandId": "<fresh command id>",
  "threadId": "<fresh thread id>",
  "projectId": "<project created through the app>",
  "title": "Proof empty composer",
  "modelSelection": { "instanceId": "<from server config>", "model": "<from config>", "options": {} },
  "runtimeMode": "full-access",
  "interactionMode": "default",
  "branch": null,
  "worktreePath": null,
  "createdBy": "user",
  "creationSource": "mobile"
}
```

Copy the complete model selection, including real option values, from app/config state rather than the illustrative empty options above. Use the app's typed RPC client/Effect RPC transport, not an invented JSON-RPC envelope. Await command success and the shell/detail update, then open the native `Thread` route with `{environmentId, threadId}` (link path `threads/<environmentId>/<threadId>`). Do not send a turn. Confirm the actual text “No conversation yet” is present. UI New Task alone cannot create this fixture; S1 needs this RPC driver. This is real-app capture, not a harness, because the thread is created through the supported server command.

**S2 — New Task D0 and normal P1.** New Task UI → project → selected model produces D0 (native `Thread` route with a `draft` parameter; public alias `new/draft`). Do not send while testing its draft-specific settings navigation. Separately send a small deterministic prompt through the app and let it finish to create P1; its text is not part of proof of placeholder stability.

**S3 — drafts and settings.** Type/paste/edit through `agent-device` into the native composer; these drafts/attachments are client-local, so server DB edits or server RPC alone do not seed them. Tap the existing “Model and reasoning settings” control, select M+/M0/MB and effort in the real UI, then return. Existing-thread sheet route is `ThreadSettingsSheet`; D0 uses `NewTaskThreadSettings` (`new/draft/settings`). Do not fabricate an existing-thread settings deep link; that screen has no linking path.

**S4 — attachment assets.** Put four small synthetic, visually different PNG/JPEG images and one short MP4 into the simulator/emulator media library; put one small text document/PDF in its file picker. Add using “Add attachment” → “Photo Library” or “Choose Files”; native paste is a separate supported entry when the fixture calls for it. Wait for uploads to settle for layout captures. A file-picked image is typed `file` but still enters the media strip; a non-media file becomes an inline document chip and does not hide the wide label. Use photo `one.png` for the one-image fixture. Never use a maintainer photo library or sensitive files.

**S5 — geometry/theme/text.** Device rotation, Pixel Fold fold/unfold, iPad split-window divider, or in-app panes change R. Theme and Text size live in Settings → Appearance (`settings/appearance`); use explicit Light or Dark rather than System. Text size limits are 11–22 pt, default 16. Prefer an existing geometry observer/inspector readout for R; a scratch-only instrumentation harness is acceptable but must be labelled. Do not alter app rendering or force the responsive flag to fake a real-app capture. Hardware-keyboard fixture: connect/enable emulator or simulator hardware keyboard and suppress the soft IME, then focus with an empty draft; record which keyboard setting was changed.

**S6 — floating coverage.** For E0, use the app's Settle action if offered or the same authenticated driver to `orchestration.dispatchCommand` a `thread.settle` command (`commandId`, `threadId`), then open the thread. Confirm a visible Settled control and no feed entries. Reverse via its Un-settle action (or `thread.unsettle` with `commandId`, `threadId`, and `reason: "user"`). This reaches `showFloatingStatus = true` without inventing a running empty conversation. If settlement is unsupported in the chosen environment, use a supported lifecycle/goal control or label a harness; do not send a message and call it empty.

**S7 — unavailable and time-bound state.** Need a shell selected in `ThreadRouteContent` while detail is absent. A normal deleted-thread link can redirect to New Task before this feed mounts. Cache eviction/network timing is not a reliable deterministic seeder here. Unless the capturer has an app-RPC driver that can retain the shell and deliberately fail/delay detail, use a labelled native harness for F21–F24 with the exact `ThreadDetailScreen`/`ThreadFeed` props specified below. This is a declared limitation, not evidence that these paths cannot occur in production. Do not write SQLite tables or pretend the outer route's fallback is the changed placeholder.

**S8 — queue/voice/menu/blocked states.** Queue via app follow-up/held-queue controls, then Edit queued message; native draft attachments are separate from `queuedEdit.existingAttachments`. Set up voice through Microphone settings if available; use a controlled native harness for preparing/transcribing/error phases if the service cannot be driven deterministically. Type a slash command to open a menu, or a pull-request trigger to reach loading/error/empty states; no PR mutation is needed. Use labelled props for unavailable models, advisories, permission-denied connection, and an absent config if not safely reproducible with isolated app settings. Do not break the host provider configuration just to seed a capture.

## Capture matrix

All rows currently have status **MISSING — no captures yet**. A capture can cover multiple rows only when its manifest identifies each interval and endpoint. Theme assignments below are the minimum specified cells; there is no requirement to multiply every fixture by every theme/device.

| ID / priority | Named fixture and needed data | Diff effect / expected visible result | Real-app seed or labelled harness | Minimum viewport × theme; format | Status |
|---|---|---|---|---|---|
| F01 MIN (control frames) | Narrow resting E0; empty then short saved draft, unfocused, no media | C: one row, provider icon/chevron only, no model/reasoning text; empty placeholder visible | S1/S3; endpoint frames of F02/F03 | N-I × Dark; N-A × Light; still/frame | MISSING |
| F02 MIN | iOS narrow focus → empty card → short text → feed-tap blur; E0, no floating status | A: focus opens model+reasoning card; short draft returns to row on blur/keyboard close; placeholder stays fixed through card-height change | S1/S3; do not use D0 hero | N-I × Dark; recording | MISSING |
| F03 MIN | Android narrow focus → `Hi` → Back; focus again → Back gesture; E0 | A: card while IME visible; Back/gesture close IME and card even with retained editor focus; re-tap opens again; placeholder stable | S1/S3; record IME and both Android dismissal paths | N-A × Light; recording | MISSING |
| F04 MIN | Wide empty/short rest → focus → short edit → blur; M+ | A: full model+reasoning label at rest, focus/short draft keep row and label, no collision | S1/S3; also include one D0 segment to cover draft entry caller | W-I × Dark; W-A × Light; recording | MISSING |
| F05 MIN | Wide one-line threshold; longest normal M+ label/summary; add/remove characters near label | A: card opens roughly a character before collision; label font does not shrink away; delete back to short/empty returns row without bounce | S3; save exact strings, R, font, mic availability; compare base and takeover | W-I × Dark; W-A × Light; recording | MISSING |
| F06 MIN | Wide direct paste + explicit wrap + >4-line draft; include `one\ntwo`, five lines, newline-only draft | A: paste/wrap opens directly; card remains for a genuinely long/wrapped draft with keyboard down; text scrolls at cap and is not clipped; clearing collapses | S3, moderate paste below pasted-text attachment threshold; combine with F05 | W-I × Dark; W-A × Light; recording | MISSING |
| F07 MIN (same clip as F03) | Narrow long/wrapped text; Back/blur, then clear to short | A/C: hiding keyboard does not force a text-overflow card closed; only short text-only drafts return to row | S1/S3, `one\ntwo` and long single line | N-A × Light; recording | MISSING |
| F08 MIN | Narrow settings opened from focused short card, then from idle icon; select a different effort/model and dismiss | A: card remains through sheet handoff; focus restores only when appropriate; opening from rest returns to resting row on dismissal; no flash | S3; existing ThreadSettingsSheet plus D0 NewTaskThreadSettings segment; Android underlying card is occluded | N-I × Dark and N-A × Light; recording | MISSING |
| F09 MIN | Wide overflow card → settings → change model/effort → dismiss → shorten | A: an already-open card persists through sheet; short wide draft is allowed to remain a row; label/slot remeasure after selection; reverse works | S3 after F05, both sheet and keyboard transitions | W-I × Dark; W-A × Light; recording | MISSING |
| F10 MIN | Wide one image + empty/`Hi`; at rest → focus → thumbnail preview → return → blur → remove image | A: resting thumbnail in row and icon-only model; focus keeps text in row but moves removable image strip above it; no duplicate thumb; final removal restores full label | S4/S3; use `one.png`; preview returns to prior focus appropriately | W-I × Dark; W-A × Light; recording | MISSING |
| F11 MIN | Narrow one image + `Hi`; rest → focus → keyboard hide/blur → remove | A: focused narrow card has full label/removable media; resting row icon-only; record Android Back's retained-focus attachment placement separately from actual blur | S4/S3; code may leave the removable strip above row while Android focus remains; do not incorrectly expect it to be a thumbnail until blur | N-A × Light; plus N-I × Light focus/blur smoke; recording | MISSING |
| F12 MIN (combine F13) | Four ready media items (three images + one short video), no draft; fold/resize and focus | A through width branch: narrow shows 1 thumb + `+3`; wide shows 3 + `+1`; focussed removable strip shows all; media forces icon-only wide row | S4/S5; optionally remove down to 3/1 to show no overflow badge at the exact count | F-A closed/open × Dark; recording | MISSING |
| F13 MIN | Resize while focused, short/no-media draft; folded → unfolded → folded, then Back/blur | A: width selects narrow card vs wide row while focus persists; new measurements settle, no stuck card/repeated bounce or lost text | S3/S5; R must cross 500; clear F12 media first | F-A closed/open × Dark; recording | MISSING |
| F14 MIN | Resize with text that overflows narrow slot but fits wide; repeat with genuine multiline text | A: after widening, a short-fitting draft can recover row; genuinely multiline stays card; narrowing recovers appropriate focus/overflow behavior | S3/S5, fit-calibrated single line then `one\ntwo`; combine F13 | F-A closed/open × Dark; recording | MISSING |
| F15 MIN | iPad split/resized window crosses 500 while focused; short text, no media, E0 | A: same width rule on an iPad; focus card in narrow pane, label row in wide pane; empty placeholder not shifted by card expansion at fixed viewport | S1/S5; compare card vs row only at same viewport for placeholder Y; a window resize itself can legitimately move its center | S-I narrow/wide × Light; recording | MISSING |
| F16 MIN conditional H | Exact measured R=499 → 500 → 499, focused empty/short, M+ | A: inclusive threshold: 499 focus card, 500 focus row/full label; no zero-width transient asserted as resting UI | S5 if exact native sizing possible; otherwise **labelled native width harness** around real composer with 499/500 pt row, not a forced flag | Native iOS or Android width harness × Light; recording | MISSING; real exact-width feasibility unverified |
| F17 MIN | Text size 11 → 22 pt; longest normal M+ label/summary; short then threshold/wrap | A: scaled inset/minimum height and early-open margin; large text not clipped, cursor centered at rest; reasoning stays legible; threshold recalibrates | S3/S5 Settings → Appearance → Text size; take both values as interval checkpoints | W-I × Light and N-I × Light; recording | MISSING |
| F18 EXTRA | M0, no reasoning summary; wide rest and short draft | A: full model name without an empty suffix gap; more text space than M+ | S3 real model without applicable descriptors; H if none installed | W-A × Light; still/frame | MISSING |
| F19 EXTRA | MB, two instances, visible badge + longest available model name and summary | A: badged icon/full-label geometry, truncation without stealing editor's reserved share | S3 real second provider instance; no account secrets in capture; H if none configured | W-I × Dark; still/frame | MISSING |
| F20 MIN | E0 with floating lifecycle control; rest → focus/card → close, then unsettle | A: placeholder retains collapsed inset plus status coverage; card height alone does not move it; status does not overlap it | S6; confirm empty-ready feed, status present; compare fixed viewport/keyboard state | N-A × Light; recording | MISSING |
| F21 EXTRA H | Shell exists, detail deleted, unavailable title “Thread unavailable”, zero queue | A: explicit unavailable placeholder uses stable collapsed inset during composer expansion | S7: H with contentPresentation unavailable/deleted text, queuedMessages=[], composer mounted; cannot replace with route-level unavailable screen | N-I × Dark; recording | MISSING; harness unless deterministic retained-shell driver |
| F22 EXTRA H | Shell exists, failed detail, “Could not load conversation”, long two-line detail | A: same explicit placeholder with wrapped error copy; no jump on focus and no overlap with composer/status | S7 H, title/detail data; add a visible floating connection control | N-A × Light; recording | MISSING; harness unless deterministic detail-failure driver |
| F23 EXTRA H | Shell exists, no detail cache, disconnected/available, “Messages not cached” | A: unavailable placeholder + offline floating control; still editable local draft with queue icon | S7 controlled cold client if shell-only cache possible, otherwise H; include queue length 0 → 1 control, which suppresses explicit placeholder | N-A × Dark; recording | MISSING; harness unless deterministic cache driver |
| F24 EXTRA H | Loading → ready empty → first queued message, plus unavailable + pending queue | C/A boundary: loading has no centered placeholder; ready empty gets it; presented/queued content suppresses it; no false “empty” capture | S7 H with shell, delayed detail, no worktree setup/active work; transition exact props; normal app only with controllable typed detail driver | W-A × Light; recording | MISSING |
| F25 EXTRA | Edit queued message with 1 existing image attachment and no new strip attachment | A via focus layout: existing attachment chips shown when editing; wide label remains full because only new media strip count hides it; cancel/save restores normal composer | S8 queue → Edit; existing attachment id/name/mime supplied by real queued message; H if no held queue support | W-I × Dark; recording | MISSING |
| F26 EXTRA | Non-media file/document chip; short body; focus chip, preview and return | A via slot geometry: no media thumbnail; wide label remains full; inline chip can wrap/open card; chip inert at rest and focuses editor on first tap | S4 choose small long-named text file/PDF or paste text above attachment threshold; never treat it as an image fixture | W-A × Light; recording | MISSING |
| F27 EXTRA | Voice preparation → recording → transcribing or error → cancel/return, existing short draft | C/A restoration: dictation hides native editor/draft row and model control; normal row/card and full/icon label recover correctly; no stale keyboard state | S8 real configured service; deterministic phases H if needed; H must declare audio/state inputs | N-A × Dark; recording | MISSING |
| F28 EXTRA | Slash/path/PR menu overlay; loaded items, loading/empty, error; select/cancel | C/A overlay interaction: opening narrow card does not clip popover; menu selection can lengthen draft into card; restored row remains usable | S8 type `/` for real loaded case; loading/error/empty exact variants H unless supported deterministic query; do not capture every unrelated item renderer | N-I × Dark; recording | MISSING |
| F29 EXTRA H | Model unavailable/fallback label, config present; current slug retained | A: newly exposed wide fallback label plus “Model unavailable” action, tap opens settings; no overlap | S8 H if no isolated app setting naturally produces unavailable model; null config fallback may be secondary labelled frame | W-A × Light; recording | MISSING |
| F30 EXTRA H | Provider compatibility status unsupported with one-line advisory | A through total composer height: non-alert advisory above wide labelled row/card does not overlap feed placeholder | S8 H with selectedProviderStatus.compatibilityAdvisory status unsupported and synthetic message | W-A × Light; recording focus/blur | MISSING |
| F31 EXTRA H | Provider compatibility status broken with wrapped advisory | A through total composer height: danger/alert banner above composer; stable placeholder baseline explicitly inspected for overlap | S8 H status broken with long synthetic message; do not damage real provider install | N-I × Dark; recording focus/blur | MISSING |
| F32 EXTRA | Connected read-only connection; canOperateThread=false; saved short text | A label/card remains usable to edit draft; disabled send plus permission notice adds height without overlap | S8 isolated read-only pair through app if available; otherwise H props; no server mutation required | N-A × Light; recording focus/blur | MISSING |
| F33 MIN | Hardware keyboard / soft IME suppressed; empty short draft; focus, blur, settings return | A: iOS narrow opens card on focus without soft IME; Android narrow stays row until IME shows; settings can hold card during handoff | S5 hardware keyboard toggle; distinguish IME hidden from editor blurred; restore setting afterward | N-I × Dark; N-A × Light; recording | MISSING |
| F34 EXTRA H | iOS Duo sidebar picker, large controls and +14 pt collapsed chrome, narrow/wide and safe-area/bottom-bar geometry | A: larger row/control sizes and placeholder offset still correct; not covered by Android Fold or iPad | Available devices cannot supply Apple hinge native metrics. Labelled native harness must provide duo-unfolded, usesSplitView, no vertical rail, not workspace page, picker tabs and bottom inset | Native iOS metrics harness × Dark; recording resize/focus | MISSING; no real matching hardware in brief |
| F35 EXTRA H | ThreadFeed placeholderBottomInset omitted; explicit contentBottomInset then default 18; top inset/safe-area variants | C: optional prop falls back to bottomContentInset; only production caller always supplies new inset, so not a second app route | Direct native feed harness with unavailable and empty-ready props; static labelled frames. Changing inset during a run would require recording | Native feed harness × Light; still/frame | MISSING |

## Minimum capture schedule — 120 minutes total

Priority is core acceptance, not an exhaustive device/theme cross-product. Allocate a hard ceiling, report any missing row when time expires, and upload raw evidence before deleting anything.

| Session | Budget | Exact minimum cells / rows |
|---|---:|---|
| Setup, pair devices, seed E0/D0 and media, save manifest | 20 min | S0–S4; use installed/cached native clients, lead-managed server; same state on base and takeover |
| Android phone, Light | 15 min | F01/F03/F07/F08/F11/F20/F33; one continuous or clearly indexed sequence; Back button and gesture both |
| iPhone, Dark then short Light smoke | 15 min | F01/F02/F08/F33; Light F11 and narrow F17; cover D0-specific settings route as a short segment |
| Android Tablet, Light | 12 min | F04/F05/F06/F09/F10; no-media, calibrated overflow, wrapped/tall, one image, settings |
| iPad, Dark then Light | 18 min | Dark F04/F05/F06/F09/F10; Light F15/F17; include D0 wide segment and E0 split-view |
| Fold closed/open, Dark | 12 min | F12/F13/F14; four ready media then no-media short, then fit-calibrated overflow and true multiline |
| Exact native 499/500 measurement or labelled F16 harness | 8 min | F16; do not spend time fabricating hardware. If harness unavailable, explicitly report gap |
| Upload, before/after labelling, manifest/index, retry allowance | 15 min | Upload recordings and key frames; finish by 115 min |
| Reserve | 5 min | Fix a failed core capture or one EXTRA row; do not consume this with an unbounded harness build |

The above are capture-session ceilings, not guaranteed infrastructure timings. If device/client setup consumes longer, keep both platforms’ focus/Back, wide label/threshold, image, resize and placeholder sequences ahead of extras, and report the omitted minimum rows. A 120-minute deadline never turns missing recordings into a sufficient verdict.

## Render-path inventory and collapse rationale

### Changed component exports and callers

- `useCompactComposerEditor` is exported but has one production caller: `ThreadComposer`. `ComposerSurface` is also exported from the changed file and has one production caller there; its implementation is unchanged, but it wraps every new card/row state with `animateLayout={false}`. `useKeyboardShowing` is private and has one caller, `ThreadComposer`.
- `ThreadComposer` has one JSX caller in `ThreadDetailBody`. `ThreadDetailScreen` wraps/key-remounts the body by environment/thread identity; its one JSX caller is `ThreadScreenOutlet`. `ThreadScreenBinding` supplies it from live `ThreadRouteContent` and `NewTaskDraftScreen`; the outlet has null/fallback alternatives which do not mount it. No web/desktop composer or contracts are changed.
- `ThreadFeed` has one production caller, `ThreadDetailBody`. `ThreadFeedPlaceholder` has two callers inside the feed: explicit unavailable with zero queued messages, and the empty-ready overlay. No other caller bypasses the new prop for production.
- Native `Thread` route dispatches between live controller and draft controller. A live controller may show OpeningThreadLoadingScreen or redirect to a draft before it reaches `ThreadDetailScreen`; those fallbacks are outside this diff's new placeholder behavior. `NewTaskDraftScreen` with no selected environment/model renders its own bare ComposerEditor fallback: unaffected, not a changed-composer fixture.

### Layout conditions and prop/data families

| Component/path | Exhausted branch/input family | Fixtures or reason for collapsing |
|---|---|---|
| Compact-editor hook | measured row 0 vs positive; R below/at/above 500; compact slot width 0 vs known; width unchanged vs changed; editor-layout callback ignored while expanded | Mount intervals in F02/F04; F13–F16 prove resize and threshold. Zero is not separately staged as a resting screen. |
| Compact-editor hook | opensOnFocus true/false; value length 0/nonzero (including whitespace); wrap height <=/> 1.5 line heights; measured width <=/> compactWidth minus one fontSize | F02–F07/F13/F14/F17; raw whitespace and explicit newline included in F06. No implementation line number is an acceptance oracle. |
| Compact-editor hook | Android fixed 6 pt inset vs iOS scaled lowercase alignment; minimum=max(36,lineHeight+2*inset); row=max(44,minimum); growth capped at 4 lines; Android always scrollable vs iOS at cap | F06/F17; 11/16/22 pt checkpoints reach differing alignment/height branches. |
| Focus/keyboard/settings | focus/blur; initial native keyboard visibility; keyboard will-show/will-hide subscription; Android gate vs iOS; sheet closed/opening/visible/restoring; focus restoration vs no prior focus; re-entrant settings open; component disposal on navigation/thread change | F02/F03/F08/F09/F33; return/handoff recordings. Navigating away and returning is a short control interval in F08; no artificial timer still substitutes for restoration. |
| Composer root/geometry | platform Android opaque surface/no shadow vs iOS gradient/glass; explicit/default bottomInset; contentMaxWidth undefined/Android640/iOS split960; sidebar picker false/true changes large controls, radius31 vs27, vertical pad9 vs2 | Core native device cells and F15/F34. Themes Light/Dark cover colors; no diff-specific branch for every custom palette. Surface's unused animated-layout=true default is unchanged, not a new fixture. |
| Media layout | draft attachments[] → filtered media strip vs inline non-media chip; type image vs file-image vs video; zero/one/within limit/above limit; limit1 narrow vs3 wide; focused/settings attachments strip vs unfocused thumbnails; queued existing attachments separately; remove/preview disabled when locked | F10–F12/F25/F26. One file-picked image may substitute for one photo in F12; no need to repeat every image format. Media upload pending/failed/ready changes badge/send availability, not new label predicate; ready is core, pending/failure a labelled supplemental interval if captured. |
| Model control | iconOnly = not expanded AND (narrow OR media strip count>0); option found vs missing; summary present vs absent; badge present vs absent; provider/catalog/config missing; reported selection and prompt-controlled traits can change reasoning suffix | F01–F19/F29. All six provider adapters flow through this common model control; choose M+, M0 and MB by traits/geometry, not six duplicate provider captures. Prompt-controlled trait change can be included in F09/F17 if actually supported. |
| Composer draft/props | isDraft true/false; draftKey custom/owner; editorRef supplied/fallback; draftControls present/absent; placeholder normal vs pending-approval feedback; projectCwd/project id/environment id; hidden false/true; onHeightChange; onEditorFocusChange | D0 segments F04/F08; E0 core. Keys/ids/ref plumbing have no independent visual shape. Hidden composer is display:none for open question or failed creation, so cannot prove new visible card. Placeholder text variants are interchangeable short copy except long chips/drafts separately covered. |
| Composer editing state | canOperateThread/connectionState; active busy/canStop/queue count; hasContent text trimmed/media/existing; queuedEdit null/existing/saving; interactionLocked; uploads/contextImports/pasted attachment pending; model unavailable; send blocked reason priority | F23/F25/F29/F32 cover distinct adjacent notices. Disabled/saving/send/queue/steer/restart/stop/resume action glyphs and long-press menu are unchanged fixed action footprints. Cover one busy or queued P1 control interval if convenient; no full old action-state cross-product in this diff's matrix. Saving/read-only sets editor readOnly, removes attachment actions and dims buttons; H if exact transient phase needed. |
| Composer menus/notices | trigger absent/present; items empty/nonempty; pull-request trigger allows empty/loading/error; locked suppresses menu; compatibility advisory absent/unsupported/broken; model unavailable; read-only notice | F28–F32. Menu item type details and server/query errors are unchanged siblings, not reasons to request unrelated PR/account fixtures. |
| Composer interactions | attachment add via files/photos/native paste; document/mention navigation; preview image/video mutually exclusive; focus restoration after preview; submit normal/alternate; native paste inline vs attached vs rejected; usage-limit command/dialog/panel; voice start/stop/cancel/error | F10/F12/F26/F27/F28. Sending/alternate and usage-limit command semantics unchanged; no server-side provider action required to prove new width/focus geometry. Huge paste refusal dialog is outside changed layout; moderate inline paste and a document chip exercise both geometry shapes. |
| Voice branch | idle vs preparing/recording/transcribing/error; editor display:none while status presented; compact toolbar and accessibility hidden vs dictation toolbar visible; voice available/unavailable mic footprint; submission lock and send visibility | F27. Capture core with actual mic availability logged; do not turn on a fake mic to force a prettier threshold. The elapsed timer/waveform makes all voice evidence recording. |
| ThreadDetail body | draft hero vs showContent true feed vs showContent false blank; provider-subagent bar vs normal composer; composerSlotHidden for pending question/failed creation; approval/queued-edit/feedback/error/usage/recovery/worktree setup sibling overlays; keyboard-sticky platform/layout ownership | Draft and live core cover changed visible paths. Hidden/showContent=false/subagent branches have no changed composer visible and are excluded as new capture requirements; still listed so no false coverage claim. Overlays other than status are unchanged; error/advisory long text is supplemental F22/F31. |
| ThreadDetail inset | initial measured composer height null → measured height; collapsedChrome60 + sidebar14 + bottomInset; estimated overlay remains actual height for feed; placeholder uses collapsed height + floating coverage; showFloatingStatus false/true | F02/F03/F20/F34. Floating predicate ORs lifecycle/working/compacting/background/child input/goal, queue, agents, device previews, browser tabs, disconnected, pending messages or acknowledged entries. One empty lifecycle fixture covers true without creating feed rows; the contents of each status variant are unchanged. |
| Feed placeholder gates | unavailable AND queuedMessages=0 early-return; ready AND presentedFeed=0 AND no worktreeSetup AND activeWorkStartedAt=null overlay; all complements suppress it | F02/F20–F24. presentedFeed is derived feed + pending messages, not just raw feed length. A working/thinking or pending message can make it nonempty. Worktree setup and active work each suppress normal placeholder, so never seed them for E0. |
| Feed optional geometry | explicit placeholderBottomInset vs inherited bottomContentInset vs default18; top inset supplied0 vs safeTop+navigation header; side safe-area left/right; split padding20 vs compact16; max width; theme and font sizes; iOS automatic content insets vs Android | Core platform cells, F15/F17/F35. Production passes the new inset in all cases. External API fallback gets only a harness because there is no second app caller. |
| Feed remaining render paths | LegendList populated entries: `guest-pending`, `run-fold`, `thinking`, `html-render`, `mcp-app`, `work-toggle`, `message` (user/assistant with text/media/context/review data), specialized `activity-group` (handoff/secret-request/compaction), and normal activity work log; setup; header earlier-history loading/error; footer pending handoff; media preview modals; content centering, scroll/follow/jump/reflow, error boundary | No changed rendering code in these populated paths; their normal contentBottomInset remains driven by actual composer height. A P1 endpoint during F04/F05 is a sufficient regression control for this proof plan; no new catalog of every historical feed row. Outer render-failure fallback also keeps actual overlay inset and is unchanged. |

### Source anchors (read at the audited head)

- [ThreadComposer](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/features/threads/ThreadComposer.tsx): compact hook, keyboard gate, focus-vs-card split, attachments and iconOnly.
- [ThreadDetailScreen](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/features/threads/ThreadDetailScreen.tsx): collapsed inset, floating status, draft/live feed dispatch and composer caller.
- [ThreadFeed](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/features/threads/ThreadFeed.tsx): both placeholders and suppression gates.
- [ThreadScreenHost](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/features/threads/ThreadScreenHost.tsx), [live route](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/features/threads/ThreadRouteScreen.tsx), [draft screen](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx), [Stack](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/Stack.tsx).
- [ComposerModelControl](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/components/ComposerModelControl.tsx), [ComposerToolbar](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/components/ComposerToolbar.tsx), [media filtering](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/lib/composerImages.ts), [settings presentation](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/features/threads/use-thread-settings-sheet-presentation.ts).
- [sidebar tabs](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/features/workspace/useSystemWorkspaceTabBar.ts), [device-type derivation](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/lib/device-type.ts), [content presentation](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/apps/mobile/src/features/threads/threadContentPresentation.ts), [orchestration command schema](https://github.com/pingdotgg/t3code/blob/2d2443fd90a71d0b2ad5b2eced4baa3677d1847b/packages/contracts/src/orchestrationV2.ts).

## Handoff manifest requirements

For each run create a new timestamped directory inside `/tmp/lead-23986-r1/proof-audit-1/`; do not overwrite/delete previous runs. Include head/base SHA, platform/OS/client/native fingerprint if available, device and physical viewport, measured R (or explicitly unmeasured), theme/text size, model label/effort/provider badge/mic state, thread E0/D0/P1 identity, seed method, fixture IDs, recording timestamps, raw video, key before/after frames, and public uploaded URLs. Never put auth/pairing tokens in the manifest or uploads. Preserve raw evidence until uploaded. Capturer must say which H rows were actually harnessed, which remained unseedable, and which minimum cells were omitted at budget.

## Audit scope and friction

Read all three changed files at the pinned head; traced production callers, draft/live route split, model-control and toolbar geometry, media filtering, settings presentation, placeholder gates, platform/sidebar behavior, appearance settings, and seeding contracts. Read target PR metadata/comments/reviews/status via GitCafe. No worktree edits, tests, servers, browsers, simulators, PR comments, pushes, or other PR work.

FRICTION: none — no issue blocked this source-only pass. Real-device and harness feasibility are deliberately unverified and labelled in the matrix.

VERDICT: INSUFFICIENT no captures yet
