# Keyboard routing proof matrix — pass 1

Read-only planning audit; no behavioral verification or captures performed.

Scope: local `/home/ubuntu/wt/issue-18300`, branch `fix/issue-18300`, [5b8970471f](https://git.cafe/pingdotgg/t3code/commit/5b8970471f417048a6007c7077d07855c9d12fc3) against [51273020db](https://git.cafe/pingdotgg/t3code/commit/51273020db). Existing [#18338](https://git.cafe/pingdotgg/t3code/pulls/18338) is a stale remote reference, not the source of the audited diff. Five changed files: shared keyboard predicate, chat shortcut caller, guide-tab engagement/wrapper, tour listener/marker, and browser tests. No new render styles. This is a fixture/evidence plan, not a merge-readiness verdict.

## Real app and fixture route

Use the lead's already-running real app at `http://localhost:7320`, with task-isolated `/home/ubuntu/wt/issue-18300/.t3`. Do not start another server. Before evidence, the lead should establish which revision that process serves; this audit only verified the on-disk git head.

Read-only SQLite inspection confirmed `pull_request_review_guides` has `(host=git.cafe, repository=pingdotgg/t3code, number=18338)` with three stops named Stop 1, Stop 2, Stop 3. The stored guide head is `af68de7a2a51bf626d630916fdf711383f32fd03`; it is not the audited code head. Do not rewrite it merely to hide a stale banner. A stale banner depends on the host-reported current PR head, not the local checkout head.

Settings: `/settings/source-control` → Review guides, environment-scoped `reviewGuidesEnabled=true` (confirmed in isolated `userdata/settings.json`). Theme: `/settings/appearance` → explicit Light/Dark. No generation or provider run is needed for the seeded three-stop path.

Primary route: open an existing isolated thread at `/<environmentId>/<threadId>`, then its linked PR surface for [#18338](https://git.cafe/pingdotgg/t3code/pulls/18338) → Guide. Use this beside-chat route for the keyboard conflict; `/pull-requests` alone does not prove the chat caller. At 1440 use the inline right panel; at 390 use the real right-panel sheet, with keyboard input to the web client. 390 is narrow web, not a claim about native mobile. Reset to Stop 1 using the tour UI before each sequence; progress persists by environment/PR/guide head.

Future state variants belong in the same isolated real-app persistence/service path. Preserve the three-stop fixture; use separate disposable PR fixtures or lead-managed snapshots for null/zero/one-stop guides. Never change the live developer home, mutate SQLite under a running server, send a review/comment, or invoke an agent merely for evidence. Loading/arrival should use a cold real query with network throttling and an actual persisted guide. A genuine generation-arrival path is an alternative only if separately authorized. Do not substitute a component harness for the real route. If a low-level DOM/event condition cannot occur through real UI, leave its recording unclaimed and use focused automated coverage as a supplement, explicitly labeled; no synthetic app screenshot.

## Matrix

Affected: **D** = directly changed branch/ownership; **W** = unchanged render branch now inside the new wrapper; **C** = caller guard/regression boundary. Every interaction or time-dependent row requires a **recording**, never a still. Rows share recordings; each is not a separate clip. Full viewport/theme cross-product applies to the core ownership sequence, not to every guard.

| Row | Affected path / condition | Real fixture and action | Observable acceptance | Evidence / placement |
|---|---|---|---|---|
| 1 | D: `active && stops>1 && engaged`; initial mount | Open seeded Guide; without first clicking its text, press j, then k | Stop 1→2→1; no j/k added to composer; proves initial engagement and late listener ordering | Recording; C1–C4 |
| 2 | D: pointer engagement; non-focusable prose | Click heading/prose, j,j,j,k,k,k | 1→2→3→3→2→1→1, exactly once per key; composer unchanged; boundaries clamp | Recording; C1–C4 |
| 3 | D: pointer outside guide panel | At 1440 click non-editable chat transcript, then j | Tour stays put; composer gains j and focus; clear draft afterward | Recording; C1, C2; at 390 close sheet first (row 10) |
| 4 | D: focusin outside/inside; editable guard | Focus composer by keyboard and type j/k; return to guide prose; j | Typed characters stay in editor, no stop movement while editing; subsequent guide click restores stepping | Recording; C1, with focus handoff at C3 |
| 5 | D: closest panel boundary | Click PR surface content outside the tour, then j; compare click outside the panel | Content inside `data-right-panel-surface-content` retains guide ownership; outside hands it off. Header chrome outside that element must not be assumed inside | Recording; combine C1 |
| 6 | D/C: non-j/k and modifiers | While guide engaged on prose, type a; re-engage, try Shift+J/K, Ctrl/Meta/Alt chords without destructive bindings | Ordinary a still reaches composer where type-to-focus is allowed; modified keys never step. Other shortcuts retain their configured behavior | Recording; combine C1. At 390 the containing sheet already blocks general type-to-focus |
| 7 | D: floating layer exclusion | Re-engage guide; open a real menu/popover/select above it; type j/k; dismiss and re-engage | Overlay receives/handles its own input; tour and composer unchanged; guide steps again after dismissal | Recording; C1 and C3, including sheet plus second overlay |
| 8 | C: command-palette explicit guard | Open real command palette, type j/k, dismiss, re-engage guide | Palette handles typing; no guide movement or composer insertion | Recording; combine C1 |
| 9 | D: sheet ancestor is allowed, focusin ancestor ignored | At 390 click guide prose that leaves focus on the hosting sheet, then j/k; include opening and settling sheet | Guide still steps; sheet containing guide does not count as a competing overlay; no lost ownership on ancestor focus | Recording; C3, C4 |
| 10 | D/C: inactive/hidden/closed, listener cleanup | Switch Guide→Summary/Code and back; close/reopen PR surface or sheet; press j in chat while hidden | Hidden guide never steps or reserves j; chat gets it when otherwise eligible; reopened guide can be re-engaged without duplicate steps | Recording; C1–C4 (chat handoff after sheet close in C3/C4) |
| 11 | D: engagement tracked before guide exists | Cold real guide query: while spinner is visible click chat; allow 3-stop guide to arrive; j; click guide prose; j | Arrival does not steal ownership: first j enters composer and Stop 1 remains; after prose click second j moves to Stop 2 without another composer j | Recording; C5, 1440 light; no sleep-based or harness substitute claimed |
| 12 | W/D: pending+null; settled null | Real pending query and disposable no-guide fixture; engage panel vs chat and type j | Spinner/empty shell fits panel; no tour key reservation without a guide; later arrival honors last engagement | Recording; pending in C5, empty in C6, 1440 light |
| 13 | W: no-guide running / failure / query error / write permission | Real persisted run/query states; inspect wrapper, enabled/disabled Write/Try again controls without invoking generation | Existing writing/empty/error content and permissions remain usable; no tour marker/listener exists. Same wrapper as row 12 | Recording if exercised; not separate minimum captures: unchanged content, wrapper equivalence covered by rows 11–12 |
| 14 | D: stopCount=0 and stopCount=1 | Disposable real guide fixtures with zero and one stop; engage prose; j/k | Zero: existing no-files message; one: sole stop; neither reserves tour keys. At 1440 printable input can go to chat; sheet retains its existing input-blocking rules | Recording; C6, 1440 light |
| 15 | W: populated guide banner branches | Existing guide with current head mismatch, failed rewrite, running rewrite, or no banner | Wrapper does not clip banner/tour or change scroll; j/k uses the same ownership rule regardless of banner | Recording if exercised; core seed covers whichever actual banner exists. No extra banner permutation minimum |
| 16 | C: editable inside guide; comment/ask callbacks | If available, open a real local draft comment/ask editor, type j/k, cancel; never submit | Editor receives j/k without navigation; closing/re-engaging restores guide keys | Recording; combine C1 when real field available; disabled/unavailable callbacks do not need separate captures |
| 17 | C: terminal/model-picker chat guards | Focus an already-available terminal or open model picker; j/k; return to guide | No stale guide engagement steals their keys; existing input owners continue to work | Recording if exercised; supplementary guard check, no terminal/server should be launched for this audit |
| 18 | D: defaultPrevented, IME/isComposing, legacy keyCode 229 | Use an actual IME session on real client if available; otherwise retain low-level automated coverage gap | No guide navigation from composing/consumed events. Predicate rejects each case; composer behavior remains its existing independent policy | Recording for any real interaction; supplemental only. Already-prevented and legacy 229 may require event-level tests, not invented visual fixtures |

## Minimum capture set

Six planned recordings; zero screenshots required. All remain **not captured**. No before/after style gallery: this change is keyboard routing.

| Clip | Viewport / theme | Minimum content |
|---|---|---|
| C1 | 1440×900 / light | Core ownership sequence rows 1–5 and 10; append modifier/letter, overlay, palette, and available editable-field checks from 6–8/16 in the same take |
| C2 | 1440×900 / dark | Core rows 1–4 and 10; confirm visible stop headings and composer draft |
| C3 | 390×844 / light | Core rows 1–2, narrow sheet ancestor-focus path 9, competing overlay 7, and sheet close/chat/reopen 10 |
| C4 | 390×844 / dark | Same narrow core as C3, including ancestor-focus path and close/reopen handoff; no need to repeat every overlay family |
| C5 | 1440×900 / light | Real pending→arrival race, row 11 plus pending wrapper in 12; show click into chat before completion, arrival, j, re-engagement, j |
| C6 | 1440×900 / light | One fixture-state reel: settled null, zero-stop, one-stop, then three-stop recovery; rows 12/14. Changing fixture between segments must be labeled, not presented as product behavior |

C1–C4 are executable from the confirmed seed. C5 requires a reproducible real cold query long enough for the click; C6 requires additional isolated state variants. Therefore six is the minimum complete planned set, not a claim that all six are immediately ready. If C5 cannot be made observable through the real route, report it unproven; the new browser test is useful corroboration but does not replace the requested recording. Existing diff adds tests only for prose-click j and a mock no-guide→guide late-arrival handoff; it does not itself prove the full matrix.

Rows 13/15 enumerate every affected wrapper render branch without demanding separate evidence for unchanged copy, permissions, or banner styling. Rows 17/18 retain caller/low-level guard boundaries without multiplying the core visual set. Zero/one/multiple stops, active/inactive, engaged/disengaged, editable/non-editable, allowed sheet/competing layer, initial mount/late arrival, and both directions are distinct and retained.

## Surface and scope decisions

Web inline and narrow web sheet apply. Desktop reuses the web code; a separate Electron capture is not part of this proportional routing proof. Native mobile has separate components and is outside this diff. Providers, RPC contracts, server effects, transport/remote origins, MCP tools, and scheduled tasks are unchanged; no provider or connection-mode matrix is justified. Settings only gates the Guide entry; command palette is an input-owner boundary, not a new entry point. Existing guide buttons/rail are reset controls, not redesigned behavior. No new render styles means no typography, color, excerpt-content, note, attention, or rail-layout permutation gallery.

## FRICTION

| Category | What | Prevention |
|---|---|---|
| Revision provenance | Remote PR is stale; linked metadata names a different branch, and stored guide SHA differs from local code head. Running process revision was not verified. | Audit/capture local 5b8970471f against 51273020db; record served revision and fixture guide SHA separately; never infer code version from PR/guide metadata. |
| Fixture readiness | Confirmed three-stop seed cannot alone prove null/zero/one or an observable pending→arrival handoff. | Lead supplies isolated real state variants and cold-query timing before capture; label remaining gaps rather than introducing a default harness. |

No code edits, pushes, PR comments, captures, tests, server starts, or browser starts. Only artifact creation outside the worktree, the requested ghdrop upload, read-only source/state inspection, and required T3 PR registration were performed.
