# TMDB Catalog Loading Anti-Pattern: `hasApiKey()` Blocks Everything

## The Bug

In MR.VHS (formerly NexStream/UNSPOOLED), three ViewModels had the same blocking pattern:

```kotlin
// BROKEN — blocks priority/local-data rows that don't need TMDB
fun loadHome() {
    if (!tmdbCatalogRepository.hasApiKey()) {
        _uiState.update { it.copy(isLoading = false, error = "API key missing") }
        return@launch  // ← Nothing loads. Continue Watching, Watchlist, Hero — all dead.
    }
    loadPriorityRows()  // ← Never reached
    loadCatalogRows()   // ← Never reached
}
```

This was found in:
- `HomeViewModel.kt` (line 175, fixed 2026-05-30)
- `MoviesViewModel.kt` (line 76, fixed 2026-05-30)
- `TvShowsViewModel.kt` (line 77, fixed 2026-05-30)

## Root Cause

`hasApiKey()` calls `DataStore.first()`, which can throw `IOException` if the preferences file is corrupted. The check also fires BEFORE loading any local-data rows (Continue Watching, Watchlist, hero items) that don't need TMDB at all.

## The Fix: Progressive Loading

```kotlin
// FIXED — priority rows load first, API key check is non-blocking
fun loadHome() {
    // 1. Load local-data rows FIRST (Continue Watching, Watchlist from Room DB)
    val priorityState = loadPriorityRows(profile, watchedIds)
    
    // 2. Try/catch the API key check — DataStore can throw
    val hasApiKey = try {
        tmdbCatalogRepository.hasApiKey()
    } catch (_: Exception) { false }
    
    // 3. Show priority rows + hero immediately
    _uiState.update { it.copy(priorityRows = orderedPriorityRows, heroItems = heroItems) }
    
    // 4. Only block catalog rows if no key; NEVER use errorMessage (blocks entire home screen)
    val definitions = if (hasApiKey) filterCatalogDefinitions(...) else emptyList()
    
    // 5. Show a gentle banner (catalogsUnavailable), not a full-screen error
}
```

## Key Rules

1. **Never set `errorMessage` for missing API keys** — it blocks the entire screen in `when { error != null → ErrorScreen }`.
2. **Use `catalogsUnavailable` flag instead** — shows a non-blocking banner in the LazyColumn.
3. **Priority rows load first, always** — Continue Watching, Watchlist, Up Next use local DB/Trakt, not TMDB.
4. **Wrap `hasApiKey()` in try/catch** — DataStore can throw.
5. **Error only shown when ALL rows are empty, not proactively.**

## Detection: Grep for the pattern

```bash
grep -rn "hasApiKey()" app/src/main/java/ --include="*.kt"
```

If any ViewModel returns early with an error message BEFORE loading local priorities, it has this bug.
