# Evidence capture matrix — executed 2026-10-22

Scope: issue [#21789](https://git.cafe/pingdotgg/t3code/issues/21789), immutable baseline [8d3b061121](https://git.cafe/pingdotgg/t3code/commit/8d3b061121) against product candidate [cee69f8f29](https://git.cafe/pingdotgg/t3code/commit/cee69f8f29afc0b37e99793de173aab682858d69). Two production files: `apps/web/src/components/ui/sidebar.tsx` and `apps/web/src/components/Sidebar.motion.ts`, 25 additions / 8 deletions. Captured candidate [4d3e3d92c3](https://git.cafe/pingdotgg/t3code/commit/4d3e3d92c31957981711527e22cfa78e146157d0), with identical production source, adds `scripts/seed-sidebar-evidence.py`; the reusable fixture. 120 minute evidence budget. 22 accepted BEFORE/AFTER recording pairs cover all rows below, with limitations stated explicitly in evidence.md. No public forge writes.

The native Electron I10 pair is the authoritative 639 px comparison. The archived web I10 take has contaminated relative timestamps (BEFORE Alpha/Gamma 4m; AFTER 2h) and is excluded from matching-fixture proof. The accepted set is 44 recordings and 40 comparisons; the original archive retains the rejected supplemental take for traceability.

## Change and path inventory

`SidebarProvider` now marks `mobileSheetMounted` true on opening during render; mobile `Sidebar` resets it only on completed close. `SheetPopup keepMounted` retains descendants and their window listeners while closed. `SidebarControl` consumes the flag to hide the external toggle through the closing transition. The second production file suspends row motion and discards cached geometry when the retained list has no client rects, so hidden zero offsets cannot seed the next animation. The mobile mount flag also resets when the narrow Sidebar unmounts across md, including unfinished closing transitions. A hidden retained popup must remain noninteractive; keeping children alive must not leave the backdrop, viewport, focus trap, or hidden controls active.

The only production `<Sidebar>` caller is `AppSidebarLayout`: left, offcanvas, resizable on desktop. Its children are current `ThreadSidebar`, feature-selected `LegacyThreadSidebar`, or `SettingsSidebarNav` on settings routes. Thread children also remain on home, thread/draft, usage and pull-request utility routes; Settings replaces them. Shared header/footer controls, row/search-result/draft/new-thread navigation and utility links can close the mobile sheet. External trigger, keyboard toggle and desktop menu action open/toggle it; Settings find/slash explicitly opens it. Desktop rail toggles when collapsed and resizes when expanded. No current caller uses right/top/bottom, icon, floating, inset or `collapsible=none`; the last branch returns before the changed mobile branch. These are source controls, not reasons to build artificial UI fixtures.

Source anchors: [sidebar primitive](https://git.cafe/pingdotgg/t3code/blob/4d3e3d92c31957981711527e22cfa78e146157d0/apps/web/src/components/ui/sidebar.tsx), [layout and toggle tracking](https://git.cafe/pingdotgg/t3code/blob/4d3e3d92c31957981711527e22cfa78e146157d0/apps/web/src/components/AppSidebarLayout.tsx), [Sheet lifecycle](https://git.cafe/pingdotgg/t3code/blob/4d3e3d92c31957981711527e22cfa78e146157d0/apps/web/src/components/ui/sheet.tsx), [current thread listeners](https://git.cafe/pingdotgg/t3code/blob/4d3e3d92c31957981711527e22cfa78e146157d0/apps/web/src/components/Sidebar.tsx), [legacy listeners](https://git.cafe/pingdotgg/t3code/blob/4d3e3d92c31957981711527e22cfa78e146157d0/apps/web/src/components/LegacySidebar.tsx), [Settings find listener](https://git.cafe/pingdotgg/t3code/blob/4d3e3d92c31957981711527e22cfa78e146157d0/apps/web/src/components/settings/SettingsSidebarNav.tsx).

## Real-app fixture and evidence contract

Use committed `scripts/seed-sidebar-evidence.py` against a stopped, initialized, isolated `.t3` home. It writes explicit synthetic projection columns, refuses nonfixture data or active servers, and seeds Alpha/Beta/Gamma with fixed timestamps and the same synthetic workspace; `--empty` supplies I8. It neither copies private messages nor creates runnable work. Repeat seed, empty seed, and active-server refusal were verified. Both isolated homes were checked for zero runs/requests/tasks/outbox effects. Projection-only fixtures prove client behavior, not event-sourced backend reconstruction.

Each interaction row MUST have a recording showing setup, inputs, visible outcome, close completion and resulting focus/route. A screenshot cannot satisfy an interaction row. Inputs must be identifiable in the recording (key overlay or visible annotation); confirm focus with a read-only active-element check in addition to visible caret/focus ring. One continuous recording may satisfy several explicitly timestamped rows. Capture base and candidate with matching seed, settings, binding profile, viewport and route; use separate isolated state/checkout preparations, not checkout mutation under a running server. Base failure is expected for closed narrow thread shortcuts; base is a comparison, not an acceptance oracle. All rows have paired recordings; consult evidence.md for exact steps/timestamps and qualified controls.

## Capture matrix

A = affected mobile-sheet path; C = unchanged/control path. Widths are CSS viewport pixels; standard height 900 (390 may use 844). L/D = light/dark. Every listed interaction requires recording, including controls. Four baseline visual combinations are mandatory: 1440 L, 1440 D, 390 L, 390 D; capture closed and open sidebar in each recording with readable labels and main content. Do not multiply every logic row by every theme.

| ID | Class / render path | Fixture and width/theme/platform | Interaction sequence | Required observable proof | Evidence / status |
|---|---|---|---|---|---|
| V1 | C desktop current | 3 threads; 1440 L + D; real desktop or web | Open, collapse, reopen via external trigger; navigate Beta while open | Layout, selected row and main route correct; collapsed sidebar does not overlay content; theme parity | Recording, base + candidate; CAPTURED (see evidence.md) |
| V2 | A current mobile | 3 threads; 390 L + D; web with explicit jump bindings or real desktop narrow | Fresh load closed without ever opening; jump 2 then 3 then 1; open to expose resulting selection | Candidate shortcuts work on initial closed mount, correct route/title once per input; sheet stays closed until requested | Recording, base + candidate; CAPTURED (see evidence.md) |
| I1 | A retained current listener | 3 threads; 390 D | Open then close with Escape; jump 2; reopen, click Gamma (auto-close), jump 1; reopen and dismiss by outside backdrop | Listener survives each close route; main content usable and external toggle restored after close completion | Recording, base + candidate; CAPTURED (see evidence.md) |
| I2 | A adjacent navigation | 3 threads; 390 L | While initially closed, Beta → previous → next; from first and last exercise both boundaries; open then adjacent shortcut (auto-close), repeat closed | One navigation per input; ordering and boundary behavior match shared helper/base-open behavior, no duplicate listener after reopen | Recording, base + candidate; CAPTURED (see evidence.md) |
| I3 | A lifecycle/toggle | 390 D current | Trigger open; keyboard toggle closed/open/closed; rapid close→reopen before animation completes, then repeated cycles | External toggle hidden while open/closing and returns after completed close; no stranded hidden toggle, flicker overlay, stale flag or stale close callback hiding an open sheet | Recording, candidate; base comparison for ordinary cycle; CAPTURED (see evidence.md) |
| I4 | A focus and hit testing | 390 L current; composer plain and rich-text input classes | Initial closed Tab/Shift+Tab and type in composer; open, focus sidebar search, Escape; Tab and click/type in main content after close; Mod+B in rich text then plain composer | Hidden sidebar/search/rail never receives tab focus or steals typing; no invisible backdrop/trap; closing returns usable visible focus; rich text bold retains precedence, plain toggle works | Recording + active-element checks, base + candidate; CAPTURED (see evidence.md) |
| I5 | A alternate child tree | Legacy enabled, 3 threads; 390 D | Fresh closed numeric and adjacent navigation; open, row select auto-close; repeat shortcut; toggle and dismiss | Legacy listener retained too; labels/order use legacy visible jump order; no duplicate navigation or hidden focus | Recording, base + candidate; CAPTURED (see evidence.md) |
| I6 | A Settings listener | Settings route; 390 L | Initially closed Mod+F opens/focuses search; type query, keyboard select result; close; slash from noneditable body reopens; click section closes; type slash in editable field | Find works before first open and after close, focus targets visible search; section/result navigation closes; editable slash stays text, no hidden search focus | Recording, base + candidate; CAPTURED (see evidence.md) |
| I7 | A route replacement / other close inputs | 390 D current | Open → Settings utility link → close; return main; open → Usage → return; open → pull-request page if available → return; select draft or create empty local draft from sidebar | Child replacement does not retain old thread listener on Settings; main/utility paths still toggle; draft/row/utility close class restores trigger without overlay/focus leak. No agent submission | Recording, candidate; mark unavailable PR capability explicitly; CAPTURED (see evidence.md) |
| I8 | A empty/home state | Separate empty isolated fixture; 390 L; home/no active thread | Numeric jump and adjacent commands while closed; open/close; use new-thread affordance to reach empty draft without sending | No crash, phantom route or navigation to stale three-row fixture; correct empty UI and usable toggle/focus | Recording, candidate; CAPTURED (see evidence.md) |
| I9 | A responsive remount boundary | 3 threads; D; 390→767→768→1440→390 | Cross md boundary once closed and once mobile sheet open; navigate after each transition; toggle after return | Exactly one live sidebar/listener; no retained mobile overlay on desktop, no stale mobile flag on return; desktop width/state and usable toggle | Recording, candidate; CAPTURED (see evidence.md) |
| I10 | A issue-width representative | 3 threads; 639 L; real Linux Electron | Fresh closed jump; open, close, jump; inspect sheet edge/backdrop and external toggle | Explicit issue-width sample; 639 is same <768 JS branch as 390 but exercises <640 sheet styling, not a separate shortcut logic class | Recording, base + candidate; matched native I10 authoritative; supplemental web take excluded |
| I11 | C desktop shell entry | Real Electron, 1440 D then narrow 390; OS-native modifier | Default Mod+1/2/3 and Mod+Shift+[ / ]; native sidebar menu item if present (absent in this build); collapse via keyboard, reopen via rail at 1440 | Desktop defaults and menu bridge work; collapsed desktop inert/focus transfer remains intact; narrow Electron reproduces actual default numeric shortcut path | Recording, base + candidate; CAPTURED (see evidence.md) |
| I12 | C input arbitration | 3 threads; 390 D current | Open command palette and model picker separately, send jump/adjacent keys; exercise keybinding capture; hold navigation key to repeat; with terminal focused/open test configured resolver context | Retained listener respects existing prevention/palette/picker/repeat rules and configured terminal bindings; no background navigation behind picker. Keybinding capture keeps sidebar toggle from firing | Recording, candidate against base-open behavior; CAPTURED (see evidence.md) |
| I13 | C browser default policy | Web, 390 L; stock bindings then explicit user overrides | Send default Mod+digit, then configured nonconflicting numeric jump command | Current sidebar leaves browser-owned default digits alone; explicit bindings navigate while closed. Do not count browser tab switching as failed app navigation | Recording, base + candidate; CAPTURED (see evidence.md) |

| R1 | A hidden list geometry / row motion | 3 threads,390 D,current | Open to establish geometry, close fully, pin Gamma while hidden, reopen, unpin/reorder visibly; repeat | No row flight from zero on open/reorder; read-only Element.animate trace accompanies recording | BEFORE + AFTER captured |
| R2 | A unfinished close / md unmount cleanup | Real Linux Electron,390 D | Open; pause actual animation clock, native Escape starts200ms close; cross768 while currentTime0; return390; resume; toggle open/close | Closed narrow external toggle is restored after unmount during closing | BEFORE + AFTER captured with controlled animation clock; natural wall-clock cadence unverified |

## Proportional equivalence decisions

- Required width/theme cross-product is V1/V2 only. Other rows use one theme because no changed theme-dependent logic; V1/V2 and I10 cover visual skin differences. 639 is retained for issue provenance and sheet `max-sm` styling; 767/768 is the actual JS mount boundary.
- Numeric 1/2/3 cover first/middle/last mapped targets; add one unmapped jump (e.g. 9) to V2 as a no-op. Keys 4–8 are equivalent unavailable-index inputs with only three rows. Previous/next are distinct directions; check boundaries against `resolveAdjacentThreadId`, not an assumed wrap rule.
- Current and legacy are distinct listener implementations, so neither substitutes for the other. Settings is a distinct child replacement and retained find listener. Empty state is distinct missing-target/no-active-thread input. Search-result, row and draft navigation share close setter semantics; I1/I7 represent each route shape without testing every sidebar command.
- Pointer trigger and keyboard toggle share `toggleSidebar`; direct dismiss/selection calls and completion callbacks need separate coverage. Escape/outside/row close are represented. Rapid reopen specifically probes changed completion tracking. Rail resize is unchanged: one desktop drag smoke may be included in V1's recording, not a separate resize suite.
- Browser and Electron differ materially: current sidebar passes isWeb/isDesktop into shortcut resolution; legacy supplies a different context. Use explicit nonconflicting bindings for browser numeric proof, record actual effective bindings, and do not claim Electron proof from a mocked bridge or synthetic KeyboardEvent. Native OS keyboard input is required for platform-default proof.
- Mac Cmd and Linux/Windows Ctrl are equivalent modifier classes for listener lifetime, but Electron menu/titlebar integration is distinct. One real desktop OS suffices for this small fix; label untested OS classes. A narrow browser is not native iOS/Android: native mobile uses separate React Native UI and does not import this primitive. No backend/provider/contract changes; provider permutations and local/relay/tunnel permutations do not affect this local React mount change. Remote browser can use the same server-origin behavior; no new transport proof required.
- No command-palette sidebar toggle caller was found in the source search; palette is an input-suppression control here, not an invented entry point. No new backend/MCP capability or scheduled-task path is introduced.

## FRICTION

See evidence.md for the execution-specific friction log and exact limitations. Earlier planning-only prohibitions are superseded by the user's explicit evidence-capture authorization. Native mobile does not use this primitive; macOS/Windows and natural sub200ms automation cadence remain unverified. No native sidebar menu item exists in this build. Legacy visible order is Gamma/Beta/Alpha; default current web digits are desktop-gated, so browser proof uses explicit overrides.
