docs: the transcript's edges were three faults, and what the rig found

IRIS_TODO's 2026-09-07 top-edge entry closed with the root cause of
each, the six layer-1 test names, and what was suspected and turned out
not to be it -- no culling test compared a row's top against the
viewport's, and 03c6be8's header duplicate is untouched and still open.
The later report's "you shouldn't be able to scroll below the bottom (or
above top)" is ticked with why the clamp is a correction measured from
the layout walk rather than a clamp inside the scroll setter: nothing at
the moment of a scroll knows where the content ends.

RUST.md gains the same account in "Where things stand", plus the three
things this said about the new test rig -- layer 1 found all of it in
seconds and the emulator was not used; layer 2 is where the missing clip
is visible, with the command; and an assertion that reads the wrong
thing hides the bug it is for, which is how a list resting 1398px past
its own first row passed a test about stopping at that row.

Also the last of the six tests, the bottom end of the clamp
(`scrolling_past_the_last_row_settles_on_it`) -- the same rule at the
edge the top-edge work had no reason to touch.
This commit is contained in:
iris committed 2026-09-07 16:07:37 -04:00
1 parent e922b73d7a
commit 690161e5e9
3 files changed
+134 -5

No files matched your search

+55 -3
View File
@@ -934,7 +934,7 @@ do not duplicate it there.
## From the phone, 2026-09-07 (build from ed04d4c)
- [ ] **"Some transcript blocks will be hidden until I uncover enough of
- [x] **"Some transcript blocks will be hidden until I uncover enough of
them."** Two screenshots of the bench app's transcript at the top
edge, both wrong in opposite directions: in one, rows scrolled above
the viewport are still drawn and bleed *through* the header bar
@@ -956,10 +956,62 @@ do not duplicate it there.
drawn if any part of it intersects the viewport, and the viewport is
the list's own region.
**Done, e922b73 + d507ae4.** Three causes, and the rule above is what
they are all fixed with (`List::intersects_viewport`).
`iris/transcript-fixture/tests/top_edge.rs` is the layer-1
reproduction -- the real screen under a bench-app-shaped header --
and each test was confirmed to fail on its own subject and no other.
1. *Drawn over the header*: **nothing was clipping the list at all**,
and a row straddling an edge is drawn in full, so the part above
the list was on screen. It could not be `.masked()` before, either:
`Painter::set_mask` aborted when an ancestor already had a mask,
and the list's own rows use `.masked()` (a code fence, a tool
card's title). So masks nest now -- `Mask::parent`, walked in the
fragment stage, chained rather than intersected on the CPU because
each mask moves with its own widget. `the_list_is_clipped_to_its_
own_box`.
2. *Rows already scrolled past still drawn*: the layout walk runs from
the anchor, `scroll` moves the anchor's offset and nothing else, so
panning leaves the anchor's row further and further outside the
viewport and **every row between it and the viewport was drawn,
every frame** -- measured at 64 rows for a 2012px viewport after 8
scrolls of 3000px. `place` skips a row whose known box does not
overlap, and `rehome_anchor` puts the anchor back on a visible row
each frame without moving anything drawn.
`rows_that_have_left_the_viewport_are_not_drawn`.
3. *The blank band*: not a culling rule at all -- the list could rest
**past its own first row** (`fling_toward_the_start_stops_at_the_
first_row` was leaving it 1398px below a 600px viewport, a blank
screen, and that test's own assertion could not see it).
`clamp_to_content` gives the gap back. Both ends:
`scrolling_past_the_first_row_settles_on_it`,
`scrolling_past_the_last_row_settles_on_it`. This is also the first
item of the later report below.
What was suspected and is *not* what happened: the visible-range test
never compared a row's top against the viewport's top (there was no
culling test at all), and `03c6be8`'s header duplicate is untouched by
any of this -- it stays open. A row straddling the top edge is drawn
both before and after; the test that would catch that mistake
(`the_row_across_the_top_edge_is_drawn`) is in place, and fails if the
rule is written against the row's top instead of its bottom.
## From the phone, 2026-09-07, later (build from 4274b8b, ai-app-bench b47eb73)
- [ ] **"You shouldn't be able to scroll below the bottom (or above
top)."** The list's offset is not clamped to its content range while
- [x] **"You shouldn't be able to scroll below the bottom (or above
top)."** Done in e922b73, as `List::clamp_to_content` rather than as a
clamp inside the scroll setter: nothing at the moment of a `scroll`
call knows where the content ends (that is what walking the rows finds
out), so the correction is measured from the ends the layout walk
already placed and written to the anchor. In the app that lands in the
same frame -- a scrolled list is dirty, and `redraw_updates` drains
the mark the correction sets before the frame is submitted -- so
nothing displaced is displayed; only a full-tree redraw (a resize)
could show one frame of it. A fling that reaches an end already ends
there (`tick_fling`'s `hit_bound`), and now stops *on* the end rather
than wherever the spline's last step had put it. Layer-1 tests at both
ends, listed in the item above. The list's offset is not clamped to its content range while
dragging and/or flinging. Compose's `LazyColumn` never moves content
past its ends -- the overscroll *effect* on Android 12+ is a stretch
drawn over clamped content, not a displacement. Clamp the offset in