fix(scroll): keep Android directional swipe endpoints inside the resolved scrollable

AI
2h ago
+353-25

Summary

Clamps Android directional scroll endpoints to the resolved scrollable (#2714), following the rule agreed with #1821: endpoints stay inside the intersection of the container and the safe bounds (still the 10% padding until #1821), and a shortened swipe reports pixels (dispatched) beside requestedPixels.

  • The daemon reads the container from the current stored tree before the gesture. There is no container for a stale tree, a horizontal scroll, or desktop/web.
  • buildScrollGesturePlan takes an optional container. A span that fits is unchanged.
  • Android composes it with the keyboard clip.

8 files. The planner vectors are TS-only, because the Swift twin doesn't clamp yet.

Not fixed yet: iOS, including the Oct 7 keyboard-clip repro. I'd like that as a stacked follow-up (Swift port, wire field, parity vectors). @thymikee, does that split work for you?

Validation

check:affected --run --head 95ba7e823 (exactCommit): format, lint, typecheck, layering, fallow and build pass. vitest-related has 856/857 files passing. The one failure is a new URL(import.meta.url).pathname test that breaks on this host's space-containing path, and it fails identically on main. Run directly after it: unit has 14,565 passing and 6 failures that also fail on main; provider-integration and eager-closure-budgets pass.

Live on Pixel_7_API_36 (tree on 0149438fa, before rebasing over #3736's top/bottom change). Settings list inset at y=480, scroll up 0.8:

  • main: drag starts at y=312 on the search bar → scroll_no_progress
  • branch: 480→2808, pixels 2328, requestedPixels 2496, moved

Screenshots and recordings are in the first comment.