Stay on the same video when the phone turns

Rotating mid-reel jumped the reel forward a few videos. Two reasons, both
about the same arithmetic: every scroll position in the feed is index *
scroller.clientHeight, with a spacer standing in for the slides above.

A slide was 100dvh, which is not the scroller's height on a phone whose
chrome comes and goes, so that mapping addressed the wrong slide by a
growing margin; it is now the scroller's own height, which it cannot
disagree with.

And the realign window lasted two frames. A rotation is many frames and
several resizes, with the container's re-anchor on pre-rotation pixels
landing in the middle of them -- so the window now stays open until the
slide height has held still for a stretch of frames (with a clock to close
it in a tab that isn't drawing), and orientationchange and the visual
viewport open it too. Moving the feed into a picture-in-picture window
settles the same way.

tests/smoke_rotate.py replays a rotation as the browser delivers it, on a
fixture reel so nothing waits on a real source; against the old code the
reel goes from step 7 to step 12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPZFnNdHbPGDTqQUNiE4ZN
This commit is contained in:
Simon
2026-10-18 19:40:37 +00:00
parent bc7c0047b7
commit b9f6a78fdc
4 changed files with 305 additions and 27 deletions

View File

@@ -2246,12 +2246,17 @@ body.feed-mode-open .mode-toggle-btn .icon-svg {
/* One screenful. It holds the pane tree rather than a video directly: with
several panes, a step shows that many videos at once and one swipe advances
the whole set. */
the whole set.
The height is the scroller's own, not 100dvh: every scroll position in the
feed is computed as `index * scroller.clientHeight` (the top spacer stands
in for the slides above), so a slide that is even a few pixels off that
height makes the mapping drift by a slide's worth after a handful of them --
which is what used to make rotating the phone land on a different video. */
.feed-slide {
position: relative;
width: 100%;
height: 100vh;
height: 100dvh;
height: 100%;
scroll-snap-align: start;
scroll-snap-stop: always;
overflow: hidden;