# Watch — Appearance & Winamp skins

**Job:** Let the operator choose how TVAnarchy looks — default dark UI or
Winamp-style Player chrome, including real classic `.wsz` skin files.

One-liner: *How does the Player (and sidebar tint) feel?*

**Pillar:** **Cross** settings (shell chrome). Affects Watch Player most visibly;
Library/Download tabs use accent tints only.

This doc covers **Watch chrome only**. Download has its own UI (Search,
Downloads tabs); Net has embedded UI (Settings subscriptions, Player merge hints).
All three live in the same app shell — see [correlation/ui.md](../correlation/ui.md).

---

## Scope

| In | Out |
|----|-----|
| `appTheme` picker (Standard + 3 built-in Winamp palettes) | iOS Winamp chrome (macOS only) |
| Import / install `.wsz` (zip of BMP + TXT) | Full Winamp 2.x window suite (EQ, playlist, MB) |
| Sprite-based Player transport, scrubber, volume, LED time | Federated / shared skins over Net |
| `VISCOLOR.TXT` → spectrum bars; `PLEDIT.TXT` → LED tints | Skin authoring tool inside TVAnarchy |
| Bundled reference skin `base-2.91.wsz` | Shade mode / snap-to-edge layout |
| Persist skin id + name in `settings.json` | Per-device skin overrides |

---

## Status (v1 → v2 doc)

| Slice | Status | Notes |
|-------|--------|-------|
| Built-in themes | **Shipped** | `standard`, `winamp-classic`, `winamp-modern`, `winamp-llama` |
| Theme environment + palette | **Shipped** | `ThemePalette`, `.themed()` on `RootView` |
| Built-in Winamp Player shell | **Shipped** | Bevel, LED, spectrum, transport in `PlayerView` |
| `.wsz` extract + cache | **Shipped** | `WinampSkinLoader` → `~/.local/state/tv-anarchy/skins/<sha256>/` |
| Sprite crop (Webamp coords) | **Shipped** | `WinampSkinSprites` + `WinampSkinStore` |
| Skin-sprite Player chrome | **Shipped** | When `winampSkinId` set + skin loads |
| Settings import UI | **Shipped** | Setup → Appearance → Import / Base / Clear |
| Tests | **Shipped** | `WinampSkinLoaderTests`, `SettingsStoreTests` |
| Extended sheet coverage | Planned | SHUFREP, BALANCE, EQ_EX, PLAYPAUS indicator |
| Skin validation preview | Planned | Thumbnail + missing-sheet report before apply |
| MCP / `AppLocalAPI` patch | Planned | `winampSkinId` in settings patch |
| Sidebar chrome from skin | Optional | PLEDIT title colors on nav (Phase 6 UI pass) |

---

## Architecture

```mermaid
flowchart TB
    subgraph settings [Settings — Watch]
        SS[SettingsStore.appTheme]
        SK[winampSkinId + winampSkinName]
    end

    subgraph core [TVAnarchyCore — Display]
        AT[AppTheme]
        WL[WinampSkinLoader]
        SP[WinampSkinSprites]
    end

    subgraph app [TVAnarchy — Theme]
        WS[WinampSkinStore]
        TP[ThemePalette.merging skin]
        WV[WinampSkinViews]
        WC[WinampComponents fallback]
    end

    subgraph ui [Surfaces]
        RV[RootView.themed]
        PV[PlayerView]
        SV[SetupView Appearance]
    end

    SS --> AT
    SK --> WL
    WL -->|extract .wsz| Cache["skins/&lt;sha256&gt;/"]
    Cache --> WS
    SP --> WS
    WS --> WV
    AT --> TP
    WL -->|VISCOLOR PLEDIT| TP
    TP --> RV
    WS --> PV
    WV --> PV
    WC --> PV
    SK --> SV
    WS --> SV
```

### Layer rules

1. **Core stays AppKit-free** — loader parses zip + text; sprite rects are data.
2. **App owns bitmaps** — `WinampSkinStore` loads `NSImage`, crops sprites.
3. **Fallback always** — missing sheet or sprite → built-in `WinampComponents`.
4. **Winamp chrome gate** — `AppTheme.usesWinampChrome`; Standard theme ignores skin sprites on Player (skin cache may remain on disk).

