fix(mobile): make swiping between days smoother

Merged
OC
2d ago
+509-367

The daily carousel moved with Embla. Embla drags and animates the belt with JS on the main thread. When a new neighbour's editor mounts during the snap, the belt stops, then jumps. Its rAF animation is also capped at about 60 fps in WKWebView.

This PR replaces Embla with a native horizontal scroller (overflow-x: auto, scroll-snap-type: x mandatory, scroll-snap-stop: always on each day cell). The browser now does the drag, the momentum, and the snap, so a busy main thread no longer stops the carousel. Nothing in the day area handles touches in JS, and Embla's non-passive touchmove listener is gone from it. The week strip still uses Embla.

How it works

  • Mid-swipe: when a user scroll passes half a page, the nearest day changes. The carousel then mounts the next day and calls onTarget, so the week strip follows. The mounted range only grows until the settle, so a drag back and forth over the middle mounts nothing twice.
  • Settle (watchScrollSettle):
    • The carousel adopts the nearest day and aligns to it if WebKit stopped between two snap points.
    • It drops the slides outside the range and reports the day (route navigation).
    • It re-anchors the window near an edge, as before.
    • It blurs an editor that is still focused on another day.
  • Scrolls that no finger or wheel started (a caret reveal, a focus) are pulled back to the selected day.
  • External selection (strip tap, Today, date link): an instant scroll in a layout effect. It also cancels any momentum. reconcileCarousel is unchanged.
  • Size changes: a ResizeObserver keeps the same day on rotation and split view.
  • Software keyboard: while it is up, the scroller is overflow-x: hidden.
  • No mouse drag: the carousel passes dragHandle: null to watchScrollSettle, a new option value that starts no mouse drag. Nearly every slide is an editor, where a mouse press selects text.

Behaviour changes

  • iOS gives a swipe to the slide's vertical scroll when it is more than about 5 to 10° off horizontal. Embla took anything within 45°.
  • Code blocks and tables now scroll horizontally under a finger. A swipe that starts on them does not change the day.
  • A two-finger trackpad swipe on iPad changes the day.
  • A mouse drag does not change the day. A trackpad or a horizontal wheel does.

Testing

  • use-day-carousel.test.tsx drives the real carousel with native scrolls, not a mocked Embla. CI runs it on Chromium. The WebKit job is skipped while the PR is a draft.
  • iOS Simulator Safari against the dev server:
    • Each swipe moves one day.
    • A fast fling moves one day.
    • Today and a swipe right after it work.
  • Not yet checked on a device or in the app's WKWebView. Check there that a strip tap during a fling cancels the momentum.

Written by Claude Opus 5.5.


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

Summary by Sourcery

Make mobile day navigation smoother by moving swipe, momentum, and snapping from Embla into the browser’s native horizontal scroller.

New Features:

  • Replace the mobile day carousel’s JavaScript-driven Embla belt with a native scrolling and CSS snapping experience.
  • Keep the calendar strip synchronized with the destination day during an in-progress swipe.

Bug Fixes:

  • Prevent editor mounting, main-thread workload, caret reveals, and software keyboard interactions from interrupting or misaligning day swipes.
  • Preserve the selected day across viewport resizing and correctly handle interrupted, off-grid, external, and edge-reaching scrolls.

Enhancements:

  • Add support for disabling mouse dragging in scroll-settle watchers while retaining trackpad and native horizontal scrolling.
  • Expand carousel mounting and settle behavior to support native scroll momentum, range management, focus cleanup, and route synchronization.

Tests:

  • Replace mocked Embla carousel tests with browser integration tests driven by native scrolling and touch behavior.
  • Add coverage for mouse-drag suppression, interrupted swipes, external navigation, recentering, non-user scrolls, resizing, keyboard visibility, focus cleanup, and off-snap settling.

Summary by CodeRabbit

  • New Features
    • Mobile day navigation now uses native horizontal scrolling with snap-to-day behavior.
    • The highlighted day updates as you swipe past the midpoint between days, and the final selection is confirmed when scrolling settles.
    • Nearby days remain available during a swipe, while navigation and resizing preserve the selected day.
    • Horizontal scrolling is locked while the keyboard is visible.
    • Interrupted swipes and changes to the selected day stay synchronized with navigation.