# Proof audit, pass 1 — capture specification

[PR #32682](https://git.cafe/pingdotgg/t3code/pull/32682), also [GitHub #32682](https://github.com/pingdotgg/t3code/pull/32682).

Reviewed checkout: `/home/user/wt/pr-32682`, branch requested as `fix/issue-32653`, pinned head [921a70ee4f](https://git.cafe/pingdotgg/t3code/commit/921a70ee4f8b59a860ec654b0a3b2899e7df8039). Base is its merged main parent [41443e5706](https://git.cafe/pingdotgg/t3code/commit/41443e5706c4605bc3efc17bcf431970488a146d), also the local `origin/main` and merge base when inspected. Local `main` is stale; comparing against that ref incorrectly includes 138 files. The actual topic diff has 17 files. No checkout changes, fetches, comments, pushes, UI sessions, or servers were made.

Live `cafe pr view` reported [c60d582ba9](https://git.cafe/pingdotgg/t3code/commit/c60d582ba9737cc5eff962e986cab86f6a082084) as its head. This report deliberately follows the requested pinned revision. Capture captions must name the revision actually running; do not silently relabel live-PR or earlier captures as this merge head.

## Findings first

1. **Proof gap: no captures exist.** The [earlier trimmed matrix](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/bb4567fcb896cba8/pr-32682-fixture-matrix.md) covers the core Cite and Escape paths but does not establish the complete affected surface. Its mobile row incorrectly says there is no visible behavioral change: failed-start Retry and edit-then-restore change whether a transcript appears. The Retry action in the off-screen global pill is a distinct caller from starting again in a focused composer. Capture M01–M05.
2. **Confirmed placement limitation, not transcript loss:** the broad promise “inserts where recording started” is not true for two disjoint edits around the caret. With `hello world`, caret 5, changed to `AA hello world ZZ`, actual head output is `AA hello world ZZ new text` (cursor 26), rather than insertion between `hello` and `world`. This is the conservative fallback at [controller.ts](https://github.com/pingdotgg/t3code/blob/921a70ee4f8b59a860ec654b0a3b2899e7df8039/packages/client-runtime/src/voice-input/controller.ts#L150). The current PR body already acknowledges this limit. W09 must expose it; a capture cannot certify the stronger claim. If exact placement for disjoint edits is an acceptance requirement, this remains a correctness blocker regardless of capture completeness.
3. **Missing paths in the earlier matrix:** selection replacement versus insertion; repeated selected text; leading-space citations; attachment references; draft/queued/question owners; side-chat focus; all plain-field callers; the desktop guest annotation bridge. The last is desktop-specific, despite no Electron file appearing in this topic diff. A shared web composer capture cannot prove its state/event bridge.
4. **Capture recipes need explicit seams and limits.** A microphone WAV plus a transcription stub is useful integration evidence, but must be labelled as synthetic and does not prove recognition or microphone hardware. `{state:"ready"}` alone is not a valid speech status response. Set both streaming flags false and return the complete status below. No built-in production “set dictated text” button or ready-made dev voice driver was found. Existing mocks are test-local, not an RPC available to the running app.
5. **Do not turn read-only editors into a misleading typing demo.** Busy editors deliberately freeze normal typing. Paste/mention/completion/selection-interior edits that cannot be reached through a real gesture need the controlled state seam below and a harness label. A DOM-only value change without React/store state is not evidence of a changed draft. A fresh Cite popover and editing an already saved citation have different cancel behavior; the composer recording surviving Escape is the invariant, not unconditional popover dismissal or unconditional citation removal.

6. **P2 correctness blocker at the pinned head: an edited emoji can be split into lone UTF-16 surrogates.** Pinned input `🔔`, starting caret 0, changed during recording to `🤔`, transcript `hello`, produces JSON text `"\ud83ehello\udd14"` instead of `🤔hello`. Both emojis share their low surrogate; suffix matching places the caret inside the new emoji. This is a regression from refusing changed drafts, and is distinct from selecting around an unchanged emoji (W17). Verified with the exact resolver extracted from the pinned commit, not with the later modified file. Add W21; align shared-prefix/suffix boundaries to Unicode code points and add the failing regression assertion before claiming proof complete.
7. **Additional Escape ownership branch: two visible editors sharing one dialog.** At the pinned head `inOtherShortcutTarget` returns false as soon as the recording field's dialog contains the key target; it never checks whether the key is actually inside another visible registered editor in that same dialog. The window handler consequently cancels the recording. E09 is a labelled component-harness row unless the capturer can seed a real shared-dialog host. This is source-derived, not a claimed real-client reproduction; distinguish it from the hidden-sibling E03/E04 guards.

No other implementation defect is asserted by this proof pass. This is not a full merge-readiness review. No provider adapter or wire contract changes: coding-provider choice is not a capture axis for this client-side voice change. Local, hosted-web, remote/relay and tunnel connections use the same prepared-connection speech calls; connection/consent guards are listed in U01/U10 rather than multiplying each selection fixture across transports.

## Render-path inventory derived from the pinned code

The scope below enumerates voice-relevant branches of each changed component/hook and its callers. Unrelated, unchanged chat/PR/settings content is not multiplied into a screenshot Cartesian product.

| Changed layer / component | Every runtime caller and render path | Branches / data shaping voice layout or ownership | Matrix coverage |
|---|---|---|---|
| Shared `VoiceInputController` / `resolveTranscriptCommit` | Web `useEnvironmentSpeechInput`; native `VoiceInputSession` | owner same/missing/different; empty/whitespace/nonempty transcript; caret/range; text unchanged/changed/restored; edit before/at/inside/after range; early/late alignments agree/disagree; same/repeated selected text; UTF-16 offsets; Latin/auto/spaceless language boundaries; preparing/recording/transcribing/post-processing/error/idle; buffered/streaming/native-live result | W01–W21, F01–F08, E06–E08, M01–M05, C01–C05; unchanged modes in U03 |
| Web `useEnvironmentSpeechInput` | `ChatComposer`, `useTextFieldSpeech`, `VoiceSettingsPanel.TranscriptionTest`, `PreviewAnnotationSpeech` | destination grant none/off/environment/cloud; connection/capability/operate permission; setup versus ready; cleanup enabled/disabled/skipped/failed; missing-model/loading/download/error; owner/disabled change cancels; pending Send/Save submission waits for commit; live preview present/absent; status error retry; retained recording retry | Common driver; W rows; F rows; E06–E08; U01–U04 |
| Web `ChatComposer` | One JSX call in `ChatView`; main chat layout and `SideChatPane`'s background `ChatView`; server thread and `/draft/$draftId`; queued edit; pending custom answer | draft owner versus question owner; literal versus token-expanded answer; background `targetRef` versus untargeted main composer; rich text on/off; empty/short/multiline/long prompt; zero/one/multiple chips; image/file attachment rows; collapsed/expanded mobile-web footer; live preview; phase pill; disabled if cannot operate/offline/project required/approval/responding/no custom answers; Send while active | W01–W21, E01–E09; U01–U04 |
| Web `useDictationShortcut` | `ChatComposer`; `useTextFieldSpeech`; `PreviewAnnotationSpeech` | busy owner wins, then focused field, then untargeted composer; target inside own field/dialog, other shown field/dialog, hidden/no rects field, ordinary field/page; phase/pressed chord; IME/default-prevented/command palette guard; Auto/Hold/Toggle; keyup/window blur; guest-forwarded keyboard target | E01–E09; F02–F08; W15; U02/U03 |
| Web `useTextFieldSpeech` | Direct: `AssistantCitationCommentEditor`, `PullRequestCommentForm`, `PullRequestReviewForm`. Through render-prop `SpeechTextField`: `DiffCommentAnnotation`, `ReviewThreadCard` reply in `PullRequestReviewAnnotation.tsx` | fixed owner key; controlled value versus live textarea read; focus/caret restored after commit; disabled; preview line; idle mic versus active full pill versus error/retry; setup suppressed on first use | F01–F07; E01–E05; U01–U03 |
| Citation comment | `AssistantCitationChip` only, rendered by the Tiptap citation node | fresh Cite versus saved chip; source-anchored versus chip-anchored popup; empty/saved/multiline comment; 8,000 maximum versus 8,001 error/Shorten comment; optional Save-and-send; idle controls versus busy full-width wrapped controls; voice setup retains editor; Escape own/other recording; Save/Enter/mod+Enter/Shift+Enter/Cancel | W01/W02; F01; E01/E02; U04 |
| Diff/file annotation | `DiffCommentAnnotation` at `AnnotatableCodeView` in `DiffPanel`; `FilePreviewPanel`; `PullRequestCodeTab`; `guide/GuideExcerpt` | comment display has no hook; draft uncontrolled local text versus controlled `onTextChange`; optional environment (no environment means no voice); line/range label; pending; empty/nonempty; secondary action; completion scope; comment text direction and wrapping | F02/F03/F04; U05 |
| PR review conversation reply | `ReviewThreadCard` only via `PullRequestCodeTab` | collapsed/expanded; unresolved/resolved default; canReply/noReply; reply closed/open; pending; empty/long reply; comment count, pagination/loading and editable existing comment are surrounding unchanged content; existing-comment markdown editor does **not** use this voice hook | F07; U06 |
| PR composer comment/review | Both through `PullRequestComposer` in `PullRequestDetailPanel` | keepMounted popup, inactive form hidden; capabilities may mount one/both/neither; comment/review mode; pending-comment count 0/1/many; empty/long summary; offered verdict count one/many; approve/comment/request changes; summary-required host; pending locks; comment close/reopen action; error toast retains draft | F05/F06; E03/E04; U07 |
| `PreviewAnnotationSpeech` | `PreviewView` when pickActive + runtime tab + annotation voice config | native desktop preview bridge only; owner contains thread/tab/session; guest text/selection sync; stale tab/session ignored; gesture start/stop/cancel/settings/skip/key/blur; empty/long draft; expanded annotation controls; active textarea sr-only; preview and error; IPC publish failure cancels/toasts | F08; U08 |
| `VoiceSettingsPanel.TranscriptionTest` | `/settings/voice` -> `VoiceSettingsPanel` -> `SpeechModelBrowser` -> selected model `TranscriptionTest` | card hidden by search; disconnected/unsupported; idle Test model; show transcript false/true; preview; null/empty/long transcript; Close test cancels; cleanup explicitly false; environment/project owner changes | U04 (unchanged control; no external editable draft here) |
| Native `useVoiceInputController` | `ThreadComposer` at `ThreadDetailScreen`; `SettingsScheduledTaskEditorScreen` via New and Edit routes | owner null versus set; focused versus offscreen session; session-owned busy state versus another owner's idle local state; freezes editor; mounted/current owner selection update; disabled; latest target captured per fresh Start | M01–M05; C01/C02 |
| Native `ThreadComposer` | One JSX call in `ThreadDetailScreen` | normal composer versus queued-edit draft key; empty/short versus expanded multiline; compact attachment count limit 1 below width 500, 3 above; attachment strip/overflow count; regular/large controls; keyboard/open toolbar; error notice; active dictation toolbar; send/stop-agent/resume control; local versus environment transcriber | M01–M03; C01/C02; U09 |
| Native scheduled prompt | `SettingsScheduledTaskNewRouteScreen` and `SettingsScheduledTaskEditRouteScreen` -> shared editor -> `ScheduledTaskPromptField` | null editor/owner; new versus edit title label; environment appended to owner key; selection defaults prompt end; saving locks; active prompt readonly and toolbar; error notice; discard clears editor/cancels; environment replacement makes old target unreadable | M04/M05; C01/C02 |
| Native `VoiceInputSession` global UI | `useGlobalVoiceInput` returns single registry session; focused composer/prompt and `GlobalVoiceInputControl` render it | startText captured per attempt, exact-text equality guard; retry same target with original selection; global pill hidden for focused owner; collapsed/expanded offscreen pill; long label truncation; iOS FullWindowOverlay versus Android absolute pill/bottom FAB clearance; error Retry/settings action; timer/waveform | M01–M05; C01/C02; U09 |

Source anchors: [shared resolver](https://github.com/pingdotgg/t3code/blob/921a70ee4f8b59a860ec654b0a3b2899e7df8039/packages/client-runtime/src/voice-input/controller.ts#L110), [web speech hook](https://github.com/pingdotgg/t3code/blob/921a70ee4f8b59a860ec654b0a3b2899e7df8039/apps/web/src/speech/useEnvironmentSpeechInput.ts#L143), [Escape ownership](https://github.com/pingdotgg/t3code/blob/921a70ee4f8b59a860ec654b0a3b2899e7df8039/apps/web/src/speech/useDictationShortcut.ts#L63), [plain-field adapter](https://github.com/pingdotgg/t3code/blob/921a70ee4f8b59a860ec654b0a3b2899e7df8039/apps/web/src/speech/useTextFieldSpeech.tsx#L15), [native session](https://github.com/pingdotgg/t3code/blob/921a70ee4f8b59a860ec654b0a3b2899e7df8039/apps/mobile/src/features/voice-input/voiceInputSession.ts#L86).

## Profiles, capture rules, and seeding

- **D** = real web client, CSS viewport 1440×1000, dark. **N** = real web client, 390×844, light, mobile web footer. These cover the existing responsive boundary without repeating every identical logic fixture in both themes.
- **E** = actual Electron app, 1440×1000 content viewport, dark, with its native browser guest available. F08 additionally exposes the guest at approximately 390px width within the panel, light scheme, in the same labelled sequence. Electron composer behavior is sampled there too; all web composer cases need not be duplicated in Electron.
- **I** = native iOS 390×844 logical viewport, dark. **A** = native Android 412×915 logical viewport, light. Record actual device metrics if a leased device differs; do not label a browser viewport as native. M01–M03 use I; M04/M05 use A, collectively covering both native surfaces and both prompt hosts. U09 is the optional wider/tablet visual control.
- **B+A** = before on base 41443e5706, after on pinned head 921a70ee4f, same fixture and seam. **A-only** = unchanged guard/control, head capture suffices. Before/head clips can group multiple rows if a manifest provides each row's start/end timestamps. Every interaction/time-dependent row is **recording**, not a still.
- **RC-H** = real routed app/components, synthetic microphone + explicitly stubbed ASR (integration harness). **STATE-H** = RC-H plus a labelled controlled draft mutation while the editor is readonly. **COMP-H** = labelled harness rendering production components/hooks and their real state transitions because the app fixture cannot be seeded. **NATIVE-H** = native client using dependency-injected recording/transcription/session fixtures. None is real microphone/ASR evidence.
- For all affected rows, the minimum capture set is exactly the row × profile × B/A entries in the table. No extra theme/platform cross product is required. U rows are enumerated unchanged paths, not additional mandatory captures. C rows are mandatory guard controls; their subcases can share a clip when they produce the same visible branch.
- Create/use isolated dev state, a disposable project, and one stopped Replay (dev) thread with assistant text `The cache key includes the project id.` and a second assistant paragraph long enough to wrap. Use the normal UI or `window.t3dev.replay("showcase")` (documented in the app test skill) to get assistant text, then select a real assistant range and choose Cite. Do not spend a paid agent turn just to seed text. For W18 the existing `usePasteToFocusComposer` captures stray paste and calls the composer handle even while the editor is readonly; paste to timeline/body, not into a pretend editable composer. For W19 a pre-existing menu or its callback is needed because normal typing cannot open a new mention trigger while busy. A real fixture file should contain at least 20 lines and two changed ranges so diff/file/PR annotations can be opened.
- **T** = navigate to `/$environmentId/$threadId`, or `/draft/$draftId` for W11; actual route IDs come from the disposable app. Drafts are client state; there is no server RPC that can edit these local unsent drafts. For controlled web mutations use the Vite module `/src/composerDraftStore.ts`, `useComposerDraftStore.getState().setPrompt(target, nextText)`, with the actual scoped thread target `{environmentId, threadId}` or DraftId. Establish selection in the real editor before starting. For custom-answer/queued-edit fixtures use their own production store/parent callback, never accidentally mutate the ordinary thread prompt. If not exposed by the driver, use COMP-H and label it.
- **P** = open `/pull-requests` or the thread's PR detail panel for a disposable PR fixture; comment/review mode via its composer; Code tab for line comments and reply. Fixture needs permission to comment/review/reply and two inline pending comments, with no real posting performed. Do not post to the audited PR. If a safe PR fixture is unavailable, COMP-H mounts the actual forms with the contract-shaped detail/reference and local store; it cannot claim forge integration. Toggle hidden sibling forms as production does. A real UI Submit would write to a forge: intercept submission in the fixture harness when testing submit-after-dictation.
- **L** = T -> local diff panel -> select a line range. **F** = T -> Files -> open the 20-line source file -> select lines. **B** = Electron T -> Browser -> open a benign local fixture page -> Pick element -> select element -> annotation editor. The guest receives only voice state, not the fake microphone/ASR implementation.
- **M** = native disposable thread, or Settings -> Scheduled tasks -> New/Edit. A fake recorder/transcriber injected into the production `VoiceInputSession` is required for deterministic no-audio captures on this VM. The constructor already accepts these dependencies; `voiceInputSession.test.ts:createSession` is the reference. It is not a built-in app route or global setter. A capturer must wire this into a labelled native harness, or capture a labelled component/session harness and explicitly leave native integration unverified. The native showcase availability override alone does not provide a recorder/transcriber.

### Driving dictation without real microphone audio

Preferred real-client seam: normal isolated app and normal consent choice in Settings -> Voice -> environment destination; cleanup off and language English for the base rows. The environment must advertise `voiceTranscription: true`, be connected, and allow operate scope. Stubbing only HTTP cannot bypass these client gates. Install the seam before the first status probe (or re-enter the route/choose destination after installing it).

Use Chromium's fake microphone stream/WAV when supported by the capture runner. Alternatively, in the application renderer, temporarily replace `navigator.mediaDevices.getUserMedia` with an `AudioContext.createMediaStreamDestination()` stream fed by a quiet oscillator. Resume the context on the normal trusted mic click; retain the context and track handles for cleanup. This exercises the real `createBrowserPcmCapture` and AudioWorklet path without spoken audio. Stub only the chosen environment's voice response URLs in that isolated renderer/proxy, preserving ordinary routing, authentication, capabilities, consent, and other requests. Do not patch unrelated hosts or publish credentials.

`GET /api/voice/status` must return this complete JSON with `Content-Type: application/json`:

```json
{
  "supported": true,
  "state": "ready",
  "modelId": "proof-fixture-model",
  "model": "Proof fixture (synthetic)",
  "size": 1,
  "supportsStreaming": false,
  "supportsBufferedStreaming": false,
  "supportsTranslation": false,
  "languages": ["en"],
  "supportsLanguageDetection": false,
  "acceleration": "cpu",
  "benchmarkTier": "cpu",
  "modelUnloadTimeout": "min_15",
  "gpuDevices": []
}
```

`POST /api/voice/transcribe` returns `{"text":"new text"}`. Hold its response on an explicit release gate to capture `transcribing`; reject once for the retry fixture; return whitespace for no-speech. With both streaming flags false the normal web recording path needs neither a speech WebSocket nor a model-download fixture. For Settings model-test UI, additionally serve a schema-valid model catalog with one selected/installed model. The status/transcription contract lives in [speech.ts](https://github.com/pingdotgg/t3code/blob/921a70ee4f8b59a860ec654b0a3b2899e7df8039/packages/contracts/src/speech.ts#L210), and routes in [environmentHttp.ts](https://github.com/pingdotgg/t3code/blob/921a70ee4f8b59a860ec654b0a3b2899e7df8039/packages/contracts/src/environmentHttp.ts#L838).

For the preparing row, gate `getUserMedia` or transcriber preparation, then release it explicitly. For post-processing, separately opt into cleanup in the disposable client and gate `/api/voice/post-process` returning `{"text":"cleaned text"}`; no paid cleanup model is needed under this labelled stub. For a streaming preview control use the existing `onText` seam with `{committed, tentative}`; that is COMP-H unless the capturer implements the real speech streaming protocol.

A controlled textarea mutation for STATE-H must update the production `onTextChange`/parent state, not only set `textarea.value`. In a browser harness use the component's real state setter. For the actual desktop guest, programmatically set its textarea and dispatch `input`: `PickPreload`'s existing input listener relays a `sync` event containing text/cursor/selectionEnd. Trusted clicks/keyboard are still required for its Start/Finish buttons. Record the mutation visibly as a harness operation. For native STATE-H mutate via `setComposerDraftText(draftKey, text)` or `updateScheduledTaskEditor` on the same mounted session, then stop/retry through production controls. No current public MCP/RPC mutates those local drafts.

Every clip caption: `Real client / synthetic microphone / stubbed transcription`, plus `controlled draft mutation` where used; component-only and native-harness clips must say so. Retain a visible phase indicator, edited text/chips, final text and caret, Escape target, and absence/presence of error. Do not substitute a terminal log or a static mockup for a requested UI recording.

## Fixture matrix

All statuses are **pending** unless explicitly marked code-only/excluded. “Y” means the diff changes behavior on that path, including inherited hook behavior. “G” is a necessary unchanged guard/regression control. No capture is claimed in pass 1.

| ID | Named fixture and necessary data | Seed / action / expected head result | Affected | Minimum capture | Driver / row status |
|---|---|---|---|---|---|
| W01 | Cite at end; `hello world`, caret 11, one short assistant citation | T; start, Cite real assistant selection while busy, finish; keep citation and insert `new text` after it; preserve other draft text | Y | D, B+A, **recording** | RC-H; pending |
| W02 | Narrow, long draft and wrapped citations; 12k chars, three long citations, expanded then collapsed footer | T; repeat W01 at N, with multiline text and citation/comment wrapping; no overflow hiding Finish/error; after finish footer returns to idle layout | Y | N, B+A, **recording** | RC-H; pending |
| W03 | Leading-space citation at middle caret; `hello world`, caret 5 | T; Cite inserts space+token before existing space; finish yields logical `hello [citation] new text world`; moves final caret after transcript, preserves token | Y | D, B+A, **recording** | RC-H; pending |
| W04 | Selection boundary insertion; `hello world`, original range 6–11, current `hello [cite]world` | T with valid citation or labelled literal marker; state-insert at range start; finish `hello [cite]new text` | Y | D, B+A, **recording** | STATE-H; pending; real UI if gesture preserves this selection |
| W05 | Selection interior insertion; range 6–11, current `hello wo[cite]rld` | Same; finish `hello wo[cite]rld new text`; retain entire edited range, replace none of it | Y | D, B+A, **recording** | STATE-H required; pending |
| W06 | Edit before untouched selection; range 6–11, current `> cited line\n\nhello world`; move visible selection elsewhere | T; controlled prepend while busy; finish `> cited line\n\nhello new text`, proving captured rather than current selection | Y | D, B+A, **recording** | STATE-H required; pending |
| W07 | Repeated selected text separated; original range 0–5; current `hello there hello world` | T; controlled insertion before original selection; finish `hello there hello new text world`; neither `hello` disappears | Y | D, B+A, **recording** | STATE-H required; pending |
| W08 | Adjacent repeated selection; range 0–5; current `hello hello world` | T; finish `hello hello new text world`; conservative ambiguity path | Y | D, B+A, **recording** | STATE-H required; pending |
| W09 | Two disjoint edits; `hello world`, caret 5 -> `AA hello world ZZ` | T; controlled prepend and append; actual `AA hello world ZZ new text`, retained all edits but wrong original position; label limitation, not success on exact placement | Y / contradiction | D, B+A, **recording** | STATE-H required; pending; correctness caveat |
| W10 | Attachment-bearing draft; one image, one source file and one inline context reference; middle caret | T; drag/drop real fixture files while recording; ensure at least one operation changes serialized text, finish with attachments/reference and transcript retained; an image-only draft edit is an unchanged-text control | Y for text reference | D, B+A, **recording** | RC-H for drop; STATE-H only if blocked; pending |
| W11 | New unsent draft; empty text, caret 0, chosen project | `/draft/$draftId`; start then insert citation/context; finish retained inserted text and transcript without promoting/sending draft | Y | N, B+A, **recording** | RC-H; pending |
| W12 | Queued-message edit; one queued text message with existing attachment | T -> edit queued message; start, incoming text mutation in that edit's owner, finish updates that draft only; no automatic save/send | Y | D, B+A, **recording** | STATE-H unless safe queue fixture available; COMP-H fallback; pending |
| W13 | Pending question custom answer; token-aware answer, attachments allowed | T with persisted pending user-input request permitting custom text; start, mutate custom-answer draft; finish answer+transcript, ordinary prompt unchanged | Y | D, B+A, **recording** | COMP-H if no app request fixture; pending |
| W14 | Pending question literal answer; `initialAnswer` present, token-looking text | T/request fixture; literal answer containing `@name`/markdown; mutate while busy, finish literal text preserved, no token-coordinate corruption | Y | N, B+A, **recording** | COMP-H unless request fixture available; pending |
| W15 | Side chat and parent composers mounted; distinct drafts and one citation | T -> open side chat; start there, update its draft, Escape in other shown composer; owner stays busy; finish updates side chat only | Y | D, B+A, **recording** | RC-H plus STATE-H if mutation needed; pending |
| W16 | Spaceless language with changed draft; `修正キャッシュ`, caret 7; prepend `引用 `; transcript `テストも` | T; Voice language Japanese, status languages `["ja"]`; finish `引用 修正キャッシュテストも`, no introduced English separator | Y (mapped boundaries) | D, B+A, **recording** | STATE-H; pending |
| W17 | Plain composer, multiline Unicode/token selection; rich text off; `Fix 🧪 then $review please` | T; original selection `$review`, prepend `Note: ` while recording, move visible caret; finish replaces original token range and leaves emoji/new prefix intact | Y | D, B+A, **recording** | STATE-H; pending |
| W18 | Paste arriving through paste-to-focus; middle starting caret, short pasted text appended at end | T; start, focus timeline/body, paste ` pasted suffix` through real clipboard event; finish inserts at original middle caret and retains suffix; editor is not falsely shown as editable | Y | D, B+A, **recording** | RC-H via existing paste-to-focus listener; pending |
| W19 | Mention/completion arrives during recording; `@src` trigger before start, one matching source file | T; leave file mention suggestion pending, start dictation, choose existing suggestion if reachable; selected context chip and transcript both survive; no typing into frozen editor | Y | D, B+A, **recording** | RC-H if gesture reachable; otherwise STATE-H/COMP-H using actual selection callback and explicitly label it; pending |
| W20 | Send after edited draft; one short citation and retained ordinary text | T with free Replay (dev); record, Cite, press Send while recording or while transcription response is held; release response; exactly one submitted message contains edit+transcript, error absent; do not use a paid provider | Y | D, B+A, **recording** | RC-H with response gate; pending |
| W21 | Changed emoji sharing a low surrogate; original `🔔`, caret 0 -> current `🤔`; transcript `hello` | T controlled mutation; head splits emoji as `"\ud83ehello\udd14"`; expected intact `🤔hello`. Show result and escaped serialized draft; this head fails | Y / corruption | D, B+A, **recording** | STATE-H required; pending; correctness blocker |
| F01 | Citation-comment is recording owner; long multiline text near 8,000 limit | T -> saved citation editor; start there, controlled prepend so result exceeds limit; finish retains edit+transcript, shows overlength warning/Shorten comment and full pill-to-idle wrap transition; do not save | Y | N, B+A, **recording** | STATE-H required; pending |
| F02 | Local diff draft; controlled text, line range 4–7, one saved comment nearby | L; while composer records, Escape in diff textarea must reach that editor and not composer; separately record diff field and inject prefix, mod+Enter completes dictation then saves the combined text as a local annotation | Y | D, B+A, **recording** | RC-H for Escape, STATE-H for edit; pending |
| F03 | File annotation draft; uncontrolled local text, long line-range label | F; same Escape isolation and prefix-edit/finish sequence; textarea's local state must retain transcript | Y | N, B+A, **recording** | STATE-H or COMP-H for mutation; pending |
| F04 | PR inline draft; local draft text, Add to review + Add to agent visible | P Code -> select lines; Escape while composer records spares composer; start field, mutate, finish; both actions remain available afterward; do not post | Y | D, B+A, **recording** | RC-H + STATE-H; COMP-H if no safe PR; pending |
| F05 | PR comment; long body, open PR with close action permitted | P -> Comment mode; record, controlled prepend (or completion if reachable), finish into body; no comment/close request sent; hidden review sibling present | Y | D, B+A, **recording** | STATE-H / COMP-H; pending |
| F06 | PR review; two pending line comments, long summary, multiple verdicts | P -> Review mode; record, controlled summary-store prepend, finish preserves pending count and edited summary; hidden comment sibling present; no review sent | Y | D, B+A, **recording** | STATE-H / COMP-H; pending |
| F07 | Expanded PR conversation reply; two comments, canReply, long reply | P Code -> Reply; composer busy then Escape in reply spares composer; start reply, state-edit, finish retains edit/transcript in reply only | Y | N, B+A, **recording** | STATE-H / COMP-H; pending |
| F08 | Desktop browser annotation; selected element, compact then expanded editor | B; sample Electron composer Cite first; then record annotation, guest input-sync prefix edit, forwarded chord/Finish, same session receives transcript; Attach during recording must wait for that transcript before attaching locally; switch tab/session and ensure stale result not delivered; show guest light/narrow variant | Y | E, B+A, **recording** | RC-H + STATE-H; COMP-H bridge fallback must be labelled and leaves Electron integration open; pending |
| E01 | Busy composer, fresh citation comment textarea focused | T; start composer, Cite, type optional comment in separate textarea, press Escape there, show composer still recording, finish transcript; do not assert citation necessarily removed/kept by cancellation | Y | D, B+A, **recording** | RC-H; pending |
| E02 | Busy composer, button in another dictation dialog focused | T; saved citation popup with multiline comment; Tab to Cancel or Save, press Escape; composer stays recording, finish; verify focus was button, not textarea | Y | N, B+A, **recording** | RC-H; pending |
| E03 | Recording PR review, inactive comment hidden in same popup | P; start visible review; Tab to a non-submit button in same dialog, Escape cancels recording and does not get exempted by hidden comment; popup remains for this first Escape | G, changed guard | D, A-only, **recording** | RC-H / COMP-H; pending |
| E04 | Recording PR comment, inactive review hidden in same popup | P; reciprocal of E03; own-dialog check wins, recording cancelled | G, changed guard | D, A-only, **recording** | RC-H / COMP-H; pending |
| E05 | Escape cancellation outside other registered editor | T; independent takes: own composer, page/body, ordinary search field; Escape cancels each, leaves draft edits intact and no late transcript; include a gated queued-Send cancellation take | G | D, A-only, **recording** | RC-H; pending |
| E06 | Preparing owner, other citation dialog target | T; open citation editor then hold composer recording preparation; focus comment and Escape; do not cancel owner; release preparation, finish normally | Y | D, B+A, **recording** | COMP-H or RC-H with explicit preparation gate; pending |
| E07 | Transcribing owner, other citation dialog target | T; stop composer, hold transcription response; Escape in other editor/button leaves Transcribing; release response, retained edited draft+transcript | Y | D, B+A, **recording** | RC-H with response gate; pending |
| E08 | Post-processing owner, other citation dialog target | T; separate cleanup consent, gated cleanup response; Escape in other dialog leaves Post-processing; release (or Skip) commits transcript to current draft | Y | D, B+A, **recording** | RC-H with cleanup stub; pending |
| E09 | Two visible registered textareas in one dialog; A recording, B idle | COMP-H using real shortcut hooks and two real field refs in same role=dialog; focus B, Escape; pinned code cancels A although B should own the key; verify against desired behavior and label the harness | Intended behavior missing | D, B+A, **recording** | COMP-H required unless real shared-dialog host seeded; pending; source contradiction |
| M01 | Native focused thread Retry after failed start, edited draft | M; preparation fails once on `hello world`, caret 5; edit to `hello long world`; use local mic again; finish `hello new text long world`, error clears | Y | I, B+A, **recording** | NATIVE-H required; pending |
| M02 | Native offscreen global Retry, original owner and fixed selection | M; fail start once, edit same draft while error, navigate elsewhere; expand global error pill, Retry then Finish, return to original; edited baseline accepted, original fixed caret used, other screen untouched | Y | I, B+A, **recording** | NATIVE-H required; pending |
| M03 | Native draft edited then restored while recording | M; `hello world` caret 5, start, controlled `changed` then exact restore (separate state commits), finish `hello new text world`; no stale error | Y | I, B+A, **recording** | NATIVE-H required; pending |
| M04 | Native New scheduled task Retry, long task title | Settings -> Scheduled tasks -> New; title `Nightly cache consistency verification across environments`, prompt `hello world`, preparation fails once; edit prompt, Retry; accepted on current attempt baseline; no task saved | Y | A, B+A, **recording** | NATIVE-H required; pending |
| M05 | Native Edit scheduled task, prompt restored, offscreen controls | Settings -> Scheduled tasks -> existing disposable task -> Edit; start, state-edit/restore prompt, navigate to safe other screen without discarding editor, finish through global pill; restored prompt receives transcript; task unsaved | Y | A, B+A, **recording** | NATIVE-H required; COMP-H if navigation guard prevents retaining editor; pending |
| C01 | Native changed or removed draft still refuses | Thread and scheduled prompt subcases: text differs at finish, then missing target; error `The draft changed...`, no commit; demonstrate unchanged draft/edited draft is not overwritten | G | I thread + A scheduled, A-only, **recording** | NATIVE-H required; pending |
| C02 | Owner lifecycle guard: queued edit discarded or destination replaced | T switch draft owner while busy; native queued edit discard; scheduled editor environment changes; finish old operation cannot insert into new owner | G | D + A, A-only, **recording** | RC-H / NATIVE-H; pending |
| C03 | Empty transcript after draft mutation | T; change same draft while recording, stop with whitespace ASR; retains edit, shows No speech detected / Retry instead of inserting whitespace | G (error branch), new reachable edit | D, A-only, **recording** | STATE-H; pending |
| C04 | Unchanged draft, caret and range control | T; record once at caret in empty draft, then replacing `world` in `hello world`; move visible selection before finish; original selection still determines result; never sends | G | D, A-only, **recording** | RC-H; pending |
| C05 | User docs sentence versus demonstrated behavior | Read `docs/user/voice-input.md` added sentence against W01/M03/C01; edits kept on web, mobile still refuses a differing draft; exact placement exception W09 acknowledged | Text only | No capture | Code-reviewed; do not claim universal exact placement |

## Unchanged branches intentionally collapsed out of mandatory captures

These rows give the other conditional render shapes a home without pretending the diff changes their look. Capture is optional; do not block this proof on multiplying unrelated states. If an affected-row capture unexpectedly enters one of them, record it in the manifest rather than claiming the intended row passed.

| ID | Named branch fixtures / data | Real-app seed or labelled harness | Capture / status |
|---|---|---|---|
| U01 | Voice off/no grant; disconnected destination; missing capability/permission; disabled composer (approval, responding, no custom answer, project required) | Settings -> Voice Off/unconfigured; isolated permission/connection fixture; composer requested states. No destination means main mic goes to settings, other text fields suppress first-run setup | Optional recording D; unchanged/excluded |
| U02 | Setup missing model -> catalog loading/error -> download progress/cancel -> ready; mic permission denied/retry/settings; long transcription error | Existing `VoiceInputSetup` and hook test seams, or real isolated settings | Optional recording N; unchanged/excluded |
| U03 | Streaming preview committed+tentative; retained-audio Retry after ASR error; cleanup Skip/failure fallback; Hold/Auto/Toggle, keyup/blur, IME/defaultPrevented/command palette | Existing hook/controller mocks; streaming COMP-H unless protocol fixture implemented; base buffered path is mandatory above | Optional recording D; unchanged modes/excluded |
| U04 | Settings model test: disconnected unsupported/null transcript/empty transcript/long transcript/live preview/Close; citation editor short/empty/saved/8,001-char invalid and setup retained | `/settings/voice` selected installed model fixture; saved citation popup; near-limit active citation layout covered F01 | Optional recording N; caller checked, unchanged/excluded |
| U05 | Diff annotation posted display with/without delete; no-environment draft; GuideExcerpt draft (no environment, therefore no dictation); pending/empty/secondary allowEmpty branches | L/F/P or actual components with prop fixtures; Guide path is deliberately not a dictation caller in this head | Optional still D for static display; recording for actions; unchanged/excluded |
| U06 | PR conversation collapsed/resolved/no-reply; expanded empty/one/many comments, pagination Loading, existing-comment edit | P safe PR contract fixture. Only expanded reply mounts speech, covered F07 | Optional still/recording N; unchanged/excluded |
| U07 | PR composer comment-only/review-only/no permissions; zero/one/many pending comments; one/many verdicts; required changes summary; closed/reopen or merged/no follow-up; posting/error retention | P safe PR contract fixture, intercept mutations. Both mounted hidden siblings exercised E03/E04; two-comment summary F06 | Optional recording D; unchanged/excluded |
| U08 | Desktop annotation no selected target/voice unavailable; compact/expanded; preview/error; settings navigation/Skip; IPC update failure toast | B or COMP-H fake preview bridge; actual changed draft-sync path is F08 | Optional recording E; unchanged/excluded |
| U09 | Native compact/expanded, empty/long prompt, 0/1/>limit attachments; width below/above 500; regular/large controls; global pill collapsed/expanded long label; iOS overlay/Android FAB clearance; local/environment transcribers, native-live text | M real client with labelled fake dependencies; optional tablet 768×1024 dark. Required M rows already expose focused/error/offscreen control changes | Optional recording I/A/tablet; unchanged/excluded |

| U10 | Draft environment A, transcription destination B; cloud versus environment destination; local/hosted-web/relay/tunnel | Isolated connected environments and per-device consent; status/transcribe seam on chosen B only, no fallback to A; same transcript commit logic; no changed transport contract | Optional recording D; unchanged/excluded |

## Existing evidence and what was actually checked

Read all 17 changed files' diff, followed all hook/component runtime callers listed above, inspected the earlier matrix and current PR body. `git diff --check origin/main...HEAD` passed; worktree was clean at the initial and intermediate checks. The final check observed concurrent uncommitted edits in `apps/web/src/speech/useDictationShortcut.ts`, its browser test, `packages/client-runtime/src/voice-input/controller.ts`, and its test. This auditor made no checkout edits. The audit remains pinned to committed source; later worktree changes are not reviewed or credited to this head. W21 was verified from `git show` of the pinned revision after detecting that drift. Existing focused tests were inspected, not rerun; no claim is made that the PR body's CI/test counts were reproduced by this audit.

Direct read-only imports of the pinned production modules produced:

```text
web resolver: captured hello world, caret 5
 current AA hello world ZZ -> commit AA hello world ZZ new text, caret 26
 current hello [cite] world -> commit hello [cite] new text world, caret 21
 current empty same-owner text -> commit new text, caret 8

native session with fake dependencies (not UI captures):
 failed preparation; retry against hello long world -> idle; hello new text long world
 changed then restored to hello world -> idle; hello new text world
 current edited prompt -> error The draft changed...; no commits
 current null -> error The draft changed...; no commits
```

The [pinned resolver extraction](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c3ca64093927ad58/pinned-resolver.ts) preserves the exact audited mapping/commit code and imports the unchanged range-replacement helper. Its direct import reproduced W21 as `{"kind":"commit","text":"\ud83ehello\udd14","selection":{"start":6,"end":6}}`. This is runtime text evidence, not a UI capture.

Selection-boundary, interior, caret-before-space and two repeated-text shapes already have assertions in [controller.test.ts](https://github.com/pingdotgg/t3code/blob/921a70ee4f8b59a860ec654b0a3b2899e7df8039/packages/client-runtime/src/voice-input/controller.test.ts#L282). Changed-draft controller completion is tested there. Escape tests cover another textarea and its dialog button, plus ordinary input/own PR dialog with hidden sibling at [useDictationShortcut.browser.test.tsx](https://github.com/pingdotgg/t3code/blob/921a70ee4f8b59a860ec654b0a3b2899e7df8039/apps/web/src/speech/useDictationShortcut.browser.test.tsx#L100). Native failed-start Retry has an added case at [voiceInputSession.test.ts](https://github.com/pingdotgg/t3code/blob/921a70ee4f8b59a860ec654b0a3b2899e7df8039/apps/mobile/src/features/voice-input/voiceInputSession.test.ts#L249); the old edit-restore refusal test was removed without an affirmative restored-text regression assertion. M03 is therefore particularly useful and the direct probe supports its expected result.

Earlier matrix mapping: R1 -> W01; R2 -> W02; R3 -> E01/E02; R4 -> E05; R5 -> E03/E04; M -> M01–M05 + C01/C02; DOC -> C05. The earlier no-desktop-capture statement is valid for duplicating every shared composer fixture, but not for skipping F08.

Before/head videos, native UI/device integration, Electron bridge integration, actual microphone capture and real ASR are **not verified** here. Pass 2 must inspect the resulting clips, their caption/fixture revision, and the manifest; it cannot infer them from these code probes.

FRICTION:
- tool | Web fetch could not open the earlier Markdown attachment; direct HTTPS curl succeeded | Allow attachment MIME/text fetch in the web reader.
- brief | Live PR metadata named an earlier source head than the requested merge revision; local main was stale | Pin the capture head and merged main SHA in the handoff, as this report does.
- tool | Bun was not at the suggested per-home path; it was at /usr/local/bin/bun | Resolve with command -v before assuming a per-home executable.
- other | The shared checkout acquired four uncommitted voice-file edits after intermediate clean checks | Freeze the audit checkout at the requested revision and use another checkout for concurrent fixes.
- tool | Python urllib received HTTP 403 reading back the upload; curl could retrieve it | Use the working curl client for attachment verification.
- infra | The brief supplies no real microphone audio, captures, or ready-made native voice fixture route | Use labelled synthetic recording/transcription seams and provide a disposable native harness; keep native integration explicitly open until captured.

VERDICT: INSUFFICIENT W01–W21, F01–F08, E01–E09, M01–M05, C01–C04 recordings missing; W09 contradicts universal starting-position claim; W21 corrupts edited emoji; E09 misses another visible editor in the same dialog
