# Sidebar → Content Focus Escape Pattern

## Problem

User presses RIGHT on a sidebar item, but focus stays stuck in the sidebar. The sidebar
may or may not close, but the user can never reach catalog content via RIGHT from any sidebar item.

## Root Cause — Three-Layer Failure

| Layer | Issue | Detail |
|---|---|---|
| **A — focusProperties.right target unfocusable** | Content Box has `canFocus = contentAllowed()` → `false` when sidebar is open | Focus system tries to move RIGHT but silently fails — the target is invisible to the focus search |
| **B — Column-level onPreviewKeyEvent swallows Right** | The sidebar Column catches `Key.DirectionRight` and returns `true` | Consumed event never reaches the child NavIcon, even though focus navigation happens before key event dispatch, the failed move from (A) leaves focus orphaned |
| **C — pendingContentFocus LaunchedEffect self-cancels** | The effect sets `pendingContentFocus = false` INSIDE the `LaunchedEffect(pendingContentFocus)` | Changing the key cancels the running coroutine mid-suspension — `requestFocus()` may or may not complete |

## Fix — Three-Change Pattern

### 1. Remove column-level `onPreviewKeyEvent` for Right (GlassSidebar.kt)

```kotlin
// REMOVE this entire block from the sidebar Column modifier chain:
.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyDown && event.key == Key.DirectionRight) {
        onRequestClose()
        true
    } else {
        false
    }
}
```

This lets the focus system's `focusProperties.right` do its job naturally.

### 2. Make content Box always focusable (TvAppScaffold.kt)

```kotlin
// BEFORE — gated off when sidebar is open:
.focusable(enabled = contentAllowed())
.focusProperties { canFocus = contentAllowed() }

// AFTER — always focusable, blockContentFocusWhenInactive handles internal navigation:
// (remove both lines entirely)
```

`blockContentFocusWhenInactive` (which uses `onPreviewKeyEvent` to block D-pad inside
content) is sufficient to prevent navigation within content when sidebar is open.
The content Box itself just needs to be a valid focus target so `focusProperties.right`
can land there.

### 3. Close sidebar when content receives focus (TvFocusController.kt)

```kotlin
fun onContentFocused() {
    if (_state.sidebarOpen) {
        closeSidebar()  // ← ADD THIS
    }
    if (!_state.sidebarOpen && !_state.modalOpen && !_state.sourceListOpen) {
        update { it.copy(activeZone = TvFocusZone.Content) }
    }
}
```

This ensures the sidebar closes automatically when focus successfully moves
from sidebar to content via `focusProperties.right`.

## Resulting D-Pad Flow

```
User presses → RIGHT on any sidebar item

1. Compose focus system processes focusProperties.right
   → target = entryFocusRequesterMap[route] ?? contentFocusRequester
   → target IS focusable (always — we removed the gate)
   → focus MOVES to content Box                           ✅

2. Content Box.onFocusChanged fires
   → controller.onContentFocused()
   → sidebarOpen == true → closeSidebar()                 ✅
   → activeZone = Content                                 ✅

3. Key event dispatches through preview phase
   → blockContentFocusWhenInactive sees contentAllowed() now returns true
   → returns false (doesn't consume)                      ✅

4. Next frame: sidebar animated closed, content fully interactive
```

## Verification

After applying this fix, all three layers must be confirmed:

```bash
# 1. Verify no onPreviewKeyEvent for Right in sidebar
rg -n "DirectionRight" app/src/main/java/com/unspooled/app/ui/scaffold/GlassSidebar.kt
# Expected: zero results

# 2. Verify content Box has no canFocus/focusable gating
rg -n "canFocus = contentAllowed\|focusable.*contentAllowed" app/src/main/java/com/unspooled/app/ui/scaffold/TvAppScaffold.kt
# Expected: zero results (keep blockContentFocusWhenInactive only)

# 3. Verify onContentFocused closes sidebar
rg -n "closeSidebar\|sidebarOpen" app/src/main/java/com/unspooled/app/ui/focus/TvFocusController.kt
# Expected: closeSidebar() call inside onContentFocused()
```

## Common Mistakes

- **Removing `onPreviewKeyEvent` but keeping `canFocus = contentAllowed()`** — focus still can't move
- **Making content focusable but keeping the column-level key interceptor** — the key is consumed before focus navigation
- **Closing sidebar in onPreviewKeyEvent instead of onContentFocused** — creates the `pendingContentFocus` race condition
- **Only fixing one of the three** — all three changes are required for the fix to work
