# Proof audit pass 2: PR #22930 (GitCafe #22927)

Auditor on fs-17, 2026-10-22 12:16–12:30 UTC. Read-only except `/tmp/ev-22930/audit-rebuild` and `/tmp/ev-22930/audit2`.

## (1) SHAs
- `~/wt/pr-22930` HEAD is `8d5bc57aea`; parent `2b388c27fa`. `git diff --stat 2b388c27fa 8d5bc57aea` touches only `apps/web/scripts/evidence/pr-22930/` (11 files). **OK**
- `8d3b061121` is the merge-base of AFTER with current origin/main (`ed3f965e07`, which has moved since) and the second parent of merge `45032b2b1f`. That makes it a valid BEFORE. **OK**
- **Gap:** the events JSON (`row`, `cfg{w,h,scheme}`, `events`) and the logs carry **no SHA**. The binding from build to SHA rests on ports: the logs show `localhost:6764` for BEFORE and `6511` for AFTER, matching `drv/ids-*.env` and the index. The BEFORE worktree `/tmp/ev-22930/before` has been deleted, so its HEAD can't be checked any more. **Partial**
- `out/<build>/<row>.events.json` is byte-identical to `media/<row>-<build>.events.json` for all 14 row×build pairs.

## (2) Narration vs recordings (frames decoded with ffmpeg)
- **R6 BEFORE** frames at 97, 108.6 and 119.9 s: P6 shows C2 with long-b expanded at about line 185. After (a) hide/show and after (b) reopen, it's back on All commits with all 8 files collapsed. Matches the events. **OK**
- **R6 AFTER** frames at 91, 103 and 115.8 s: all three show C2, long-b expanded, first visible line 164. Matches the events. **OK**
- **R7 BEFORE** frames at 40–52, 70–83 and 85.3 s:
  - P6 shows C2 with long-b expanded.
  - Back on A (t≈75): PR #1 on All commits, all files collapsed.
  - Back on B (t≈83–85): PR #2 on All commits, all files collapsed.
  - The clip is 85.44 s long but the final event is at 85.56 s, so the last caption never switches. The visible state still matches the event. **OK** (cosmetic caption lag)
- **R7 AFTER** frames at 140–150, 192–217 and 218.5 s:
  - P6 shows C2 with long-b at line 142.
  - Back on A: C2, long-b at line 176. That matches firstLine longB_176 and scrollTop 3760.
  - Back on B: S2 with s-4 expanded and s-5 collapsed. **OK**
- At 390 px the PR panel is a full-width sheet. Frames taken right after an event can show the next navigation already underway, so check frames just before each event timestamp, not just after it.

## (3) Index claims vs measured metrics
All per-step values in the index (scope, scrollTop, first line, headers) match the events JSON for R4, R6 and R7, which I checked line by line. The index states its non-reproductions honestly: R1's trigger did not land, R2 was not run, and R4/S1 did not show the predicted BEFORE reset. Nothing is overclaimed.

Precision notes:
- In R4 AFTER, `scrollHeight` at rest is 12636, against 12716 at P6. In R7 it is 12656 against 12916. Content height changes between save and restore, so the line shift is the better measure of drift than raw pixels: R4 is **27 lines** (longB_128 → 155) and R7 is **34 lines** (longB_142 → 176).
- R4 AFTER `codeVisible:false` at the trigger confirms the viewer remount on explicit Refresh. BEFORE did not remount.

## (4) Rebuild
- I ran `build-fixture.sh` from the scripts commit with `EV_DIR=/tmp/ev-22930/audit-rebuild`. It reproduced main `65c1abc`, review-l `6329e89` (C1 `13ab638`, C2 `1d32c27`) and review-s `c34151c`, identical to the original `fixture/src.git` and the index. PR L has 8 changed files and PR S has 7. **OK**
- I downloaded the S-390-light and S-1440-light PNGs from the ghdrop URLs. They are byte-identical to the local media. Re-running `compare -metric AE -fuzz 2%` gives **115010** and **274**, exactly the index values. **OK**

## (5) 390 px drift assessment
`PullRequestCodeScrollRestoration.restore` is a one-shot pixel restore. As soon as pages are loaded and the surface has a non-zero height, it writes `min(saved.scrollTop, scrollHeight-clientHeight)` once through `pullRequestCodeScrollSurface`, which calls `scrollTo` with the item and an offset. It then sets `pending=false`, and from then on `onScroll` saves the current position again. It does not record or restore a content anchor (file and line).

At 1440 px no line wraps, so the virtualizer's estimated row heights equal the measured ones and the restore is exact (R6, S1). At 390 px most lines wrap to two rows (visible in the frames), so the heights the viewer knows right after a remount differ from those measured when the position was saved. A pixel target set before the rows above it are measured then resolves to different content. The +340/+360 px landing and the 27–34 line shift fit that mechanism, and so do the shrinking scrollHeights.

So this is a **plausible, real limitation of the new restore logic at wrapping widths**:
- For remount paths (R7) it is still a large net improvement over BEFORE, which reset to the top.
- For explicit Refresh (R4/S2) it is a small **regression**. BEFORE did not remount and kept the exact position; AFTER remounts and lands about 27 lines lower.

Suggested follow-up for maintainers: restore against a saved first-visible item and offset, or re-apply the target after a measurement pass, or avoid the remount on explicit Refresh. Not a merge blocker on its own, but it should be noted in the PR.

## Row table
| Row | Status | Notes |
|---|---|---|
| R1 | unverified | trigger did not land (index says so) |
| R2 | unverified | not run |
| R3 | verified (data) | no regression; events match index, frames not decoded |
| R4 | verified | AFTER drift +340 px / +27 lines, plus remount; BEFORE did not reset |
| R5 | verified (data) | no regression |
| R6 | verified | frames confirm bug in BEFORE and fix in AFTER |
| R7 | verified | frames confirm; AFTER drift +360 px / +34 lines |
| R8 | verified (data) | reset by design |
| S1 | verified | diffs regenerate exactly (274/309) |
| S2 | verified | diffs regenerate exactly (115010); shows the drift |
| SHA binding | partial | no SHA in the events; BEFORE worktree deleted; bound by ports only |

Decoded frames: `/tmp/ev-22930/audit2/` on fs-17.
