# Catalog Pipeline Pitfalls (2026-05-30)

## `catch (e: Exception)` misses native crashes

FFmpeg/ExoPlayer native library loading throws `java.lang.UnsatisfiedLinkError` — which extends
`Error`, NOT `Exception`. A `catch (e: Exception)` around `ExoPlayer.Builder.build()` will MISS
this error entirely, letting it crash the app uncaught. The fallback code that creates an ExoPlayer
WITHOUT FFmpeg extensions never executes.

**Fix:** Use `catch (e: Throwable)` when guarding native library initialization paths.

```kotlin
// BROKEN — UnsatisfiedLinkError passes through:
try { ExoPlayer.Builder(ctx).setRenderersFactory(ffmpegFactory).build() }
catch (e: Exception) { /* fallback never runs, app crashes */ }

// FIXED — catches all throwables:
try { ExoPlayer.Builder(ctx).setRenderersFactory(ffmpegFactory).build() }
catch (e: Throwable) { /* fallback runs, ExoPlayer without FFmpeg */ }
```

**File:** `PlayerManager.kt:211` — fixed in this session from `Exception` to `Throwable`.

---

## TMDB API Key Format

| Key type | Format | Auth method |
|----------|--------|-------------|
| v3 API key | 32-char hex (`ab74812bfa9665ebd944ffbbb5b57d76`) | `?api_key=KEY` query parameter |
| v4 token | JWT (`eyJhbGciOiJIUzI1NiJ9...`) | `Authorization: Bearer TOKEN` header |

`TmdbAuthInterceptor` auto-detects: if the resolved key starts with `eyJ` → Bearer, else → query param.

**Pitfall:** A malformed or expired key returns TMDB 401 silently. Every catalog row comes back
empty. The user sees a blank home screen with no error. Always verify the key works:
```bash
curl -s 'https://api.themoviedb.org/3/trending/movie/day?api_key=YOUR_KEY'
```

**TMDB key fallback:** `TmdbApiKeyHolder` supports primary + fallback key rotation. On 401/403,
`TmdbHttpErrorInterceptor` calls `keyHolder.invalidate()` which rotates to the fallback key.
Add `TMDB_API_KEY_FALLBACK` in `local.properties` and the `buildConfigField` in `build.gradle.kts`.

---

## Catalog Pipeline: Don't Block Home on API Key

**Root cause of "empty home screen" bug:**

`HomeViewModel.loadHome()` checked `hasApiKey()` BEFORE `loadPriorityRows()`. If the key was
missing or invalid, the method returned immediately — blocking even local-data rows that don't
need TMDB (Continue Watching, hero items from watch history).

**Fix applied (HomeViewModel.kt:175→197):**
1. Load priority rows FIRST (Continue Watching, Up Next, Watchlist, hero pool)
2. THEN check `hasApiKey()` with try/catch
3. If no API key: set `catalogsUnavailable = true`, `errorMessage = null` (not blocking)
4. If API key present: load catalog definitions normally

**Critical:** NEVER use `errorMessage` for a non-critical condition. `errorMessage` replaces the
entire `LazyColumn` in `NetflixHomeScreen`. Use `catalogsUnavailable` flag with a non-blocking
banner instead.

---

## Addon-Catalog Fallback (NuvioTV Pattern)

When TMDB catalog rows are all empty (bad key, rate limited, network error):

1. `HomeViewModel.loadAddonCatalogRows()` iterates `addonRepository.getCatalogAddons()`
2. For each addon's manifest, extracts `catalogs[]` (up to 5 per addon, max 3 addons)
3. Queries each addon's `/catalog/{type}/{id}.json` endpoint via the tiered chain
4. Appends results as `RowLoadResult` entries to the catalog rows list

The tiered chain (`fetchCatalogSafe` → `addonRepository.getCatalogRows()`):
- Tier 1: Native TMDB (fast, local key)
- Tier 1b: Trakt native
- Tier 2: Catalog worker relay (server-side keys)
- Tier 3: TMDB direct fallback
- Tier 4: Stremio community addons (cinemeta, etc.)

This ensures catalogs populate even when TMDB is unreachable, matching NuvioTV's addon-first approach.

---

## Profile Selection Focus Visibility

Default profile cards used a subtle white ring (alpha 0.35→1.0) on focus — nearly invisible on
pure black TV backgrounds.

**Fix:** Use accent color ring that goes from transparent → full accent color on focus, plus
a `shadowElevation` glow animation (0dp → 8dp) and name text scale (1.0 → 1.08x).
