# Unspooled — Full UI/UX Audit & Fix Plan

**Date:** 2026-06-16  
**Scope:** UI/UX front end, D-pad navigation, hero banners, sidebar, settings, addons  
**Method:** Code analysis + web research (Android TV dev guides, Netflix UX, Compose TV best practices)

---

## Executive Summary

Identified **13 issues** across 5 areas. **6 Critical/High**, **4 Medium**, **3 Low**. All findings backed by Android TV developer documentation, Netflix UX research, and Jetpack Compose TV best practices.

---

## 1. SIDEBAR — Netflix-Style Audit

### Current Implementation
- `GlassSidebar.kt`: 64dp collapsed-only, icon-only, red accent bar
- 6 items: Search, Home, TV Shows, Movies, Categories, Settings
- Glass background with blur, dark `#0D0D0D` background

### Web Research Findings
- **Android TV Nav Drawer Guide** (developer.android.com/design/ui/tv/guides/components/navigation-drawer):
  - "Navigation drawers should list only five to six primary navigation destinations" ✅ (we have 6)
  - "Limit the number of main navigation destinations to five to six for better UX" ✅
  - "Avoid creating a navigation drawer without icons" ✅
  - "Labels should be clear and concise" — Our icon-only sidebar lacks labels
  - Both "standard" (pushes content) and "modal" (overlay with scrim) patterns exist
  - Our sidebar is modal (overlay with scrim) — matches Netflix behavior

- **Netflix 2025 TV Redesign** (netflix.com/tudum):
  - "Shortcuts to Search, Shows, Movies, Games, and My Netflix are now at the TOP of your screen"
  - Netflix moved AWAY from left sidebar to TOP navigation bar
  - "Press back to quickly get back to the menu at the top"
  - Our sidebar approach is the older Netflix pattern — still valid for streaming apps

- **Streaming App UX Best Practices** (forasoft.com, 2026):
  - "10-foot UI on TV — grid-based focus navigation, minimum 60×60 pt focus targets, body type 14pt or larger, audio cues on focus change"
  - "Five primary tabs is the practical maximum"
  - "Use platform conventions: Android TV Leanback"

### Findings
| # | Issue | Severity | Research Backing |
|---|-------|----------|------------------|
| S1 | Sidebar is collapsed-only (64dp) — no expanded hover/peek mode | **Design Choice** | Android TV guide says "navigation rail with icon+text when collapsed, text visible on expansion." Our 64dp matches Netflix's current pattern. No change needed. |
| S2 | No label text on hover — users can't distinguish icons at a glance | **Low** | Android TV guide: "Avoid creating a navigation drawer without icons." We HAVE icons. Labels on hover would be ideal but the 64dp width is intentional per PROJECT_STATE.md. |
| S3 | Badge count not rendered despite `SidebarItem.badgeCount` existing | **Low** | No direct research backing — purely a feature gap. |
| S4 | No search expansion on Left from content | **Low** | Android TV: "Place controls in locations that don't overlap with other clickable elements." Search is already accessible via sidebar. |

### Recommendation
**No changes to sidebar.** The 64dp collapsed-only design is intentional (PROJECT_STATE.md line 311: "Sidebar is collapsed-only, icon-only"). The red accent bar matches Netflix's brand color. The current implementation is solid.

---

## 2. D-PAD NAVIGATION — Full Audit

### Web Research Findings
- **Android TV Navigation Guide** (developer.android.com/design/ui/tv/guides/foundations/navigation-on-tv):
  - "D-pad moves focus from one element to the nearest element in the corresponding direction"
  - "Ensure a user can navigate to all focusable elements on the screen"
  - "Ensure the navigation direction is straightforward and predictable"
  - "Categories traversed on vertical axis, items within each category browsed horizontally"

- **Compose Focus System** (developer.android.com/develop/ui/compose/touch-input/focus):
  - "Two-dimensional navigation via arrow keys/D-pad — traversal order only visits elements at a given level"
  - "Use focusProperties to override default traversal order"
  - "FocusRequester.Cancel stops focus from moving in that direction"

- **Android TV Focus System Guide** (developer.android.com/design/ui/tv/guides/styles/focus-system):
  - "At any given time, users should be able to focus on only one focusable element"
  - "Focus indicators are visual devices that emphasize focused elements"
  - "Scale indication adjusts element by x factor when focused" (default: 1.025, 1.05, 1.1x)

