fix(perf): commit transcript rows on motion frames
Transcript motion currently writes scrollTop in a frame callback, while the browser's scroll event arrives after that frame paints. This keeps virtualization one frame behind a fast motion and can leave the first viewport partially blank. The change routes the existing motion notification through the transcript's normal scroll handling synchronously before paint, cancels stale queued work, and preserves reader follow intent. Zero-duration jumps remain unchanged and no overscan or native logic changes.
Leo: “i feel like the app is slow on scrolling thru stuff... claude desktop is 120hz or something and ours is wack + it has sometimes not even rendered stuff!?”
Performance
Same-worker source A/B: baseline 5dee1ab988ec69569df92ed636c5b971d43f152a, candidate 9e3e8347a19dfe28cfe1cf6c93e337c9736f7d64, fixed tests and harness. Timing and painted-coverage passes are separate. Two rounds are listed in order.
| Metric | Before | After | Delta | Harness or run link |
|---|---|---|---|---|
| First-motion painted blank frames | 1 | 0 | −1 | red/green test, run 41623659964 |
| First-motion blank time (ms) | 87 | 0 | −87 | same run |
| First-motion max uncovered (px) | 219.25 | 0 | −219.25 | same run |
| Fling painted blank frames, rounds 0/1 | 1 / 1 | 0 / 0 | −1 / −1 | same run, painted-coverage pass |
| Fling painted blank time (ms) | 16 / 16 | 0 / 0 | −16 / −16 | same run |
| Fling max uncovered (px) | 190.5 / 190.5 | 0 / 0 | −190.5 / −190.5 | same run |
| Fling interval p95 (ms) | 98 / 94 | 81 / 174 | −17 / +80 | same run, 3000-row WebKit timing pass |
| Fling longest frame (ms) | 123 / 101 | 112 / 192 | −11 / +91 | same run |
| Fling React p95 (ms) | 75 / 60 | 60 / 110 | −15 / +50 | same run |
| Fling dropped frames/s, nominal 120 Hz | 103.745 / 104.493 | 101.030 / 109.671 | −2.715 / +5.178 | same run |
| Long-scroll interval p95 (ms) | 40 / 52 | 42 / 45 | +2 / −7 | same run |
| Wheel interval p95 (ms) | 28 / 27 | 26 / 24 | −2 / −3 | same run |
| Long-scroll / wheel painted blank frames | 0 / 0 | 0 / 0 | 0 / 0 | same run, both rounds |
The coverage improvement is consistent; timing is mixed and does not demonstrate a speedup or native 120 Hz. The completed ABBA follow-up below reproduces large outliers in both variants. Native presentation remains a separate fleet measurement.
Validation at this head: full web check passes; the baseline fails the named first-motion assertion and the candidate passes it, the real six-viewport wheel test and existing regressions. The source and fixed-harness hashes, restored source and all expected stages are verified in the run receipt. Earlier wrapper failures are superseded and provide no product evidence.
Nonblocking review follow-ups: #33672 covers listener rebinding if the read-only scroller is replaced; #33673 covers the existing motion cancellation latch. Neither is a demonstrated current-flow regression.
First-motion ABBA timing follow-up
Run 41627761171, candidate 9e3e8347a19dfe28cfe1cf6c93e337c9736f7d64, baseline 5dee1ab988ec69569df92ed636c5b971d43f152a. Same worker and fixed 3000-row WebKit harness; order A/B/B/A. Each cell lists rounds 0/1. This pass measures timing only; the zero-blank coverage evidence remains the separate run 41623659964.
| Pass | Source | Fling p95 (ms) | Fling max (ms) | React p95 (ms) | row.measure p95 (ms) | Long p95 (ms) | Wheel p95 (ms) |
|---|---|---|---|---|---|---|---|
| 1 | baseline | 124 / 116 | 157 / 124 | 80 / 78 | 20 / 22 | 59 / 50 | 28 / 28 |
| 2 | candidate | 121 / 133 | 131 / 148 | 83 / 96 | 18 / 30 | 49 / 49 | 28 / 26 |
| 3 | candidate | 161 / 99 | 165 / 130 | 107 / 72 | 27 / 20 | 48 / 48 | 26 / 27 |
| 4 | baseline | 112 / 184 | 117 / 189 | 81 / 123 | 22 / 42 | 48 / 67 | 26 / 24 |
Baseline fling p95 spans 112–184 ms; candidate 99–161 ms. Both variants show large variation, and baseline also produces an outlier larger than the prior candidate 174 ms. The prior sample does not isolate a candidate regression. These results establish no reliable speedup, and they remain far above the 8.3 ms target. No averaging of per-run p95s is presented as an aggregate p95.
Receipt acceptance, fixed-source hashes, four log digests, and source restoration passed. Full web check/regression/coverage validation is reused from 41623659964 at the identical candidate tree. The native comparison is complete; the timing result is mixed and does not establish a uniform speedup.
Native pair: one run per arm on the same fleet worker with --app-cache --config=bench --vdisplay=120 --suite=budgets --only=acp --raw=acp. The native result is structural memory and first-content improvement, with mixed interaction timing; the own-mode next-chat value is a single sample and remains an unresolved follow-up rather than a claimed regression or speedup.
| Native metric (mode) | Before | After | Delta |
|---|---|---|---|
| First chat content (chromium) | 1085.2 ms | 871.0 ms | -214.2 ms (-19.7%) |
| First chat content (own) | 949.8 ms | 789.5 ms | -160.3 ms (-16.9%) |
| Next chat content median (chromium) | 125.6 ms | 76.6 ms | -49.0 ms |
| Next chat content median (own, n=1) | 35.8 ms | 109.3 ms | +73.5 ms; verify before any claim |
| Memory per extra chat (chromium) | 453.1 MB | 137.2 MB | -315.9 MB (-70%) |
| Memory per extra chat (own) | 490.7 MB | 118.1 MB | -372.6 MB (-76%) |
| Two-chat footprint (chromium / own) | 1535 / 1611 MB | 1217 / 1201 MB | -318 / -410 MB |
| CEF renderer at two chats (chromium / own) | 882 / 949 MB | 558 / 562 MB | -324 / -387 MB |
| Typing to paint p50 / p95 / max (chromium) | 6.3 / 9.9 / 16.7 ms | 5.2 / 10.5 / 18.8 ms | mixed |
| Typing to paint p50 / max (own) | 5.7 / 19.1 ms | 5.2 / 14.9 ms | better |
| Session composer (chromium / own) | 53.9 / 36.5 ms | 40.0 / 47.3 ms | mixed |
Native evidence: base step 1825c9e956d52749d471c17c at 5dee1ab988ec69569df92ed636c5b971d43f152a; candidate step 311f1ae7 at 9e3e8347a19dfe28cfe1cf6c93e337c9736f7d64; both ran once on the same fleet worker with identical arguments.
Gallery failures are tracked under #33516, which already records the same isolated Git-action scene failing across unrelated PRs. Diagnostic run 41629680371 rebuilds the exact original merge and replay base: Git-action shift is 0.02984955 on both; composer worktree-search is 0.004427–0.004447 on both, within baseline. Source, loaded CSS/font hashes and sampled geometry match. Neither scene mounts the changed scroller. The original excess shifts do not reproduce from this patch; gallery is not the reason for this hold. This does not claim the shared gallery flake itself is fixed.
Independent review: Opus 5.5 medium SHIP at 5ecd071757bf3dbb390e85cd9bbfddd151c11cc6; formatting-only test delta is carried to this head verified independently; review receipt.
🤖 Generated with Codex
Agent session: agent session