# Issue 13706 — read-only proof audit, pass 1

Scope: [#13706](https://git.cafe/pingdotgg/t3code/issues/13706), branch `fix/issue-13706`, complete diff [b5a226e49c](https://git.cafe/pingdotgg/t3code/commit/b5a226e49c) → [604f48b99c](https://git.cafe/pingdotgg/t3code/commit/604f48b99c). No PR. No captures, servers, browser sessions, repository edits, pushes, or comments performed by this audit.

## Evidence boundary and conclusion

The lead reports actual-app full long fixture plus real Claude short and long replies, including a busy main thread: **no jump**. These are negative reproductions, not a demonstrated before/after fix in the actual product. The reporter's standalone **actual LegendList dependency** reproduction intermittently lands at zero or in the middle: 150 × 900px rows, estimate 90px, initialScrollAtEnd, MVCP data+size, start offset 5000, animated scrollToEnd, remove final row after 150ms. Treat only this precise timing race as an isolated harness until it can be induced through product data updates.

Lead reports focused red tests collapse actual total size to 90 instead of 134100, then 14 passing tests after the fix. Not rerun here. Source inspection confirms the new assertion checks total size only, not final scroll position, absence of transient jumps, or native animation behavior. New test covers react.js/mjs and react-native.js/mjs; react-native.web.js/mjs receive the same guard but lack direct cases in this test.

Existing three before recordings are linked in `/tmp/13706-evidence-links.md`; filenames alone do not establish scenario/viewport/theme/commit mapping. They were not viewed or recaptured in this pass. Lead owns before web port7157 and after7050. No new server is needed.

## Why this collapses

The only behavioral change is `targetId !== null` before reading the in-flight target's size in prepareMVCP, duplicated across six CJS/ESM native/web/React bundles. Lockfile updates select that patch; other patch hunks predate this diff. getId returns null for an invalid index. Without the guard, getItemSize can measure/cache an invalid target and corrupt total size. A surviving target must retain its existing size correction.

The branching dimensions are: MVCP data/size enabled vs disabled; in-flight index valid vs null; viewPosition positive vs zero/absent; target size changed vs unchanged; data shrink vs size-only update; no target/ordinary anchor; pending end scroll before target exists. Content, provider, platform wrapper, picker category, and theme do not create additional guard branches. Width changes wrapping; short vs overflowing data changes end geometry. Therefore cover these branches once at dependency level, then representative real feed and nested-list callers. Do not multiply every provider, every tool renderer, every picker, or every connection mode by the full grid.

## Capture contract

Every interaction/time row below requires **RECORDING**, including unaffected controls; stills are insufficient. `G` = paired 1440×900 and 390×900 CSS viewports, each light and dark (four pairs). Before/after must use identical fixture, row IDs, viewport, scroll origin, persisted UI state, frozen wall-clock timestamp, DPR, loaded fonts and font size. Record exact browser/OS/version, DPR, font-family/font asset/version, reduced-motion setting, zoom, fixture/script commit SHA and content hash, application state snapshot SHA and patch SHA for each side. Freeze display timestamps while allowing animation/monotonic clocks to advance identically. End recordings after settled position and at least one subsequent layout cycle; log content height, offset and end-distance per frame. A recording must expose transient zero/middle landings, not only its last frame.

For each affected pair provide aligned before/after recordings, aligned still frames with both zero-amplification and amplified difference views, numeric changed-pixel count/max channel delta, and an explicitly stated pixel threshold. Default unchanged still expectation: zero pixels beyond threshold 0 after timestamps/caret are controlled; any nonzero result needs attribution. Scroll correctness is a separate metric: total 134100 after removal in the exact fixture; settled end distance <=1 CSS px (declare rounding tolerance), no unrelated transient reset. Pixel diffs alone cannot prove this. Repeated race runs must state run count, timing trace, failure count and whether the removal occurred while the scroll target was active.

## Smallest scenario matrix

Status `PLAN` means evidence still required or not mapped; it does not claim execution. Platform variants below are separate evidence obligations, not a browser proof of devices.

| ID | Affected? / branch | Caller / fixture | Interaction and expected observation | Width/theme | Proof / status |
|---|---|---|---|---|---|
| A1 | Affected: null target, positive viewPosition, data MVCP | Actual dependency isolated race, exact 150×900 rows, estimate90, MVCP data+size | From offset5000 animated end; remove last after150ms; repeat to expose intermittent before; after total134100 and settle at new end without zero/middle reset | G | RECORDING; PLAN, before links need mapping |
| A2 | Affected: null target, size MVCP | Same dependency fixture; invalidate target then trigger size-layout update with data branch disabled, size enabled | Execute size-only prepareMVCP/layout while stale target exists; total remains coherent, no fabricated null-row size. If lifecycle cannot expose this state, use focused branch test and label not visually reachable | 1440 light; same branch geometry as A1 | RECORDING if reachable; PLAN; test-only reachability must be stated |
| C1 | Unaffected control: valid target, size changed | Same dependency fixture; retain final key, resize final row during positive-viewPosition scroll | Size correction still follows actual row size; correct end, no regression from guard | 1440 light | RECORDING; PLAN |
| C2 | Unaffected controls: MVCP disabled; viewPosition0/absent; no in-flight target; pending end before target | Same dependency fixture, reset between controls; explicit data-only and size-only settings | Shrink with each gate disabled; ordinary anchor stable; pending end dispatch still resolves. Log actual selected branch, do not assume props imply it | 1440 light | RECORDING; PLAN; four labeled phases can share one clip |
| P1 | Potentially affected product integration: shrinking timeline rows during end scroll | Real ChatView→MessagesTimeline full long seeded thread | Scroll history→jump to latest while last activity/work/assistant rows disappear or coalesce; capture data keys and active target. Verify same collapse if reachable; otherwise report negative reproduction | G | RECORDING; lead reports no jump, not independently verified/mapped |
| P2 | Product negative/control: short vs long real Claude replies; idle/busy JS | Same real thread replay snapshot, one short reply and one long streaming reply; busy-main-thread duration fixed and logged | Send→stream→finish, scroll to latest during finish; record final row replacement and long-task timing; no jump. Use recorded real payload replay for deterministic pair; distinguish live-provider smoke | G; four phases in each clip | RECORDING; lead reports no jump, not independently verified/mapped |
| P3 | Unaffected layout branches: empty/short, remembered middle, find/citation positioning, reduced-motion/nonanimated | MessagesTimeline guard-off and viewPosition0 paths | Empty→short thread; restore middle; find/cite navigation; jump-to-latest reduced-motion. Stable anchor and correct landing; no need to repeat provider/tool variants | 390 dark (short/reflow), 1440 light (restore/find/cite) | RECORDING; PLAN |
| P4 | Potentially affected nested end-follow | TimelineWorkGroupRows; native thread-work-log counterpart | Expand long tool group, append/replace/remove last entry during follow, collapse/reopen with remembered position; no jump and correct nested extent | 390 dark, 1440 light | RECORDING; PLAN; native counterpart UNVERIFIED |
| N1 | Potentially affected native feed; distinct scrolling/inset implementation | ThreadFeed→KeyboardAwareLegendList→native core | Same A1 branch if inducible and P1/P2 transcript; keyboard open/close while short/long feed follows, history scroll and resume; MVCP disabled while following must be logged | Native narrow light/dark; 390 browser is not native | RECORDING; iOS/Android UNVERIFIED here |
| D1 | Same React guard, platform shell distinct | Electron ChatView / nested work list | Replay P1/P2; verify actual Electron scroll behavior | 1440 light/dark | RECORDING; native desktop UNVERIFIED here |
| U1 | Unaffected ancillary callers, conditional invalid-target risk only | Pickers/sidebar/files/PR/archive catalogs listed below | Filter/remove selected final entry during positive-position scroll if caller supports it; otherwise normal open/filter/select/restore control. One representative per distinct scroll API, not per catalog | 390 dark, 1440 light | RECORDING; PLAN; mobile UNVERIFIED |

A2/C2 can primarily use deterministic tests because their purpose is gate coverage; do not fabricate a visually reachable interaction. A1 is the essential red→green recording. P1/P2 negative results cannot substitute for it. G is deliberately four variants rather than an unrelated Cartesian product.

## Complete production render/caller inventory

| Surface / runtime entry | Render paths and callers found | Classification / matrix mapping |
|---|---|---|
| Web + desktop / react.js,mjs | ChatView→MessagesTimeline; useTimelineFollow (end and index navigation), useThreadFindNavigation, useTimelinePositionRestore, AssistantCitationSource/useAssistantCitationTarget; minimap navigation | P1–P3/D1. End scroll can select positive viewPosition; explicit restore/follow index paths at0 and disabled MVCP find/citation are unaffected controls. Type-only imports are not additional renders |
| Web / react.js,mjs | TimelineWorkGroupRows | P4; nested maintainScrollAtEnd with data changes; same core, different viewport and expansion |
| Web / react.js,mjs | BranchPicker; FontFamilyPicker; ModelPickerContent; CommandPalette→CommandPaletteResults; ThreadPullRequestsPanel | U1. Branch filter resets offset0; palette uses DOM nearest scrolling or scrollIndexIntoView. No separate null-target race proven. Affects only if positive in-flight target becomes invalid under MVCP, not merely because list data changes |
| Mobile native core via keyboard wrapper | ThreadDetailScreen→ThreadFeed; viewability helpers; list ref passed through detail screen | N1. End-follow disables MVCP; history/disclosure modes enable it. Insets/keyboard wrapper are pre-existing, not new patch behavior |
| Mobile native core via reanimated wrapper | thread-work-log; ThreadSettingsSheet catalog; PullRequestRouteScreen overview; PullRequestFiles | P4/U1. Wrapper delegates to native core; overview explicitly MVCP false is unaffected; work log end-follow is conditional exposure |
| Mobile native direct lists | ThreadPullRequestsSheet; ThreadNavigationSidebar (two list renders); NewTaskContextPickerScreens; SettingsOpenSourceLicensesRouteScreen; ArchivedThreadsScreen; HomeScreen; FileTreeBrowser; PullRequestsScreen | U1. Home/PR restores use offset; PR continued-data mode and file tree MVCP are controls unless a positive target is invalidated. Plain catalog render is not its own race |
| Shared refs/other virtualizers | Mobile keyboard CommandPalette, SourceFileSurface, ThreadArrangementSheet and PR reviewers have scroll-like APIs but no LegendList runtime import at these sites; inspect their concrete virtualizer before counting | Excluded as independent LegendList renders; do not infer dependency usage from method name. Type-only ThreadDetailScreen refs not independent list |
| react-native.web.js,mjs | Guard patched for platform resolution; no explicit direct import in application render sites above | Same algorithm, no extra UI matrix; add direct bundle parity test. RN-web build resolution needs actual verification if shipped |
| Reanimated/keyboard bundles | Existing wrapper patches unchanged in this diff; wrappers import native core | No independent guard logic; native integration still required |

## Fixture data and real-app seed plan

1. Use the lead's existing isolated before/after instances (7157/7050); coordinate stopped-server snapshot replacement with lead, never touch their running database. `vp run migrate-dev-db` is the repository-supported read-only-live→isolated copy path, only with that dev server stopped. Never start against live `~/.t3/userdata` or copy a sandbox back.
2. Commit a sanitized deterministic fixture/replay script in the implementation branch before claiming reproducible evidence. Current diff commits the focused test but the reproduction HTML/TSX are **untracked**, so they do not satisfy committed fixture/script evidence. This audit does not edit them. Avoid uploading private thread contents.
3. Product seed: one project, one long stopped thread from a sanitized real Claude transcript with stable message/work IDs and timestamps; retain enough historical turns to overflow by many screens. Include real short and long Claude assistant content, streaming deltas, work-group entries, finalization events that remove/coalesce transient final rows, and a second short/empty thread. Replay identical event order into both isolated states through existing test fixture/event infrastructure, not ad-hoc live SQL writes. Snapshot/hash the resulting state and capture exact row-key arrays before/after finalization. The raw real-provider interaction can be a smoke recording; deterministic before/after uses the same captured transcript.
4. Full long fixture should contain representative markdown paragraphs, fenced code, tool details and long wrapping text, because measured row sizes matter; no need to enumerate unrelated app/tool types. Pin font metrics and widths. Seed one nested long work group plus remembered-middle position. Seed small picker lists only to exercise offset/index controls.
5. Instrument actual app only through a committed test driver: observe target index/viewPosition, resolved target ID, data length, MVCP flags, measured total and offset at the mutation. If an actual product finalization makes targetId null, replay it in P1. Do not replace the product timeline with 900px divs and label it an actual-app test.
6. Isolated harness only for precise race not seedable through actual app: actual installed dependency, stable row IDs0…149, 900px border-box rows, estimate90, initial end, MVCP data+size; wait for layout, set offset5000, start animated end and remove last at150ms. The current scratch fixture has 900px height plus border; ensure box-sizing actually yields900 before comparing total134100. Commit scheduler/trigger and timing log; record active target when deleting. Busy-main-thread variant must log when timer actually fires rather than assume150ms.

## Remaining proof gaps

No verified mapping of existing recordings to scenario, commit, width/theme, clock/DPR/fonts; no after recording inspected; untracked harness; no committed deterministic product seed in this diff; total-size tests do not assert final landing; RN-web bundle tests missing; native desktop and Android (and iOS) unverified. No implementation correctness defect established by this read-only pass beyond these proof gaps. Null guard is narrow and consistent across all six modified bundles.

## FRICTION

| category | happened | prevention |
|---|---|---|
| Reproduction | Actual app/real Claude runs reportedly did not jump; precise dependency race did intermittently | Keep product negative evidence distinct from red→green dependency race; log target/data timing |
| Provenance | Three before video links lack an attached scenario/environment manifest | Name each artifact by matrix ID and record SHAs, viewport, theme, DPR/fonts and fixture hash |
| Reproducibility | Harness files are untracked; tests cover size invariant only | Commit sanitized driver/fixture and capture final offset and intermediate frames |
| Platform | Native desktop/Android cannot run here | Mark UNVERIFIED and obtain actual device/Electron recordings later; never equate narrow browser with mobile |
| Audit tooling | An initial attempted patch filename was wrong; a guessed migration filename did not exist | Use diff-listed exact paths and repository task name; neither error changed files |
| Scope | Dependency is used across many catalogs but only one guard branch changed | Inventory all renderers, collapse by MVCP/target/viewPosition/geometry and scroll API |