- **android/tv-samples Issue #158** (github.com/android/tv-samples/issues/158):
  - **KNOWN BUG**: "Focus jumps when rapidly cycling through Featured Movies carousel"
  - **Root cause**: "Buttons within a ClickableSurface" — focus lost during Crossfade animation
  - **Workaround**: Replace fadeIn/fadeOut with slideIn/slideOut, shorten to 100ms
  - This directly relates to our FeaturedHeroSection bug

### Findings
| # | Issue | Severity | Research Backing | Fix |
|---|-------|----------|------------------|-----|
| D1 | **Settings focus trap** — `pendingRailFocusRequestId` gating causes race condition | **High** | Android TV: "Ensure navigation direction is straightforward and predictable" — race condition violates this | Fix LaunchedEffect ordering |
| D2 | **Addons auto-trigger** — `LaunchedEffect(Unit)` fires focus on Settings open | **Critical** | Android TV: "At any given time, users should be able to focus on only one element" — unintended focus violates this | Gate behind `embedded` flag |
| D3 | **Hero auto-rotation nonce** — timer resets mid-cycle on rapid D-pad | **Low** | android/tv-samples #158: Known Crossfade + focus issue | Reduce animation duration |
| D4 | **CatalogBrowserScreen back handler** — goes to first card of first row, not same row | **Medium** | Android TV: "When user presses back, take them to previous destination" — should go to same row's first card | Fix `pendingBackTarget` logic |
| D5 | **FeaturedHeroSection UP focus** — `focusProperties { up = Cancel }` overrides `FocusLinks(up = sidebar)` | **Critical** | Android TV: "Ensure user can navigate to all focusable elements" — UP from buttons should go to sidebar | Remove `focusProperties { up = Cancel }` from Row |
| D6 | **PremiumHeroSection UP focus** — Same issue as D5 | **Critical** | Same as D5 | Remove `focusProperties { up = Cancel }` from Row |
| D7 | **Settings rows no focus scale** — `scale(1.0f)` makes focus feel "dead" | **Medium** | Android TV Focus Guide: "Scale indication adjusts element by x factor when focused" — missing scale violates convention | Add `focusedScale = 1.02f` |

---

## 3. HOME BANNERS — Full Audit

### Web Research Findings
- **android/tv-samples #158**: Known Crossfade + button focus issue in hero carousels
- **MegaV-IPTV commit**: Shows proper hero implementation with 8-second interval, dots, glass panel
- **Homeflix TV App** (github.com/azad25/homeflix-tv-app):
  - "Auto-sliding hero section with fade animations"
  - "Smooth D-pad navigation optimized for TV remotes"
  - "48dp side navigation with focus indicators"
  - "LEFT arrow navigates to sidebar from any screen"

