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:
@@ -1691,6 +1691,48 @@ impl MediaRepository for OnlineRepository {
|
||||
result
|
||||
}
|
||||
|
||||
/// `DELETE /Users/{userId}/PlayedItems/{itemId}` — Jellyfin's "mark
|
||||
/// unplayed", which also zeroes the resume position. On a folder (series,
|
||||
/// season) the server applies it recursively to the children.
|
||||
///
|
||||
/// TRACES: UR-064 | DR-106, JA-033
|
||||
async fn clear_watch_history(&self, item_id: &str) -> Result<(), RepoError> {
|
||||
let endpoint = format!("/Users/{}/PlayedItems/{}", self.user_id, item_id);
|
||||
let url = format!("{}{}", self.server_url, endpoint);
|
||||
|
||||
let result = async {
|
||||
let request = self
|
||||
.http_client
|
||||
.client
|
||||
.delete(&url)
|
||||
.header("X-Emby-Authorization", self.auth_header())
|
||||
.build()
|
||||
.map_err(|e| RepoError::Network {
|
||||
message: format!("Failed to build request: {}", e),
|
||||
})?;
|
||||
|
||||
let response = self
|
||||
.http_client
|
||||
.request_with_retry(request)
|
||||
.await
|
||||
.map_err(|e| RepoError::Network {
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(RepoError::Server {
|
||||
message: format!("HTTP {}", response.status()),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
|
||||
self.report_outcome(&result).await;
|
||||
result
|
||||
}
|
||||
|
||||
async fn get_person(&self, person_id: &str) -> Result<MediaItem, RepoError> {
|
||||
let endpoint = format!("/Users/{}/Items/{}", self.user_id, person_id);
|
||||
let item: JellyfinItem = self.get_json(&endpoint).await?;
|
||||
|
||||
Reference in New Issue
Block a user