fix(player): letterbox the picture, and stop racing the session

Two bugs found by resizing the window during playback. Neither was introduced
by this branch; both are the kind that only surface when somebody actually
drags a window edge.

The picture cropped and sat at the top instead of letterboxing. The video's
flex wrapper had no `min-h-0`, and a flex item defaults to `min-height: auto` —
it refuses to shrink below its content's intrinsic size, and a <video> reports
the *media's* natural dimensions. So whenever the picture was larger than the
window the wrapper grew past the viewport, the overflow went off the bottom,
and what was visible was the top-left of an uncentred, uncropped image.
`object-contain` was doing its job the whole time, inside a box that was the
wrong size. This is also what put the picture at the bottom in fullscreen,
reported earlier and unexplained until now.

"Not connected to a server", shown as a *playback* error. The player page asks
for the repository on mount, but the session is restored asynchronously at
startup, so losing that race turned a perfectly good stream into a fatal error
screen. `getRepository()` throwing instantly is right for a click handler,
where the user is present; it is wrong for anything that runs on mount.
`waitForRepository()` resolves as soon as the session lands and still rejects
when there genuinely is not one, so a real logged-out state surfaces — just not
as a race.

Worth recording how this was found, because it was nearly misdiagnosed: the
symptom correlated with window resizes, but the log showed 230 Vite HMR updates
against a single app start — the frontend was being remounted under the test by
edits made while it ran, and a remount empties the in-memory auth store. The
race is real and worth fixing on its own merits, but "resize causes it" was an
artifact of how it was being observed, not a property of the bug.

UT-215 covers the waiting contract: resolves when already restored, resolves
when the session arrives late, still rejects when there is none, unsubscribes
once settled, and leaves no armed timer to reject an already-resolved promise.
This commit is contained in:
2026-08-22 13:45:04 +02:00
parent fecd6022fe
commit a8c44145ff
6 changed files with 1212 additions and 995 deletions
+16 -1
View File
@@ -2533,7 +2533,22 @@
aria-label="Video player"
>
<!-- Video -->
<div class="flex-1 flex items-center justify-center relative">
<!--
`min-h-0` / `min-w-0` are load-bearing, not defensive. A flex item defaults
to `min-height: auto`, which refuses to shrink below its content's intrinsic
size — and the <video> inside reports the *media's* natural dimensions. So
without them this wrapper grows past the viewport whenever the picture is
larger than the window: the overflow goes off the bottom, which reads as the
image being cropped and aligned to the top rather than letterboxed and
centred. `object-contain` was never the problem; it was doing its job inside
a box that was itself the wrong size.
Reproduces by resizing the window during playback, and by entering
fullscreen — where the same overflow put the picture at the bottom.
TRACES: UR-005 | DR-024
-->
<div class="flex-1 min-h-0 min-w-0 flex items-center justify-center relative">
{#if !!useHtml5Element}
<!-- HTML5 video for desktop/non-Android platforms -->
<video