iris: a scroll area whose content grew asks to be drawn again

Iris's phone: typing newlines into the composer with the keyboard up
dropped the caret flush against the bar's bottom edge, eating the 12dp
padding, and closing the keyboard fixed it.

`Scroll::draw` offers its child last frame's content length on purpose,
so an ordinary scroll tick is an O(1) move rather than a redraw. The
comment claimed the lag self-corrects on the next frame; nothing asked
for that frame. A keystroke dirties the field, that frame draws it in a
box one line short of its text, and the tree is clean afterwards -- so
the stale placement is the last one drawn. The composer's text is
centred in its box, so one line short hung half a line past each end and
put the caret's line box a whole padding low. Closing the keyboard
rewrote the bar's inset, dirtied it, and forced the missing redraw.

`Scroll::draw` now calls `Painter::draw_again` when what it measured
differs from what it offered, and a frame that leaves anything dirty asks
for another frame on both backends -- `draw_again` sets its mark during
the update, after the input path's own check has run, so nothing asked
before this (which applied to `List::clamp_to_content` too).

Verified at layer 1 (the new test fails on the old code with the caret
exactly on the bar's edge) and on the emulator: the caret's bottom moved
from 1535 -- the bar's own bottom edge -- to 1509, 26px inside a 31px
padding, the remainder being parley's line box overhanging its line
height. `phone.rs` grew `--typed TEXT`, which enters text over frames
rather than preloading it; only that reproduces this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-08 16:59:06 -04:00
1 parent 1a9655414e
commit ba57086361
10 files changed
+229 -9

No files matched your search

+15
View File
@@ -5,6 +5,21 @@ 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)
From Iris's phone report about the composer's padding while typing
newlines. 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.
## 2026-09-08 (every crate to its latest version, wgpu 28 -> 30)
At Iris's request. RUST.md's "Every crate to its latest version" box has
+43
View File
@@ -12,6 +12,49 @@ things still stay out.
An entry gives the date, what changed, why, and a short before/after where
it helps judge the change without the session that made it. Newest first.
## 2026-09-08 (later): a `Scroll` whose content grew asks to be drawn again
Iris's phone: "when typing with the keyboard up and entering enough
newlines ... the text drops down close to the bottom and seems to ignore
the padding. If I close (and optionally reopen) the keyboard it seems to
fix itself."
`Scroll::draw` offers its child **last** frame's content length rather
than measuring afresh, deliberately: an ordinary scroll tick then hands
the child the same box it already has, which is what makes a tick an O(1)
move instead of a redraw (LAYOUT.md sections 2 and 4). The comment there
said the lag "self-corrects the next frame". It does not, because nothing
asks for that frame: a keystroke dirties the *field*, the frame it
requests draws the field in a box one line short of its new text, and the
tree is clean afterwards -- so the stale placement is simply the last one
drawn. The composer's text is centred in its box, so a box one line short
put half a line past each end, and the caret's line box landed a full
12dp below the bar's inside edge, flush against its bottom. Closing the
keyboard rewrote the bar's inset, which dirtied it, which is why it
"fixed itself".
Two halves to the fix, and the second is the general one:
- **`Scroll::draw` calls `Painter::draw_again` when what it just measured
differs from what it offered** (by more than half a pixel). Costs one
extra draw when the content's length actually changes, and nothing on
an ordinary scroll tick, so the O(1)-move path is untouched. It
converges: the next draw offers the measured length and measures the
same thing again.
- **A frame that leaves anything dirty now asks for another frame**, on
both backends (`android::view`'s `post_frame_callback`,
`default`'s `request_redraw`). `draw_again` sets its mark *during* the
update, after the input path's own "does anything need redrawing?"
check has run, so before this nothing asked -- which quietly applied to
`List::clamp_to_content`'s use of the same mechanism too.
Covered by `a_newline_leaves_the_caret_inside_the_composers_padding`
(layer 1, `transcript-fixture/tests/phone_screen.rs`), which fails on the
old code with the caret exactly on the bar's edge. `phone.rs` grew a
`--typed TEXT` argument beside `--message`: the two are different cases,
since one lays the composer out from scratch and the other grows one
already drawn, and only the second reproduces this.
## 2026-09-08: what a cancel means, what a row's box is, and one fling for every scroll area
Iris's second 2026-09-08 report, from the bench on her phone. Four items,