feat(series): land on the current episode, not season 1 (UR-062, UR-063, UR-064)
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 16m59s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 5m36s
Traceability Validation / Check Requirement Traces (push) Successful in 18s
Build & Release / Run Tests (push) Successful in 5m9s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 8m54s
Build & Release / Build Linux (push) Successful in 18m49s
Build & Release / Build Windows (push) Successful in 14m4s
Build & Release / Build Android (push) Successful in 30m17s
Build & Release / Create Release (push) Successful in 18s
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 16m59s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 5m36s
Traceability Validation / Check Requirement Traces (push) Successful in 18s
Build & Release / Run Tests (push) Successful in 5m9s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 8m54s
Build & Release / Build Linux (push) Successful in 18m49s
Build & Release / Build Windows (push) Successful in 14m4s
Build & Release / Build Android (push) Successful in 30m17s
Build & Release / Create Release (push) Successful in 18s
Opening a series dumped the viewer at the top of season 1, and its Play button played nothing at all: it resolved `$libraryItems[0]` — the first *season* by SortName — and navigated to `/player/<seasonId>`, which the player route bounced straight back to `/library/<seasonId>`. The backend could already answer "where is this viewer in this show": `repository_get_next_up_episodes` has accepted a `series_id` since it was written and no caller had ever passed one. Backend (DR-101, DR-106) - `repository/series_progress.rs`: `pick_current_episode` — in progress, else Next Up, else first unwatched, else the premiere. The third rung is the offline path, where Next Up is always empty. `sort_series_order` puts specials (season 0) after the numbered seasons. - `repository_get_series_episodes` takes over the season fan-out and the flat-series fallback, which were domain knowledge living in the frontend. - `clear_watch_history` maps to Jellyfin's mark-unplayed (recursive over a container, also zeroes resume). Offline it refuses rather than diverging state the next sync would undo. Frontend (DR-102, DR-103, DR-104, DR-107) - Seasons collapse; only the current one is expanded, and the current episode is badged and scrolled into view. - Hero button reads `Resume S2E4` / `Play S1E1` and opens that episode's focus view, where Play commits (ux-flows §5B.5). - Seasons are no longer a destination: `/library/<seasonId>` redirects to `/library/<seriesId>#season-N`, and every inbound link follows. - The "More Episodes" strip spans the whole series, so a season finale offers the next premiere instead of dead-ending (§5B.2). - Clear-history buttons on the series hero and each season header. Routes (DR-105) - `/library/tv` and `/library/movies` absorb their all-titles and genres pages as `?view=` tabs; the four legacy routes redirect. 6 video routes become 2, and `/library/shows/genres` stops being the odd one out. Logic extracted to `seriesNavigation.ts`, `episodeStrip.ts` and `libraryView.ts` so it is unit-tested rather than buried in components. Spec: docs/specs/series-current-episode-navigation.md
This commit is contained in:
+25
-2
@@ -688,10 +688,10 @@ A movie has no continuation set, so cast follows the hero directly.
|
||||
### 5B.4 Series detail — section order
|
||||
|
||||
```
|
||||
Hero (poster, title, metadata, Play / Download)
|
||||
Hero (poster, title, metadata, Resume SxEy / Download / Clear history)
|
||||
→ Crew links
|
||||
→ Genre tags
|
||||
→ Seasons + episodes (per-season sections)
|
||||
→ Seasons (collapsible; only the current season expanded)
|
||||
→ Cast
|
||||
→ More Like This
|
||||
```
|
||||
@@ -700,6 +700,29 @@ The same principle as §5B.2: **episodes come before cast and similar shows.**
|
||||
The reason a user opens a series page is to pick an episode; discovery content
|
||||
is secondary and sits underneath.
|
||||
|
||||
**Rules for the seasons block** *(UR-062, UR-064)*:
|
||||
|
||||
- **The page opens where the viewer is.** The backend resolves the current
|
||||
episode — in progress, else Next Up, else first unwatched, else the premiere —
|
||||
and the page scrolls it into view with an `Up next` badge and a highlight ring.
|
||||
Never season 1 by default, unless season 1 *is* where the viewer is.
|
||||
- **Seasons collapse; only the current one is expanded.** A ten-season show
|
||||
otherwise renders hundreds of rows and buries the episode the viewer came for.
|
||||
A collapsed season still names its episode count and watched count, so
|
||||
progress is readable without expanding it.
|
||||
- **The hero button opens, it does not play.** It reads `Resume S2E4` /
|
||||
`Play S1E1` — naming its target — and navigates to that episode's Focus View,
|
||||
where Play commits. Play on a *container* is navigation (§5B.5); Play on a
|
||||
*leaf* is the commitment.
|
||||
- **A season is never its own page.** `/library/<seasonId>` redirects to
|
||||
`/library/<seriesId>#season-N`. Every affordance that names a season — the
|
||||
episode breadcrumb, a season card in a grid, a Downloads drill-in — lands on
|
||||
the series with that season in view, so the episodes of all seasons stay one
|
||||
browsable list.
|
||||
- **Watch history is erasable** per series (hero) and per season (season
|
||||
header). It confirms first, cannot be undone, and needs the server. Clearing a
|
||||
whole series returns it to S1E1 by the same path a never-watched show takes.
|
||||
|
||||
### 5B.5 Home-card interaction — tap opens, long-press plays
|
||||
|
||||
Cards on the Home screen carousels (Next Movie, Next Episode, Continue
|
||||
|
||||
Reference in New Issue
Block a user