# Catalog Fallback Chain — 2026-05-22 Session

## Problem

The app relied exclusively on Stremio community addons for catalog, metadata, and streams. When no addons were installed (or they failed), every screen except Home broke:

- **Search**: `"No catalog addons available"` — called `addonRepository.getCatalogRows()` directly
- **TV Shows**: Completely blank — the screen was a stub with no ViewModel
- **Details**: `"Could not load metadata for this title"` — called `addonRepository.getMetaItem()`
- **Sources**: `"No sources available, try a different addon"` — called `addonRepository.getStreamsForContent()`

**Home worked** because `HomeViewModel` had its own `fetchCatalogSafe()` with a private 3-tier fallback (Stremio → Worker → TMDB). No other ViewModel had this.

## Solution: Universal Fallback Chain

Made the fallback chain universal by injecting `NexStreamCatalogRepository` and `TmdbCatalogRepository` into `AddonRepository`. Now `getCatalogRows()` has the 3-tier chain:

```
Tier 1: Catalog Worker    → nexstream-catalog.tw24fr.workers.dev (server-side keys)
Tier 2: TMDB direct       → TmdbCatalogRepository.getCatalogRow() (local key)
Tier 3: Stremio addons    → Community catalog addons (user-optional)
```

### Files modified

| File | Change |
|------|--------|
| `data/addon/AddonRepository.kt` | +`catalogRelay: NexStreamCatalogRepository`, +`tmdbCatalog: TmdbCatalogRepository` constructor params; `getCatalogRows()` rewritten with 3-tier try/catch fallback |
| `data/tmdb/TmdbApi.kt` | +`searchMulti(query, apiKey, page)`, +`getMovieDetail(movieId, apiKey)`, +`getTvDetail(tvId, apiKey)`, +`TmdbMovieDetail`, +`TmdbTvDetail`, +`TmdbGenre` |
| `data/tmdb/TmdbCatalogRepository.kt` | +`"tmdb.search"` case in `getCatalogRowInternal()` — calls `tmdbApi.searchMulti()` with `extra["search"]` |
| `ui/screens/details/DetailsViewModel.kt` | +`tryFetchTmdbMeta()` fallback when `getMetaItem()` returns null; builds `MetaItem` from TMDB detail endpoints |
| `ui/screens/search/SearchViewModel.kt` | Now automatically benefits from fallback — no code change needed since it calls `addonRepository.getCatalogRows()` |

### New screens created

| File | Description |
|------|-------------|
| `ui/screens/movies/MoviesScreen.kt` | Full-screen movie catalog with sidebar + 5 rows (Trending, Popular, Top Rated, Now Playing, Upcoming) |
| `ui/screens/movies/MoviesViewModel.kt` | Hilt ViewModel loading 5 catalog rows in parallel via `addonRepository.getCatalogRows()` |
| `ui/screens/tvshows/TvShowsScreen.kt` | Rewrote from empty stub — now shows 5 TV catalog rows with proper D-pad navigation |
| `ui/screens/tvshows/TvShowsViewModel.kt` | Hilt ViewModel for TV show rows (Trending, Popular, Top Rated, Airing Today, On the Air) |
| `ui/navigation/Screen.kt` | +`Screen.Movies` route |
| `ui/navigation/NexStreamNavHost.kt` | +`composable(Screen.Movies.route) { MoviesScreen(...) }` |

### Sidebar routing centralized

| File | Description |
|------|-------------|
| `ui/components/netflix/SidebarRouting.kt` | NEW — `sidebarNavigate(navController, route)` centralizes all 7 route mappings |
| `ui/components/netflix/NetflixSideNavigation.kt` | Updated — 7-item sidebar: Home, Movies, TV Shows, Search, My List, Profiles, Settings |
| `ui/screens/home/NetflixHomeScreen.kt` | `onNavigate` replaced with single `sidebarNavigate(navController, route)` call |
| `ui/screens/mylist/MyListScreen.kt` | Same — was routing "movies" to Search (the bug), now centralized |
| `ui/screens/search/NetflixSearchScreen.kt` | Same — was routing all unknown routes to Home, now centralized |

## Verification

```bash
# Health check the worker
curl https://nexstream-catalog.tw24fr.workers.dev/v1/health
# → {"ok":true,"service":"nexstream-catalog","version":"1"}

# Test catalog endpoint
curl "https://nexstream-catalog.tw24fr.workers.dev/v1/catalog/movie/tmdb.trending_week?skip=0&limit=3"
# → Returns real TMDB data with posters, ratings, genres

# Build verification
cd /home/rurouni/nexstream
./gradlew compileDebugKotlin testDebugUnitTest assembleDebug assembleRelease
# → BUILD SUCCESSFUL (1m 28s), 17 tests pass
```

## Known remaining gaps

- **`getMetaItem()`** still only uses Stremio meta addons — TMDB fallback is in `DetailsViewModel`, not in the repository. If other screens need metadata, they'll need the same pattern.
- **`getStreamsForContent()`** has NO fallback — still Stremio-addon-only. This is why "No sources available" persists even with the catalog fix.
- **Catalog Worker** doesn't support all catalog IDs (`tmdb.search` returns empty, handled by TMDB direct fallback)
