# Netflix-Style TV Sidebar Pattern (homeflix reference)

Proven pattern extracted from homeflix-tv-app and applied to Unspooled (NexStream).
When building an Android TV app sidebar, use this pattern — not expand/collapse, not Haze blur,
not complex state machines.

## The Pattern

**Fixed 48dp icon rail — no expansion, no labels, no width animation.**

```kotlin
@Composable
fun NetflixSideNavigation(
    selectedRoute: String,
    onNavigate: (String) -> Unit,
    modifier: Modifier = Modifier,
    onNavigateToContent: (() -> Unit)? = null,
) {
    Column(
        modifier = modifier
            .width(48.dp)
            .fillMaxHeight()
            .background(Color.Black.copy(alpha = 0.92f)),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        navItems.forEachIndexed { index, item ->
            NetflixNavIcon(/* 36dp Box, scale 1.0→1.5 on focus */)
            Spacer(Modifier.height(16.dp))
        }
    }
}
```

## Individual nav icon

Each icon is independently focusable using the **triple pattern**:
`focusable() + onFocusChanged + onKeyEvent`

```kotlin
Box(
    modifier = Modifier
        .size(36.dp)
        .scale(scale)  // 1.0→1.5 on focus (200ms tween)
        .background(
            when {
                isSelected -> accent              // solid accent color
                isFocused -> Color.White(0.20f)   // subtle white highlight
                else -> Color.Transparent
            },
            RoundedCornerShape(6.dp)
        )
        .focusRequester(focusRequester)
        .onFocusChanged { isFocused = it.isFocused }
        .focusable()
        .onKeyEvent { event ->
            if (event.type == KeyEventType.KeyDown) {
                when {
                    event.key == Key.DirectionRight -> { onNavigateRight?.invoke(); true }
                    event.key == Key.Enter || event.key == Key.DirectionCenter -> { onClick(); true }
                    else -> false
                }
            } else false
        }
        .clickable(onClick = onClick),
) {
    Icon(icon, tint = Color.White, modifier = Modifier.size(20.dp))
}
```

## Screen layout (Row pattern)

The sidebar is a sibling of content in a `Row`, not a wrapper:

```kotlin
Row(modifier = Modifier.fillMaxSize()) {
    NetflixSideNavigation(
        selectedRoute = "home",
        onNavigate = { route -> sidebarNavigate(navController, route) },
        onNavigateToContent = { heroPlayButtonFocusRequester.requestFocus() },
        modifier = Modifier.focusRequester(sideNavFocusRequester),
    )
    Box(
        modifier = Modifier
            .weight(1f).fillMaxHeight()
            .onKeyEvent { event ->
                if (event.type == KeyEventType.KeyDown && event.key == Key.Back) {
                    sideNavFocusRequester.requestFocus()  // Back → sidebar
                    true
                } else false
            }
    ) {
        // LazyColumn with hero + catalog rows
    }
}
```

## Key rules

1. **Fixed width, no animation.** The 48dp rail never changes size.
2. **No labels.** Only icons. Scale animation provides focus feedback.
3. **Each icon is independently focusable.** No focusGroup, no container focus.
4. **Right on sidebar → exits to content.** Via `onNavigateToContent` callback.
5. **Back on content → returns to sidebar.** Via `onKeyEvent` on content Box.
6. **Sidebar is a sibling, not a wrapper.** Use `Row { sidebar + Box(content) }`.
7. **No HazeState, no blur, no complex state.** The pattern is dead simple.
8. **Selected state = solid accent color.** Focused state = white 0.20 alpha.

## Anti-patterns (DO NOT USE)

- ❌ Expand/collapse width animation (80dp→260dp) — causes focus race conditions
- ❌ Label text inside sidebar — adds complexity, layout issues, text cutoff
- ❌ HazeState blur overlay — visual glitches, blocks D-pad propagation
- ❌ `focusGroup()` on sidebar column — breaks natural D-pad flow
- ❌ `BackHandler` intercepting Back to reopen sidebar — conflicts with content nav
- ❌ Multiple pending state booleans (`collapsePending`, `pendingContentFocusTransfer`) — race conditions
- ❌ `derivedStateOf` for sidebar/blocks/content keys — overcomplicated
- ❌ Wrapping sidebar as a global `NavigationRailWrapper` — each screen manages its own layout
- ❌ `onPreviewKeyEvent` workaround eating D-pad events to prevent sidebar interception — root cause is sidebar architecture

## When to use this pattern

Any Android TV app with tab-style navigation (Home, Search, Movies, TV Shows, My List, Settings).
Port it 1:1 from a reference app (homeflix-tv-app) rather than patching a broken implementation.
