Skip to content

fix(mobile): stop horizontal recognizers from hijacking diagonal vertical scrolls - #14601

Merged
dylanjeffers merged 3 commits into
mainfrom
fix/mobile-diagonal-scroll-hijack
Sep 24, 2026
Merged

dylanjeffers merged 3 commits into
mainfrom
fix/mobile-diagonal-scroll-hijack

Conversation

@dylanjeffers

Copy link
Copy Markdown
Contributor

Problem

Vertical scrolls with some sideways drift either do nothing or peek a screen/drawer and snap back. Most noticeable on Weekly Rotation, but it affects every nested screen and the root tabs. Three full-screen horizontal recognizers compete with the list's scroll:

  1. Nested screens — since Fix mobile drawer and screen navigation #14205 (animation: 'simple_push' + customAnimationOnGesture), react-native-screens uses its own RNSPanGestureRecognizer for full-screen swipe-back. Upstream gestureRecognizerShouldBegin: has no direction test, and once it begins it refuses to run alongside the scroll view's pan. A drag whose first ~10pt has any rightward component gets claimed as a back gesture. Before Fix mobile drawer and screen navigation #14205 iOS 26 used Apple's interactiveContentPopGestureRecognizer, which yields to scroll views — that's the "it used to work" state.
  2. Root tabs — the drawer navigator's pan spanned the full screen (swipeEdgeWidth: SCREEN_WIDTH) with hardcoded 5pt/5pt thresholds, so it won any touch that moved sideways first.
  3. Feed / For You — the drawer-opener pan from fix(mobile): restore left nav drawer swipe on the feed #14483 had no failOffsetY, so 20pt of rightward drift anywhere in a long vertical drag opened the drawer.

Changes

  • patches/react-native-screens+4.18.0.patch (native — needs an App Store release, not OTA): RNSPanGestureRecognizer may only begin on a committed horizontal drag — ≥5pt toward dismissal and ≥2× the cross-axis travel — evaluated once at the pan's activation threshold. Clean horizontal swipes still pop; anything diagonal stays with the scroll view.
  • useOpenDrawerGesture (OTA-able): one direction-locked rightward pan (activeOffsetX 20, failOffsetY ±12) that opens the left drawer from mid-screen. Used by Feed (replacing its inline pan) and wrapped around every tab's stack in AppTabScreen, gated to root + focused + not-now-playing.
  • AppDrawerScreen (OTA-able): the navigator's own swipe is kept to a 40pt left edge; swipe-to-close is unaffected (swipeEdgeWidth only applies while closed).

Verification (iOS simulator, Release builds, iPhone 17 Pro / iOS 26.5)

Reproduced with a synthetic "thumb-arc" drag on the Weekly Rotation page — 16ms samples starting ~15° from horizontal for the first ~13pt, then straightening to vertical — starting on a loaded tile:

Drag Unpatched This branch
Thumb-arc zero scroll; header icon circles vanish (interactive pop began and snapped back) scrolls ~3 tiles
30° fine-grained diagonal ~185pt, no fling ~310pt with fling
Fast coarse diagonal scrolls scrolls

Also on this branch: Feed Latest ⇄ For You paging, mid-screen drawer open on For You and Trending, drawer swipe-close, Track back-swipe, and diagonal scrolls on Feed/Trending/Track all behave.

Fast drags never show the bug on either build (both recognizers cross their thresholds in one event and the scroll view wins the tie); it's the slow curved start that gets claimed.

Notes

  • Debug simulator builds abort on react_native_assert(attempts < 1024) in ShadowTree::commit (via Reanimated's mount hook) when touching tiles — unrelated to this change, but it makes Debug useless for testing this; use Release.
  • Xcode 27 refuses the pods with deployment targets < 15; that needs a post_install bump in the Podfile and will come separately.
  • Podfile.lock drift from pod install is intentionally not included.

🤖 Generated with Claude Code

…ical scrolls

Three full-screen horizontal recognizers competed with list scrolling, so a
vertical scroll with some sideways drift either did nothing or peeked a
screen/drawer and snapped back:

- Nested screens: react-native-screens' RNSPanGestureRecognizer (in use since
  #14205) has no direction test. Commit the patch that requires a committed
  horizontal drag (>= 5pt toward dismissal, >= 2x the cross-axis travel).
  Native change: ships with the next binary, not OTA.
- Root tabs: the drawer navigator's pan spanned the full screen with fixed
  5pt thresholds. Keep it to a 40pt edge and open from mid-screen with a new
  angle-gated pan (useOpenDrawerGesture) around each tab's stack.
- Feed: the For You drawer pan had no failOffsetY, so 20pt of rightward drift
  anywhere in a long vertical drag opened the drawer. It now uses the shared
  angle-gated gesture.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: bab3931

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

dylanjeffers and others added 2 commits September 23, 2026 16:47
Shorten the drawer/swipe-back comments and fix the Feed comment that still
described the drawer's swipe edge as full screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…croll-hijack-wt

# Conflicts:
#	packages/mobile/src/screens/app-screen/AppTabScreen.tsx
@dylanjeffers
dylanjeffers merged commit 6c4ea8e into main Sep 24, 2026
3 checks passed
@dylanjeffers
dylanjeffers deleted the fix/mobile-diagonal-scroll-hijack branch September 24, 2026 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant