62c22e38b7e601198bd95d288aea0e143192ca9b
Scrolling was laggy, and measuring said where the time went. Instrumenting the transcript's main-thread work on the emulator, against `/stream 200`: markdown parsing ran fifty-eight times in three seconds -- once per streamed delta, each one re-parsing the whole message the reply had grown into -- for 49-78ms of main-thread work per three seconds, with single parses reaching 7.3ms. That is most of a frame at 60Hz and more than a whole one at 120. Everything else the transcript does per event was under a tenth of it. So only the first parse stays on the composing thread. That one has to: the renderer's asynchronous path draws an empty loading slot until its result arrives, which measures a row at nothing before it is measured at its real height, and the whole transcript above it collapses and springs back. Every parse after the first is the same row growing, and there is a previous parse to keep drawing until the new one lands -- so those go to a background thread and no frame is ever without a height. What is on screen stays a real prefix of the reply rather than a guess at it; it is simply one parse behind. The same measurement found `loaded` costing an ArrayList copy per event, and nothing reading it. It recorded every event the screen had ever seen against the possibility that a page arriving in front of them would need the events themselves to stitch on -- but `joinPages` heals the boundary from the folded rows and has since it was written, so this was a list that only ever grew. Checked on the emulator with ui-trace at 1kHz. Streaming at the newest end: the row's bottom edge holds at y=1940 while it grows upward, and the header, status row and composer do not move for six seconds. Scrolled back with a reply streaming: nothing moves at all, 0 of 45 elements over five seconds. Scrolling a mixed transcript: rows keep a constant height as they translate, so none of them arrives blank and fills in afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Languages
Rust
54%
Kotlin
43.6%
Shell
2.4%