# Catalog Focus Crash Patterns — June 2026

## Crash #1: `rememberLazyListState(initialFirstVisibleItemIndex = N)` out-of-bounds

**Symptom:** `IndexOutOfBoundsException` at composition time when returning to a previously-visited catalog screen. Exception internal to Compose LazyList, no app-code stack trace.

**Root cause:** After progressive catalog loading, `patchCatalogRow` calls `deduplicateAcrossRows` which removes items from rows. A saved scroll position that was valid at N items becomes ≥ the new count M → Compose crashes.

**Fix — clamp everywhere:**
```kotlin
// Per-row LazyRow:
val safeIndex = initialScrollIndex.coerceIn(0, (items.size - 1).coerceAtLeast(0))
val rowState = rememberLazyListState(initialFirstVisibleItemIndex = safeIndex)

// Parent LazyColumn:
val totalItems = 2 + priorityRows.size + catalogRows.size
val safeTarget = savedScrollPosition.coerceIn(0, (totalItems - 1).coerceAtLeast(0))
val listState = rememberLazyListState(initialFirstVisibleItemIndex = safeTarget)
```

---

## Crash #2: FocusRequester override orphans `focusRestorer`

**Symptom:** D-pad stops working after accessing a row. Focus stuck or lost.

**Root cause:** First card's `.focusRequester()` gets `catalogEntryRequester` instead of `cardRequesters[0]`. `focusRestorer(rowFocusRequester)` searches children for matching requester — no child matches → restorer fails silently → next D-pad event loses focus.

**Affected files (4):**
- `UnspooledHomeScreen.kt:415-418`
- `NuvioButtons.kt:69-72`
- `GlassSourceFilterRow.kt:56-59`
- `CatalogBrowserScreen.kt:296-298`

**Fix:**
```kotlin
// Always use indexed requester; entry requester goes in focusProperties only
.focusRequester(cardRequesters[index])
.focusProperties {
    left = cardRequesters.getOrNull(index - 1) ?: if (index == 0) entryRequester else null
    right = cardRequesters.getOrNull(index + 1)
    if (index == 0 && entryRequester != null) up = entryRequester
    down = if (index == 0) rowFocusRequester else null
}
```

---

## Crash #3: Source filter chips missing UP/DOWN wiring

**Symptom:** UP from filter chips doesn't return to hero. DOWN from non-last chips doesn't reach catalog rows.

**Root cause:** `SourceFilterRow` (NuvioButtons.kt, GlassSourceFilterRow.kt) only sets `down` on last chip. No `up` wiring. Non-last chips rely on unreliable Compose geometric search in LazyRow.

**Fix:**
```kotlin
.focusProperties {
    left = chipRequesters.getOrNull(index - 1) ?: FocusRequester.Cancel
    right = chipRequesters.getOrNull(index + 1) ?: FocusRequester.Cancel
    if (index == 0 && entryFocusRequester != null) up = entryFocusRequester
    if (index == filters.lastIndex && downFocusRequester != null) down = downFocusRequester
}
```
