Skip to content

feat: reel UI improvements — pointer events, gestures, bottom row layout#40

Merged
GraysonCAdams merged 33 commits intomainfrom
feature/reel-ui-improvements
Mar 2, 2026
Merged

feat: reel UI improvements — pointer events, gestures, bottom row layout#40
GraysonCAdams merged 33 commits intomainfrom
feature/reel-ui-improvements

Conversation

@GraysonCAdams
Copy link
Copy Markdown
Collaborator

Summary

  • Pointer events migration: Feed gestures, ProgressBar scrubbing, ClipOverlay swipe-to-dismiss, and CommentRow long-press all migrated from touch/mouse events to unified pointer events with setPointerCapture for reliable multi-device behavior
  • Drag-to-dismiss sheets: BaseSheet now has a drag-to-dismiss gesture; ClipOverlay swipe-to-dismiss migrated to pointer events; sheets now block pull-to-refresh and bottom nav
  • Reel bottom row redesign: CommentPrompt and MusicDisc now live in a shared bottom-row flex container, replacing fragile absolute positioning; MusicDisc moved into ActionSidebar (replaces external-link for music clips)
  • Auto-scroll deferral: Auto-scroll now waits until the user finishes engaging with comments/reactions and fires after a 3 s cooldown; forceLoop keeps media looping during the wait
  • Scrub-seek optimization: Implements a "seek on seeked" state machine in ReelItem to prevent mobile seek queue buildup; ProgressBar tracks position locally for instant visual feedback
  • Compact GIF carousel: GifPicker gains a compact mode (horizontal scroll row) for use inside the comments sheet; CommentsSheet expands to full viewport height when the GIF picker is open
  • AddVideoModal in layout: Modal moved to app layout so the + tab opens it from any page, not just the feed
  • SkeletonReel polish: Added comment prompt skeleton bone; sidebar/overlay bones repositioned to match real layout
  • ClipOverlay slide-in animation: Entry animation guards against swipe-to-dismiss firing during the animation
  • Sheet drag zone expanded: Full header area (not just grip bar) is now the drag target for bottom sheets
  • Minor fixes: Comment input padding, comment sheet bg/pill theming, input focus on iOS keyboard, hide bottom nav when sheet is open

Test plan

  • Feed swipe up/down scrolls correctly on mobile and desktop
  • Scrubbing the progress bar follows finger immediately with no lag or jitter
  • Auto-scroll waits for comment/reaction sheet to close before advancing
  • MusicDisc appears in ActionSidebar for music clips, external-link button for video clips
  • Tapping + opens AddVideoModal from any page (feed, settings, etc.)
  • GIF picker in comment sheet shows horizontal row; sheet expands to full height
  • Drag-to-dismiss works on BaseSheet and ClipOverlay
  • Skeleton reel matches real reel layout (comment prompt bone visible)
  • All CI checks pass

- Add sheetOpen store that tracks the count of mounted BaseSheet instances
- BaseSheet increments/decrements the count on mount/unmount
- Bottom nav gets display:none via .sheet-hidden when anySheetOpen is true,
  eliminating the z-index stacking context conflict on iOS Safari
- Feed pull-to-refresh ignores touchstart events while a sheet is open,
  preventing the drag-to-dismiss gesture from triggering a feed refresh
- CommentsSheet auto-focus is now decoupled from the async comments fetch
  so the keyboard appears promptly after the sheet animation on iOS
move comment bar to bottom (nav+8px), progress bar above it (nav+36px),
captions/sidebar above progress bar (nav+92px). music disc aligns on
same row as comment bar. CommentPrompt extracted to own component.
ClipOverlay baseline simplified to safe-area-only.
Simplifies REACTIONS to a plain string[] and REACTION_MAP to a Set.
ReactionPicker now renders emoji chars directly instead of SVG components.
Removes the custom DoubleExclamationIcon SVG component.
Adds --reel-bg / --reel-bg-elevated tokens (always dark regardless of theme)
so empty/end states in the feed stay immersive. Applies html.feed-context on
the feed route (both at hydration and via inline script at cold start) so the
iOS black-translucent status bar shows the correct dark background color.
Adds /favorites route with a 3-column thumbnail grid. Tapping a cell opens
a full-screen reel with snap-scroll. Adds Faves tab to the bottom nav.
Improves theme-color sync to use --reel-bg on the feed vs --bg-primary elsewhere.
Favorites moved to its own dedicated route (/favorites). Removes the
'favorites' option from the feed filter bar and updates labels to New/Seen.
Adds a push notification enable button on the feed end-slide for users
who haven't enabled notifications yet.
Replace top: 50% / transform approach with a calculated top: 5px that
aligns the icon center with the text baseline regardless of UA-assigned
container height. Icons switch to bottom-anchored when input grows
past single-line.
Disabling user scaling is a WCAG 2.1 SC 1.4.4 violation. The existing
touch-action: manipulation already prevents double-tap zoom, making
user-scalable=no redundant and harmful for accessibility.
…ture

