# Root Focus Controller Migration Pattern

Captured from Unspooled HTML-shell migration work.

## Problem
A TV app can compile clean while still having multiple independent focus state machines:
- `MainActivity` creates/provides one controller
- scaffold creates another with `rememberTvFocusController()`
- player/details/source screens create local controllers
- overlay/dialog code updates a controller that the scaffold/sidebar never observes

Symptoms:
- Back/overlay/source panels do not coordinate with root navigation
- sidebar/content blocking state is inconsistent
- modal/source/player zones appear correct in one file but are invisible to other layers
- new shell files exist but app behavior remains legacy because NavHost/screen call sites still target old components

## Fix Pattern
Use one root-owned controller per app surface and pass it through `CompositionLocal`.

```kotlin
// MainActivity/root
val focusController = rememberTvFocusController()
CompositionLocalProvider(LocalTvFocusController provides focusController) {
    AppNavHost(...)
}

// Scaffold/screen/player
val focusController = LocalTvFocusController.current
// do NOT call rememberTvFocusController() again here
```

## Migration Checklist
1. Search for all `rememberTvFocusController()` calls.
2. Keep exactly one root call unless the screen is a truly isolated preview/test surface.
3. Replace scaffold/player/detail/source local calls with `LocalTvFocusController.current`.
4. Verify modal/source/player zone transitions update the same controller observed by scaffold/sidebar/content blockers.
5. After creating replacement shell/card/sidebar files, verify they are imported and called from NavHost or parent screens; a clean build alone does not prove they are active.
6. Run `compileDebugKotlin`, `assembleDebug`, and relevant unit tests before reporting completion.

## Pitfalls
- `FocusRequester.Default` is not a safe missing-target fallback for D-pad edges. Use `FocusRequester.Cancel` when no real attached requester exists.
- Do not report “migration complete” if legacy call sites still instantiate old cards/scaffolds/screens. Report the exact wired surface and remaining legacy paths.
- Static verification must include both new-file existence and old-call-site removal/import replacement.