---

## Modules

### Core (`Sources/TVAnarchyCore/Display/`)

| Module | Role |
|--------|------|
| `AppTheme` | Enum: standard + 3 built-in Winamp palettes; `usesWinampChrome` |
| `WinampSkinLoader` | Install `.wsz`, parse `VISCOLOR.TXT` / `PLEDIT.TXT`, validate sheets |
| `WinampSkinSprites` | Webamp-compatible crop rects per sheet name |
| `WinampSkinPackage` | Installed skin metadata (`id`, `visColors`, `pledit`, `availableSheets`) |

### App (`Sources/TVAnarchy/Theme/`)

| Module | Role |
|--------|------|
| `ThemePalette` | Resolved colors; `merging(skin:)` overlays VISCOLOR / PLEDIT |
| `ThemeEnvironment` | `@Environment(\.themePalette)`, `.themed(_:skin:)` |
| `WinampComponents` | Built-in bevel, transport, LED, spectrum, title bar |
| `WinampSkinStore` | `@Observable` sheet cache + sprite crop |
| `WinampSkinViews` | Sprite transport, scrubber, volume, LED, title bar |

### Consumers

| Surface | Behavior |
|---------|----------|
| `RootView` | `WinampSkinStore` state, reload on settings change, inject environment |
| `PlayerView` | `winampPlayerShell` — skin sprites when `winampSkin.isActive` |
| `SetupView` | Theme picker + `.wsz` import / Base / Clear |
| `MiniTransport` | Built-in bevel buttons when Winamp theme (no sprites yet) |

---

## State

| Artifact | Path | Writer | Reader |
|----------|------|--------|--------|
| Theme + skin pointers | `settings.json` → `appTheme`, `winampSkinId`, `winampSkinName` | `SettingsStore` | `LibraryController`, `RootView` |
| Extracted skin cache | `~/.local/state/tv-anarchy/skins/<sha256>/` | `WinampSkinLoader.install` | `WinampSkinStore` |
| Source archive copy | `…/skins/<sha256>/source.wsz` | install | re-extract if needed |
| Bundled reference | `TVAnarchy.app/Resources/base-2.91.wsz` | build (`project.yml`) | Setup “Use Base Skin” |

Override dir for tests: `TV_ANARCHY_STATE_DIR` (same as other Watch state).

### `settings.json` fields

```jsonc
{
  "appTheme": "winamp-classic",      // standard | winamp-classic | winamp-modern | winamp-llama
  "winampSkinId": "<sha256>",        // null = built-in palette only on Player
  "winampSkinName": "Base 2.91"      // display label
}
```

Import auto-selects `winamp-classic` when current theme is `standard`.

---

## Sprite coverage (TVAnarchy subset)