- **NN/g Carousel Usability** (nngroup.com):
  - "Do not auto-forward on mobile" (we're TV, so auto-forward is OK)
  - "Test for right timing — 3 words per second as guideline"
  - "Don't stop at the last frame — continue cycling" ✅ (we do this)
  - "Ensure buttons and links are clear, large enough to decipher and click"

### Findings
| # | Issue | Severity | Research Backing | Fix |
|---|-------|----------|------------------|-----|
| H1 | **FeaturedHeroSection buggy on Movies/TV** — center card + side posters overlap | **Critical** | android/tv-samples #158: Known carousel focus issue. The centered card layout is non-standard for Android TV. | Remove FeaturedHeroSection from Movies/TV Shows |
| H2 | **PremiumHeroSection backdrop fallback** — poster-only content shows small in full-width banner | **Medium** | NN/g: "Ensure important content is placed mindfully" — poor fallback wastes banner space | Improve ContentScale logic |
| H3 | **Left gradient too aggressive** — 58% width, 48% alpha makes right half look unbalanced | **Low** | NN/g: "Ensure buttons are clear, large enough" — gradient shouldn't obscure content | Soften gradient |
| H4 | **No hero skip/dismiss** — 6-second auto-rotation on slow networks shows loading | **Low** | NN/g: "Test for right timing" — 6 seconds is reasonable | Add pause button |
| H5 | **Side peeking posters don't scale** — fixed 64x96dp ignores layout metrics | **Medium** | Android TV: "Use large text, high-contrast colors, bold graphics for 10-foot UI" — fixed sizes break on different screens | Scale with LocalTvLayoutMetrics |

---

## 4. SETTINGS PAGE — Bug Audit

### Web Research Findings
- **Android Settings Guide** (developer.android.com/design/ui/mobile/guides/patterns/settings):
  - "Use list-detail layout for settings"
  - "Group settings in smaller relevant groups"
  - "Don't allow single pane settings items to stretch to full width — set max-width or add supplementary content"
  - "Provide clear and polite defaults"

- **Android TV Layout Guide** (developer.android.com/design/ui/tv/guides/styles/layouts):
  - "Two-pane layout performs better when page shows hierarchical content"
  - "Panels visually separate and organize content"
  - "Avoid using too many panels — creates unnecessary cognitive load"
  - "Place related contents together in one panel"

### Findings
| # | Issue | Severity | Research Backing | Fix |
|---|-------|----------|------------------|-----|
| ST1 | **Addons auto-triggers URL input** — LaunchedEffect(Unit) fires on Settings open | **Critical** | Android TV: "Ensure focus is straightforward and predictable" — unintended dialog violates this | Gate LaunchedEffect behind `!embedded` |
| ST2 | **Buttons not centered** — `Arrangement.SpaceBetween` stretches rows | **High** | Android TV: "Don't allow single pane items to stretch to full width" — current layout stretches content | Change to consistent left-aligned with padding |
| ST3 | **DiagnosticsContent not centered** — values are right-aligned, text is left-aligned | **Medium** | Android Settings Guide: "Use clear and precise language" — misaligned values confuse users | Add consistent alignment |
| ST4 | **Settings header too large** — 40sp HeroTitle for sub-header | **Low** | Android Settings: "Provide overview" — header should be prominent but not overwhelming | Use Headline (32sp) |
| ST5 | **No scroll in detail panel** — Box without scroll can overflow | **High** | Android TV: "For scrolling lists, make sure D-pad up/down scroll the whole list" — long settings overflow without scroll | Wrap in LazyColumn |
| ST6 | **Category rail auto-focus race** — double focus requests on LaunchedEffect | **Medium** | Android TV: "At any given time, users should be able to focus on only one element" — race causes confusion | Deduplicate focus requests |

---

## 5. ADDONS PAGE — Bug Audit

### Findings
| # | Issue | Severity | Research Backing | Fix |
|---|-------|----------|------------------|-----|
| A1 | **URL input auto-shows** — LaunchedEffect(Unit) requests focus in embedded mode | **Critical** | Android TV: "Ensure focus is straightforward and predictable" — unintended UI state | Gate behind `!embedded` |
| A2 | **No embedded mode background** — transparent colors blend inconsistently | **Medium** | Android TV: "Use a uniform scheme for indicating focus" — inconsistent backgrounds break visual hierarchy | Add proper embedded background |
| A3 | **AddonSmallButton no focus requester** — hard to reach via D-pad | **Medium** | Android TV: "Ensure a user can navigate to all focusable elements" — buttons unreachable | Add FocusRequester |

---

## 6. MOVIES / TV SHOWS PAGES — Bug Audit

### Findings
| # | Issue | Severity | Research Backing | Fix |
|---|-------|----------|------------------|-----|
| M1 | **FeaturedHeroSection buggy** — same as H1, M1 | **Critical** | android/tv-samples #158: Known issue | Remove FeaturedHeroSection |
| M2 | **Hero items empty** — no fallback behavior | **Low** | NN/g: "Ensure important content is placed elsewhere in IA" — no fallback wastes space | Add empty state handling |
| M3 | **onIndexChange is no-op** — hero index not tracked | **Low** | No research backing | Wire up callback |

---

## Implementation Plan

### Phase 1: Critical Fixes (Must-Do)

#### Fix 1: Remove FeaturedHeroSection from Movies/TV Shows
**Files:** `MoviesScreen.kt`, `TvShowsScreen.kt`  
**Action:** Remove `hero` parameter from `CatalogBrowserScreen` call. Movies/TV Shows will show clean catalog rows without the buggy hero banner.  
**Rationale:** android/tv-samples #158 confirms this is a known Crossfade + focus issue in hero carousels. The centered card layout is non-standard for Android TV.

#### Fix 2: Fix Addons Auto-Trigger in Settings
**Files:** `AddonsScreen.kt`  
**Action:** Change `LaunchedEffect(Unit)` to only fire when `!embedded`. The `firstActionRequester.requestFocusAfterFrames()` should NOT run when embedded in Settings.  
**Rationale:** Android TV guide: "At any given time, users should be able to focus on only one focusable element."

#### Fix 3: Fix Hero Button UP Focus
**Files:** `PremiumHeroSection.kt`, `FeaturedHeroSection.kt`  
**Action:** Remove `Modifier.focusProperties { up = FocusRequester.Cancel }` from the Row containing buttons. The `FocusLinks(up = sidebarRequester)` on the buttons will then work correctly.  
**Rationale:** Android TV: "Ensure a user can navigate to all focusable elements on the screen." UP from hero buttons should go to sidebar.

### Phase 2: High Priority Fixes

#### Fix 4: Center Settings Page Buttons
**Files:** `SettingsDesignSystem.kt`  
**Action:** In `SettingsContentActionRow` and `SettingsContentToggleRow`, change `Arrangement.SpaceBetween` to consistent left-aligned layout. Add max-width constraint.  
**Rationale:** Android TV: "Don't allow single pane items to stretch to full width."

#### Fix 5: Add Scroll to Settings Detail Panel
**Files:** `SettingsScreen.kt`  
**Action:** Wrap `SettingsCategoryContent` in a `LazyColumn` or `verticalScroll(rememberScrollState())`.  
**Rationale:** Android TV: "For scrolling lists, make sure D-pad up/down scroll the whole list."

#### Fix 6: Fix CatalogBrowserScreen Back Handler
**Files:** `CatalogBrowserScreen.kt`  
**Action:** Change `pendingBackTarget` to track the current row's index and restore focus to the first card of the same row, not the first row.  
**Rationale:** Android TV: "When user presses back, take them to previous destination."

### Phase 3: Medium Priority Fixes

#### Fix 7: Add Focus Scale to Settings Rows
**Files:** `SettingsDesignSystem.kt`  
**Action:** Change `scale = CardDefaults.scale(focusedScale = 1.0f)` to `focusedScale = 1.02f` in `SettingsContentActionRow` and `SettingsContentToggleRow`.  
**Rationale:** Android TV Focus Guide: "Scale indication adjusts element by x factor when focused."

#### Fix 8: Fix Settings Category Rail Auto-Focus Race
**Files:** `SettingsScreen.kt`  
**Action:** Add guard to prevent double focus requests when `selectedCategoryIndex` changes. Use `snapshotFlow` instead of multiple `LaunchedEffect` blocks.  
**Rationale:** Android TV: "At any given time, users should be able to focus on only one element."

---

## Files to Modify

| File | Changes | Priority |
|------|---------|----------|
| `app/src/main/java/com/unspooled/app/ui/screens/movies/MoviesScreen.kt` | Remove hero parameter | Critical |
| `app/src/main/java/com/unspooled/app/ui/screens/tvshows/TvShowsScreen.kt` | Remove hero parameter | Critical |
| `app/src/main/java/com/unspooled/app/ui/screens/addons/AddonsScreen.kt` | Gate LaunchedEffect behind !embedded | Critical |
| `app/src/main/java/com/unspooled/app/ui/components/PremiumHeroSection.kt` | Remove focusProperties { up = Cancel } from Row | Critical |
| `app/src/main/java/com/unspooled/app/ui/components/FeaturedHeroSection.kt` | Remove focusProperties { up = Cancel } from Row | Critical |
| `app/src/main/java/com/unspooled/app/ui/screens/settings/SettingsDesignSystem.kt` | Center buttons, add focus scale | High |
| `app/src/main/java/com/unspooled/app/ui/screens/settings/SettingsScreen.kt` | Add scroll, fix auto-focus race | High |
| `app/src/main/java/com/unspooled/app/ui/components/CatalogBrowserScreen.kt` | Fix back handler | High |

---

## Verification

After each fix:
1. Run `./gradlew :app:compileDebugKotlin` — must pass
2. Run `./gradlew :app:testDebugUnitTest` — 171 tests must pass
3. Manual D-pad testing on each affected screen
