cc41a0374612b03d1206d74bd15c5f65e0880434
Iris's readings made the shape unarguable: smooth with one page loaded, a step worse at the next, worse again at the one after, and 62.7ms at the median by 390 rows -- with layout at 0.1ms and the GPU at 1.8ms the whole time. The cost was following what was *loaded* rather than what was on screen, and keeping every row built is the only thing in here that does that. So the window is bounded. Eight screens either side stay fully built, which is about sixteen times what a lazy list keeps: everything somebody has just read is still there, and only a deliberate journey back through the conversation pays to rebuild anything. That was the point of retaining rows and it survives; what does not survive is retaining all of them. A row outside the window is replaced by a spacer of the height it was last measured at, so the transcript's total height is unchanged and nothing under the reader moves. A row that has never been measured has no height to stand in for it and is kept whatever its distance, which is exactly the row that has just been paged in. Each row decides for itself, in a composable of its own, from a derived state -- so a row hears about the scroll only when its own answer changes. Read from the list's body instead, every row would recompose whenever any row crossed the edge. On the emulator, same transcript and same gesture, the frame's draw phase goes from 2.7ms at the median to 0.4ms. Iris's own timing reading from before this says where the rest of her frame goes: the transcript's whole draw is 3.0ms mean against a 4.2ms median draw phase, so the median frame was already close to budget and what is left is the tail -- 20ms of waiting and 2.3 seconds of parsing in bursts, both of which arrive with a page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Languages
Rust
54%
Kotlin
43.6%
Shell
2.4%