fix(studio): iPhone Safari layout + touch-drag scrubber#308
fix(studio): iPhone Safari layout + touch-drag scrubber#308miguel-heygen merged 2 commits intomainfrom
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
jrusso1020
left a comment
There was a problem hiding this comment.
Nice scoped fix. The 100dvh with 100vh fallback, viewport-fit=cover + safe-area inset pairing, h-screen → h-full cascade, and Pointer Events migration with touch-action: none on the scrubber (not page-wide) are all textbook. Appreciate the explanatory comment blocks on the CSS and touch-action decisions — exactly the kind of context the next person maintaining this will need.
Two small concerns inline, both minor. Unblock on your call — happy to ship this and follow up.
One item from the test plan to flag: the Android Chrome sanity pass is still unchecked. Pointer Events are well-supported there, but env(safe-area-inset-bottom) semantics differ from iOS (0 on most devices). Worth two minutes in Chrome DevTools device mode or on a physical Android before merge — the controls row will look a touch tighter at the bottom on gesture-bar Android devices if the inset is 0.
…focus Addresses #308 review feedback from @jrusso1020: - visibilitychange + window blur: synthesize drag cleanup when the page is backgrounded mid-drag. iOS Safari does not reliably fire pointercancel on alt-tab / incoming call / app switch, which would leave isDraggingRef latched true until the next pointerdown. - Restore focus on pointerdown via e.currentTarget.focus() after the preventDefault() call, so the click-then-arrow-key seek workflow works without requiring an explicit Tab first. - Clarify the releasePointerCapture try/catch comment so the next reader does not chase the second-invocation no-op throw as a bug.
jrusso1020
left a comment
There was a problem hiding this comment.
Walked through 1a21d93. All three prior concerns addressed cleanly.
Prior concerns — resolved
-
Mid-drag cleanup on background/blur. Shared release extracted into a
cleanup()closure;visibilitychange+ windowblurboth call it. Cleanup removes its own listeners, so repeat invocation is a no-op. Covers the iOS alt-tab / incoming-call / app-switch paths wherepointercanceldoesn't fire. -
Click-to-focus regression.
e.currentTarget.focus()afterpreventDefault()restores the click-then-arrow-key workflow. Comment explains the native<input type="range">divergence and why we're intentionally diverging. -
Comment ambiguity on
releasePointerCapturethrow. Rewritten to read as intentional rather than a latent bug.
One non-blocking concern introduced by the delta
Listener leak if the component unmounts mid-drag. cleanup is registered on document (visibilitychange) and window (blur) and removes itself only when invoked. If PlayerControls unmounts while a drag is active (route change, React StrictMode double-mount in dev), neither listener fires, so the closures — holding target (now detached) and pointerId — linger on window / document until the next visibility or blur event. Practically harmless since one of those fires eventually, but it's a detached-node reference in the meantime.
Cheap fix if you want to close it: track the active drag's cleanup on a ref and call it from a useEffect return, or gate on isDraggingRef at unmount. Ship as-is and follow up is also fine.
Test plan
Android Chrome sanity pass still unchecked — acknowledged in your reply. Trust-but-verify before merge.
Approve.
Merge activity
|
Three mobile UX bugs that made the studio unusable on iPhone Safari. Untappable Play button / bottom controls ---------------------------------------- - #root used 100vh. iOS Safari reports 100vh as the largest viewport (toolbar hidden) and never shrinks it, so with the toolbar visible the bottom of the layout sat under it. Play button + timecode were occluded. Switched to 100dvh with 100vh fallback; old browsers keep the existing behaviour. - App.tsx's two h-screen containers updated to h-full so nested children fill the now-dynamic #root instead of asserting 100vh and overflowing it. - Added viewport-fit=cover to the viewport meta so iOS exposes real env(safe-area-inset-bottom) values to CSS. - PlayerControls bottom padding now calc(0.5rem + env(safe-area-inset-bottom)) so the controls clear the landscape home indicator / curved bottom edge. No-op on desktop. Scrubber not draggable by touch ------------------------------- - Seek bar only had onMouseDown — iOS Safari fires Pointer Events for touches and nothing responded. Replaced with onPointerDown + setPointerCapture so one handler covers mouse / touch / stylus and drags keep tracking when the finger leaves the 6px hit zone. - Added pointercancel handler so a home-indicator swipe doesn't wedge the drag in progress. - touch-action: none (was 'manipulation') so Safari doesn't grab horizontal swipes for back-navigation while the user scrubs left. - Window-level pointerup fallback defends against older WebKit builds that don't honour setPointerCapture. Verified live on iPhone via cloudflared tunnel on the factory-series-c-video repro: Play button tappable, scrubber drags smoothly left/right with touch, landscape home indicator no longer overlaps the controls.
…focus Addresses #308 review feedback from @jrusso1020: - visibilitychange + window blur: synthesize drag cleanup when the page is backgrounded mid-drag. iOS Safari does not reliably fire pointercancel on alt-tab / incoming call / app switch, which would leave isDraggingRef latched true until the next pointerdown. - Restore focus on pointerdown via e.currentTarget.focus() after the preventDefault() call, so the click-then-arrow-key seek workflow works without requiring an explicit Tab first. - Clarify the releasePointerCapture try/catch comment so the next reader does not chase the second-invocation no-op throw as a bug.
9afafc5 to
c49181f
Compare
1a21d93 to
db1f696
Compare

Stacked on top of #307. Fixes three mobile UX bugs that made the studio unusable on iPhone Safari — discovered while testing the audio-ownership work from #307 on a physical device.
Bugs fixed
1. Untappable Play button / bottom controls
#rootwas set to100vh. iOS Safari reports100vhas the largest viewport (toolbar hidden) and never shrinks it — so with the toolbar visible, the bottom of the layout sits under it. The Play button + timecode were fully occluded.2. Scrubber not draggable by touch
The seek bar had only
onMouseDown. Mouse events don't fire for touches on iOS Safari, so nothing responded. You could tap to jump but not drag.3. Safari's horizontal swipe hijacked scrubber drags
Even when the seek bar caught
pointerdown,touch-action: manipulationstill let Safari consume horizontal edge-swipes for back-navigation — dragging the scrubber left was impossible.What changed
packages/studio/src/styles/studio.css#root→height: 100dvhwith100vhfallback. Dynamic viewport height shrinks when the iOS toolbar is visible, so the bottom of#rootlines up with the visible area.packages/studio/src/App.tsxh-screencontainers →h-fullso nested children fill the now-dynamic parent instead of asserting100vhand overflowing.packages/studio/index.htmlviewport-fit=coverso iOS exposes realenv(safe-area-inset-bottom)values.packages/studio/src/player/components/PlayerControls.tsxpadding-bottom: calc(0.5rem + env(safe-area-inset-bottom))so it clears the landscape home indicator. Scrubber replacedonMouseDownwithonPointerDown+setPointerCapture, plustouch-action: noneso Safari doesn't hijack horizontal swipes. Addedpointercancel+ window-levelpointerupfallbacks.All desktop code paths are unchanged:
100dvhfalls back to100vh,env(safe-area-inset-bottom)is0off-iOS, Pointer Events subsume Mouse Events on desktop.Verified live
Via the
cloudflaredtunnel I ran during review on the factory-series-c-video project:Stacked dependency
Base is
fix/player-audio-ownership-review(PR #307). Once #307 merges, rebase this branch ontomain— the changes are fully independent; the stacking is just to avoid waiting on the review for #307 before shipping pure UX wins.Test plan
tsc --noEmitonpackages/studio— cleanbun run --filter @hyperframes/studio build— clean