de049bcec646dd053044568b4ec0a89750a66f42
Two separate defects, both of which moved the list under the reader. The first: a row that grows drags the view toward the newest end. The list is laid out from the bottom, so it anchors on the first visible item's *bottom* edge -- and a reply streaming in extends that row upwards, pushing everything already on screen with it. Measured against a reply streamed in four hundred pieces: scrolling back one screen and waiting six seconds ended at the very bottom, forty lines further on than where it was left. So the transcript now only changes while the reader is at the newest end; anything arriving before then waits in order and lands when they return. Status, tokens and the model still update live, because none of those are drawn in the list and freezing them would trade a jumping transcript for a status row that lies. The second: every markdown row was measured at nothing before it was measured at its real height. The renderer's `content: String` overload parses in a coroutine and draws an empty loading slot until it finishes, so a row composes with no height and springs open a frame later. Seen with five replies on screen at once, all blank, the whole conversation shrunk to a single screen. Parsing in the composition costs a few milliseconds on the main thread and is worth it: no scroll anchoring can survive a row that lies about its height first. `/stream N` in the echo driver is what made the first one reproducible -- `/slow` emits a line a second, and the growth has to be continuous for the anchor row to drag. Verified on the emulator: scrolled back through a whole 400-piece stream, the transcript region is pixel-identical across ten seconds while the status row goes from working to idle; returning to the bottom brings the backlog in one go. Checked the tool-call rig too, which this change had no reason to touch -- paging back still works and every group still reads "Called 8 tools".
Languages
Rust
54%
Kotlin
43.6%
Shell
2.4%