iris: pin the nested-scroll axis rule, and record the capture fix in RUST.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-08 13:46:39 -04:00
1 parent b863f9f3df
commit 8310431497
2 files changed
+139

No files matched your search

+59
View File
@@ -7885,6 +7885,65 @@ arbiter may never have seen the `press_start` (`DragArbiter::update`'s doc
records that recovery path for `Selection`). Say what the arbitration
actually is rather than adding a second recovery.
#### Done, 2026-09-08 (b863f9f and the commit after it)
Both symptoms were one thing each, and neither was the arbitration: the
axis test in `DragArbiter` was already right, so a vertical drag on a
fence always did fall through. What was wrong was **how a gesture ends**.
- **The snap back while scrolling horizontally.** `scrollable_on`
registered `click_or_drag() | unclick()`. A `Scroll` that commits to a
pan takes pointer capture, and a captured widget's gesture ends with
`CursorSense::Drop` in place of `PressEnd` -- which `should_run` only
matches for a widget that registered `Drop`. It had not, so it never
learned its own gesture had ended: the `DragArbiter` stayed `Panning`
with `last` at the position the finger left, and the *next* drag's
first frame was measured from there and applied in one step. Now
`CursorSense::drag_senses()` (frames + `unclick` + `Drop` + `Cancel`)
is what every widget driving a `DragGesture` registers, stated once
rather than per call site. Test:
`a_scroll_area_that_captured_the_pointer_learns_its_gesture_ended`,
which fails with the old registration reporting exactly the symptom
("a fresh touch-down moved the content by -40").
- **The jump on a tap.** Taking pointer capture is a one-way door for
everybody else: from that frame `run_sensors` delivers to the capturer
alone, so anything else tracking the same press sees no `PressEnd` and
no `Drop` either. The transcript's own `DragGesture` was therefore left
`Undecided` at the origin of the gesture the fence stole, and the next
touch anywhere on screen was measured from it -- `Undecided`'s slop
test crossed instantly, and the list panned by the distance between two
unrelated fingers. `CursorSense::Cancel` is the state that was missing:
delivered exactly once to each loser of a capture race, the way Android
sends `ACTION_CANCEL` and the web sends `pointercancel`. It is a
separate sense from `Drop` deliberately -- `Drop` means "your gesture
finished" and callers act on it (a fling, a tap, a link followed),
which is precisely the wrong thing here. `GestureOutcome::Cancelled`
is its half of the enum, and `attr.rs`'s `press_track` registers it
too, since a `press_origin` left set has the same failure (a stray
selection, or a keyboard summoned by a tap somewhere else). Test:
`taking_the_pointer_cancels_everyone_else_tracking_the_press`.
- **The direction the change had no reason to touch**, pinned so it stays
true: `a_drag_pans_whichever_nested_scroll_area_owns_its_axis` drives a
real X area inside a real Y one, both through `scrollable_on`'s own
registration, and checks each drag moves one and leaves the other at 0.
**Where the pointer's state lives now.** Iris, on seeing this work:
"never reach for atomics or locking stuff first, and if you genuinely
think it's necessary, bring it up with me first", and "everything global
should be stored in the general input handler, not in specific senses
with locking stuff." So the captured widget and the pressed set are no
longer a `std::sync::Mutex` on `UiRenderState` (a structure that merely
happened to be reachable from every handler). They are `PointerInput`,
the cursor senses' `Event::Global` -- owned by the event manager that
runs the dispatch and reached by `&mut`, with `run_sensors` taking it out
for the length of a pass the same way it already takes the active-sensor
map, and a per-dispatch `PointerRequests` slot for what a handler asks
of it. What had forced the lock in the first place was a `Data: Send`
bound on `task_on` that nothing needs: the future it spawns takes
`|_, rsc|` and never sees the event's data at all. **Still a `Mutex` and
worth revisiting: `UiRenderState::last_input_at`**, for the same
`&self` reason, untouched here.
### 2. "Opening the folded cards seems pretty buggy right now" -- and the
### sanity check she asked for