// Home screen data store - featured items, continue watching, recently added // TRACES: UR-023, UR-024, UR-034, UR-059, UR-067 | DR-026, DR-027, DR-038, DR-039, DR-089, DR-118 import { writable, derived } from "svelte/store"; import type { MediaItem } from "$lib/api/types"; import { auth } from "./auth"; import { filterSupersededResumeItems } from "./continueWatchingFilter"; interface HomeState { heroItems: MediaItem[]; resumeItems: MediaItem[]; nextUpItems: MediaItem[]; latestItems: MediaItem[]; recentlyPlayedAudio: MediaItem[]; resumeMovies: MediaItem[]; /** Favourites per scope. Empty rows are not rendered. TRACES: UR-067 | DR-118 */ favoriteMovies: MediaItem[]; favoriteShows: MediaItem[]; favoriteMusic: MediaItem[]; isLoading: boolean; error: string | null; } function createHomeStore() { const initialState: HomeState = { heroItems: [], resumeItems: [], nextUpItems: [], latestItems: [], recentlyPlayedAudio: [], resumeMovies: [], favoriteMovies: [], favoriteShows: [], favoriteMusic: [], isLoading: false, error: null, }; const { subscribe, set, update } = writable(initialState); async function loadHomeSections() { // Only show loading spinner when no data is available yet update(s => ({ ...s, isLoading: s.heroItems.length === 0 && s.latestItems.length === 0, error: null })); try { const repo = auth.getRepository(); // Use allSettled so one failing section (e.g. Next Up is online-only and // rejects offline) doesn't wipe out the whole homepage. Each section falls // back to an empty list; cached sections (resume/latest/recent) still show. const settled = await Promise.allSettled([ repo.getResumeItems(undefined, 12), repo.getNextUpEpisodes(undefined, 12), repo.getLatestItems("", 16), repo.getRecentlyPlayedAudio(12), // Backend now handles intelligent grouping repo.getResumeMovies(12), // Favourites, one request per row. The scope is opaque — Rust decides // which item types it covers. TRACES: UR-067 | DR-118 repo.getFavorites("movies", { limit: 20 }), repo.getFavorites("tv", { limit: 20 }), repo.getFavorites("music", { limit: 20 }), ]); const valueOr = (i: number, fallback: T): T => settled[i].status === "fulfilled" ? (settled[i] as PromiseFulfilledResult).value : fallback; const rawResume = valueOr(0, [] as typeof initialState.resumeItems); const nextUp = valueOr(1, [] as typeof initialState.nextUpItems); // Drop episodes the user has already moved past (their series' Next Up // points further ahead) so Continue Watching isn't cluttered with stale // partial positions left behind by skipping. const resume = filterSupersededResumeItems(rawResume, nextUp); const latest = valueOr(2, [] as typeof initialState.latestItems); const recentAudio = valueOr(3, [] as typeof initialState.recentlyPlayedAudio); const resumeMovies = valueOr(4, [] as typeof initialState.resumeMovies); const emptyResult = { items: [] as MediaItem[], totalRecordCount: 0 }; const favoriteMovies = valueOr(5, emptyResult).items; const favoriteShows = valueOr(6, emptyResult).items; const favoriteMusic = valueOr(7, emptyResult).items; // Use resume items or latest as hero items const hero = resume.length >= 3 ? resume.slice(0, 5) : latest.slice(0, 5); update(s => ({ ...s, heroItems: hero, resumeItems: resume, nextUpItems: nextUp, latestItems: latest, recentlyPlayedAudio: recentAudio, resumeMovies: resumeMovies, favoriteMovies, favoriteShows, favoriteMusic, isLoading: false, })); } catch (error) { const message = error instanceof Error ? error.message : "Failed to load home sections"; update(s => ({ ...s, isLoading: false, error: message })); console.error("Failed to load home sections:", error); } } function reset() { set(initialState); } return { subscribe, loadHomeSections, reset, }; } export const home = createHomeStore(); // Derived stores for convenience export const heroItems = derived(home, $home => $home.heroItems); export const resumeItems = derived(home, $home => $home.resumeItems); export const nextUpItems = derived(home, $home => $home.nextUpItems); export const latestItems = derived(home, $home => $home.latestItems); export const recentlyPlayedAudio = derived(home, $home => $home.recentlyPlayedAudio); export const resumeMovies = derived(home, $home => $home.resumeMovies); export const favoriteMovies = derived(home, $home => $home.favoriteMovies); export const favoriteShows = derived(home, $home => $home.favoriteShows); export const favoriteMusic = derived(home, $home => $home.favoriteMusic); export const isHomeLoading = derived(home, $home => $home.isLoading);