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:
1 parent
e922b73d7a
commit
690161e5e9
3 files changed
+134
-5
No files matched your search
+55
-3
@@ -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
|
||||
|
||||
+54
-2
@@ -43,6 +43,52 @@ gated on her verdict**, so this pass works the P0 defects and the pure
|
||||
prerequisites in this order. Each item is ticked here by the agent that
|
||||
closes it.
|
||||
|
||||
### The transcript's edges (2026-09-07, e922b73 + d507ae4)
|
||||
|
||||
Iris's two screenshots of the top edge -- rows drawn over the header in
|
||||
one, a blank band in the other -- were **three** faults, and the rule
|
||||
that fixes all three is the one the IRIS_TODO entry asked for: *a row is
|
||||
drawn if any part of it overlaps the list's own box, and nothing outside
|
||||
that box reaches the screen* (`List::intersects_viewport`). Neither
|
||||
suspected cause was right, which is worth reading before trusting the
|
||||
next suspicion in this file: there was no visible-range test comparing a
|
||||
row's top against the viewport's, and `03c6be8`'s header duplicate is
|
||||
untouched and still open.
|
||||
|
||||
What was actually wrong: **nothing clipped the list at all** (and it
|
||||
could not be `.masked()`, because `Painter::set_mask` aborted whenever
|
||||
an ancestor had a mask and the list's own rows use one -- so masks
|
||||
nest now, `Mask::parent`, walked in the fragment stage); **the layout
|
||||
walk drew every row between the anchor and the viewport**, which after
|
||||
panning is however far you have panned, measured at 64 rows placed for
|
||||
a 2012px viewport; and **the list could rest past its own first row**,
|
||||
which is the blank band, and is also the first item of Iris's later
|
||||
report the same day. Details and the six layer-1 test names are in
|
||||
docs/IRIS_TODO.md's 2026-09-07 entry.
|
||||
|
||||
Three things this says about the rig, since the rig is new:
|
||||
|
||||
- **Layer 1 found all of it, in seconds.** The tests open the real
|
||||
screen over the real fixture under a bench-app-shaped header
|
||||
(`iris/transcript-fixture/tests/top_edge.rs`, `cargo test -p
|
||||
transcript-fixture`, ~5s), and each was confirmed to fail on its own
|
||||
subject and no other by breaking that subject on purpose. The
|
||||
emulator was not used, and the phone will only be asked to confirm.
|
||||
- **Layer 2 is where the clip is visible.** `iris/run-headless.sh phone
|
||||
--phone --replay transcript-fixture/touch/flick-120hz.touch --shot
|
||||
/tmp/p.png -- -p transcript-fixture`, run with `.masked()` removed,
|
||||
draws the bottom row's text over the composer bar; with it, the same
|
||||
flick clips cleanly at the bar. The window has no header, so the top
|
||||
edge is the window edge there -- the header case is layer 1's.
|
||||
- **An assertion that reads the wrong thing hides the bug it is for.**
|
||||
`fling_toward_the_start_stops_at_the_first_row` asserted the first
|
||||
row's top was `>= -0.5` while that row sat 1398px *below* a 600px
|
||||
viewport with the screen blank: `extents` then held rows that were
|
||||
not on screen, so the read was satisfied by the failure. `extents`
|
||||
now holds only what is on screen -- which is what `key_at` always
|
||||
claimed of it -- asserted at the end of every draw, and the test
|
||||
checks both directions.
|
||||
|
||||
### APK size (2026-09-07)
|
||||
|
||||
Iris's question: the iris bench APK is about double the Compose bench APK
|
||||
@@ -223,9 +269,10 @@ closes it.
|
||||
- [x] Test rig, layers 1 and 2 ("Three test layers" below), landed 2026-09-07.
|
||||
- [ ] Fling parity with Compose, and the phone's keyboard push-up, with
|
||||
insets shown in the diagnostics overlay. Running, in a worktree.
|
||||
- [ ] Rows at the transcript's top edge: culled too early in one state,
|
||||
- [x] Rows at the transcript's top edge: culled too early in one state,
|
||||
drawn through the header in the other (docs/IRIS_TODO.md, 2026-09-07).
|
||||
First after the rig lands, using its layer-1 harness.
|
||||
Done 2026-09-07, e922b73 + d507ae4; the root causes and the test names
|
||||
are in that IRIS_TODO entry, and the short version is below.
|
||||
- [ ] Phone logging through Dev Updater (Iris has no logcat; see
|
||||
docs/TODO.md and the memory note): research how Dev Updater shows an
|
||||
app's runtime log, design the smallest route (the app keeps its own
|
||||
@@ -249,6 +296,11 @@ closes it.
|
||||
2026-09-07)". A mask references a primitive already drawn
|
||||
(rect SDF, texture or glyph alpha), chained and multiplied; `.masked()`
|
||||
points at the widget's own primitives; hit-testing applies the shape.
|
||||
**Chaining landed early**, 2026-09-07 (d507ae4): a mask carries the
|
||||
mask it was set inside and the fragment stage walks that chain, so
|
||||
nesting works and `Painter::set_mask` no longer aborts on it. Still
|
||||
rectangles only -- the shape half, and the hit-testing half, are what
|
||||
is left of this item.
|
||||
- [ ] Compose app: the `Reversed range` crash in `ToolInput.highlighted`
|
||||
(docs/TODO.md). Main branch, not rustify.
|
||||
|
||||
|
||||
Reference in new issue
Block a user