* feat(playback): Up Next episode rail for the inline series player
On wide windows the inline series player now docks left and fills the
leftover stage column with a Netflix-style "Up Next" rail: the rest of the
current season plus next-season spillover, the playing episode highlighted,
and watch-progress bars from playback positions. Clicking an episode plays
it inline through the host's existing episode flow (Xtream serial-details
and Stalker series view).
- New app-up-next-rail component + buildUpNextRailItems() util in
ui/playback; entries carry the host's raw episode object so selection
needs no id lookup.
- PortalInlinePlayerComponent measures the theater stage with a
ResizeObserver and docks the rail only when the leftover beside the 16:9
player is >= 320px; narrower stages keep the centered theater/ambient
behavior from #1223. Movies and live never show the rail.
- New playerUpNextRail setting (Settings > Playback, default on, built-in
web players only), mirroring playerAmbientMode; enforced at runtime for
non-web engines.
- i18n: SETTINGS.PLAYER_UP_NEXT_RAIL(+_DESCRIPTION) and PORTALS.UP_NEXT in
all 18 locales.
- The rail renders as an opaque panel on top of the stage, so the ambient
fill stays behind it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(playback): address Greptile review on the Up Next rail
- Stage overflow: `.player-shell__viewport` had no border-box sizing (the repo
has no global reset), so the docked-rail modifier's 12px padding widened the
stage past its container and the right edge was clipped.
- Width gate: compute the width the rail actually receives (stage minus the
docked layout's padding, the height-driven 16:9 player, and the flex gap)
instead of raw stage slack, and observe the stage's border box so the
modifier's own padding cannot feed back into the measurement.
- Stalker lazy seasons: Ministra VOD-series seasons hold no episodes until
opened, so the rail's next-season spillover stopped at the current season.
Prefetch the following season while an episode plays inline.
Adds regression coverage for the gate boundary, gate stability across the
padding toggle, and the lazy-season prefetch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(stalker): stop the rail spillover prefetch from retrying forever
A failed or genuinely empty Ministra season resets isLoading while leaving
episodes empty, so the prefetch effect re-requested the same season on every
emission for as long as inline playback continued. Remember which seasons this
view already requested and ask at most once each.
Regression test asserts the empty-response case fetches exactly once and does
not retrigger on further playback in the same season.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(stalker): let a failed spillover prefetch recover on the next episode
The previous guard was permanent, so a transient network or authorization
failure disabled the rail's next-season prefetch for the component's lifetime.
Distinguish the two outcomes instead:
- Answered (even with zero episodes) — a real answer, never asked again.
- Failed — the claim is released, but pinned to the episode that triggered it,
so the retry waits for the next playback change. Retrying immediately would
loop, since the failure itself flips isLoading and re-runs the effect.
The claim is taken synchronously; awaiting first let the isLoading flip re-run
the effect and fire a duplicate request before the answer arrived.
`loadEpisodesForSeason` now reports whether the portal answered; existing
callers ignore the result and are unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(tmdb): purge obsolete search cache rows
* feat(playback): theater stage and opt-in ambient fill for the inline portal player
On wide-short windows the VOD/series inline player left a strip of app
surface next to the video: with `width: auto`, the viewport's `max-height`
transferred through `aspect-ratio` into a max-width (CSS transferred size
constraints), re-clamping the stage to 16:9 and leaving the leftover
outside it.
- Theater stage: give `.player-shell__viewport` a definite `width: 100%`
so it always fills the content row; the player renders as the largest
16:9 box that fits the stage height, centered — the leftover is always
the stage's black background, never app surface (YouTube-style
letterbox). Applies to every inline engine.
- Ambient fill: new `playerAmbientMode` setting (default off, Settings >
Playback, web players only) renders a blurred, dimmed copy of the
poster behind the player, filling the letterbox margins. Enforced at
runtime too: Embedded MPV never gets the extra DOM layer. Live channels
and non-http(s) poster URLs are excluded.
Verified live via CDP at 1720x760 (stage 1362x532, player 946x532 with
symmetric 208px margins) and 1280x950 (stage exactly 16:9, no bars).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(i18n): add ambient-mode setting keys to all remaining locales
The i18n drift gate requires SETTINGS.PLAYER_AMBIENT_MODE and its
description in every locale; the feature commit only covered en and ru.
Translated via the i18n-fill workflow (per-locale patch + mechanical
merge, glossary-matched against each existing file).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(settings): include playerAmbientMode in expected default settings
settings.component.spec asserts the persisted settings object with
toEqual; the new default-off field has to be part of the fixture.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the embedded mpv frame-copy pipeline to Windows with WGL rendering and named shared memory. Includes packaging validation, platform gates, tests, and architecture documentation.