# Proof audit, pass 1 — right-panel tab scrolling

## Findings

1. **Proof is insufficient at this pass.** [#33396](https://git.cafe/pingdotgg/t3code/pull/33396) has no gesture recordings. Its Electron runtime transcript is useful supporting evidence, but cannot show the tab movement, indicator, release, and resulting route over time. Still images of the fixture do not fill this gap.
2. The transcript used older main and fix revisions, not the requested pair. Capture **before** at [73fb49d0b3](https://git.cafe/pingdotgg/t3code/commit/73fb49d0b3a61bff74aebc362fc6c17d6c0d39be) and **after** at [0263c024fb](https://git.cafe/pingdotgg/t3code/commit/0263c024fb575767d8affba5af2aa19c35a2af4c), with the same fixture and gesture parameters. The current PR metadata matches that head and observed base. The local `main` ref has an older merge-base, so do not substitute an unqualified local `main` for the supplied before revision.
3. The newly native branch includes **equal-magnitude diagonals**, not just pure horizontal input. Include A7: the old handler selected deltaY at equality; the new handler returns. Signed opposing deltas distinguish those behaviors. The transcript does not cover this branch.
4. There are two production caller chains and three production presentation paths: chat inline, chat sheet, and pull-request-page inline. A synthetic embedded-component browser test does not demonstrate the desktop shell or either full caller. Include representative caller coverage without multiplying every tab glyph, provider, or connection mode.
5. Linux Electron under Xvfb can demonstrate the Chromium/Electron gesture event sequence using CDP. It cannot establish physical macOS trackpad momentum behavior. The existing driver's `gestureSourceType: "mouse"` and `preventFling: true` must be disclosed; they are not a hardware momentum run.

This pass derived fixtures from source and reviewed existing proof only. It did not launch an app, perform computer use, run tests, or create captures. No new implementation defect was established by this proof audit.

## Source-derived render and interaction inventory

- The production diff changes only [RightPanelTabs.tsx](https://git.cafe/pingdotgg/t3code/blob/0263c024fb575767d8affba5af2aa19c35a2af4c/apps/web/src/components/RightPanelTabs.tsx#L1284); the other changed file is its new [browser test](https://git.cafe/pingdotgg/t3code/blob/0263c024fb575767d8affba5af2aa19c35a2af4c/apps/web/src/components/RightPanelTabs.browser.test.tsx). All nine surface kinds use one ScrollArea viewport and one wheel handler.
- **Every production caller:** [ChatView](https://git.cafe/pingdotgg/t3code/blob/0263c024fb575767d8affba5af2aa19c35a2af4c/apps/web/src/components/ChatView.tsx#L3918) → [ChatRightPanel](https://git.cafe/pingdotgg/t3code/blob/0263c024fb575767d8affba5af2aa19c35a2af4c/apps/web/src/components/chat/ChatRightPanel.tsx#L19) → RightPanelTabs, and [PullRequestsPage](https://git.cafe/pingdotgg/t3code/blob/0263c024fb575767d8affba5af2aa19c35a2af4c/apps/web/src/components/pullRequest/PullRequestsPage.tsx#L1469) → RightPanelTabs. Chat requires a panel presence, active thread ref, and a non-embedded ChatView. It supports server threads and project drafts; an embedded side chat has no nested panel. PR-page rendering requires panel presence, a selected PR surface, and a non-null panel environment; capability absence prevents this path.
- **Layout props and platform:** chat uses inline above 980 CSS px; at or below 980 it uses a right-side dialog sheet unless maximized. Sheet sizing changes again at 760 px: normally min(42vw, 28rem), min-width 20rem; below 760, min(88vw, 24rem), without that minimum. Maximized uses inline/full available width. PR page always uses inline, initially half the window width, with a separate width-storage key. Inline saved/default width, resize clamp (minimum 360 px, maximum 70% of viewport and sibling-space cap), supplied inlineSize, maximization, layoutControls, native window-controls overlay, and collapsed-sidebar inset alter available tab width. Chat inline omits layoutControls inside the tab bar; chat sheet supplies them; PR inline omits them. Inline without controls reserves right padding. Electron inline owns a draggable titlebar with explicit no-drag tab regions and native-control reservations. Web and sheet do not own it. Closed inline content can remain during collapse, becomes inert/pointer-disabled, and omits Add; sheet is keepMounted; absent panels have no usable strip. `sidebar` and `embedded` are accepted shell modes but have no production RightPanelTabs caller here; the added browser test uses embedded.
- **Counts and lengths:** zero surfaces/activeSurfaceId null shows the launcher; null active id can also coexist with nonempty surfaces in a harness. A non-null active id shows children. Zero, one, and several fitting tabs share the no-overflow wheel outcome. Tab titles truncate at max-width 9rem, so long names and tab count affect overflow. Overflow is detected above 1 px, revealing both scroll arrows; at left only right is enabled, in the middle both are enabled, at right only left is enabled. Native scroll and resize update arrow/fade state; active changes or newly acquired overflow scroll the active tab into view.
- **Data shapes and decoration:** file (including attachment) uses basename and a theme-sensitive file icon; files, diff, linked PRs, terminal, preview, device, side-chat, and individual PR each have their own icon/title. Terminal title uses its label map/fallback. Preview title is Browser for missing/Idle session, else title/URL-host/fallback; favicon uses same-origin capture, public fallback, or globe. Audible/muted overlay plus a resolvable runtime id adds a mute button, otherwise it is absent. Pending ids add a dot. Device uses supplied title/target-name/fallback, generic/iOS/Android icon, optional inline rename input, and target-dependent tooltip. Side-chat shell can supply title and running/approval/input dots. PR state comes from linked/detail/shared/seed data or a neutral unknown fallback and changes the state glyph. `environmentId`, sessions, desktopByTabId, runtime id resolver, terminal labels, and PR seeds select those states. Loading and errors are not separate tab-strip layouts: missing status/session falls back, pending is a dot, and pane loading/error content is passed as children. Light/dark affects icons/colors, not the wheel branch.
- **All interactions in this component:** native horizontal scroll; vertical wheel conversion; ctrl+wheel bypass; pixel/line/page delta modes; both signs and diagonal axis dominance including equality and zero; overflow arrow clicks (smooth unless reduced motion); tab activation and automatic reveal; pointer drag reorder (6 px threshold, disabled without callback or while renaming); close button and middle-click; context menu close/others/to-right/all, file copy-path, preview mute, device rename; rename blur/Enter/Escape; preview mute button; title/device tooltips; Add via button or keybinding, menu keyboard actions, browser profile submenus, and touch/default-profile difference; empty launcher keyboard arrows/Enter and letter shortcuts, disabled reasons, and optional side-chat action. Availability flags, browser profiles (empty/one/many), keybinding context, modal/typing guards, open state and optional callbacks select these branches. These interactions are unchanged; only those involving the viewport wheel path need new interaction proof.
- **History integration:** [HistorySwipeNavigation](https://git.cafe/pingdotgg/t3code/blob/0263c024fb575767d8affba5af2aa19c35a2af4c/apps/web/src/components/HistorySwipeNavigation.tsx#L92) is mounted by AppSidebarLayout only in Electron. It distinguishes back/forward availability, horizontal scrollability at gesture start, dialog paths, axis lock, release and momentum-tail recognition. [DesktopWindow](https://git.cafe/pingdotgg/t3code/blob/0263c024fb575767d8affba5af2aa19c35a2af4c/apps/desktop/src/window/DesktopWindow.ts#L694) relays Chromium gesture begin/end. Web still gets the new native scrolling behavior but has no T3 custom history-swipe listener. Native mobile does not render this component. Providers, RPC contracts, and local/relay/tunnel transport do not select this handler; no provider × connection cross-product is needed.

## Seed recipes and capture protocol

**F — overflowing files in the real app:** use an isolated synthetic project with at least eleven real files named `synthetic-module-with-a-long-name-01.ts` through `11.ts` (add more for wide/maximized layouts). Open a project draft (`/draft/<draftId>`) or stopped thread (`/<environmentId>/<threadId>`), use Add → Files, and open each file through the actual file explorer. A driver may automate those clicks. This is a real-app fixture. The equivalent existing client-store method is `useRightPanelStore.getState().openFile(ref, relativePath)`; it is not a public window API or server RPC. Do not invent a `window.t3dev` panel-seeding method. The shipped dev helper exposes replay, not right-panel seeding.

For a deterministic alternative in an isolated renderer profile, seed its existing `t3code:right-panel-state:v2` localStorage envelope (version 14), preserve other keys, add `state.byThreadKey[environmentId + ":" + threadId] = {isOpen:true, activeSurfaceId:"file:<path>", surfaces:[...]}`, and reload. Each file shape is `{id:"file:<path>", kind:"file", relativePath:<path>, revealLine:null, revealRequestId:1}` and paths must exist in that synthetic project. Preserve the app-created valid draft/thread key rather than guessing it. This uses the real persisted client state and real panes; disclose the seed method. Do not seed the live install. Select a stable active tab and wait for automatic reveal before setting the run's initial scrollLeft.

**P — PR page:** `/pull-requests`, with a source-control-capable linked environment and a real project/repository. Click distinct PRs to accumulate enough short-number tabs for overflow (usually more tabs than F); set the panel width using its resize handle. Use actual readable public PR records. The in-memory panel ref is `{environmentId:"pull-requests-panel",threadId:"pull-requests-panel"}` and `openPullRequest(ref,{environmentId, projectId, host, repository, number, url})` is the existing store method. This panel is deliberately excluded from persisted localStorage, so do not promise a reload seed for it. If forge/capability data cannot be supplied, label a fixture using injected PR surfaces/status seeds as **harness**, not a real-app PR-page recording.

**H — history:** establish route A → tested route B through normal app navigation so back is available. For forward cases establish B → C → Back to B so forward is available. Keep current thread/draft identity and a route/readable view marker visible. Confirm history availability before each run; reload/recreate the same history for each case. A no-navigation run without available history proves nothing about the reported failure.

**Gesture driver:** aim at a real tab title/icon inside `[data-right-panel-tab-list] [data-slot="scroll-area-viewport"]`, not the titlebar drag region, Add, arrows, or a popup. In Electron use a single phased CDP `Input.synthesizeScrollGesture` as in the PR description, not a sequence of independent `mouse.wheel` calls or `dispatchEvent` as the core reproduction. Positive CDP xDistance scrolls content left; DOM deltaX sign is opposite. Log actual wheel deltas, preventDefault, begin/end, scrollLeft, indicator visibility, history availability, current route, and final route. Preserve raw logs beside the recording. Report whether fling was disabled. A DOM-dispatched wheel can exercise arithmetic/guards but does not prove Chromium gesture latching or native scrolling; label it harness. For diagonal/equality inputs, verify actual delivered deltas before claiming that subcase; if Chromium cannot produce exact equality, cover that exact boundary in a labelled component harness and retain a real diagonal gesture run.

**Video:** capture before gesture onset through finger-release/gesture-end and settled UI/indicator fade. Show whole-app context plus enough tab detail to see motion, and caption row, revision, synthetic input and starting position. An input/log overlay is helpful but must accompany the live app image. Keep before and after explicitly labelled with identical viewport, theme, data, width and parameters. The matrix describes capture requirements, not observations. Multiple rows may be chapters in one continuous recording; files need not be one-per-row.

## Fixture matrix

`D` = Linux Electron/Xvfb, 1280×800 CSS viewport, dark theme, chat inline, panel width 360 px, F + H. Measure the viewport's actual clientWidth and scrollWidth; do not assume the historic 196/1504 px. Let `max = scrollWidth - clientWidth`. Aim for max >= 800 px. All gesture/time rows require **recording**, including controls. No still is sufficient for those rows.

| Row / named fixture | Data, start and action | Diff affects it? / expected outcome | Seed / surface | Minimum capture (viewport × theme × row) | Type | Pass-1 status |
|---|---|---|---|---|---|---|
| A1 — interior flick reaches left edge | F, active file, back history; scrollLeft 150; one leftward phased gesture ~600 px | Yes: consumed→native. Before may show back indicator and navigate; after reaches 0 and keeps route, including tail/end | F + H, D | D × dark × A1, **before + after** | recording | REQUIRED MISSING; older-revision transcript only |
| A2 — rightmost start crosses whole strip | F; scrollLeft max; leftward gesture > max + 120 | Yes, same cause as A1; initial left arrow enabled/right disabled; after keeps route through 0 | F + H, D | Extra coverage; A1 already covers reaching left edge | recording | NOT CAPTURED; older transcript exists; not minimum |
| A3 — gesture stays inside, both directions | F; start ~0.65max; left ~0.2max, end; fresh right ~0.2max | Yes: native motion must work, not merely suppress navigation; arrows/fades update, no history indicator/navigation | F + H, D | D × dark × A3, **before + after**; both directions in one clip | recording | REQUIRED MISSING; older transcript covers left only |
| A4 — interior flick reaches right edge | F; forward history; start max−150; one rightward gesture ~600 px | Yes, symmetric forward path; after clamps at max and keeps route | F + forward H, D | D × dark × A4, **before + after** | recording | REQUIRED MISSING; no supplied runtime row |
| A5 — swipe starts at an edge | F; start 0/outward left with back history; then fresh max/outward right with forward history | No intended behavior change: indicator + corresponding history navigation remains allowed; distinguish this from A1/A4 | F + H, D, reset between subcases | D × dark × A5, **after**; both edge subcases | recording | REQUIRED MISSING; older transcript covers left only |
| A6 — vertical mouse wheel still translates | F; start mid-strip; pixel deltaY +100,+100,−50, deltaX 0 | Guard retained: scrollLeft +100,+100,−50, no history navigation; at a clamp no extra movement/preventDefault | F + H, D; actual wheel driver, log delivered input | D × dark × A6, **after**; include a clamp attempt | recording | REQUIRED MISSING; older arithmetic transcript only |
| A7 — horizontal-dominant and exact diagonal tie | F; start mid-strip; separate dx/dy subcases (−60,+20), (−40,+40), (+40,−40), with end/reset between gestures | Yes: all return to native; dx chooses horizontal direction. Equality changed from old deltaY translation. Do not claim a tie from an approximate diagonal | F + H, D; real diagonal CDP plus labelled harness if exact tie cannot be delivered | D × dark × A7, **before + after**; disclose harness boundary subcases | recording | REQUIRED MISSING; transcript has no diagonal/tie evidence |
| S1 — narrow chat dialog sheet | F; 390×844, not maximized, overflowing strip; run A1 gesture | Yes: same handler, different shell; native movement visible. Dialog independently suppresses T3 history swipe, so this cannot substitute for A1 | F + H, resize real app below 760 | Electron 390×844 × dark × S1, **after** | recording | REQUIRED MISSING; production sheet caller uncovered |
| S2 — medium-width chat sheet | F; 900×800, not maximized; run A1 | Yes, same handler; uses 42vw/min-width sizing and in-panel controls, no Electron titlebar ownership | F + H, resize between 760 and 980 | Extra responsive sample; S1 covers production sheet handler | recording | NOT CAPTURED; not minimum |
| P1 — PR-page overflow | P; enough PR-number tabs to overflow, back history; start 150; A1 gesture | Yes: same native handler, separate caller/reserved controls; after keeps route and scrolls | P + H, real `/pull-requests`; harness fallback must be labelled | Electron 1280×800 × dark × P1, **after** | recording | REQUIRED MISSING; use real route or explicitly report harness limitation |
| W1 — web tab-strip scrolling | F; same overflowing project; interior horizontal gesture, then vertical mouse wheel | Yes: shared web component; native horizontal and translated vertical motion work. No T3 custom swipe claim in web | F in isolated browser renderer, project draft/thread route | Web 1280×800 × dark × W1, **after** | recording | REQUIRED MISSING; desktop transcript is not web proof |
| R1 — resize/maximize/active reveal | F; start overflowing; widen/maximize until fitting (add tabs if needed), restore; activate previously offscreen tab; use arrows | Handler shared; resize detects overflow, arrows disappear/reappear; reveal and arrow scroll remain functional. Smooth/reduced-motion choose different timing | Real F; resize handle, maximize control, tab/arrow clicks; reduced-motion is an optional second run | Optional 1280×800 × dark × R1 | recording | NOT CAPTURED; unchanged layout/lifecycle controls, not minimum |
| E1 — empty and fitting strips | Zero surfaces + null active id, then one short file, then a few short tabs with scrollWidth <= clientWidth+1; horizontal/vertical input | Changed horizontal early return also runs here; no tab movement, no arrows. In Electron an outward horizontal swipe may navigate with available history; empty launcher/one active pane differ visually | Real F route; Close all, then Add Files/open files; use show-panel after Close all if needed | Optional 1280×800 × dark × E1; three phases | recording | NOT CAPTURED; no-overflow branch, not minimum |
| G1 — ctrl, zero and unit guards | F midpoint; ctrl+wheel, (0,0), vertical-dominant dx20/dy100, line ±1 and page ±1 | ctrl untouched (app zoom may occur); zero no movement; vertical dominant uses dy; line multiplies by16, page by measured clientWidth, clamps at edges | Real wheel for ctrl/pixel; explicit **harness** for artificial deltaMode/zero if input driver cannot deliver them | Optional 1280×800 × dark × G1; tests/logs may cover arithmetic instead | recording if captured | NOT CAPTURED; unchanged conversions/guards, not minimum |
| T1 — theme, mixed surface decoration and pending/fallback | Mixed F plus files/diff/terminal/browser/device/side-chat/PR/linked-PR tabs; short and truncated names; active/inactive; pending dot; audible/muted; unknown/loaded PR; missing/Idle/loaded preview; device rename | No new layout/glyph branch. These affect intrinsic width/decorations; once measured overflow/position matches, wheel behavior is A1/A3. Light file glyphs/colors differ | Real app Add/menu flows and actual resources; deterministic pending/status/audio injection is **labelled harness**, not a seeded real provider/device run | Optional 1280×800 × light × T1; no theme cross-product required for the handler | recording for rename/loading/mute/gesture; still only for static decoration | INVENTORIED, not minimum; do not start paid agents or devices merely for glyph variants |
| N1 — absent/closed/embedded parent | Closed panel; missing active ref; embedded side-chat ChatView; incapable PR environment/no selected PR | No usable strip or inert collapse content; no changed gesture target. Close/reopen transition is unchanged | Real panel toggle/side chat or capability state; harness if invalid prop combination | No additional capture needed | N/A; recording only if transition inspected | OUT OF SCOPE for new proof; mapped render exits |
| H1 — accepted nonproduction modes/prop combinations | `mode=embedded` or `sidebar`, null environment/active id with many terminal tabs, absent onMove/rename/runtime resolver, optional Add capabilities | Same wheel path; browser test uses 16 terminal tabs in embedded mode with a simulated bridge. Not a production desktop surface | **Labelled component harness only** for those otherwise unreachable combinations | Existing browser test supports A1 logic; no extra capture minimum | recording if demonstrated | HARNESS ONLY; does not replace full-app recording |
| M1 — physical macOS momentum | Overflowing real file tabs; physical trackpad flick begins away from edge and coasts into left/right boundary | Target issue surface; needed to claim hardware/macOS momentum verified | Real macOS desktop with a person/physical trackpad; unavailable on this VM | External follow-up; not a Linux synthetic capture requirement | recording | CANNOT VERIFY HERE; retain explicit limitation |

## Minimum and acceptance

Minimum: **nine fixture rows** A1, A3, A4, A5, A6, A7, S1, P1, W1. A1/A3/A4/A7 require matched before/after; A5/A6/S1/P1/W1 require after recordings. One dark theme is sufficient because no theme-specific wheel branch changed. Viewports: Electron 1280×800 for A/P; Electron 390×844 for S1; web 1280×800 for W1. Optional rows enumerate distinct exits, widths and decorations without demanding an unrelated full-product screenshot suite. Combine compatible rows into clearly chaptered recordings.

Proof acceptance requires visibly moving tabs, the indicator/route outcome through release, measured overflow and start position, exact revision and input provenance, plus supporting raw events. A1 must reproduce before; if it does not, label that attempt non-reproducing rather than claiming a before/after fix. A tie harness must be labelled, and a harness PR page does not establish integration with real forge data. A5 edge-origin navigation is intentional behavior, not a failed fix. The macOS hardware limitation remains open even after the Linux minimum passes.

Coverage: exact two-file diff; all production callers; layout and data branches; wheel/history interaction and reverse direction; existing PR description/driver, issue, comments/reviews and head-bound CI state read via cafe. No tests or app runs performed in this pass. Read-only worktree; no edits, comments or pushes. No processes started. Disk had 12 GB available at both checks, above the 5 GB stop threshold.

FRICTION: none

VERDICT: INSUFFICIENT A1, A3, A4, A5, A6, A7, S1, P1, W1 recordings missing
