tv-anarchy/v2/pillars/watch-appearance.md
Natalie 4a2ceb9781 feat(offline): inline star-to-keep and trash-to-cull on cache rows
Surface the existing pin (keep-from-cull) and per-file delete actions as
visible inline buttons on each offline cache row instead of context-menu-only:
a star toggles protection from auto-cull (and restore-if-missing), a trash
culls that file early. Aligns wording/icons to the star metaphor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:12:41 -04:00

10 KiB
Raw Permalink Blame History

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.


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

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 bitmapsWinampSkinStore loads NSImage, crops sprites.
  3. Fallback always — missing sheet or sprite → built-in WinampComponents.
  4. Winamp chrome gateAppTheme.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.jsonappTheme, 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

{
  "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 — 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_0DIGIT_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, 23 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:

  • Sidebar selection tint from imported PLEDIT.SelectedBG when Winamp theme active.
  • Optional “Appearance” subsection label in sidebar docs (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.ymlbuildPhase: 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

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.