# UI Improvement Iteration Pattern

## Overview

Systematic approach to improving Android TV Compose UI: research → patch → build → verify → refine → build again. Two passes per tier ensures fixes are correct before moving on.

## Tier Structure

| Tier | Focus | Examples |
|------|-------|----------|
| 1 | Performance | Card scale/shadow, animation durations, backdrop delays, auto-rotation timing |
| 2 | Visual Polish | Contrast ratios, typography sizes, accent glows, visual hierarchy |
| 3 | Nice-to-Have | Empty states, shimmer effects, focus animations, iconography |

## The Loop

1. **Research** — Check Android/Compose docs, WCAG specs, TV UX guidelines for the specific issue
2. **Patch** — Apply fixes to code files
3. **Build** — `./gradlew assembleDebug`
4. **Verify** — If compile errors, fix them. If clean, proceed.
5. **Refine (Pass 2)** — Go back and optimize: add polish, fix edge cases, improve aesthetics
6. **Build again** — Verify Pass 2 is clean
7. **Move to next tier**

## Key Pitfalls Discovered

### `Modifier.scale()` Not Available in Compose 1.10.3
The `scale` modifier was added in Compose Foundation 1.8.0 but is NOT in the `foundation` artifact used by this project (androidx.compose.foundation:foundation:1.10.3). Checking the AAR jar confirms no `scale` class. **Workaround:** Keep `graphicsLayer { scaleX, scaleY }` but fix the shadow issue by expanding the parent Box height to `baseCardHeight * scale` so the shadow renders at the correct expanded size.

### Hero Auto-Rotation Interrupts Reading
6s rotation is too fast for TV hero text. **Fix:** 8s delay + pause rotation when the hero section is focused (`isSectionFocused` state). Use a `while(isSectionFocused) { delay(500) }` loop to wait until unfocused before resuming.

### Sidebar Icon Contrast
`TextTertiary` (0xFF7A7F99) on `BackgroundDeep` (0xFF000000) = ~4.1:1 — borderline WCAG AA. **Fix:** Use `TextSecondary` (0xFFB8BCD0) = ~6.5:1.

### Accent Bar Needs Glow
Flat accent bar looks dead on TV. **Fix:** Add a wider, semi-transparent layer behind the core bar: `AccentColor.copy(alpha = 0.15f)` at 6dp width behind the 2dp core bar.

### Animation Durations Should Use Tokens
Hardcoded `tween(250)`, `tween(200)`, `tween(150)` scattered across composables. **Fix:** Use `UnifiedTokens.Motion` constants (`Card=200`, `CardExpand=280`, `Default=220`, `Focus=160`). Add new tokens as needed.

### Backdrop Expansion Delay
Unlimited `backdropExpandDelaySeconds * 1000` can be very slow. **Fix:** Cap at 1500ms: `(delaySeconds * 1000L).coerceAtMost(1500L)`.

### Player Title Too Small for 65" TV
Default `titleMedium` is ~16sp — unreadable at 8ft on 65". **Fix:** Explicit `22.sp`.

### Seek Buttons with Text Labels
"⏪ 10" / "⏩ 10" text is cluttered. **Fix:** Icon-only (`⏪` / `⏩`), show dynamic seconds only on rapid press.

### Timeline Time Labels
12sp is too small for TV. **Fix:** 14sp minimum.

### Details Hero Poster Too Small
0.62f of metrics width = 62% of standard — poster looks diminished. **Fix:** 0.85f with shadow (8dp elevation).

### Filter Sheet Visual Hierarchy
Section titles at 12sp/TextSecondary blend into chips. **Fix:** 14sp/TextPrimary + accent-tinted header background pill.

## Tier 3 — Nice-to-Have Patterns

### Shimmer Diagonal Sweep
Two shimmer systems in the codebase. Both default to purely horizontal sweep:
- **`PlaceholderShimmer.kt`** — Draws shimmer rectangles via `drawWithCache` / `drawRect` with `Brush.linearGradient`. Start/end Offset both have `y = 0f` for horizontal-only.
- **`Skeletons.kt` `rememberShimmerBrush()`** — Returns a `Brush.linearGradient` for skeleton backgrounds. Offset start/end Y = 0f.

