# Proof audit, pass 1: fixture matrix

## Findings

1. **Current proof is insufficient.** No recordings exist for the reviewed local head, [bc12eab90e](https://git.cafe/pingdotgg/t3code/commit/bc12eab90ed6159cefa45a6b9b345aab8987074d), in [#32826](https://git.cafe/pingdotgg/t3code/pull/32826). The PR body's dependency-patch captures are stale. Their row names are retained below where applicable; they do not establish any row as passed. The linked local commit will only become available on the forge after a push.
2. **The new behavior is narrower than the old patch.** The actual diff against merge-base [feb9c6b842](https://git.cafe/pingdotgg/t3code/commit/feb9c6b8425efc05dd0d3b198999ef90e16fc62c) changes the timeline viewport observer and adds one browser test. An instant end pin occurs only when the wrapper's integer `clientHeight` changes, the active config is the smooth constant, and Legend currently reports `isFollowingEnd`. Width-only and content-only changes do not call this new pin. The guard reads the current committed config via `useStableCallback`.
3. **Two positive cases and release controls are essential.** A quiet working turn isolates the viewport resize; an actively streaming turn tests pre-emption and continued smooth content following. Manual history reading and Find must retain their positions instead of being forced to the end. Opening a new turn normally anchors its user message near the top: starting a turn alone does not establish the affected smooth-follow path.
4. **Source inspection is not visual proof.** The added browser test asks for a smooth content pin, shrinks its container from 600 to 200 px, expects an instant pin, then another smooth pin, then checks that a released follower is not moved by another resize. That is useful protocol/guard coverage, but does not show a real drawer's animation or painted content. I did not run tests or captures in this pass and found no confirmed implementation defect in this proof-focused inspection.

Scope: read-only inspection of the supplied checkout, complete two-file diff, live PR description/issue/review/check metadata, callers, follow/restore/citation logic, resize utility, terminal drawer sizing/entry points, and Replay implementation. Forge checks/reviews are on an older pushed revision and are not evidence about this local head. No source edits, pushes, comments, servers, or real provider turns were performed.

## Render and input inventory

The only changed production component is the memoized `ConversationTimeline`, reached through `MessagesTimeline`. The wrapper merges `ThreadFindTimelineContext` over its props and disables live follow whenever Find is open. The other changed file is a browser test, not another product render path.

### All callers and surfaces

- [ThreadTimeline](https://git.cafe/pingdotgg/t3code/blob/bc12eab90ed6159cefa45a6b9b345aab8987074d/apps/web/src/components/chat/ThreadTimeline.tsx) forwards to `MessagesTimeline`. Its sole production caller is [ChatView](https://git.cafe/pingdotgg/t3code/blob/bc12eab90ed6159cefa45a6b9b345aab8987074d/apps/web/src/components/ChatView.tsx), including normal server threads, optimistic draft entries/promotion, provider subagents, side chats/embedded panes, and remembered entries during thread switching. The normal thread route is `/$environmentId/$threadId`; drafts use `/draft/$draftId`. `routeThreadKey`/`displayThreadKey` distinguish identity and position restoration. Web and Electron desktop use this code. An embedded or maximized pane can have different dimensions; it does not select a different resize algorithm.
- [GuestPage / GuestTimeline](https://git.cafe/pingdotgg/t3code/blob/bc12eab90ed6159cefa45a6b9b345aab8987074d/apps/web/src/components/guest/GuestPage.tsx) calls `MessagesTimeline` directly on `/guest`. It derives working state from the streamed runtime, has its own follow hook, no new-turn anchor, zero composer inset, no rollback, history controls and an optional unplaced-guest-message footer. It has no terminal drawer. Window height changes can still exercise the new callback here.
- Native mobile has its own timeline and is not changed. A narrow web viewport still uses this component and can change header/composer geometry. No wire contract, server handler, provider adapter, connection mode or persisted state changes. Local/remote/relay connections render the same branch once the same data arrives; provider labels do not choose the scroll configuration.

### Every top-level branch relevant to this diff

- **No list:** `rows.length === 0`, no unmatched visible approval, idle, no parent link, no history controls object and no footer. `hideEmptyPlaceholder` selects a blank loading/hero area; otherwise the centered empty prompt renders. `emptyPlaceholderInset` shifts its center. Both return before the observed viewport. A history-controls object or parent/footer can mount a list even with zero data rows.
- **Follow disabled:** citation positioning; restoration of a remembered off-end position; anchored new-turn end space; `liveFollowEnabled === false` (history navigation or Find); or a fullscreen HTML/MCP app row. The resize callback must do nothing in each. Citation history has loading/found/failed phases; a failed or completed citation no longer counts as active positioning, but follow may still be released separately.
- **Non-animated follow:** not working; reduced-motion media query; or list identity still settling. Working includes normal running/waiting, optimistic send, compaction and runless work as derived by ChatView. Settling ends only on a content-size notification for the currently displayed data. These states retain Legend's existing non-animated config.
- **Smooth follow:** working, normal motion, settled identity, no disabled-follow condition. Within this branch, Legend `isFollowingEnd` true/false independently determines whether the new pin runs. Null/detached list and initial unchanged-height observer delivery also do nothing. A real height increase and decrease both enter the new callback; width-only changes and item/footer growth alone do not.

### Layout data and interactions, collapsed by the resize behavior

The following are layout inputs, not independent screenshot products of a theme/viewport grid:

- `timelineEntries`, run identity/status/times, `latestRun`, `runningRunId`, `runlessWorkActive`, worktree setup and rollback/checkpoint availability derive the data rows. Zero rows, short/fitting content, crossing into overflow, and already overflowing content are the distinct geometry classes. Text length affects wrapping and Legend's message-size estimate buckets; long messages get individual item types. User grouping, assistant metadata/copy/diff summary, attachments and markdown can change row size without changing the resize guard.
- All row kinds share the viewport: `message` (user/assistant; system rows have no corresponding message body branch), `assistant-meta`, `work`, `work-live`, `work-toggle`, `thinking`, `working`, `turn-fold`, `attempt-fold`, `context-compaction`, `worktree-setup`, `event` (including subagent groups), `proposed-plan`, `html-render`, and `mcp-app`. Their padding/work-log grouping varies. Collapsed/expanded runs, attempts, work groups/entries; pending/active/completed/failed work; deferred-work loading skeleton/error/retry; tool visibility including Zen; streaming thought/answer text; approval/input/compaction/revert/preparing state all change height. Use Replay's mixed text/work/markdown content as one representative, rather than requiring a separate drawer recording for each unchanged renderer.
- `pendingApprovals`, held IDs and matching work entries determine whether approvals appear inline or in the list footer; responding IDs/permissions change their interactive state. Footer content can include current attention/status, handoff, guest messages and queued prompts. `contentInsetEndAdjustment` reserves the overlaid composer; anchored space instead supplies the end adjustment. Footer growth has a separate follow handler. No new pin should run solely because row/footer content grows at constant viewport height.
- Header branches: top fade versus spacer; parent/subagent versus side-chat divider, optional environment label/open-parent action; history-controls absent, exhausted/hidden, load-earlier button, loading-disabled label, error text, and button-plus-error. Minimap counts/visible ranges, persistent scrollbar gutter and hit width respond to content/viewport geometry. `resolvedTheme`, timestamp format, workspace/cwd/skills/runs and render/source callbacks affect child content; none changes the new guard directly. Light/dark affects appearance; normal/reduced motion affects behavior. Width, chat-width/font preferences and responsive spacing can rewrap content. A width change that also wraps a header and changes measured viewport height must be classified as a height change.
- Scroll interactions: terminal toolbar toggle and `terminal.toggle` (normally Mod+J) converge on `useChatTerminalActions`; terminal new/split/open-setup actions can also reveal the same drawer. Drawer drag, open/close, rapid reverse, window height resize, banners/composer/header layout can resize this viewport. Terminal count, tab/split/sidebar and empty-terminal UI change drawer content but are not separate timeline code paths. Explicitly use 200 ms panel animation for the positive cases; a 0 ms preference is a different control, and is not equivalent to reduced motion.
- Manual wheel/touch/PageUp/scrollbar/minimap navigation releases follow; clicking the scroll-to-end control re-arms it and clears the new-turn anchor. Find open/query/next/previous/close also changes navigation. Citation activation/escape, thread switch/restore, new prompt anchor, disclosure expansion/retry, and fullscreen enter/exit/unmount are other ways to change position/height or disable follow. Playing audio and fullscreen rows remain mounted through `alwaysRender`; audio alone is not a new scroll mode. Copy/file/image/diff/parent/fork/rollback/template/app-message/approval/worktree actions use existing callbacks and can indirectly change rows, overlays or navigation; they do not add a resize guard.

## Common real-app seeding recipe

All items below are instructions for the capture owner, not actions performed by this audit.

1. Run before at the exact merge-base above and after at the exact reviewed head, each against fresh isolated state. Start dev with `T3CODE_DEV_SEED=0 vp run dev`, retain the server's actual ports, and use its pairing flow. Never use live userdata, real provider credentials, or baked-in Vite origins. Do not refresh the baseline to a moving main tip between the two sides.
2. Add a synthetic project via the normal UI (or the typed project-add CLI), choose **Replay (dev)**, then its **Showcase** display entry in the dev picker. The documented hidden picker/console helper starts a **new thread** each time: use it once, then send additional ordinary prompts in that same thread. Replay ignores their semantic content and repeats its recorded turn. Approve its synthetic write when advancing; no paid model or real write tool runs. See [test-t3-app](https://git.cafe/pingdotgg/t3code/blob/bc12eab90ed6159cefa45a6b9b345aab8987074d/.agents/skills/test-t3-app/SKILL.md).
3. Build long history with repeated completed replay turns until measured content visibly exceeds the viewport by at least two viewports; keep identical prompt text/turn count/disclosures on both sides. For a quiet working interval, leave Showcase at its approval request: waiting maps to working. For streaming, approve and target the final streamed markdown interval; repeat the replay if the interval ends too early. Do not claim active streaming from a static approval pause. A short fixture can use the **Write approval** display entry with one short prompt and no prior history.
4. Explicitly scroll to the end after a new turn starts (use the app's end button when available), clearing the new-turn anchor. Confirm the thread remains working, content has laid out, no Find/citation/fullscreen is active, and the actual follow state is engaged before opening the drawer. A harness may inspect the real list state to document this precondition; never force its flags just to obtain an app capture.
5. Fix viewport, DPR/zoom, fonts, lane width, theme, drawer height and animation preference on both sides. Use 1280 x 800 CSS px, dark, 200 ms panels, no reduced motion for the default rows. Record actual measured timeline heights and content dimensions, not only window dimensions. Hide provider/model identifiers and pairing tokens in every uploaded image/video/manifest.
6. For exact short/boundary geometry, tune only synthetic turn count, disclosure state, drawer height or window height until the measured inequalities in the table hold on both sides. Do not silently substitute different histories. If built-in replay cannot produce a required duration, exact geometry or specialized payload, mark that fixture as a **labelled component harness recording**, and keep at least R01/R02 as real integrated-app recordings. A harness can render the unchanged production component with deterministic entries and live props in a resizable container; it must identify its synthetic nature. Do not present DOM-only smooth-scroll completion as timing evidence.

## Fixture matrix

`MIN` is the required fresh before/after set for this timing fix; `EXTRA` is an identified supplementary path, not a missing-proof blocker. All interaction/time rows require **recording**; a final still cannot pass them. Rows may be chapters in the same before/after videos if each chapter visibly establishes its preconditions.

| Row | Named fixture and data needed | Diff effect / action and acceptance | Real-app seed, or labelled harness fallback | Minimum viewport x theme; evidence | Status |
| --- | --- | --- | --- | --- | --- |
| R01 | Quiet working, long history, following end; normal motion, settled list; one drawer session | **Direct.** Toolbar open/close, Mod+J open/close, drag taller/shorter, rapid open-close-open. Newest content stays at the visible end without the delayed chase; no late snap after reversal. | Common recipe; Showcase paused at approval is a quiet working state. Clear prompt anchor and follow before each segment. Prepare a shell before timing the toggle so shell startup is not the measured event. | **MIN: 1280x800 x dark; recording pair.** | PENDING CAPTURE |
| R02 | Same long history, actively streamed assistant markdown; follow active; smooth content pin in flight | **Direct + continuation.** Open and close during chunks; include a large height shrink whose loss exceeds the new timeline height. Continue recording arriving text after resize; normal content following remains smooth and does not stop/rebound. | Same Replay thread; approve and drive during final markdown. Pre-size the drawer tall, close, then reopen during streaming to obtain the large shrink. If drawer caps prevent that, perform fixed-width window-height reduction and document actual old/new timeline heights. | **MIN: 1280x800 x dark, plus a measured reduced-height segment; recording pair.** | PENDING CAPTURE |
| R03 | Long history, idle/completed, at end; same drawer/200 ms preference | **Non-animated guard control.** Open/close stays pinned and shows the reference behavior; no new post-resize drift. | Finish the identical Replay turn; retain history, viewport and drawer settings. | **MIN: 1280x800 x dark; recording pair**, can share a chapter with R01. | PENDING CAPTURE |
| R04 | Long history, working/following, OS/browser reduced-motion media query true | **Non-animated guard control.** Open/close and resume text with reduced motion; behavior unchanged. | Common recipe with real OS/browser reduced motion. Confirm `prefers-reduced-motion: reduce`, not merely panel duration 0. If unavailable, clearly labelled harness with media-query emulation, not a falsified app preference. | **MIN: 1280x800 x dark; recording pair**, can share a control video with R03. | PENDING CAPTURE |
| R05 | Long history, working/following, normal motion, panel duration 0 ms | **Direct single-step resize control.** Drawer jumps open/closed and end remains pinned. Smooth content config can still be active: panel duration is independent. | Appearance > Advanced > Panel animations = 0; retain R01 data and normal motion. | **EXTRA: 1280x800 x dark; recording pair if taken.** | OPTIONAL, NOT CAPTURED |
| R06 | Short working timeline with real content strictly shorter than the usable area both closed and open | **Direct, fitting geometry.** Drawer open/close does not create phantom overflow, top displacement, or a blank tail. | One short Replay approval scenario; keep content plus footer/inset below the smallest usable viewport. If Showcase is too tall use Write approval. Exact synthetic component data is a labelled harness fallback. | **MIN: 1280x800 x dark; recording pair**, may precede R07 in one geometry video. | PENDING CAPTURE |
| R07 | Boundary working timeline: content fits closed, overflows open; follow engaged | **Direct, overflow crossing.** Open/close crosses the threshold; tail stays reachable/visible and following survives subsequent chunks. | Tune Replay history/disclosures and drawer/window height so real-content height is between closed/open usable heights; use identical content on both sides. Resume replay for continuation. Harness only if the real app cannot reach this geometry. | **MIN: 1280x800 x dark; recording pair.** | PENDING CAPTURE |
| R08 | Long wrapped working text at end; width-only resize, then restore width | **Height-unchanged control.** 1280 -> 600 -> 1280 at fixed window height; text rewraps without a new forced end pin attributable to width alone. Note any responsive header-induced height change separately. | R02 data; resize web viewport or right panel. Appearance chat-width can provide a pure lane-width control. Match content and actual timeline heights. | **EXTRA: 1280/600x800 x light; recording pair.** No full theme grid. | OPTIONAL, NOT CAPTURED |
| R09 | Long working history, reader away from end (include near-end released follow and far-up reading), then explicit re-follow | **Released-follow controls.** Open/close/drag while reading; no jump to latest content. Resume text while released; then end button and reopen confirm follow is restored. | R02; wheel/PageUp/scrollbar to a labelled old message; a minimap selection is another equivalent release. Record marker position. Use the real app's return-to-end control. A component harness can isolate smooth-config plus Legend-follow=false if that transient cannot be held in app. | **MIN: 1280x800 x dark; recording pair.** | PENDING CAPTURE |
| R10a | Newly mounted/switched working list, following target, not yet settled | **Non-animated settling control.** Resize at opening, then let current data settle and repeat. No old-thread rows or stale offset jump. | Switch between two synthetic Replay threads with distinct text. For repeatable pre-paint timing, use labelled harness controlling first content layout. | **EXTRA: default viewport/theme; recording if taken.** | OPTIONAL, NOT CAPTURED |
| R10b | Returning to a remembered off-end thread | **Disabled restore control.** Switch away/back while drawer heights differ; restore the same old message rather than pinning to bottom. | Read older history in thread A, switch B, return A with a different stored drawer state; inspect restored marker. Harness can hold restoration deterministically. | **EXTRA: default viewport/theme; recording if taken.** | OPTIONAL, NOT CAPTURED |
| R10c | Working new-turn prompt anchored near top, with anchored end space | **Disabled anchor control.** Open drawer before clearing anchor; preserve prompt-anchor behavior, then end-button releases anchor and enables R01 behavior. | Send a new prompt in the existing Replay thread; deliberately do not click end first. | **EXTRA: default viewport/theme; recording if taken.** | OPTIONAL, NOT CAPTURED |
| R10d | Citation positioning into an old assistant response; loading/expanding/ready, or failed target | **Disabled citation control during positioning.** Resize during reveal; honor citation. After dismissal/completion, follow resumes only through existing rules/user action. | Create a citation to synthetic Replay response text with the app's selection/citation flow. A missing/slow history target needs labelled harness data or controlled history response; no built-in Replay citation scenario guarantees it. | **EXTRA: default viewport/theme; recording if taken.** | OPTIONAL, NOT CAPTURED |
| R10e | Working timeline with fullscreen HTML/MCP app row, then exit/unmount | **Disabled fullscreen control.** Resize must not dislodge the viewer; close/unmount releases its pin, and normal follow can work again. | Built-in Replay has no such payload. Use a **labelled component harness** with a synthetic HTML-render reference or MCP app and real fullscreen callbacks, unless an existing synthetic app fixture can be driven. Not a paid provider run. | **EXTRA: default viewport/theme; recording if taken.** | OPTIONAL HARNESS, NOT CAPTURED |
| R11 | Long working history, Find open and a known older match selected | **Disabled Find control.** Open/close drawer; preserve selected match/reading position. Close Find and explicitly return to end, then reopen drawer to confirm recovery. | R02 thread; open thread Find, query fixed replay text, select earlier match; keep both sides working. Prior stale evidence mixed idle/working and cannot pass this row. | **MIN: 1280x800 x dark; recording pair.** | PENDING CAPTURE |
| G01 | Guest timeline, working/following, long history; zero composer inset | **Direct alternate caller.** Window height shrink/grow while receiving live Replay updates; no terminal controls exist here. | Owner creates a disposable synthetic guest share via normal UI, second browser uses `/guest` invitation, owner runs Replay in shared thread. Do not publish invite/session credentials. If local guest sharing is unavailable, **labelled GuestTimeline harness**. | **EXTRA: default viewport/theme with fixed-width height change; recording if taken.** | OPTIONAL, NOT CAPTURED |
| E01 | Truly empty idle timeline with visible centered placeholder | No observed list; new callback cannot run. Placeholder inset/theme/width variations do not test the fix. | New empty thread with no footer, parent/history controls or approvals. | Outside minimum; still adequate only for this static empty branch. | SOURCE-COVERED, NO CAPTURE REQUIRED |
| E02 | Empty idle loading/draft hero with placeholder hidden | No observed list; new callback cannot run. Hero/loading layout is owned by caller. | New draft or thread detail loading; labelled harness if a held loading state is needed. | Outside minimum; recording if demonstrating loading-to-list transition. | SOURCE-COVERED, NO CAPTURE REQUIRED |
| E03 | Zero data rows but parent/header/footer/history object or unmatched approval mounts a list | Mounted-list exceptional geometry; without rows, Legend end-scroll has no row target. Errors/loading/header labels are existing layout variants. | Parent/side-chat or attention/footer app flow where available; otherwise **labelled harness** with zero entries and each header/footer prop under inspection. | EXTRA; recording if exercising resize or loading/error transition; no per-error still grid. | OPTIONAL HARNESS, NOT CAPTURED |

## Minimum capture set and acceptance

Required fresh rows: **R01, R02, R03, R04, R06, R07, R09, R11**. Each needs a labelled **before and after recording**: eight row-pairs, which can be chapters in fewer continuous video pairs. Use the single default dark 1280x800 viewport; R02 additionally needs the documented large-height shrink. R08 alone provides an optional light/narrow pass, rather than multiplying every row by themes and widths. Windows desktop at R01 is a useful optional platform confirmation for the original report [#32778](https://git.cafe/pingdotgg/t3code/issues/32778); Linux web proves the shared app path, not Windows-specific runtime behavior.

- Begin recording before the resize. Show the whole drawer/timeline/composer relationship, the working/idle state, identifiable last/reading row and the full transition. Continue at least one second after the last resize, and through additional arriving chunks in R02. Use sufficient captured frames to judge the 200 ms animation and approximately 550 ms chase; a recording with only pre/post endpoints is insufficient.
- Reproduce the visible delayed chase on the before build for R01 or R02. If the browser completes smooth scrolling instantly, the recording is not a discriminating reproduction: label the limitation and obtain a browser runtime that animates it. Do not pass the row based on a scroll-call spy or a zero final gap.
- Pair identical data, disclosures, viewport, theme, drawer sizes and settings. Count/hash synthetic fixture text locally. Dynamic clocks/caret/title/approval labels must be matched or disclosed; do not compare an idle side to a working side. In R06/R07 prove the geometry instead of inferring it from a short prompt.
- Observe pixels first. A read-only frame sampler may record viewport height, scroll offset/content height, the last visible row's rectangle, composer top, and reading marker. It must account for the composer inset/footer and be supplemental: list state can lead painted pixels. R09/R11 judge a stable chosen reading marker, not end distance.
- In R02 show a real active smooth pin overlapping resize, loss of height greater than the new viewport, instant resize correction and sustained smooth content following after it. Allow documented transient rendering limits, but a persistent hidden tail, delayed rebound, or new follow loss contradicts the row.
- Store each capture-driver run under a new timestamped directory inside `/tmp/lead-32826-r1/proof-audit-1/`. Never publish runtime model identifiers or credentials. Label harnesses, real-app clients and unsupported platform claims explicitly. Upload before deleting; this audit created only this Markdown artifact and did not delete anything.

FRICTION: none

VERDICT: INSUFFICIENT R01, R02, R03, R04, R06, R07, R09, R11 need fresh before/after recordings
