Filter caching, end to end
The first tap on a genre, streaming provider or studio tile now lands instantly. The previous build was supposed to cache results to UserDefaults but tvOS' 1 MB CFPreferences cap was crashing the app the moment the cache filled up. The whole pipeline got rebuilt around file-based persistence with stale-while-revalidate, plus background pre-warming so the cache is already populated by the time you reach for it.
Stability
- No more SIGABRT on filter loads. Moved FilterCache from UserDefaults to file-based JSON in
Library/Caches/FilterCache/. UserDefaults' 1 MB cap couldn't hold a fully-populated provider tile and was crashing the entire app on the first big write. - Catalog empty-grid quit fix. Tapping a streaming-service tile that resolved to zero results dropped the user into a grid with no focusable elements, and pressing Menu on the Siri Remote escaped the navigation stack and quit the app instead of popping back. Empty / error / loading states now have proper focusable Back buttons.
Cache + performance
- Cache hydrates from
init(), not.task. Filter grids now paint their cached items in the very first render frame instead of flashing the loading spinner for one tick. - Bottom-spinner flicker on revalidation gone. Background refresh runs silently behind the cached grid; only real pagination shows the loading hint.
- Wholesale-replace flicker gone. When a refresh returns the same id list as the cache, the items aren't reassigned, so SwiftUI doesn't re-diff every cell.
- Home-page genre tiles now cache. They were missing a
cacheKeyand re-fetched from scratch on every tap. - Pre-warm on home + catalog load. The genre and streaming-provider grids are populated in the background as soon as the home / catalog screen mounts, so the first tap renders straight from disk.
Streaming providers
- Reordered into popularity tiers. Top globals first (Netflix, Prime, Disney+, HBO, Apple TV+), then second-tier SVOD, international, niche, premium cable, free AVOD, broadcast, kids/animation. Same list feeds the home row and catalog row.
- Empty tiles auto-hide. TMDB watch-provider data is region-specific, so Hulu / Peacock outside US, Hotstar outside India etc. now drop out of the row on the first catalog render instead of teasing a tile that opens to nothing.
- Crunchyroll added. Anime catalogue support.
- Nine international providers added. MUBI (global arthouse), Sky (DACH + UK), Viaplay (Nordic), BBC iPlayer (UK), Canal+ (France), Hotstar (India), U-NEXT (Japan), Tubi + Pluto TV (free AVOD).
- Real TMDB logo paths. The 10 newly-added providers shipped with hallucinated logo paths in 0.3.1 — every single one returned 404 from
image.tmdb.organd the tiles fell back to a plain text-name placeholder. All replaced with verified paths.
Refactor
- One source of truth for filter cache keys — every key format moved to
FilterCacheKey. - Centralised focus-defer pattern as
deferOnMain(by:_:)— the project's documented workaround for the @focusstate commit race finally has a name. - HomeViewModel precompute task lifecycle — handles are held now and cancelled on re-entry, so a rapid profile switch can't stack two fan-outs writing into the same cache.
- Watch-provider ID resolver lives once on
SeerrDiscoverServiceProtocolinstead of once each inFilteredGridViewandHomeViewModel.