fix(mobile): make swiping between days smoother
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.
reconcileCarouselis unchanged. - Size changes: a
ResizeObserverkeeps 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: nulltowatchScrollSettle, 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.tsxdrives 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](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
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.