Two things committed in 9052e5f and 94b1509 were never wired up. The row
count was tracked and nothing observed it, so the fix for the gap when
sending was inert; `standUpSome` and `growing` were written and nothing
called them, so the window still jumped in one step. Both are connected
now, which is what the last two commit messages already claimed.
The third thing is what the counters were pointing at all along. The
frame's draw phase sat at a flat 7.7ms -- p99 only 1.8x p50, so not
shaping, which is spiky -- while the transcript's own recording ran 43
times in 3638 frames and accounted for a fortieth of it. A stood-down row
drew nothing but was still a layout node, and the framework's per-frame
bookkeeping after a scroll walks live nodes rather than visible ones. So
the cost grew with the conversation instead of with the screen, which is
exactly the step the reader felt at each page loaded.
The rows outside the window are now two spacers rather than one per row,
which they can be because the window is a range: what is not in it is one
run before and one run after. Live nodes are bounded by the window now.
The gaps between collapsed rows are added into the spacer, because
`spacedBy` puts a gap between children and a run drawn as one child is
short by all of them -- which would not read as a spacing bug, it would
move everything under the reader.
Measure and place are timed separately now. The draw phase carries
Compose's measurement, so "draw is high while nothing is recorded" could
not say which half, and the two have different fixes.
Diagnosis from the ai-app-2-6d session: foundation already places scrolled
content with `placeRelativeWithLayer`, so the scroll is a layer transform
today and re-placing children was never the cost -- the node-count walk
is, and collapsing the runs is the experiment that settles it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>