feat(portals): mark a whole series as watched in one click (#1451)

* feat(portals): mark a whole series as watched in one click

Adds a series-level watched toggle to the season header's new overflow
menu on both Xtream and Stalker series detail pages (issue #1442 v2,
building on the season-level toggle from #1447).

- Shared: buildSeriesWatchToggleRequest flattens every loaded season
  with the season builder's mark/unmark semantics; the direction is
  always the one the label advertised, never re-inferred at persist
  time. Watch-toggle state math for both scopes moves into the new
  component-provided SeasonWatchPresenter (the container component sat
  at the max-lines cap).
- Xtream: the series request reuses SerialDetailsSeasonWatchService
  through a scope-parameterized handle(), the same stillCurrent
  ownership guard, and the XtreamStore.loadAllPositions badge refresh.
- Stalker: the season handler's core is extracted into
  runWatchToggleBatch (feedback keys per scope). Lazy Ministra VOD
  hydrates unloaded seasons sequentially first (zero writes on a failed
  fetch, silent abort on navigation), re-runs the position reconcile
  synchronously so newly hydrated episodes' legacy rows are cleaned,
  then rebuilds the request keeping the clicked direction; an
  all-watched outcome reports an honest count-0 snackbar.
- Container: new hasUnloadedSeasons input blocks the fully-watched
  verdict and the count label while lazy seasons are unloaded, and the
  empty mark request contract lets the host hydrate-then-rebuild.

Six new XTREAM i18n keys, synced to all 18 locales via the i18n-fill
workflow. No new IPC: the existing playback-position batch channels are
season-agnostic.

Refs #1442

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(portals): treat an empty Stalker season answer as loaded, not pending

A lazy season the portal ANSWERS for with zero episodes was still
counted as unloaded (episodes.length === 0 heuristic): the series label
stayed countless forever and every series toggle re-fetched the empty
season, while a glitch-empty answer could silently skip a season and
still report success as if nothing remained.

VodSeriesSeasonVm gains an episodesLoaded flag set by every successful
episode fetch — including an empty one — and the series toggle's
pending predicate, hydration re-check, and hasUnloadedVodSeasons now
key on it. A loaded-and-empty season unblocks the count label and the
fully-watched verdict instead of re-fetching; a fresh detail mount
still re-fetches, so a one-off glitch self-corrects next session.

Addresses the Greptile P1 on PR #1451.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(portals): harden lazy season hydration against malformed and racing loads

Two review findings on the series watched toggle:

- fetchVodSeriesEpisodes now trusts an empty answer only when the
  envelope actually carried a well-formed array; a malformed envelope or
  an answer whose rows contain no recognizable episode rejects, so the
  load fails instead of the season being recorded loaded-and-empty and
  silently skipped by the series batch.
- loadEpisodesForSeason is single-flight per season: a tab click, the
  spillover prefetch, the quick-start recursion, and the series-toggle
  hydration join one in-flight request instead of duplicating portal
  traffic — previously a second request's failure could abort a series
  toggle whose original request succeeded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 authored and GitHub committed 2026-08-16 01:04:12 +02:00
1 parent 7fc9380bff
commit 0a2fe263db
48 files changed
+2340 -261

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
---
type: feature
area: portals
issues: [1442]
---
The series pages' season header now has a ⋮ menu that marks the WHOLE series
as watched (or unwatched) in one click, for both Xtream and Stalker portals.
On Ministra portals that load seasons lazily, the app first fetches the
remaining seasons and then marks everything — so catching up on a show you
started mid-way is a single action.