← Lab

Proof 03 · obeys law 06

A hundred thousand lines that still scroll at frame rate

A terminal’s scrollback is the hardest ordinary problem in the product: a hundred thousand lines that must scroll smoothly while a process appends to the bottom faster than anyone can read. The number this panel leads with is the longest frame in the last second, never an average — a run that holds 60fps and drops one 300ms frame averages to about 57 and reads as healthy, but that stall is the entire experience. And the virtualization has an off switch, so the claim can be watched to fail.

worst frame · last second

budget 16.7ms

average

smooths the stall away

last mount · style + layout

building the rows

rows in DOM
31

what the browser lays out

rows in buffer
100,000

what you can scroll to

1ERROR core/index.ts: unexpected EOF while reading frame header

2info parser/mux.ts resolved — 12 symbols, 183ms

3info session/mux.ts bundled — 57 symbols, 546ms

4info transport/wire.ts cached — 62 symbols, 159ms

5ERROR core/ring.ts: unexpected EOF while reading frame header

6info pty/mux.ts cached — 22 symbols, 497ms

7info grid/pane.ts emitted — 20 symbols, 633ms

8info grid/index.ts cached — 26 symbols, 234ms

9trace grid/mux.ts:167 → flush(1766b)

10info transport/index.ts emitted — 16 symbols, 760ms

11trace transport/index.ts:233 → flush(2146b)

12info parser/buffer.ts linked — 26 symbols, 590ms

13info transport/pane.ts linked — 63 symbols, 743ms

14info grid/wire.ts bundled — 24 symbols, 622ms

15info renderer/index.ts cached — 59 symbols, 530ms

16info runtime/wire.ts bundled — 55 symbols, 385ms

17info parser/mux.ts resolved — 21 symbols, 253ms

18info pty/mux.ts emitted — 5 symbols, 881ms

19info renderer/pane.ts bundled — 45 symbols, 47ms

20info pty/wire.ts compiled — 17 symbols, 344ms

21info parser/wire.ts cached — 49 symbols, 496ms

22info renderer/codec.ts compiled — 24 symbols, 606ms

23info transport/ring.ts resolved — 1 symbols, 756ms

24info session/index.ts linked — 41 symbols, 310ms

25OK parser cached in 587ms

26info renderer/codec.ts bundled — 53 symbols, 861ms

27info pty/mux.ts bundled — 49 symbols, 600ms

28info grid/index.ts emitted — 53 symbols, 493ms

29info runtime/pane.ts compiled — 53 symbols, 557ms

30info parser/codec.ts resolved — 12 symbols, 891ms

31info runtime/index.ts compiled — 46 symbols, 127ms

Mount costs far more than scroll, and that ordering surprised me. Flip the switch and watch the mount meter above: it goes from single-digit milliseconds to roughly a third of a second — two orders of magnitude — while the worst scroll frame goes from about 9ms to about 75ms, call it ten times. Both are real and they are not close. The expensive thing is building twenty thousand nodes, not moving them afterwards. The ratio moves run to run, which is why the meter is live rather than a number I typed in.

It depends on what a row is made of, which is the part worth knowing. The same test against a bare page of plain <p> elements showed scrolling 20,000 rows costing exactly what 40 cost — Chrome composites a scroll and there is nothing per-frame to save. This panel's row is a flex container with two children, so 20,000 rows is 60,000 elements with a layout pass each, and that is enough to make scrolling cost real time. Whether virtualization buys you scroll performance is not a property of the list. It is a property of the row.

This panel does claim the scroll keys while it has focus — arrows, j and k, PageUp/PageDown, Home/End, g and G — because scrolling the log is the whole subject. Unlike the chord proof, that claim is earned. Tab and Escape are never taken.

Law 06

It holds frame rate when the data stops cooperating.

Measured, shown on screen, and degradable. A number you can read beats a claim you cannot check.