🏗️ 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
45 lines
1.4 KiB
Svelte
45 lines
1.4 KiB
Svelte
<!--
|
|
Browse / All / Genres for a video library.
|
|
|
|
These were three routes per library with names that did not agree across the
|
|
two libraries; they are now tabs on one route, driven by `?view=` so a tab is
|
|
linkable and survives a back navigation.
|
|
|
|
TRACES: UR-063 | DR-105
|
|
-->
|
|
<script lang="ts">
|
|
import { goto } from "$app/navigation";
|
|
import { LIBRARY_VIEWS, libraryViewUrl, type LibraryView } from "$lib/utils/libraryView";
|
|
|
|
interface Props {
|
|
/** Route the tabs live on, e.g. `/library/tv`. */
|
|
basePath: string;
|
|
active: LibraryView;
|
|
/** Per-view labels — "All Shows" vs "All Movies". */
|
|
labels: Record<LibraryView, string>;
|
|
}
|
|
|
|
let { basePath, active, labels }: Props = $props();
|
|
|
|
function select(view: LibraryView) {
|
|
if (view === active) return;
|
|
// replaceState: switching tabs is not a navigation step worth a back press.
|
|
goto(libraryViewUrl(basePath, view), { replaceState: true, noScroll: true });
|
|
}
|
|
</script>
|
|
|
|
<nav class="flex items-center gap-1 px-4" aria-label="Library sections">
|
|
{#each LIBRARY_VIEWS as view (view)}
|
|
<button
|
|
onclick={() => select(view)}
|
|
aria-current={view === active ? "page" : undefined}
|
|
class="px-4 py-2 rounded-lg text-sm font-medium transition-colors
|
|
{view === active
|
|
? 'bg-[var(--color-jellyfin)] text-white'
|
|
: 'text-gray-400 hover:text-white hover:bg-white/10'}"
|
|
>
|
|
{labels[view]}
|
|
</button>
|
|
{/each}
|
|
</nav>
|