fc82d9d7e8624bb67acf51d1e123ab3bc223ef7c
Three of the four defects in Iris's 2026-09-08 report, each with a layer-1 repro that fails without the change. **A gesture the platform takes away is now a cancel, not a release** (`CursorState::cancelled`, `SensorUi::run_sensors`). Android mapped `ACTION_CANCEL` onto the same arm as `ACTION_UP`, so the system's own swipe up from the bottom edge to leave the app reached iris as a flick released at speed and the transcript flung while the app was in the background -- "leaving and reopening the app also randomly moved the vertical scroll". A cancelled sample now delivers `CursorSense::Cancel` to the capture holder *and* every widget still tracking the press, clears both, and derives nothing else: no tap, no selection, no fling. The harness's `TouchAction::Cancel` says the same thing, so it is testable from a `.touch` file. **A `DragGesture` ignores a `Cancel` when it is the one holding the capture.** A cancel goes to every pressed widget that did not capture, and one gesture is routinely driven by several of those -- a transcript row's text block feeds `Selection`'s shared gesture, which captures under the *list's* id, so the block is a "loser" on the very frame its own pan committed. Acting on that released the pan the frame it started (`catch_a_fling.rs` fails without the guard). With it, a row's block can register the whole `drag_senses()` set, `Cancel` included, which is what the doc on that set has always said a widget driving a gesture must do. **A row whose measurement disagrees with the box it was offered is drawn again at its true box, this frame** (`List::place`, both placements). A row is offered its *cached* height and a `.background(rect(..))` fills whatever box it is handed, so on the frame a row changed height its text laid out at the new height and its background painted at the old one -- "collapsing and opening an edit card draws the card background a frame late, so it looks closed even when there's text". The bottom-anchored half used a `reposition`, which writes an offset and never a size, so it could not fix it either. **The nested-`Span` workaround in `tool.rs` is gone**, restoring the 4dp inset a tool group holds its cards off its edge by. "A `Span` of `Pad`ded children inside another `Span` places those children a slot out of step" is **not reproducible on 2026-09-08** -- verified both with `IRIS_TOOLS_EXPANDED=1 run-headless.sh transcript --shot` and with a new layer-1 test. Tests: `transcript-fixture/tests/gesture_cancel.rs` (three, including a real code fence pushed into the screen so the pan has something to capture it), `list.rs`'s `a_row_that_changes_height_draws_its_background_at_the_new_height_immediately`, `layout_tests.rs`'s `a_span_of_padded_children_inside_a_span_draws_each_where_its_box_is`. Each was confirmed to fail with the change backed out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Languages
Rust
53%
Kotlin
44.4%
Shell
2.6%