**Fix:** Set the y-component to `0.4f` of the x-component for a diagonal (down-right) sweep:
```kotlin
// Before (horizontal)
start = Offset(shimmerOffset * DISTANCE_PX, 0f)
end = Offset((shimmerOffset + WIDTH) * DISTANCE_PX, 0f)

// After (diagonal)  
start = Offset(shimmerOffset * DISTANCE_PX, shimmerOffset * DISTANCE_PX * 0.4f)
end = Offset((shimmerOffset + WIDTH) * DISTANCE_PX, (shimmerOffset + WIDTH) * DISTANCE_PX * 0.4f)
```

### Empty State Icon Enhancement
The `UnspooledEmptyState` composable uses text-based emoji icons in a circular container:
- **Default icon**: Changed from `"◇"` to `"✦"` for a better generic empty-state visual
- **Icon background**: Replaced solid `SurfaceHigh` with `Brush.radialGradient(Accent.copy(alpha=0.08f), Color.Transparent)`
- **Contextual icons per screen**: Search screen gets `"🔍"` initial / `"📭"` no-results. Other screens pass screen-appropriate emoji.

### Settings Content Action Row Focus Shadow
`SettingsContentActionRow` changes background to white on focus but lacks depth. **Fix:** Add conditionally `Modifier.shadow(elevation=6.dp, shape=RoundedCornerShape(32.dp), ambientColor=Color.Black.copy(alpha=0.3f))` when focused. Needs `import androidx.compose.ui.draw.shadow`.

### SeeAll Screen Back Button
`SeeAllScreen` had no visible back button. **Fix:** Add a clickable Row overlay at `Alignment.TopStart` inside the outermost Box:
- Use `Modifier.clickable { navController.popBackStack() }` + `Modifier.focusable()` for d-pad
- Style with `← Back` text, `NuvioTheme.colors.Accent` color
- **Pitfall:** Do NOT use `Modifier.onClick {}` — requires `@OptIn(ExperimentalFoundationApi::class)`. Use `Modifier.clickable {}` from `foundation.clickable`.

### Profile Entrance Animation
`ProfileSelectScreen` had no entrance animation. **Fix:**
- `entered` + `LaunchedEffect(Unit) { delay(80ms); entered = true }`
- `animateFloatAsState if(entered) 1f else 0f, tween(600ms, EaseOutCubic)`
- `graphicsLayer { alpha=entranceAlpha; scaleX=0.8f + 0.2f*entranceAlpha; scaleY=... }` on the main Column
- Start at 0.8x (not 0.5x — too jarring on TV).

## Loop Behaviour (Post-Build Flow)

When the user says "do it in a loop" or "once it builds go through the loop again":

1. **Apply Pass 1** — All changes simultaneously (`delegate_task` for parallel edits, max 3 concurrent)
2. **Build** — `./gradlew assembleDebug`. Fix any compile errors immediately (wrong APIs, missing imports/opt-ins)
3. **Pass 1 complete** — Immediately proceed to Pass 2 refinement without stopping to ask
4. **Apply Pass 2** — Refine values, aesthetics, edge cases based on what Pass 1 revealed
5. **Build again** — Verify clean
6. **Move to next tier** — No sign-off needed; the loop covers all tiers

## Build Command
```bash
cd ~/Unspooled && ./gradlew assembleDebug
```

## Files Typically Modified
- `ui/cards/PremiumContentCard.kt` — card animations, scale, shadow
- `ui/components/FeaturedHeroSection.kt` — hero rotation, backdrop
- `ui/theme/UnifiedTokens.kt` — motion tokens, colors
- `ui/scaffold/GlassSidebar.kt` — sidebar contrast, accent bar
- `ui/screens/player/PlayerControls.kt` — typography, seek buttons
- `ui/screens/details/DetailsScreen.kt` — poster size, shadows
- `ui/screens/search/FilterSheet.kt` — visual hierarchy