Coordinates match [Webamp `skinSprites.ts`](https://github.com/captbaritone/webamp) — required for cross-skin compatibility.

| Sheet | Sprites used | Player surface |
|-------|--------------|----------------|
| `CBUTTONS` | prev / play / pause / next (+ active) | Transport row |
| `POSBAR` | background, thumb | Position scrubber |
| `VOLUME` | background, thumb | Volume slider |
| `NUMBERS` | `DIGIT_0` … `DIGIT_9` | LED elapsed / duration |
| `TITLEBAR` or `MAIN` | title bar / window bg | Player header |
| `PLAYPAUS` | play / pause / stop indicators | *planned* — status LED |
| `VISCOLOR.TXT` | RGB rows | `WinampSpectrum` bar colors |
| `PLEDIT.TXT` | Normal, Current, NormalBG, SelectedBG | LED + accent tint |

**Minimum compatible skin:** `CBUTTONS` + `POSBAR` (+ `MAIN` or `TITLEBAR`).

---

## v2 build plan (enhancements)

Appearance docs ship in **Phase 0** (this file). Code is already on `main`;
remaining work is a **parallel Watch track** — does not block Net phases.

### Track A — Polish (1 PR, ~2 days)

| Task | Target |
|------|--------|
| `PLAYPAUS` indicator next to title | `PlayerView` + `WinampSkinViews` |
| MiniTransport sprite buttons when skin active | `MiniTransport.swift` |
| `AppLocalAPI` / `AppSettingsPatch` for skin fields | `AppLocalAPI.swift` |
| Missing-sheet banner in Setup | `SetupView` |

**Tests:** `AppLocalAPITests` patch round-trip; UI test optional.

**Exit:** Operator can set skin via local API; Player shows play/pause LED.

### Track B — Validation UX (1 PR, ~1 day)

| Task | Target |
|------|--------|
| Post-install compatibility report | `WinampSkinPackage` + Setup |
| Preview strip (transport + scrubber thumbs) | `SetupView` |
| Reject skins missing `CBUTTONS` or `POSBAR` before persist | already throws; surface message |

**Exit:** Import shows what works before switching theme.

### Track C — Extended chrome (optional, 2–3 PRs)

| Task | Target |
|------|--------|
| `SHUFREP` shuffle/repeat | `PlayerView` when queue active |
| `BALANCE` pan slider | low priority |
| `REGION.TXT` window shapes | defer — high effort, low value |
| Read `BASE.SKIN` coordinates | only if we add more windows |

**Exit:** Closer to Winamp 2.x fidelity; still single-window Player.

### Track D — Phase 6 UI alignment

Fold into [plan.md § Phase 6](../plan.md#12-phase-6--ui--docs-alignment):

- Sidebar selection tint from imported `PLEDIT.SelectedBG` when Winamp theme active.
- Optional “Appearance” subsection label in sidebar docs ([ui.md](../correlation/ui.md)).

---

## Tests (current + planned)

| Test | Asserts |
|------|---------|
| `WinampSkinLoaderTests.testInstallBaseSkin` | Extract, sheets, vis colors |
| `WinampSkinLoaderTests.testReloadInstalledSkin` | Cache reload by id |
| `WinampSkinLoaderTests.testParseVisColors` | `r,g,b` + comment strip |
| `WinampSkinLoaderTests.testParsePledit` | hex RGB keys |
| `WinampSkinLoaderTests.testWinampSkinSettingsPersist` | settings round-trip |
| `SettingsStoreTests.testAppThemePersists` | theme enum |
| *planned* `WinampSkinStoreTests` | BMP crop non-empty for Base skin |
| *planned* `AppLocalAPITests` | patch `winampSkinId` |

Fixture: `Sources/TVAnarchy/Resources/base-2.91.wsz` (repo path; also app bundle).

---

## Risks

| Risk | Mitigation |
|------|------------|
| BMP crop Y-flip wrong on some skins | Webamp coords + unit test against Base skin thumbs |
| Security-scoped import fails silently | `startAccessingSecurityScopedResource` in Setup |
| Large skin cache on disk | SHA256 dedupe; one cache dir per skin |
| Nonstandard `.wsz` layouts | Require Webamp sheet names; fallback to built-in chrome |
| XcodeGen drops `.wsz` from bundle | `project.yml` → `buildPhase: resources` on explicit path |

---

## Out of scope (v2)

- **Net part for skins** — skins are opaque blobs, not TVAnarchy facts; no `contentKey`.
- **iOS** — Lilith tokens / native chrome stay default on phone.
- **Winamp 3+ / Modern skins** — classic 2.x BMP only.
- **Skin editor** — use external Winamp Skinning Tutorial / Webamp.

---

## Correlation

- Pillar definition: this file (cross chrome + Watch Player)
- Component map: [correlation/components.md](../correlation/components.md)
- UI surfaces: [correlation/ui.md](../correlation/ui.md)
- State paths: [correlation/state.md](../correlation/state.md)
- Master schedule: [plan.md § 12.4 + full Appendix C (theming deep-dive, not legoblocks, custom sprite system)](../plan.md#appendix-c--best-coding-practices-pillar-separation-dry-switching-and-ui-theming)

**Theming architecture summary (see plan Appendix C)**: Hybrid custom (AppKit sprites from real .wsz + SwiftUI environment/views + WinampComponents fallbacks). Whole shell gets palette; heavy chrome Watch/Player only. iOS standard only. Detailed implementation + extension rules in the plan appendix.