# Proof audit matrix — pass 1

Audit target: [#33038](https://git.cafe/pingdotgg/t3code/pull/33038), local head [8e252d57cd](https://git.cafe/pingdotgg/t3code/commit/8e252d57cd524d2ed2beca9a55ca0b4a0252c21b), against merge-base [577c524089](https://git.cafe/pingdotgg/t3code/commit/577c524089368176e00b331a8217b642fd6d06a5). Read-only source and evidence audit, 2026-11-10. No app run or test rerun; no worktree edits. Existing captures were downloaded and inspected, not regenerated.

## Findings

1. **The capture freshness premise is contradicted by git.** The brief says merging main did not touch these files. Comparing capture head [e48c6aad62](https://git.cafe/pingdotgg/t3code/commit/e48c6aad62f0bf4a18a3fab73d6735d7d9bf63f8) with the audit head changes MessagesTimeline.tsx by 18 added / 50 removed lines, including disclosure positioning, end-follow maintenance, item-size refresh wiring, viewport access, and long-message virtualization types. The merge also changes timelineViewport.ts and useTimelineFollow.ts and upgrades LegendList from 3.3.5 to 3.7.2 with a replacement patch. TimelineMinimap.tsx itself is identical between those heads. A conflict-free merge is not rendering or timing equivalence. The existing images are acceptable as explicitly labelled historical appearance evidence, but not sufficient proof of current-head scroll behavior. Refresh the required rows at the audit head; use the current merge-base for any new before/after comparison. Cafe currently advertises the capture head, so the local audited head must not be described as already published.
2. **The exact-bottom branch is not captured.** The only above-bottom capture is at a claimed 120px. It cannot distinguish the intended <=1px rule from the rejected 40px follow threshold. Required recording F03 must include measured 0, 1, >1, and 30px distances and actual Next navigation. The browser test asserts the 30px state, but a test assertion is not the requested visual recording.
3. **The restoration evidence is an endpoint still.** C12 shows B mid-history with Next enabled. It cannot establish the preceding A-bottom state, absence of a manual scroll on B, correct restoration timing, or the reverse transition. F11 requires a recording; C12 remains useful for F10's historical endpoint appearance.
4. **Live growth is described but not recorded.** No video is linked in the PR body. The streaming/follow interaction and the new refresh after restore are timing-dependent. A still and the manual-check table cannot substitute for recordings F11/F13.
5. **Alternate geometry and caller coverage is missing.** All full images are 1280x800 files described as downscaled from a 1440x900 CSS viewport. There is no 1100px capture despite the reproduction text, no demonstrated nonpersistent gutter, and no GuestPage caller capture. The desktop omission is declared; it wraps the same web component and introduces no changed Electron-specific branch, so it is not an additional minimum gate here. Native mobile does not render these components; narrow/coarse-pointer web does.

This is a proof-sufficiency verdict, not a finding that the implementation is wrong.

## Scope derived from the code

Production call graph:

- ThreadRouteView renders ChatView for server threads, drafts, and promoted drafts. ChatView renders ThreadTimeline, which renders MessagesTimeline, whose only production ConversationTimeline render then renders TimelineMinimap. ThreadTimeline can supply live entries, optimistic draft entries, or cached entries while switching/loading. ChatView is also embedded by SideChatPane, narrowing the viewport and adding parent-link context.
- GuestPage's guest thread view directly renders MessagesTimeline. It has a guest header/composer, no anchored message, contentInsetEndAdjustment=0, history controls, and possibly unplaced guest messages in its footer. This is a second caller, not merely the normal chat route in another theme.
- TimelineMinimap has exactly one production caller, ConversationTimeline. Its navigation helper renders the previous and next buttons. Tests additionally render MessagesTimeline directly. Native mobile uses its own timeline.

Changed-state partition:

- refreshMinimap returns early during restore, for stale list data, a missing list state, or zero user items. Otherwise it computes the current turn from actual buffered-row geometry and records DOM distance from bottom <=1px. Missing viewport yields atEnd=false.
- The minimap returns null below two user-message items. With two or more, null/invalid current index disables both directions; current=first disables Previous; current=last disables Next; an interior current has both neighbors, but atEnd now additionally disables Next. The important new combination is current<last AND atEnd=true, including several short turns sharing the last screen and a thread fitting entirely on one screen. Non-message rows do not add strips but do change scroll geometry.
- Restoration has first mount vs reused list, remembered history vs bottom/default, anchor present vs missing fallback offset, citation ownership, find-open follow release, and navigation cancellation. Completion now schedules a minimap refresh. Scroll, total-size changes, and item-size changes also schedule refreshes; one animation frame coalesces them. Mount/unmount cancels pending work.
- End geometry also depends on composer inset/anchored space, footer growth, headers/history, work disclosures, streaming, media/content resizing, and viewport rewrapping. Normal live following is animated only while working, settled, and reduced motion is off; find, citation positioning, anchored space, disabled live follow, and fullscreen app content suppress follow. These collapse into stationary, following growth, detached growth, and explicit restore/navigation fixtures below; unrelated card designs are not multiplied into every row.
- Width is measured on the timeline node and content lane, not assumed from window width. Let g=(viewportWidth-min(viewportWidth,contentWidth))/2. Persistent gutter iff g>=48px; hit width=max(0,min(40,floor(g)-12)); navigation pointer interaction iff hit width>=14 and no pass-through drag. Thus distinguish g>=48, 26<=g<48, 12<g<26, and g<=12. Comfortable/wide/full chat widths, sidebar/panels, and embedded chats can cross these boundaries. A nonpersistent rail is fine-pointer hover/focus only and hidden for coarse pointers; a persistent rail is visible regardless. No provider or transport-mode branch exists in this change.
- Pointer hover/focus opens a preview. First/interior/last previews align top/center/bottom; user text is single-line ellipsis with a fallback for empty/attachment-only text; assistant preview is absent or clamped to three lines after bounded markdown parsing. Count sets 8px natural strip spacing, capped at calc(100vh - 18rem). Hover distance scales strips, and in-view state highlights them. Pointer strip selection, previous/next click, wheel forwarding, keyboard arrows/Home/End/Enter/Space, blur/leave dismissal, preview text selection, and drag pass-through are all enumerated below. Only disabled Next and refresh scheduling change; unchanged interactions are regression inventory, not extra minimum gates.

## Capture catalogue and usability

All twelve attachments downloaded successfully. C01/C02/C05/C06/C11/C12 are 1280x800; C03/C04/C07/C08 are 1280x800 diff masks; C09/C10 are 800x400 enlarged minimap pairs. The PR states 1440x900 CSS px, default font scaling, no device emulation, same database/server, before [ade473018d](https://git.cafe/pingdotgg/t3code/commit/ade473018d32003658b7d38693e083ee2e314bd8), after [e48c6aad62](https://git.cafe/pingdotgg/t3code/commit/e48c6aad62f0bf4a18a3fab73d6735d7d9bf63f8). Viewport and SHAs are caption provenance, not values independently recoverable from the pixels. The images show the claimed themes and endpoint content; exact scrollTop/bottom distances and disabled DOM attributes are not encoded in them.

| ID | Linked capture | Audit of visible content |
|---|---|---|
| C01 | [Before light](https://git.cafe/api/attachments/attach_01m6y7ahwee8m9jqqqmwnyv037) | Nine-strip rail, several final turns visible, enabled Next hover background. Usable historical before. |
| C02 | [After light](https://git.cafe/api/attachments/attach_01m6y7ajwbe8m9jqqypybtkv90) | Same content/geometry; dimmed Next with hover background absent. Usable historical after; current-head refresh needed. |
| C03/C04 | [Light Diff0](https://git.cafe/api/attachments/attach_01m6y7akg5e8m9jqr3xpp6xb3v), [light DiffT](https://git.cafe/api/attachments/attach_01m6y7am32e8m9jqraryp6v86h) | Sparse differences at Next, sidebar relative timestamps, composer caret; supports static localization, not timing or threshold. |
| C05 | [Before dark](https://git.cafe/api/attachments/attach_01m6y7amnse8m9jqrhg4jmvqcy) | Dark-theme counterpart to C01. Usable historical before. |
| C06 | [After dark](https://git.cafe/api/attachments/attach_01m6y7ana7e8m9jqrzf129vj2d) | Dark-theme dimmed Next; same endpoint content. Usable historical after; current-head refresh needed. |
| C07/C08 | [Dark Diff0](https://git.cafe/api/attachments/attach_01m6y7anyze8m9jqs2tgwbzej2), [dark DiffT](https://git.cafe/api/attachments/attach_01m6y7apjee8pt7kdcy8kn1cyp) | Same small-difference categories as light. Not independent fixture coverage. |
| C09/C10 | [Light enlarged pair](https://git.cafe/api/attachments/attach_01m6y7p6b0e8m9jqx3ks4y601q), [dark enlarged pair](https://git.cafe/api/attachments/attach_01m6y7aqs0e8pt7kdk854wvnqs) | Left before/right after is readable and the Next difference is clear; crops cannot establish viewport, scroll distance, or order of interactions. |
| C11 | [After 120px above bottom](https://git.cafe/api/attachments/attach_01m6y7p71sfeyvax0h6n453a9c) | Light, content moved up, Scroll to end visible, Next enabled hover background. Usable historical away-from-bottom endpoint; not the 1–40px boundary. |
| C12 | [After B restored mid-history](https://git.cafe/api/attachments/attach_01m6y7p7vmfeyvax0rh9f9pf4e) | B selected, mid-history and in-view strips visible, enabled Next. Usable historical endpoint; cannot show prior thread or no intervening scroll. |

## Seed recipes

All future runs must use isolated development data, not live state. These are instructions for the lead; none were executed by this auditor.

- **A — short threads:** use the existing PR seed driver with its configured free Replay fixture to create two threads A and B, each with 9–16 short user turns and short assistant replies. Open /<environmentId>/<threadId>. At 1440x900, comfortable width and normal composer, ensure the last viewport contains several user strips and an earlier strip remains out of view. Theme is changed in Settings > Appearance; record actual timeline/lane widths, CSS viewport, zoom, font scale, and SHA. Do not publish pairing credentials or private runtime model identifiers.
- **B — fit/singleton:** use a fresh empty thread, then one short turn, then a second short turn. Verify the two-turn scrollHeight<=clientHeight scene before capture. A new/draft route supplies the empty/loading placeholder variants without editing application state.
- **C — tall final turn:** after several turns, send a long multiline user message, expand it in the app, and leave a short Replay response below. Scroll so only the final turn is being read while distance from bottom is >1. For a dense rail, extend A to at least 80 short turns (the exact count depends on viewport height).
- **D — positioning driver:** through browser interaction on the real scroll node, set a requested distance using scrollTop=scrollHeight-clientHeight-distance and log the actual resulting distance and button.disabled values. Record the page continuously, not just the script output. To test restoration, leave B at an anchored mid-history row, switch to A at its exact bottom, then return to B without a scroll on B; reverse once. Use the sidebar or command palette so the shared unkeyed timeline is reused.
- **E — changing geometry:** at A's bottom send another Replay turn and record until completion; repeat while detached >40px above bottom. Expand/collapse one work/message disclosure and resize the chat or change chat width. Log measured distances after layout. For reduce-motion use the browser's media emulation or the OS accessibility preference; this is a motion variant, not another theme.
- **G — guest:** in an isolated environment use the app's Share with guest action (serverEnvironment.createGuestShare), redeem its returned /guest URL in a separate test session, and show seeded A. Do not publish the token-bearing URL. Guest has different footer/inset geometry; test bottom then away. Delete/revoke only the test share if a lead creates one.
- **H — labelled harness only:** directly render TimelineMinimap for invalid index/missing viewport synthetic states, or MessagesTimeline with controlled remembered positions when the saved anchor row has been removed from its loaded window. Use the existing browser-test fixture style. These are labelled component-harness captures, never described as production app screenshots. No worktree changes are authorized for this auditor.

## Fixture matrix

The Minimum column is the targeted capture set for this PR. Inventory-only rows document unchanged render branches; their missing captures alone do not fail this audit. A single continuous recording may cover multiple required rows, with timestamps. A still can cover a stationary state, but never the transition row. Existing historical coverage is explicitly distinguished from current-head proof.

| Row | Named fixture / necessary data and expected visible state | Diff impact | Minimum viewport × theme × format | Real-app seed / interaction | Existing coverage / status |
|---|---|---|---|---|---|
| F01 | Several final short turns visible, current<last, exact bottom: Next disabled, in-view strips unchanged | Direct new atEnd override | 1440x900 × light AND dark × paired stills at current base/head | A, D distance=0; stationary Next hover reveals dimmed appearance | **MISSING current-head pairs**; C01/C02/C09 light and C05/C06/C10 dark are usable historical coverage, with C03/C04/C07/C08 localization masks |
| F02 | Interior current, 120px above bottom: Next enabled | Negative control of override | Inventory; covered by F03 recording on fresh head | A, D distance=120 | **covered-by-existing C11**, historical light endpoint only; no dark or current-head evidence |
| F03 | Cross exact-bottom boundary: distance 0, 1, >1 (e.g. 2), 30, 40, 120px; Next disabled only <=1 where next neighbor exists; enabled click actually moves viewport | Direct threshold and scroll refresh | 1440x900 × light × **recording**; include measured distances/disabled state | A+D, then click enabled Next, return to bottom; log actual clamped values | **MISSING**; C11 at 120px cannot distinguish <=1 from <=40; no linked video |
| F04 | Top of overflowing short thread: current first, Previous disabled, Next enabled | Unchanged previous branch | Inventory; add top stop to F03 without separate file | A, scroll to top and hover Previous | **MISSING**; PR manual-check table is text, not capture |
| F05 | Last turn occupies viewport, current=last, >1px above end: Next already disabled; Previous enabled | Existing last-neighbor branch, unaffected by override | Inventory, 1440x900 × light still | C, position inside tall final turn | **MISSING** |
| F06 | Exactly two short turns fit without overflow: first current with next neighbor but atEnd true; both directions disabled | Direct new override, non-overflow geometry | 1440x900 × light × still | B, confirm scrollHeight<=clientHeight; show both buttons | **MISSING** |
| F07 | No user items / one user item: no minimap; empty placeholder vs hidden loading placeholder vs list with footer/history only | Unchanged return/early-return guards | Inventory; no added minimum | B; draft/loading state via app; a footer-only impossible deterministic state is H | **MISSING** |
| F08 | Two-strip compact rail vs >=80 turns capped rail; top/interior/bottom current; non-message work/approval/media rows alter height without adding strips | Unchanged density/highlighting geometry | Inventory; two-count represented by F06; long density optional still | B and C; existing real seeded histories for mixed rows, otherwise labelled H | **MISSING**; C01/C02 cover only nine-strip density |
| F09 | current=null, negative/out of range; no valid visible range; temporarily missing viewport/list/stale data or restore in progress | Unchanged invalid/skip guards plus new atEnd fallback | Inventory; **recording** for transient states; synthetic invalid prop states labelled H | H; real loading can exercise transient skip but not a deliberate invalid prop | **MISSING** |
| F10 | B restored mid-history endpoint, current interior and Next enabled, correct strips | Direct post-restore refresh | Inventory endpoint; full required proof is F11 | A+D | **covered-by-existing C12**, historical light endpoint only |
| F11 | Reused timeline A at bottom -> B remembered mid-history with no B scroll -> A remembered bottom; Next disabled -> enabled -> disabled, strips/anchors correct | Direct restore completion scheduler | 1440x900 × light × **recording** | A+D; capture both switches continuously and confirm no corrective scrolling | **MISSING**; C12 cannot establish the transition or reverse state |
| F12 | Initial mount/default bottom vs remembered row at reload; anchor missing -> saved offset; navigation cancels restore; citation owns position; find-open restore then match reveal | Restore paths share changed completion callback; position logic otherwise unchanged | Inventory; **recording** for every exercised path; fresh-open bottom folded into F01 | A+D for mount/reload/manual navigation; app Find and citation link; missing-row fallback uses H when not reachable through loaded-history changes | **MISSING**; no capture proves these alternatives |
| F13 | New turn/streamed size growth at followed bottom vs detached history; Next remains disabled at actual end and enabled if a next turn exists away; ordinary and reduced-motion following | Refresh on total/item size; interaction with merge's follow changes | 1440x900 × light × **recording**, ordinary motion minimum; reduced motion inventory | A+E; after new input wait for actual anchored space/follow transition before asserting exact bottom; detached run stays >40px | **MISSING**; manual growth claim has no recording |
| F14 | Disclosure/composer/footer/media size or viewport rewrap moves end without explicit user scroll; recompute Next after settled layout; no stale state | Changed state reads item/total-size refresh; merge changed disclosures | Inventory; **recording**; one disclosure/resize sequence can extend F13 as useful merge regression proof | E; queues/footer via app; image decode or fullscreen viewer only with actual attachment fixture, otherwise H | **MISSING** |
| F15 | Hover/focus preview for first/interior/last strip; short vs ellipsized/fallback user text, absent vs three-line clamped assistant, markdown flattened; leave/blur dismisses | Unchanged preview/layout | Inventory, **recording** because hover/focus and dismissal are interactions | A/C plus attachment-only prompt; pointer/focus at rail endpoints and middle | **MISSING**; Next tooltip in C01–C12 is not a strip preview |
| F16 | Rail keyboard focus, ArrowUp/Down clamps, Home/End, Enter/Space selection; Previous/Next keyboard activation and disabled skip | Shared onSelect, unchanged keyboard path except Next disabled state | Inventory, **recording**; can append disabled/reenabled keyboard check to F03 | A, Tab to rail, key actions; do not synthesize a click on disabled button | **MISSING** |
| F17 | Strip click/drag origin, text selection from preview, cross-rail drag pass-through until release, wheel forwards scroll and end state | Shared scroll refresh; interactions otherwise unchanged | Inventory, **recording** | A; record wheel down to bottom/up, drag from message across rail, strip press and release | **MISSING** |
| W01 | Persistent gutter g>=48px, interactive hit width>=14 | Same changed button in normal desktop-width geometry | Included in F01/F03; no extra row capture | A comfortable chat width; measure actual timeline/lane widths | **covered-by-existing C01/C02/C05/C06** as historical wide appearance; no fresh-head measurement |
| W02 | Nonpersistent but interactive gutter 26<=g<48px: rail/nav hidden until hover/focus, disabled Next still reachable at bottom | Changed button behind alternate visibility branch | Start 1100x900 × light × **recording**; tune window/panel/width until measured g is in range | A; Settings > Appearance > Chat width, sidebar/panel resize; hover/focus and move bottom/away | **MISSING**; 1100px reproduction mention has no attached capture and alone does not prove this gutter |
| W03 | Small positive hit area 12<g<26px with noninteractive navigation, then zero hit width g<=12; wide/full chat or embedded panel | Unchanged hit testing; changed button may be unreachable to pointer | Inventory, **recording** for resize/focus behavior | A, wide/full width and narrow panel; record g and pointer media; keep keyboard distinction | **MISSING** |
| W04 | Fine vs coarse pointer when nonpersistent; narrow browser <640px changes padding, no native minimap claim | Unchanged CSS visibility/platform branch | Inventory, 390x844 × light; **recording** if toggling pointer media or resizing | A, browser device emulation; coarse+nonpersistent hides rail, persistent remains visible | **MISSING**; native mobile is not this branch |
| C01 | GuestPage caller, zero composer inset, header/history and possible unplaced guest-message footer: correct bottom and away Next states | Same changed component with different end geometry | 1440x900 × light × **recording**; bottom and away in one run | G then D; use ordinary seeded entries; record footer presence | **MISSING** |
| C02 | SideChatPane embedded ChatView with parent header and narrowed content; parent and side chat independent scroll nodes | Same changed component; collapses to width/restore geometry | Inventory; W02 may use embedded pane as its one required narrow fixture; **recording** for navigation | Open real side chat via app with at least two turns; if unavailable seed via app's side-chat action, not raw DB edits | **MISSING** |
| C03 | Electron desktop wrapper, same web timeline; OS scrollbar/font scale can change layout | No Electron-specific code branch changed | Inventory; desktop capture useful but no separate minimum gate | Existing isolated desktop test client using A; do not modify live install | **MISSING**, explicitly declared by PR |
| C04 | Local web, hosted web connected remotely/relay/tunnel, multiple environments; same entries and geometry | No transport/provider contract or condition changed | Collapsed into A; no extra minimum capture or paid provider run | Use seeded environment; route identity includes environment; remote host is optional regression context | **covered-by-existing C01/C02** for local-web representative only; no claim of remote execution |

## Minimum delivery, without a Cartesian explosion

1. Refresh F01 before/after stills at 1440x900 in light and dark, identifying actual current base/head, geometry and scale. The old pairs stay linked as historical evidence. Do not reuse the old assertion that the merge left timeline code unchanged.
2. One 1440x900 light recording can cover F03 (and top F04), F11 both switch directions, and F13 followed/detached growth, with measured bottom distances and timestamped stops. Split files if that is clearer. Include F14 disclosure/resize opportunistically because the merge changed that behavior; it is not an independent minimum gate.
3. F06: one 1440x900 light no-overflow two-turn still.
4. W02: one light recording at a measured nonpersistent interactive gutter, starting around 1100x900 and adjusting width as needed. Reuse an embedded side chat for this if desired; never claim the viewport alone proves the gutter branch.
5. C01: one 1440x900 light guest recording bottom/away. It exercises the only other direct production caller.

Thus the mandatory missing rows are F01 current-head freshness, F03, F06, F11, F13, W02 and C01. No additional full theme/viewport product is needed for every unchanged branch. No new test is requested by this audit; the two focused browser tests were inspected in source, not rerun.

FRICTION:
brief | The stated merge-freshness premise was false: the merged head changes MessagesTimeline and upgrades LegendList. | Compare capture SHA to audited head, including scroll dependencies, before describing evidence as current.
forge | Cafe reports the pre-merge capture head while the brief audits a newer local merge commit. | State local and published heads separately and refresh published metadata after the lead pushes.
tool | Pillow was unavailable when attempting a contact sheet; ImageMagick was available and used instead. | Discover installed image tools before selecting an image helper.
VERDICT: INSUFFICIENT F01 current-head freshness, F03, F06, F11, F13, W02, C01
