A reply's display list holds every glyph of it and is re-recorded
whenever drawing is invalidated, so one long message costs as much to
draw as a hundred short ones and skipping the rows around it cannot help
while it is the one on screen. That was the whole of the remaining draw
cost: 97% of rows correctly skipped, and the tallest one still drawn was
36,982px -- about twenty-five screens in a single message.
So a message is cut into its top-level blocks and each is drawn, or not,
on its own. The cut comes from the parser's own boundaries rather than
from a line scanner looking for blank lines, which is what makes it
safe: a heading, a table, a fenced block and a list are each one node
whatever is inside them, so a loose list does not become five one-item
lists and a fence is never split down the middle. Checked against a real
reply whose list items are separated by blank lines -- it still draws as
one list with its bullets aligned, which is the case a blank-line split
gets wrong.
It bounds parsing too, which was the other symptom in the same reading:
one message took 1.4 seconds to parse as a single unit, and a block is a
paragraph.
The row and the message each know half of where a block is, so they meet
at an interface declared where it is used: the list supplies the row's
position, the message supplies the block's offset inside it, and drawing
a message does not have to know it is inside a transcript.
What this cannot divide is a single node, and a long fenced code block
is one -- so the report now also carries the tallest *drawn block*,
which is the number that says whether splitting bounded anything. This
emulator's tallest row is one such fence, which is why its own figures
do not move.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>