Replace split mouse + touch event handlers with a single pointer event
model. setPointerCapture ensures move/up events reach the element even
when the pointer leaves its bounds during a scrub, eliminating the need
for document-level mousemove/mouseup listeners. Add touch-action: none
so the browser yields all gestures on the scrubber to JS.
Replace ontouchstart/ontouchend/ontouchcancel with the pointer events
equivalent. Add onpointerleave to cancel the long-press popover when
the finger slides off the element, matching the touch cancel behavior.
Pull-to-refresh and horizontal filter swipe both used raw touch events
with passive:false touchmove to call preventDefault(). Replace with
pointer events (filtering to pointerType=touch for PTR). Drop the
preventDefault() calls — touch-action: pan-y on .feed-slide declares
that horizontal touches are owned by JS, so the browser yields without
needing an explicit cancel. Also remove deprecated
-webkit-overflow-scrolling: touch (iOS handles this automatically now).
Replace touchstart/touchmove/touchend with pointer events and remove
the preventDefault() on horizontal moves. Add touch-action: pan-y so
the browser knows horizontal touches are handled by JS, eliminating
the need for a non-passive listener.
Dragging the handle bar downward now follows the pointer in real time
(transition disabled during drag for responsiveness). Releasing past
120px dismisses the sheet; releasing short of the threshold snaps it
back. A small movement (<5px) is treated as a tap and also dismisses,
preserving the existing click-to-close behavior. touch-action: none on
the handle prevents browser scroll interference during the drag.
Replace the calculated top offset with transform: translateY(-50%) for
cleaner vertical centering. The multi-line override resets the transform
and pins to the bottom as before.
Wrap the handle bar and title header in a single drag zone so users can
initiate a drag-to-dismiss gesture from anywhere in the card header.
Uses lazy pointer capture — the drag is only committed (and the pointer
captured) after 6px of clearly-downward movement, so tapping the title
text does nothing. Tapping the grip bar still dismisses via its own
onclick handler.
… entry

The overlay now slides in from the right (matching the left-swipe-to-dismiss
direction), making the gesture feel like a native navigation push/pop.

A 300ms isEntering guard prevents accidental swipe tracking while the CSS
entry animation is running, avoiding a jarring jump if the user touches
the screen immediately after the overlay appears.
Glyphs sit above the geometric midpoint of their line box, so a slightly
larger top padding (11px vs 7px) shifts the perceived text center down to
align with the vertically-centered icons.
Previously AddVideoModal was rendered only inside the feed page, so the +
tab on non-feed pages navigated away. Now it lives in the layout and the +
tab always opens the modal regardless of the current page.
…ions

Adds a pill-shaped comment prompt bone at the bottom so the skeleton
matches the real reel layout. Also moves the sidebar and overlay bones
upward to leave room above the comment bar.
… opens

GifPicker gains a compact=true mode that renders a horizontal scrolling
carousel instead of the masonry grid. CommentsSheet switches to full-dvh
height (with an animated transition) when the GIF picker is open so the
grid is never hidden behind the keyboard.
…ayback

- MusicDisc moves from absolute-positioned overlay into ActionSidebar,
  replacing the external-link button for music clips and removing the
  need for a separate uiHidden prop
- CommentPrompt switches from absolute to flex child of a new .bottom-row
  container so the prompt and disc sit on the same baseline
- Auto-scroll defers when the user is engaged (comments/picker/viewers)
  and fires after a 3 s cooldown when engagement ends; forceLoop keeps
  media looping during that window
- Scrub-seek uses a "seek on seeked" state machine to prevent mobile seek
  queue buildup; ProgressBar tracks displayTime locally for instant visual
  feedback independent of video decode lag
@GraysonCAdams GraysonCAdams merged commit 8e7ac3c into main Mar 2, 2026
17 checks passed
@GraysonCAdams GraysonCAdams deleted the feature/reel-ui-improvements branch March 2, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant