# NuvioTV Focus Pattern Reproduction

## Source: NuvioTV ModernHomeRows.kt (1413 lines)

This is the primary reference implementation for Android TV catalog row focus.
The key mechanism that Unspooled was missing:

### The Pattern (lines ~800-835 of ModernHomeRows.kt)

```kotlin
LazyRow(
    state = rowListState,
    modifier = Modifier
        .focusRequester(rowFocusRequester)
        .focusRestorer {
            val savedIdx = rowFocusedIndex.value
            itemFocusRequesters[savedIdx]
                ?: itemFocusRequesters[0]
                ?: FocusRequester.Default
        }
        .focusGroup()
        .then(
            if (row.isLoading) {
                Modifier.onPreviewKeyEvent { event ->
                    event.type == KeyEventType.KeyDown && event.key == Key.DirectionRight
                }
            } else Modifier
        ),
    contentPadding = PaddingValues(horizontal = rowStartPadding),
    horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
    itemsIndexed(
        items = row.items.list,
        key = { _, item -> item.key },
        contentType = { ... }
    ) { index, item ->
        val requester = itemFocusRequesters.getOrPut(index) { FocusRequester() }
        // ... card composable with .focusRequester(requester)
    }
}
```

### Key Observations

1. **`focusGroup()` on LazyRow** — creates a horizontal-only navigation scope
2. **`focusRestorer()` on LazyRow** — restores last focused card when focus re-enters row
3. **No `focusProperties` on individual cards** — the LazyColumn handles vertical row-to-row
4. **`onPreviewKeyEvent` blocking RIGHT during loading** — prevents focus escape to placeholder
5. **`itemFocusRequesters` is a `MutableMap<Int, FocusRequester>`** per row, NOT a global registry

## Source: Unspooled UnifiedChromeScaffold (broken version)

```kotlin
// WRONG — LEFT key interception steals focus from catalog rows
Box(
    modifier = Modifier
        .weight(1f)
        .onKeyEvent { keyEvent ->
            if (keyEvent.type == KeyEventType.KeyDown && keyEvent.key == Key.DirectionLeft) {
                sidebarExpanded = true
                runCatching { sidebarEntryRequester.requestFocus() }
                true
            } else false
        },
) { content() }
```

### Why This Is Broken

Pressing LEFT on the 3rd card in a catalog row should navigate to the 2nd card.
Instead, the outer Box intercepts the LEFT key event and opens the sidebar.
The `onKeyEvent` on the parent Box fires AFTER the child LazyRow's focusGroup
has already handled the LEFT navigation within the row — but it returns `true`,
which marks the event as consumed, preventing further processing.

## Source: BackHandler (broken version)

```kotlin
// WRONG — extra guard prevents sidebar entry
BackHandler(enabled = !sidebarExpanded && contentHasFocus) {
    sidebarExpanded = true
    sidebarEntryRequester.requestFocus()
}

// CORRECT — simple guard
BackHandler(enabled = !sidebarExpanded) {
    sidebarExpanded = true
    sidebarEntryRequester.requestFocus()
}
```

### Why This Is Broken

`contentHasFocus` is tracked by `onFocusChanged` on the content Box.
If focus is on a child composable (card, hero button) rather than the content Box
itself, `contentHasFocus` will be false, and the BackHandler won't fire.
The sidebar becomes unreachable.
