# Onboarding Redesign — Session 2026-05-28

## Context

Complete rewrite of the onboarding wizard UI after 4 root-cause bugs were discovered:

1. **D-pad broken** — `Key.DirectionCenter` doesn't fire (see `references/dpad-key-fix.md`)
2. **Hidden Continue buttons** — Debrid/Trakt steps showed Continue only when connected; user saw only "Skip" and didn't know how to advance
3. **Misrouted navigation** — Services Overview "Set up →" buttons went to wrong steps
4. **Content cut-off** — No scroll on panels; buttons off-screen on lower-res TVs

## Design Principles (TV-optimized)

- **Large fonts**: headings 36-42sp, body 17-22sp, buttons 18-22sp
- **Large targets**: buttons min 56dp height, cards min 130dp
- **Strong focus affordance**: 3dp accent border on focus, colored shadow glow
- **Single action per step**: one Skip + one Continue (ghosted when unavailable)
- **Scrollable panels**: `Modifier.verticalScroll(rememberScrollState())` on all step columns
- **Sidebar**: 300dp, numbered steps with checkmarks, accent glow on active

## Component Patterns

### Provider Grid (2×2, Debrid)
```kotlin
// 4 cards in a 2×2 grid, each with provider-specific colored border on select
ProviderCard(
    info = ProviderInfo(provider, label, emoji, color, desc),
    isSelected = selectedProvider == info.provider,
    onClick = { selectedProvider = info.provider; viewModel.startDebridFlow(info.provider) },
)
// Focus: 3dp colored border + shadow glow; unselected: 1dp neutral border
```

### Device Code Box (Debrid + Trakt)
```kotlin
DeviceCodeBox(
    label = "Go to",
    url = verificationUrl,
    codeLabel = "Enter this code:",
    code = deviceCode,   // large 36sp + 10sp letter spacing
)
```

### Ghosted Button Pattern
```kotlin
// Two buttons always visible — one ghosted when unavailable:
val connected = uiState.debridStatus == "connected"
Button(onClick = { if (connected) onContinue() },
    colors = ButtonDefaults.colors(
        containerColor = if (connected) accent else TextDisabled,
        contentColor = if (connected) White else TextSecondary,
    ),
) { Text(if (connected) "Continue →" else "Skip →") }
```

### Trakt Error State
Show a styled error card with actionable message, not just a text string:
```kotlin
Box(
    modifier = Modifier.fillMaxWidth(0.7f).clip(RoundedCornerShape(20.dp))
        .background(Error.copy(alpha = 0.10f))
        .border(2.dp, Error, RoundedCornerShape(20.dp))
) {
    Text("Connection failed", ...)
    Text("API credentials not set. Add TRAKT_CLIENT_ID...", ...)
    Button("Retry", ...)
}
```

## File Changed

- `OnboardingScreen.kt` — full rewrite (was 879 lines, now 650 lines cleaner)
- Removed `Key.DirectionCenter` references
- Removed conditional button rendering (all buttons always visible)
- Added scroll to all step panels
- Added `isSelectKey()` / `isBackKey()` helper functions

## Build Verification
```bash
./gradlew :app:compileDebugKotlin  # ✅ 0 errors
./gradlew :app:assembleDebug       # ✅ 31 MB APK
./gradlew :app:testDebugUnitTest   # ✅ all passing
```
