# No-switch keyboard follow-up for [#33210](https://git.cafe/pingdotgg/t3code/pull/33210)

Both failures are **PRE-EXISTING on main**. Each reproduces in a single thread with no switch, on the merge-base and the PR head.

Before: [4c361199f4](https://github.com/pingdotgg/t3code/commit/4c361199f47f6ea32a2c14835df392640d0f7c63).
After: [a1d7a2f67a](https://github.com/pingdotgg/t3code/commit/a1d7a2f67a6f984d8dbc35196774c7f45535c2d8).

## Boundary and method

Real web app in T3's Browser panel, 1440 × 1000 CSS pixels, DPR 2, Chromium on Linux. Each case starts in a fresh Replay (dev) thread. No thread navigation occurs between Start and the final readback. Synthetic oscillator audio goes through the real recording path; the browser fetch stub holds the speech response and then returns the synthetic transcript `the login bug`. Transcript cleanup is disabled. Every server starts with `T3CODE_DEV_SEED=0`; no live database was read or seeded. The capture-1 stub freezes page Date, so the app's relative ages are artificial.

Actions use preview_act click and press. Runtime readbacks capture active element, editor attributes/text, draft store, page text, and requests at Finish, after click, after key, before release, and after completion. The event logs record trusted keyboard input. The preserved DOM paths are identical throughout each individual case.

## Case (a): click the finishing editor, press x

**PRE-EXISTING on main.** Both revisions insert `x` into the draft while the editor has `contenteditable="false"` and `aria-readonly="true"`. Focus stays on DIV / role=region / aria-label=Message input. Releasing the transcript leaves draft `x`, shows **Draft changed**, and rejects the transcript.

Steps and timestamps below are elapsed seconds from the page clock at recording start (action completion markers, not exact encoded-video seek offsets). Recording UTC starts: before 2026-11-10T08:29:12.925Z; after 2026-11-10T08:31:34.886Z.

| Step | Before: main | After: head |
|---|---:|---:|
| Start no-switch dictation | 0.059s | 0.019s |
| Recording confirmed | 0.703s | 0.446s |
| Finish: speech response held | 3.424s | 2.305s |
| Clicked finishing composer | 4.972s | 3.438s |
| Pressed x | 5.234s | 3.626s |
| Before transcript release | 6.364s | 4.753s |
| Released transcript: the login bug | 6.486s | 4.815s |
| After transcript settled | 7.659s | 5.882s |

Before (main):
[before-x.mp4](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/8b95718a32f0ba6e/before-x.mp4)

After (PR head):
[after-x.mp4](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/b3a4261602623915/after-x.mp4)

## Case (b): click the finishing editor, press Enter

**PRE-EXISTING on main.** Both revisions keep focus on DIV / role=region / aria-label=Message input for Enter. The trusted event is not prevented. No Send is queued: after release, `the login bug` lands in the draft and stays unsent; it does not appear as a submitted user message. The editor then becomes editable and focused. This is independent of a thread switch.

Steps and timestamps use the same elapsed-time convention. Recording UTC starts: before 2026-11-10T08:29:48.408Z; after 2026-11-10T08:30:38.133Z.

| Step | Before: main | After: head |
|---|---:|---:|
| Start no-switch dictation | 0.031s | 0.039s |
| Recording confirmed | 0.567s | 0.510s |
| Finish: speech response held | 2.513s | 2.464s |
| Clicked finishing composer | 3.971s | 3.924s |
| Pressed Enter | 4.114s | 4.221s |
| Before transcript release | 5.209s | 5.330s |
| Released transcript: the login bug | 5.387s | 5.451s |
| After transcript settled | 6.610s | 6.542s |

Before (main):
[before-Enter.mp4](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/87a8bee48471ef6a/before-Enter.mp4)

After (PR head):
[after-Enter.mp4](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/07f18f51fa25fa37/after-Enter.mp4)

## Keyboard runtime transcript

At Finish and after click, both cases have an empty draft and an editor reporting read-only. After click, the active element is the outer Message input region rather than the editor textbox.

| Case | Revision | Trusted key / target | Draft after key | Editor after key | Draft after release | Result |
|---|---|---|---|---|---|---|
| x | main | x / KeyX / Message input region | x | contenteditable=false, aria-readonly=true | x | Draft changed; transcript rejected |
| x | head | x / KeyX / Message input region | x | contenteditable=false, aria-readonly=true | x | Draft changed; transcript rejected |
| Enter | main | Enter / Enter / Message input region | empty | contenteditable=false, aria-readonly=true | the login bug | Transcript lands, remains unsent |
| Enter | head | Enter / Enter / Message input region | empty | contenteditable=false, aria-readonly=true | the login bug | Transcript lands, remains unsent |

The raw Enter arrays contain two observations of the same single press because an earlier document observer and the later window observer both record it. The driver presses Enter once. The x event stops at the window capture handler and is observed only there.

[keyboard-runtime.json](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e43291b69d0b6cf2/keyboard-runtime.json)

[no-switch-runner.js](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/f4dd0420d565b97f/no-switch-runner.js)

[driver.js](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e8118118e1ded568/driver.js)

The final runner adds a snapshot, hover, and four-second hold after completion to ensure the last outcome is visible; the selected head x clip uses that final hold. The other selected clips already showed their completion.

## Cleanup

Both retained server sessions were stopped; ports 7104, 15144, 8258, and 16298 had no listener afterward. Both owned preview tabs were closed. The temporary before2 worktree was removed. The head checkout is still clean at the requested head. No source edits, commits, pushes, or PR mutations were made. Scratch remains in /tmp/lead-33210-r1/capture-2/. Available disk space after capture: 105 GB.

FRICTION:
- tool | preview_open rejected about:blank; preview_evaluate rejected an untrimmed script | Omit URL for a blank tab and trim script text.
- other | First dependency install ran before checkout finished and missed workspace dependencies | Wait for worktree creation to finish before installing.
- runbook | Reused rename helper timed out; a Draft changed state also prevented immediate reuse of Start | Skip renaming and use a fresh Replay thread per case.
- tool | Document-level logging missed x because the app stops propagation at window capture; earlier observer caused duplicate Enter observations | Install only one window capture observer before testing.
- tool | One head x video omitted the final painted state despite a completed DOM readback | Force a final snapshot/hover and hold the result; the replacement clip includes it.

AI-assisted; verified with the tests and tools listed above.

