iris: a Scroll measures and places its content in the same frame

Follows Iris on the previous commit: "nothing in the framework should
ever self heal because it should not be drawn incorrectly in the first
place. If you need 2 draws to get something into the correct position
then that should happen within the same frame. Layout should never be
frame dependent, it should be a pure function of the state."

So `Scroll::draw` no longer places its child against last frame's
content length and asks for a corrective frame. It draws the child once
at that length purely to measure it, then places it at the length just
measured, with the end-pin and the clamp applied only to the second
placement -- the measure-then-place idiom `Span::draw` and `List::place`
already use. Last frame's length survives as a hint that keeps the
common case cheap: when the content's length did not change the two
regions are identical, so the first call is `draw_inner`'s O(1) `mov`
and the second returns at its first line. Nothing drawn depends on the
hint.

Reverts the frame-loop change from the previous commit (a frame that
left anything dirty asked for another), which existed only to deliver
that corrective frame and would have made any widget marking itself
dirty spin at full rate.

Knock-on: an end-anchored Scroll now sits at its end on its first drawn
frame rather than its second, since the end-pin no longer waits for a
length. Two layout tests that scroll down from what they assumed was the
top now build their area with `at_end: false`, which is what they meant.

`List::clamp_to_content` is the only next-frame correction left. Its
comment cited Scroll's lag as precedent, which no longer exists; it now
says it is a deviation from the rule, and docs/IRIS_TODO.md carries it.

Verified: the layer-1 test draws no settling frame and still passes; on
the emulator the caret's bottom is 1509 against a bar edge of 1535, 26px
inside a 31px padding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-08 17:12:26 -04:00
1 parent ba57086361
commit a00376994e
9 files changed
+181 -131

No files matched your search

+16 -11
View File
@@ -5,20 +5,25 @@ they can be judged and reversed later. Detail lives in RUST.md (and IRIS.md
for iris API changes); this file is only the summary. Newest first. Items
marked **DEFERRED** are ones the agent chose not to decide alone.
## 2026-09-08 (later: a scroll area that grew redraws once)
## 2026-09-08 (later: a scroll area measures and places in one frame)
From Iris's phone report about the composer's padding while typing
newlines. IRIS.md's entry has the account.
newlines, and the rule she stated when she read the first fix: layout is
a pure function of the state, nothing self-heals, and two draws to place
something happen in the same frame. IRIS.md's entry has the account.
- **A `Scroll` whose measured content length differs from the length it
offered its child asks for one more draw** (`Painter::draw_again`),
rather than the stale box being the last one drawn. Pays one extra
draw of the scroll's subtree when the content's length changes --
including its first frame, where the offered length is a placeholder --
and nothing on an ordinary scroll tick.
- **A frame that leaves any widget dirty now requests another frame** on
both backends. Previously only an animation did, so any use of
`draw_again` depended on some later input to deliver its correction.
- **`Scroll::draw` draws its child twice** -- once at last frame's length
to measure it, once at the measured length to place it -- instead of
placing against the stale length and leaving a wrong frame on screen.
The second draw is free unless the content's length changed.
- **An end-anchored `Scroll` is at its end on its first drawn frame**, a
consequence of the above. Two layout tests now build their area with
`at_end: false`, which is what they meant: they scroll down from the
top.
- **`List::clamp_to_content`'s next-frame correction is left in place**
and written down in docs/IRIS_TODO.md instead of fixed here, because
`List::place` is a larger piece of machinery and deserves its own
before/after on the phone.
## 2026-09-08 (every crate to its latest version, wgpu 28 -> 30)