A favorite outlives its thumbnail. Sources sign their CDN paths and the
signature expires, so a video kept months ago has a `thumb` that 404s and
a card that is a grey box with a title under it. Asking the source page
for a fresh picture covers the case where the page still exists; nothing
covered the case where it doesn't.
The video does, because playback re-resolves from the page URL every time.
So once every route to the listed picture is exhausted, a frame a quarter
of the way in becomes the card's picture -- past the black lead-in, and a
fair guess at what the video looks like. It goes through our own proxy, so
the canvas can be read back, is scaled to card width and JPEG'd to a
handful of KB, and is kept: later mounts and later sessions paint it
without a request, instead of asking again for a picture that is known to
be gone.
Only for favorites, once per item, one at a time, and never for a live
stream or a manifest. Two things worth knowing, both found with the
browser rather than reasoned about: a browser fires `seeked` for its own
initial seek to zero, so taking the frame on the first one captures the
opening of every video; and the same item is on screen in more than one
place, so the captures are deduped by a promise rather than the first
asker getting the picture and the rest being told there isn't one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPZFnNdHbPGDTqQUNiE4ZN
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