# Proof audit, pass 1: Android on-device dictation

PR [#26734](https://git.cafe/pingdotgg/t3code/pull/26734) ([GitHub #26734](https://github.com/pingdotgg/t3code/pull/26734)). Target: `main`; head repository [PixPMusic/t3code](https://github.com/PixPMusic/t3code), branch `feat/android-google-dictation`. Audited local head [05411d1148](https://github.com/PixPMusic/t3code/commit/05411d1148bc828562fbb5a50af6d44e18e8dbe6), **not pushed at audit time**; its link may not resolve until publication. Verified merge-base [6f41364805](https://github.com/pingdotgg/t3code/commit/6f41364805e31a1f8d59c18c7cc451630cd9265d). Source checkout: `/home/ubuntu/wt/pr-26734`. Read `git diff origin/main...05411d1148` and affected callers; no captures, device sessions, builds, tests, edits, pushes, or comments were performed. This is a fixture/proof audit, not a merge-readiness verdict.

## Findings first

1. **No current-head captures exist for this pass. Every affected row is pending.** Older evidence mentioned in the remote PR description is not accepted as proof of this unpushed head. The remote PR head observed during inspection was a different revision; its CI cannot certify the local head.
2. **The scheduled-task placement contradicts a blanket “above the editor” description.** `ThreadComposer.tsx:885` inserts the padded error before the editor/attachments. `ScheduledTaskPromptField.tsx:59` inserts it **after** the `TextInput`, below the prompt, and removes the recording toolbar on error. Capture both; if “above the editor” is required for both callers, this needs a code decision before acceptance. Rows E04/E05 deliberately describe the actual code, not the blanket description.
3. **Pooled emulators cannot establish supported Google inference.** Per the brief, the real Google SDK reports unsupported there; the iOS simulator also lacks Apple transcription. Normal app captures can prove hidden microphones and unavailable Settings. Supported Settings, permission controls, preparation, recording/transcription UI, and later errors need a labelled JS harness on this pool. They must not be presented as real Google/Apple transcription.
4. **Showcase mode contaminates unsupported captures.** `useGlobalVoiceInput()` ORs availability with `getNativeShowcaseScene() !== null`. Use a normal paired launch with no showcase scene for C01–C06/A01–A02. Showcase is not a valid way to prove that the unsupported app hides the microphone. The Settings capability gate does not have this override.
5. **A long native SDK message is not automatically the displayed error.** `VoiceInputController` maps preparation/transcription failures to short generic strings. Use an actual controller error (for example the stale-draft message) for wrapped-copy evidence, or label a longer supplied string as synthetic. Do not describe a synthetic long error as Google's actual UI error.
6. **External input routing remains unverified on this pool.** Saved Bluetooth/USB names can be seeded for layout, reorder, and forget captures. This does not prove enumeration, matching communication output, microphone selection, reconnect identity, cancellation cleanup, or audible capture from an external microphone. Keep physical-device rows U01/U02 explicitly unverified.

## Capture contract and minimum axes

Each row below is a named visual fixture or a required interaction sequence. Multiple rows can share one continuous video if every named state is held legibly and timestamped; that does not waive a row. **`recording` means video required; stills alone are insufficient.** A still extracted from a clip is useful as a before/after keyframe, but does not replace the clip for timing, gestures, navigation, permission prompts, or dismissal.

Viewport codes (record actual device name, OS/API, physical screenshot size, logical window/content-pane dimensions, font scale, keyboard state, navigation style, and theme in the evidence manifest):

- **P:** pooled Pixel 9, portrait phone; normal app chrome, actual Gboard, default app font size. Do not assume physical pixels equal React Native layout units.
- **F:** pooled Pixel 9 Pro Fold from the brief, cover closed then inner open. Verify the live pool catalogue before leasing; the installed runbook names newer models. Use the available equivalent only with an explicit model/geometry label. Capture both poses and the resize, not just an unrelated wide viewport.
- **T:** pooled Pixel Tablet, landscape/split view; include Settings sidebar and detail, plus the thread's actual content pane. Check the composer row's measured width before claiming its 500-unit branch was reached.
- **I:** pooled iPhone 17 Pro from the brief, portrait, native software keyboard. Verify the actual catalogue/model and record any difference.
- **IL-H:** labelled iOS large-control layout harness, 834 × 1194 logical content viewport with `Platform.isPad=true`, because the stated iPhone simulator does not exercise iPad/Duo large controls. It is not proof of native iPad shell behavior; a real pooled iPad can replace it if available.
- **D/L:** default **T3 Code** palette (`t3-code`) in dark/light appearance. `P·D,L` expands to both themes on that viewport. Unspecified axes stay at P, D, normal font, no attachments, one connected isolated environment/project, stopped thread, no provider advisory, no queue, no showcase mode. W03 is the explicit Material You/font/reduced-motion stress case.

The table is the minimum coverage set, not a Cartesian product. Repeated states use D; L is repeated for each changed visual family (Settings row, unavailable explanation, preferences, error text/surface, and active toolbar). Counts/width, large controls, and keyboard interactions have their own rows. Other theme palettes do not introduce a new branch in these components; they reuse semantic color tokens. More themes are optional unless a listed capture exposes a contrast defect.

Before/after:

- `B/A` requires a matched comparison at base and head (same viewport, theme, text, keyboard, scroll, and harness inputs). No PR-only evidence is committed. Label the revision and harness on each side.
- `A` requires the after-state/flow. For new Android-only states that do not exist on main, use the normal before screen without the feature as the baseline from S01/C01/C03; do not manufacture a native Android recording toolbar on main.
- For iOS error comparisons, inject the same state at the dependency boundary into both revisions; main's transcriber getter is synchronous while head's is async, so adapt the fixture's API to each revision without altering render code.

## Seeding and harness definitions

**Real app R:** the capturer leases through `agent-device` with a task-specific proxy session; there are no local VM devices. Reuse the lead's healthy isolated backend and Metro. Do not launch another server or compete with the running Android build. Install the matching native fingerprint with `vp run mobile:install <platform> --session <session>` after the lead's build/cache is ready, using the pool's x86_64 Android client. Do not rebuild or edit `apps/mobile/android` or `node_modules`. Device and backend must be mutually reachable over the tailnet; pool localhost is not the VM's localhost. Keep credentials/pairing URLs out of captures and uploaded manifests. Use real software keyboards; no hidden test IME.

Use `t3code-dev://settings`, `t3code-dev://settings/microphone`, `t3code-dev://settings/scheduled-tasks`, `t3code-dev://settings/scheduled-tasks/new`, and `t3code-dev://threads/<environmentId>/<threadId>` after pairing. The app's scheduled-task edit route has no standalone link: open an existing task from the list. Create a new-thread draft using the normal New Task flow or `/new/draft`. A thread/new-task editor must actually be visible; provider-owned subagent detail and hidden composer slots are not substitutes.

Pair to **isolated demo state** containing one project, one stopped normal thread, one new-thread draft, and one existing scheduled task titled `Nightly review` with a saved prompt. Use the app to type drafts/attach demo files. Create/edit the scheduled task through the normal app or the isolated environment's `upsertScheduledTask` command; no scheduled execution is needed. Native capability, OS microphone permission, and mobile microphone preferences are **client-local**, not seedable through a server transcription RPC (none is added). Do not write the live T3 database or start tasks merely to make a fixture.

**H0, availability-only harness:** a temporary JS-only driver replaces the capability result with a controlled promise resolving to `null` or a transcriber. Keep production rendering, controller, permission flow, and native recorder. For the preparation-failure variant, force only availability to true and let the real unsupported native `prepare` reject. This remains a harness capture even though that rejection is real. Probe rejection maps to `null`. Foreground reprobes preserve the last resolved value while pending; they do **not** set `checking` again.

**H1, transcriber harness:** replace only `getLocalVoiceTranscriber`/the availability seam with a `VoiceTranscriber` whose `prepare({signal})` and `transcribe(uri,{signal})` are controlled by driver-resolved promises. Use locale `en-US` and transcript `Review the complete paragraph, including the final sentence.`. Retain the production controller, target selection, real OS permission, expo-audio recorder, input selection, and UI. Resolve/reject/cancel via explicit driver commands rather than hoping to catch short states. This can prove UI state, real permission and built-in recorder integration; it cannot prove Google/Apple preparation or inference. If the virtual recorder cannot provide levels, use H2 and say so.

**H2, component/state harness:** supply the real components/hooks with controlled voice state, elapsed seconds, waveform samples, preference AsyncResult, and/or delayed save response. This is used only where a deterministic state is otherwise unreachable (long synthetic errors, exact timer boundary, loading/failed preferences, large-iOS geometry). Label all altered inputs. A fixed waveform/time is synthetic metering, not real recording. Use the app's providers/navigation/safe areas rather than a static HTML recreation. Do not claim gesture/RPC/native lifecycle proof from a component snapshot.

**Preferences driver:** in an isolated capture client, use the real client registry and `updateMobilePreferencesAtom` with `{microphones: [...]}`. Keep that seam in the labelled harness, not a fake server endpoint. Use real persistence for restart/reorder proof. For initial/waiting states, H2 must intercept loading at the store/atom boundary. No source edit is made by this audit; the capturer/lead owns any temporary harness and its removal. Disable it, restart normally, and recapture unsupported R rows at the end.

Data fixtures:

- `empty`: `""`, selection `{start:0,end:0}`. `short`: `"Review this draft."`. `eight`: eight explicit newline-separated lines, `Draft line 1` through `Draft line 8`; this exceeds the thread editor's four-line cap. `longPrompt`: ten newline-separated lines to exceed scheduled prompt max height (min 96, max 160 logical units at default sizing).
- `shortError`: `Could not prepare voice transcription.` (real preparation path). `wrappedError`: `The draft changed while voice input was running. The transcript was not added.` (real controller stale-draft path). For E06, use `Synthetic recorder error: the preferred microphone stopped responding while recording. The existing draft is preserved. Dismiss this message and try again with the built-in microphone.`; explicitly label synthetic.
- `defaults`: `microphones` unset or `[]` → Android exactly three kind rows (Other wired, Other Bluetooth, Built-in); iOS four, adding CarPlay. An empty preference is **not** an empty-list screen.
- `twoDevices`: `[{kind:"wired",uid:"fixture:usb",name:"USB mic"},{kind:"wired"},{kind:"bluetooth",uid:"fixture:bt",name:"Studio headset"},{kind:"bluetooth"},{kind:"builtIn"}]` → five Android rows. Both named rows can be swiped; kind rows cannot.
- `crowded`: six uniquely keyed devices (three wired, three Bluetooth), one named `Alex's exceptionally long Bluetooth conference headset — second room`, plus all three Android kind rows → nine rows; names/descriptions wrap and the list scrolls. Add a saved `{kind:"carPlay"}` to test Android filtering (still nine visible), and optionally a repeated identical uid to test normalization without inventing another visual state. On iOS the extra CarPlay kind makes ten visible rows.
- Demo attachments: four small, nonsecret local images/documents with distinct names. Compact row width `<500` shows one thumbnail plus `+3`; `>=500` shows three plus `+1`. A separate single attachment covers no overflow badge. Expanded mode shows the removable strip; queued-edit existing attachments use their own strip.

## Complete render/caller inventory

| Changed component or downstream consumer | Callers and render branches that determine fixtures |
| --- | --- |
| `SettingsRouteScreen` / `SettingsListScreen` / index sections | Native `Settings` stack route; workspace Settings root; Android split `SettingsWorkspaceLayout` directly calls `SettingsListScreen`. Sidebar vs grouped list changes background/selection highlight; Android wraps in `SettingsScreen` with environment filter and optional workspace search, iOS uses native chrome. Cloud-configured vs local changes account/notification rows; account loading/sign-in/email and saved environment count change surrounding content, not Microphone. Search has empty query, match/alias, no match; selected page may be remembered on tab return. Microphone is now unconditional on Android and iOS, never disabled for no server targets; Keyboard remains iOS-only. Navigation-tabs row is conditional in code, but `navigationCustomizationAvailable()` is false at this head's default three registered tabs; do not fabricate it as a required production row. Server-target counts/project labels and overflow tabs do not gate Microphone. S01–S04 cover the affected row in compact/split/search/platform contexts; unrelated account counts are collapsed. |
| `SettingsMicrophoneRouteScreen` | `SettingsMicrophone` stack route, entry from index/search or direct `settings/microphone`. Capability `checking` → plain empty `bg-sheet` View (native route header remains); `null` → unavailable icon/title plus platform-specific Google/Apple explanation; transcriber → `MicrophoneSettings`. No spinner or permission CTA in the checking/unavailable branches. A01–A04. |
| `MicrophoneSettings` | `preferencesResult` Success and not waiting enables handles; initial/non-success or waiting disables reorder, with defaults or previous successful entries respectively. Permission checking/granted share no access section; ask shows Allow action/explanation; denied shows Open Settings/action-specific explanation. Android hides CarPlay and appends fallback-routing copy. Defaults, named wired/Bluetooth devices, reordered mixed kinds, long names, many rows/scroll, permissions, and content/padding/safe-area are M01–M09. |
| `MicrophoneRow` / drag handle / swipe | Device title/name vs kind title; device description is its kind, kind description explains fallback. Nonfirst nonlifted rows have a divider. Lifted row has shadow/z-index; adjacent rows shift up/down, or don't shift when canceled/same-position. `reorderable` toggles handle; first/last affect available accessibility actions. Row measured height drives drop rounding/clamping. Device-only swipe exposes destructive Forget; partial, full swipe, accessibility Forget, and removing last device are M06–M08. `windowWidth-40` determines full-swipe width, so a tablet/fold clip is required. Position/count change accessible labels. No connected/disconnected badge is rendered; saved disconnected device looks the same. |
| `ThreadComposer` | Sole direct caller is `ThreadDetailScreen`; reached from an existing thread, new-task draft through the shared detail screen, and queued-run editing. Props `isDraft`/`draftControls` change surrounding draft controls; `draftKey`/environment/thread select voice owner; selected model/provider/report control model name/icon and advisories; project/server config controls commands/files. `hidden` passes to editor, and outer caller may hide/replace the composer (not a dictation entry point). `contentMaxWidth`/bottom inset/safe area determine available width/clearance. Focus or retained settings-sheet focus expands attachments; text content size independently promotes editor from inline to full width and clamps at four lines. Compact threshold 500 controls one vs three thumbnails and overflow badge. Blank text + no attachments chooses Stop, Resume, or Send; nonempty/queued edit chooses Send/Save and may offer Queue/Steer/Restart. Connection, can-operate, model unavailable, pending upload/context import, interaction lock, queued saving and voice busy control disabled states/warnings. Error now renders before attachments/editor and returns the normal editable draft row; only active voice phases hide/flip the draft and show the absolute bottom dictation toolbar. Android opaque surface vs iOS glass, floating split Android padding 6 vs normal 2, iOS large controls padding 9/radius31 vs normal radius27, font and RTL geometry affect fit. All change-relevant combinations are C01–C06, D01–D05, P/R/T, E01–E03/E06–E08, W01–W04; independent unchanged command/media previews/advisories are listed below, not multiplied across every voice state. |
| `ScheduledTaskPromptField` | Sole caller `SettingsScheduledTaskEditorScreen`, reached through New and Edit wrappers. No editor/environment → field absent. Props `value`/`selection` change empty placeholder, multiline growth/scroll and transcript insertion; `disabled` (saving) or voice busy makes TextInput read-only. Idle/error with available transcriber renders absolute bottom-right microphone on `bg-card`; unavailable/checking/another busy owner hides it. Only active phases render toolbar below input. Error row is below input and replaces active toolbar. New/edit title, task/environment selection, permission/can-operate/offline save restrictions and form scroll affect surrounding content; fields/save stay blocked by `dictationPending`. C03/C04/C06, D04/D05, P03/R02/T02, E04/E05/E07/E08, F01–F04 cover it. Saving parent removes pointer events; no claim of touching disabled mic through an inaccessible parent. |
| `ComposerDictationError` (new) | Exactly two callers: thread and scheduled prompt. `null`/empty string returns nothing. Nonempty string wraps without `numberOfLines`, in `min-w-0`, flexed danger text, padding 16×12, gap8; fixed 28-unit dismiss button with extra hit slop centers across all wrapped lines. Error text is an accessibility alert; dismiss calls voice.cancel, retaining editable draft/selection. Short/real wrapped/synthetic long + caller placement + keyboard/width/attachments cover nonempty cases. Empty/null no-gap control is the idle rows; an empty-string error state is not produced by ordinary controller errors. |
| `ComposerDictationStatus` (changed) | Exactly thread toolbar and scheduled toolbar. Missing label or `statusKind:error` returns null; errors are now in sibling `ComposerDictationError`. Active preparing/transcribing show centered one-line label; recording crossfades to waveform plus elapsed time. Width determines waveform bar count (0 before layout, then clamped to 1..sample count); silent/nonzero levels and timer text including minute rollover vary pixels over time. Phase controls polite live region vs none while recording. P/R/T rows capture all actual active labels; E rows verify absent status/error toolbar and restored draft. Synthetic inconsistent combinations of props are not production fixtures. |
| Other exports in `ComposerDictationControl` | `DraftContent` only in thread: compact hidden rotates/fades, disables pointer/a11y; compact false is an unused generic branch at this head. `Toolbar` in both: visible false blank internal shell, draft/dictation flip keys; reduced-motion skips entry/exit animation. `CancelAction` active-only, `PrimaryAction` confirm while active, disabled/spinning during preparing/transcribing and enabled during recording, otherwise delegates start. `StartAction` absent unavailable; normal mic or error/settings action; disabled opacity unless loading. Regular/large size and Android font scaling change circles/icons; primary large reserves width62. `VoiceWaveform` measures width, `CompactVoiceWaveform` eight bars and `DictationElapsedTime` tabular clock are also consumed by global pill. W03/W04 and P/R/T/E/F rows cover supported caller combinations. |
| `VoiceInputProvider` / availability / shared controller (no direct JSX) | Availability starts checking, false/rejected/missing native/API<31 → null; Android AVAILABLE/DOWNLOADABLE/DOWNLOADING in either mode → transcriber; iOS locale-supported native transcriber now returned asynchronously. First mount and foreground refresh; refresh retains old value until settle. Showcase overrides composer availability only. Global session one-at-a-time; owner focus routes active/error status to local composer or global pill; another busy owner hides this composer's mic. Phase idle/preparing/recording/transcribing/error, editor/submission freeze, permission retry/settings, selection replacement and stale/empty transcript all feed render branches. Prepare waits permission/foreground, model prepare, audio configure and asynchronous input route; cancel/restart/background/finish release resources. A/P/R/T/E/F/G rows cover visible consequences; U rows reserve unavailable hardware claims. |
| `GlobalVoiceInputControl` (unchanged, downstream) | Wraps navigation in `App.tsx`. Local focused owner hides pill; owner unfocused with any status shows it. Compact recording timer/red dot vs preparing/transcribing spinner vs error icon. Tap expands: label, cancel, waveform/finish during recording, spinner busy, error retry/settings mic only if available. Width clamped to280/safe area; Android bottom includes FAB sizing vs iOS overlay/chrome clearance. Tapping outside/label collapses, returning owner hides, future reappearance starts collapsed. G01/G02; this is a shared-runtime regression check, not a newly added entry point. |

Unchanged surrounding thread branches do not get a voice-state Cartesian product: attachment media/file/video/document preview, remove, native text/image paste and large-paste attachment handling; mention/command/PR popover (trigger, items/loading/error), model unavailable button and provider advisory (unsupported vs broken styling), settings-sheet focus restoration, usage-limits dialog, follow-up long-press menu, connection read-only notice. Exercise model-sheet keyboard restoration and one attachment in E03/F04; existing previews/commands/account loading/screens without a composer add no new dictation visual treatment. Provider adapters, server/transport scopes, web/desktop and scheduling execution add no new dictation entry points: transcript edits the mobile local draft before ordinary submission. No wire schema changes.

## Fixture matrix

Status: **PENDING R** = real app reachable on the stated pool, no capture yet; **PENDING H0/H1/H2** = labelled harness required there; **UNVERIFIED U** = cannot establish real behavior with the stated devices. “Affects” is **direct**, **downstream**, or **control**. Each row's minimum is explicitly viewport × theme × state; only the listed extra axes are required.

| ID / named fixture | Affects / required data and expected frame | Reachability and exact seeding/action | Minimum capture / mode | Pass-1 status |
| --- | --- | --- | --- | --- |
| S01 Android settings entry | direct; unfiltered grouped Settings, Microphone row present after Chat, no Keyboard row | R; `/settings`, normal launch, one connected demo environment. Also open without a connection: row stays enabled. Local/cloud account header does not gate it. | P·D,L; B/A stills | PENDING R |
| S02 Settings search entry and reverse | direct; alias match `dictation` returns Microphone; `zz-no-setting` shows No results; clearing restores list | R; type into actual Settings search, open result, Back, clear query | P·D; A **recording** | PENDING R |
| S03 Split Settings selection | direct; sidebar unselected then highlighted Microphone with unavailable detail, remembered on tab return | R; T Settings workspace, tap Microphone, switch Threads and back; environment target filter empty must not disable local row | T·L; B/A keyframes + A **recording** | PENDING R |
| S04 iOS settings neighbors | control/downstream; Keyboard and Microphone both remain, native list navigation | R; I `/settings`, open Microphone then Back | I·D; B/A stills; navigation **recording** may share A02 | PENDING R |
| A01 Android unavailable sheet | direct; unavailable row + full Google/Android explanation, no permission request or priority rows | R; `/settings/microphone`, real unsupported SDK; scroll if needed, keep no showcase mode | P·D,L; A stills | PENDING R |
| A02 iOS unavailable sheet | downstream; Apple/iOS explanation, no priority rows | R; I `/settings/microphone`, real unavailable Apple transcription | I·D,L; B/A stills | PENDING R |
| A03 Initial capability checking sheet | direct/downstream; blank sheet body under native Microphone title, then unavailable; no false unavailable flash before supported resolution | H0; cold-mount with probe held pending; release null and, separate cold start, transcriber. Real transient may be observed but is not reliably seedable for capture. Include P and I. | P·D + I·D; A **recording**, both resolutions | PENDING H0 |
| A04 Foreground re-probe | direct/downstream; previous unavailable/supported UI retained during pending refresh; updates only when probe settles; rejection equals null | H0; null→foreground held→supported, then supported→foreground held→null/reject. Visit both composers and Settings with settled results. Do not expect a fresh checking sheet on every resume. | P·D; A **recording** | PENDING H0 |
| C01 Android unsupported empty existing-thread composer | direct; empty inline editor, no mic, ordinary disabled Send or applicable Stop/Resume; no error gap | R; stopped demo thread, empty draft, no attachments, no showcase | P·D,L; B/A stills | PENDING R |
| C02 Android unsupported focused long thread draft | direct; eight-line draft grows/caps at four lines with Gboard, no mic or error gap | R; type `eight`, scroll editor then blur/focus; keyboard must be visible in one frame | P·D; A **recording** | PENDING R |
| C03 Android unsupported new-thread draft | direct; New Task controls above same composer, empty placeholder and no mic | R; normal New Task flow, choose demo project, do not submit/start an agent | P·D; A still | PENDING R |
| C04 Android unsupported scheduled prompt: New | direct; empty Prompt placeholder/min-height, no bottom-right mic | R; `/settings/scheduled-tasks/new` after pairing/selecting demo environment/project | P·D; B/A stills | PENDING R |
| C05 Android unsupported scheduled prompt: Edit | direct; existing `Nightly review`, `longPrompt`, max-height scroll and Gboard, no mic | R; existing task via list → Edit; type/scroll; discard or save only in isolated state, never execute | P·D; A **recording** | PENDING R |
| C06 iOS unsupported composers | downstream; thread inline/multiline and scheduled New/Edit remain editable without mic | R; I same thread/task data as C01/C02/C04/C05; type/clear, no inference claimed | I·D; B/A keyframes + A **recording**, two caller contexts | PENDING R |
| D01 Supported idle empty thread / Send | direct Android, downstream iOS; mic plus normal model/attachment/disabled Send, no status/error padding | H1; resolved capability, empty stopped-thread draft, permission pregranted; do not call prepare yet | P·D,L + I·D; A stills | PENDING H1 |
| D02 Supported idle empty thread / Stop | downstream width guard; available mic plus Stop agent on blank draft, active cancellable replay/dev turn | H1 plus isolated free Replay state or H2 shell props; do not start paid agent merely for screenshot | P·D; A still | PENDING H1/H2 |
| D03 Supported idle empty thread / Resume | downstream width guard; mic plus Resume queue on blank draft; pending resume dims action | H1 plus held demo queue; if no safe app driver for held queue, H2 props. Tap Resume only on disposable state. | P·D; A **recording** for pending→resumed | PENDING H1/H2 |
| D04 Supported nonempty thread draft | direct/downstream; `short` inline then `eight` full-width capped editor, available mic + enabled Send/Queue variant; model name shrink | H1; normal typing, select a long available model label; no need to submit. Keep keyboard up, then blur. | P·D + I·D; A **recording** | PENDING H1 |
| D05 Supported scheduled idle prompt | direct/downstream; New empty min-height then Edit longPrompt max-height, overlay mic at bottom right, editor scroll | H1; normal New/Edit TaskForm, prompt typing/selection, no save yet | P·D,L + I·D; A **recording** covering both value shapes | PENDING H1 |
| D06 Locked/saving composer controls | downstream; scheduled saving makes mic dim and prompt read-only, form pointer/a11y blocked; queued-edit saving or interaction lock dims thread mic and blocks editing | H2 for deterministic held save/lock (or safe delayed isolated RPC for scheduled save); retain production parent restrictions | P·D; A **recording** entering/leaving each lock, two callers | PENDING H2 |
| P01 Preparing compact thread | direct/downstream; draft toolbar flips away, Cancel, centered Preparing, disabled/spinning confirm; editor/submission frozen | H1; empty thread, tap mic, hold prepare promise; cancel and demonstrate draft restored | P·D,L + I·D; A **recording** | PENDING H1 |
| P02 Preparing expanded thread with attachments | direct/downstream; eight-line draft and removable attachment strip retain geometry, active toolbar on surface bottom, editing disabled | H1; focus draft with one demo attachment; tap mic/hold prepare; include settings-sheet-kept-expanded variant in F04 | P·D; A **recording** | PENDING H1 |
| P03 Preparing scheduled prompt | direct/downstream; prompt remains visible but read-only, mic overlay absent, Cancel/Preparing/spinning confirm below prompt; Save blocked | H1; longPrompt, tap mic, hold prepare | P·D + I·D; A **recording** | PENDING H1 |
| R01 Recording thread | direct/downstream; Cancel, measured waveform, elapsed timer, enabled Finish; editor hidden; silent then nonzero levels | H1 with real virtual-device recorder; hold transcriber preparation only until start, then capture real recorder state. If exact levels/time must be driven, label H2. Include 0:59→1:00 without pretending a forced timer is elapsed audio. | P·D,L + I·D; A **recording** | PENDING H1 (H2 for deterministic levels/time) |
| R02 Recording scheduled prompt | direct/downstream; same waveform/clock/Finish but toolbar below visible read-only prompt, Save blocked | H1; start on nonempty scheduled prompt; real native recording after stub prepare | P·D + I·D; A **recording** | PENDING H1 |
| T01 Transcribing thread | direct/downstream; waveform fades to Transcribing, confirm spinner/disabled, Cancel remains; no draft edit/send | H1; Finish from R01, hold transcribe; include compact and expanded text layouts already seeded by P01/P02 | P·D + I·D; A **recording** | PENDING H1 |
| T02 Transcribing scheduled prompt | direct/downstream; visible frozen prompt, label/spinner below, no idle mic, Save blocked | H1; Finish from R02, hold transcribe then resolve or reject | P·D + I·D; A **recording** | PENDING H1 |
| E01 Short error in empty thread | direct; padded single-line error above editable empty editor, centered dismiss; normal mic/Send restored, no recording-status error | H0 availability-only + real native prepare rejection, or H1 reject prepare; label exact seam. Tap dismiss, show error row completely gone. | P·D,L + I·D; B/A matched error keyframes + A **recording** | PENDING H0/H1 |
| E02 Wrapped error with capped thread editor | direct; real `wrappedError`, `eight`, Gboard; error unrestricted height above editor, dismiss centered, draft stays editable/scrollable | H1 stale-target driver while transcribing or H2 exact controller state; type one→eight lines, scroll, dismiss without text loss. Do not replace with arbitrary SDK copy. | P·D,L + I·D; B/A keyframes + A **recording** | PENDING H1/H2 |
| E03 Error plus attachments/queued edit | direct; error above expanded removable strip; blur collapses to thumbnails; single attachment no badge vs four giving +3 on narrow; queued existing strip also retained | H1 error + normal file picker with demo files, or H2 attachment props. Focus/blur, remove one, open/close one preview, preserve error/draft; queued edit with one existing file | P·D; B/A keyframes + A **recording** | PENDING H1/H2 |
| E04 Short error in scheduled New prompt | direct; padded error **below** empty prompt, restored overlay mic, no active toolbar; dismiss removes row | H0/H1 preparation failure in New scheduled task; inspect actual below-editor placement and compare with acceptance wording | P·D,L + I·D; B/A keyframes + A **recording** | PENDING H0/H1; placement discrepancy |
| E05 Wrapped error in scheduled Edit prompt | direct; `wrappedError` below longPrompt at max-height, form scroll/keyboard keep text and dismiss reachable | H1 stale draft during transcribe or H2 supplied state; edit/select/scroll prompt then dismiss without text loss | P·D,L + I·D; B/A keyframes + A **recording** | PENDING H1/H2; placement discrepancy |
| E06 Synthetic long-error stress | direct; five-or-more visual lines, centered fixed dismiss, no clipping at rounded thread surface or scheduled form; max editor text | H2 supply explicitly synthetic recorder error text; both real components, two separate caller frames, baseFontSize22; no inference claim | P·L; B/A keyframes + A **recording** editing/dismissing, both callers | PENDING H2 |
| E07 Permission-denied error actions | direct/downstream; real permission text; retryable denial mic retries; permanently denied changes mic action to Open microphone settings; draft editable | H1 availability on; reset OS permission for Allow/Deny paths, return via OS Settings. Both thread and scheduled target must show/dismiss their own error; OS UI itself is real | P·D + I·D; A **recording**, deny/retry/settings/grant | PENDING H1 |
| E08 Error after capability disappears | direct/downstream; error row persists, available mic absent, dismiss still works; no stranded status toolbar | H0/H1 set capability null on foreground while a controller error belongs to current target; H2 if injecting exact combined state. `errorAction:null` uses normal mic only when available. Include both callers. | P·D; A **recording** | PENDING H0/H1/H2 |
| F01 Successful transcript insertion | direct/downstream; preparing→recording→transcribing→idle, restored draft/normal actions, insertion replaces selected text and preserves surroundings | H1; thread and scheduled prompt `Before REPLACE after`, select REPLACE, resolve stated transcript; repeat collapsed cursor/end or empty draft as keyframe; send/save remains manual | P·D + I·D; A **recording**, both callers | PENDING H1 |
| F02 Cancel and immediate reuse | downstream; cancel from preparing, recording, transcribing returns editable original draft without late insertion; immediate restart must become usable | H1; explicit held promises/abort, both callers. For route-selection pending cancellation, H2 fake input-selection deferred; no physical Bluetooth claim | P·D; A **recording**, each phase and restart | PENDING H1/H2 |
| F03 Android permission activity vs actual background | direct; first mic request can background activity without producing stopped error; later real background while preparing/recording produces existing error and preserves draft | H1; real OS permission grant/deny while transcriber controlled. Then separately background app during held prepare and during recording, return/verify error. Do not conflate navigation with background. | P·D; A **recording** | PENDING H1 |
| F04 Composer focus/settings and target switch | downstream; settings sheet keeps composer expanded, return restores keyboard; another target cannot edit original recording, and draft insertion goes to original owner | H1; focus thread `eight`, open model/settings sheet and close; while busy navigate to another thread/scheduled target, then return; cancel before deleting disposable data | P·D + I·D; A **recording** | PENDING H1 |
| F05 Real virtual-device recorder and stale-input fallback | direct native integration/downstream UI; successful built-in recording and repeated reuse; stale preferred input rejects but recording continues on system input; canceling queued input selection returns idle and next start works | H1 transcriber only, keeping real expo-audio recorder/permission/route. JS driver supplies stale preferred uid for one attempt and cancels one pending/queued selection. Use production lazy recorder/controller, not a second recorder implementation. Label the seam; include native status, nonempty audio-file metadata and restored audio-mode transcript. Virtual-device audio is not proof of physical/external sound capture. | P·D; A **recording** of prepare→record/cancel→restart plus sanitized runtime transcript | PENDING H1; native built-in path reachable under harness |
| M01 Supported default priority, permission granted/checking | direct Android/downstream iOS; three Android kind rows/no CarPlay; no Microphone access section; handles present when preferences ready; Android fallback copy | H0/H1 force supported, defaults, OS permission granted. H2 hold permission-check read only if checking frame needs determinism; checking/granted are visually equivalent here. | P·D,L; A stills | PENDING H0/H1 |
| M02 Supported Settings permission ask | direct Android; Allow microphone access row/explanation above priorities; request opens real OS dialog, Grant removes section | H0/H1 capability; reset app OS permission or clean isolated install. Tap Allow; record whole dialog→active/settings update | P·D; A **recording** | PENDING H0/H1 |
| M03 Supported Settings permission denied | direct Android; Open Settings row/explanation; priorities still visible; granting externally refreshes on foreground | H0/H1 capability, OS permanently denied; tap Open Settings then grant and return. Do not mistake unavailable sheet for denied state. | P·D; A **recording** | PENDING H0/H1 |
| M04 Preferences not ready, default rows | direct; initial/non-success preferences produce three defaults with no handles, then handles appear on successful hydration | H2 hold preferences load; capability supported, permission granted; failure load normally recovers to empty preferences, not an error banner | P·D; A **recording** | PENDING H2 |
| M05 Waiting preferences with previous data | direct; five saved rows from twoDevices retained while waiting, handles hidden until ready; no added loading spinner | H2 AsyncResult Success with waiting:true and prior preferences, then false | P·D; A **recording** | PENDING H2 |
| M06 Saved named devices / normalization | direct; twoDevices gives five mixed rows, distinct icons/subtitles and handles; saved CarPlay filtered; disconnected devices look identical | H0/H1 supported plus real preferences driver; no physical devices needed for saved labels. Seed optional duplicate uid without duplicate visible row. | P·D,L; A stills | PENDING H0/H1 + seeded prefs |
| M07 Long names, nine rows and scrolling | direct; crowded nine-row Android list, wrapping names/descriptions/dividers; full fallback copy readable at bottom | H0/H1 capability + preferences driver, all synthetic device names disclosed; scroll top→bottom | P·D + T·L; A **recording** | PENDING H0/H1 + seeded prefs |
| M08 Reorder, boundaries, cancel and restart | direct; lifted shadow/no divider, other rows shift up/down; first/last handles expose correct actions; cancellation restores order; saved drop persists | H0/H1 supported and persisted twoDevices. Drag both directions, drop first→last/last→first, drag beyond boundary, cancel one gesture; use accessibility step if supported; restart and reopen | P·D + F-open·D; A **recording** | PENDING H0/H1 + seeded prefs |
| M09 Forget device and last-device removal | direct; named-device swipe reveals Forget, kind row has no swipe; partial reveal/cancel, button/full-swipe/accessibility Forget remove only device, defaults remain | H0/H1 supported + twoDevices. Record phone partial/button and wide full swipe; remove final named device, reopen to confirm only three kind rows. Accessibility path can supplement transcript if automation cannot invoke it. | P·D + T·L; A **recording** | PENDING H0/H1 + seeded prefs |
| M10 iOS supported priority regression | downstream; four default kinds including CarPlay, six with twoDevices, no Android fallback sentence; same reorder/forget behavior | H0/H1 on I; preferences driver, permission granted; capture defaults then twoDevices; reorder/forget once to check async availability integration | I·D,L; B/A keyframes + A **recording** | PENDING H0/H1 |
| G01 Unfocused active owner's global pill | downstream; recording collapsed clock/red dot→expanded waveform/Finish, preparing/transcribing spinners; return to owner hides pill, other composer mic hidden while busy | H1; start in thread, navigate away while prepare/record/transcribe held; expand/collapse/outside-tap, cancel or finish; repeat from scheduled prompt target | P·D + I·D; A **recording** | PENDING H1 |
| G02 Unfocused error global pill | downstream; collapsed warning→expanded one-line error, Retry or Settings mic; capability-null removes retry; return owner shows local padded error, dismiss clears both | H1/H2; retain target while navigating away, inject real controller error, variants retry/settings/unavailable. Does not replace E rows at the focused editor. | P·D + I·D; A **recording** | PENDING H1/H2 |
| W01 Fold transition with keyboard and errors | direct/downstream layout; closed cover narrow vs open split, long error and capped draft stay above keyboard; Settings detail reflows; Android floating-tabs padding 6 vs ordinary 2 | R unsupported Settings baseline, H1/H2 for error/dictation. Fold/unfold actual pooled F with Gboard open; record actual content width and Android navigation style | F-closed→open·D; A **recording**, Settings and thread | PENDING R + H1/H2 |
| W02 Wide composer threshold and tablet form | direct/downstream layout; compact width>=500 shows 3 thumbnails/+1 (four total), vs <500 1/+3; zero/one no badge; long error spans correctly; scheduled form scrolls independently | H1/H2, four attachments; T landscape then resize/pose or F pane to cross measured threshold. If content max-width prevents >=500, use labelled exact-width H2, not a false tablet claim. | T·L + measured <500/≥500 views·D; A **recording**, error + attachment threshold | PENDING H1/H2 |
| W03 Android font/theme/reduced-motion stress | direct/downstream layout; baseFontSize22 enlarges controls/text; Material You semantic colors; reduced-motion toolbar final frame remains visible across start/error/cancel | H1/H2; Settings Appearance font22 and Material You, actual OS reduced motion on, run P01→R01→T01→E02 and scheduled P03→E05; reset after | P·L; A **recording** (include error keyframes) | PENDING H1/H2 |
| W04 iOS large-control branch | downstream layout; large circle/icon, confirm width62, radius31/padding9, short/wrapped errors retain full width and centered dismiss | H2 IL-H with actual components/providers; idle/preparing/recording/transcribing/error states. Real iPad simulator may replace the geometry harness; stated iPhone cannot. | IL-H·D; A **recording**; native large-iOS shell remains unverified without iPad | PENDING H2; native shell unverified |
| U01 Real supported SDK and model paths | direct native behavior, not a distinct extra visual design; real Advanced/Basic, downloadable/downloading preparation, offline inference, paragraph pauses/final tail, cancel cleanup | Cannot prove on stated Android/iOS pool. Requires supported physical Google device or supported Apple hardware. H1 only demonstrates the UI rows above. If hardware supplied, record language/status/model and real waveform→final editable transcript, with runtime evidence. API24–30 compatibility also needs suitable OS/runtime validation beyond this pool. | Physical supported device·D; real **recording** + runtime transcript; not replaceable by harness | UNVERIFIED U |
| U02 Real Bluetooth/USB routing and fallback | direct native behavior; enumerate/remember real external device, reconnect stable uid, choose matching output, wait for readiness/5s timeout, cancel before route call/while pending, restore audio mode/default fallback | Cannot attach Bluetooth/USB to pooled emulator. Requires physical Android and real external microphones; screenshots of seeded M06 names are insufficient. Native/unit results, if lead has them, can supplement but do not prove audible external capture. | Physical device·D; real **recording** + selected-input/audio-mode/file evidence | UNVERIFIED U |

## Coverage boundaries and collection checklist

All writes by this pass are confined to `/tmp/lead-26734-r1/proof-audit-1/`. This audit made no checkout edits, and `apps/mobile/android` and `node_modules` were neither read nor changed. A concurrent uncommitted change to `apps/mobile/src/features/voice-input/lazyVoiceRecorder.test.ts` appeared at the final status check; it was left untouched and is outside this commit-pinned matrix. No device was leased and no service started. The lead's running build was left alone. Tests were inspected as source where relevant; none were executed and no CI success is claimed for this local head.

For each delivered artifact record row IDs, revision, real vs H0/H1/H2, changed dependencies, device/viewport/theme, input data, and video timestamps. Use one normal real launch first and last. Keep before/after labels visible outside the product image, and do not hide keyboard or substitute a browser rendering for React Native. Supported-feature videos must retain the harness label even if the recorder and OS prompt are real. Reusing an SDK failure obtained after forcing availability is H0 evidence, not proof of the normal unsupported app flow.

Not a separate fixture: unavailable due to native module missing, API<31, unavailable locale, both modes unavailable, or capability-probe rejection all collapse to A01/C01/C04 pixels. AVAILABLE/DOWNLOADABLE/DOWNLOADING and Advanced/Basic collapse to the same supported idle/preparing UI; real model semantics remain U01. Empty preferences normalize to three/four defaults. Saved input disconnected vs connected has no visual badge. Error retry vs null action uses the same mic glyph; only settings action changes the action/accessible label. Preferences storage failure has no new error component. Null and empty error do not leave padding; ordinary idle controls cover their absence. A provider-owned subagent detail without composer, hidden composer slot, or scheduled editor with no environment has no dictation control to capture; a normal visible editor is required for affected rows.

FRICTION: none. The known pool/SDK/external-device limitations are planned constraints, not attempted operations that failed. No approval or infrastructure retry was needed.

VERDICT: INSUFFICIENT <all affected rows: captures not yet taken>
