tv-anarchy/v2/plan.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

79 KiB
Raw Permalink Blame History

Building v2 from v1

⚠️ REFRESH STATUS (2026-06-21): The original detailed plan text below had become partially stale. Library titles + display names (old "Phase 1b / 7.0") are largely shipped in code (TitleLibrary.swift, LibraryDisplayNames.swift, scanner integration, models, tests green). Phase numbering, PR stack (§18), and some task lists in the early detailed sections no longer matched reality or the council-of-experts synthesis.

2026-06-22 update: Discord planes + bridge (previously only in docs/roadmap.md + .project/history/20260608_fleet-manager-mesh-design.md + pillars/devices.md duties) added to master phase table (§5), v1 baseline, new §13.5 Phase 8 spec (full design, invariants, modules, exits, risks, open decisions), success metrics, timeline, execution, backlog, and infra security checklist. Living doc now authoritative for the integration. No new top-level files.

We have refreshed the living document in place:

  • High-level phases, target state, and schedule updated in §5 and new sections.
  • New Appendix C (best practices, pillar DRY/separation, switching, theming) added.
  • Old detailed subsections retained as "historical baseline" for reference but annotated.
  • Stale PR110 stack replaced with council-refined slices.
  • Cross-refs to shipped library work and council archive added throughout.

The authoritative current plan is the combination of the refreshed high-level sections + Appendix C + the per-pillar specs in the rest of v2/. The raw council output lives in the history archive. This keeps v2/plan.md as the single living source without new files.

See the dedicated v2/features.md for the comprehensive feature state comparison table (v1 vs v2).

Companion docs: README.md (overview + practices), roadmap.md (phase checklist), correlation/ (inventory + tags), pillars/, packages/, schema/net/*, glossary.md.

Library titles + canonical display names (Phase 1b/parallel) are largely shipped (TitleLibrary + LibraryDisplayNames + LocalLLMEpisodeRefiner + scanner integration + tolerant models + tests). v2/plan + manifest status must be reconciled on changes. See pillars/library*.md.

Companion docs: README.md (overview + practices), roadmap.md (phase checklist), correlation/ (inventory + tags), pillars/, packages/, schema/net/*, glossary.md.

Library titles + canonical display names (Phase 1b/parallel) are largely shipped (TitleLibrary + LibraryDisplayNames + LocalLLMEpisodeRefiner + scanner integration + tolerant models + tests). v2/plan + manifest status must be reconciled on changes. See pillars/library*.md.


1. What v2 is (and is not)

v2 is

  • A product and engineering lens for everything already in the repo.
  • A new pillar (Net) with schemas, runtime, and UI hooks.
  • A sequenced build that ships value at every phase without blocking on friends, seedboxes, or a universal web client.

v2 is not

  • A rewrite of Watch or Download.
  • Three separate apps — pillars are organizational; one shell (RootView) hosts Watch tabs, Download tabs, and Net settings/Player hooks.
  • A rename of TVAnarchyCore into three frameworks (premature).
  • A move of governor/ or mcp/ under v2/ (paths stay stable for ops).
  • Moving the current (v1) sources into a top-level v1/ directory or similar fork (big-bang structural change that would break builds, deploys, Xcode, paths, and imports; creates exactly the legacy baggage v2 is designed to avoid).
  • Using personalized device/host names (e.g. “black”, “plum”, or any other host-specific name for the Phase 7 control-plane daemon) anywhere in planning, code, docs, or examples. Always use abstract terms: “the always-on host” / “central storage host”, “roaming client” / “laptop client”, and for the Phase 7 feature “the control-plane host daemon on the always-on host” (or short “host daemon” after first use). This applies to all discussions of improvements or features for it (e.g. from prior projects like WatchTV-UWU).
  • “Net” as a name for VPN, WireGuard, HTTP bridge, or torrent transport.

UI model (one container)

Layer Role
Sources/TVAnarchy/RootView macOS shell: nav, global chrome, routes to pillar views
Watch views HomeView, PlayerView, LibraryView, Theme/, …
Download views SearchView, DownloadsView
Net views No dedicated tab — SetupView Net section, PlayerView merge UX (planned)
TVAnarchyCore Controllers for all pillars; UI-agnostic

Download and Net are affected by v2 UI work (Settings subscriptions, Search ranking, Player badges). Watch appearance (Winamp) is one Watch-only slice — see watch-appearance.md.

Target end state

TVAnarchy (one product, one app shell)
├── Watch      — playback + Home/Player UX                     [v1 shipped]
├── Library    — catalog, scan, metadata, grouping + titles    [v1+ shipped titles/display]
├── Download   — acquire + retain media via BitTorrent         [v1 mostly shipped]
├── Net        — sync TVAnarchy facts between installs         [v2 greenfield]
└── Devices    — registry, install pairing, shared media vol.  [partial → storage v2]

Settings: per-pillar sections + cross (theme + shell). See pillars/settings.md.

Product term install replaces “fleet” in UI; fleet.json stays internal until migration. Pillars own their state paths, controllers, and Settings sections; cross owns theme chrome and global shell.

Best practices in this doc (new 2026-06): See Appendix C for how pillars achieve separation while staying DRY, how "switching" works in the single shell, UI theming implementation (custom AppKit+SwiftUI hybrid + Winamp sprite system — not pure SwiftUI legoblocks, no Godot/Rust in this client), and full coding standards application (SOLID/DRY/strong-typing/error-handling/zero-debt from project instructions).


2. v1 baseline (today)

Use this as the migration floor. Do not regress these while building v2.

Area Status Location
macOS app (all tabs) Shipped Sources/TVAnarchy, TVAnarchyCore
iOS companion Shipped Sources/TVAnarchyiOS + bridge
Library + player + metadata Shipped TVAnarchyCore/Library, PlayerController
Winamp themes + .wsz Player skins Shipped (macOS) TVAnarchy/Theme/, TVAnarchyCore/Display/watch-appearance.md
Torrent search + transmission UI Shipped SearchController, TorrentService, mcp
Governor watch + prefetch Shipped governor/src/watch.ts, keeper.ts
Fleet engine (single fleet) Shipped governor/src/fleet/*
peers_for CLI + fleet serve Shipped governor/src/fleet/peers.ts, serve.ts
Re-pin actuation, probe, daemon Shipped actuate.ts, probe.ts, daemon.ts
Net runtime Absent Spec: v2/schema/net/, v2/pillars/net.md
Friend-mesh byte relay Designed Blocked on external fleets + WG fabric
Control-plane HTTP daemon on the always-on host Designed docs/roadmap.md north star (planning name "host daemon" or equivalent)
Discord planes + bridge (control/ops + private availability; identity layer) Designed (post-mesh) .project/history/20260608_fleet-manager-mesh-design.md; runs on meshAnchor/broadcast duty (Devices pillar); notify surface only, never transport backbone

v1 technical constraints (preserve)

  • Control plane: native Swift talks to players directly (VLC HTTP, mpv SSH).
  • Data plane: heavy work shells to mcp CLI and recommender (no MCP link in app binary).
  • Governor: independent launchd daemon; app does not embed it.
  • No NFS for playback; local players use offline cache or route to the always-on storage host.
  • Watchlog: append-only JSONL at ~/.local/state/tv-anarchy-mcp/watched.jsonl.

3. Migration principles

  1. Ship each phase independently. Every milestone must be usable on a single-fleet home install with zero friends.
  2. Pillar tags, not folder chaos. New code gets a pillar comment / path convention (e.g. // pillar: net header); avoid big-bang moves of v1 files. This includes not moving the current sources into a v1/ top-level directory. Separation between v1 baseline and v2 features is achieved through:
    • Tolerant decoding (missing net: block or library titles = full v1 behavior).
    • Correlation manifest + components.md as the ownership map.
    • Segregated state paths under ~/.local/state/tv-anarchy/.
    • New Net code landing in TVAnarchyCore/Net/, BTD as shared package with faces.
    • The v2/ doc tree (plan, pillars, correlation) as the "clean" boundary.
  3. Net keys are content-based. Never path-based (fingerprinting / adult scope).
  4. Trust is per part. friends (f2f_only) vs network (k-anon) — not one global privacy mode.
  5. Download transport ≠ Net product. Reuse friend_mesh source kind; do not merge custody logic into Net merge logic.
  6. Net + Devices share BitTorrentDrive — external face (editions) vs internal face (storage pins). One package; see packages/bittorrentdrive.md.
  7. Tests gate promotion. Each phase adds tests before the next phase starts.
  8. Docs follow code. Update v2/ and docs/data-model.md when schemas land.

4. Architecture delta (v1 → v2)

flowchart TB
    subgraph v1 [v1 — shipped]
        W1[Watch UI + Core]
        D1[Download UI + governor fleet]
        T1[friend_mesh designed only]
    end

    subgraph v2 [v2 — add]
        N[Net runtime]
        E[Editions over torrent]
        M[Merge per part]
    end

    W1 -->|observations| N
    N -->|intro-markers, grouping| W1
    N -->|quality, hints| D1
    D1 -->|friend_mesh bytes| E
    N --> E
    M --> N

New modules (planned locations)

Module Pillar Location (recommended)
NetController Net Sources/TVAnarchyCore/Net/
ObservationStore Net same
IntroMarkersMerge Net same
BitTorrentDrive package TVAnarchyCore/BitTorrentDrive/, governor/src/drive/
EditionPublisher Net governor/src/net/publish.ts → drive external face
EditionSubscriber Net governor/src/net/subscribe.ts → drive external face
PlacementController Devices Swift → drive internal face
Net daemon tick Net governor/src/net/daemon.ts (hooked from fleet daemon)

Swift owns read path for Watch (low latency skip-intro). Governor owns publish/subscribe tick (alongside custody), because it already runs 24/7 and talks to transmission.


5. Phase plan (master schedule)

Refined post-council (2026-06): Library titles/display (old 1b) largely shipped — treat as Phase 0 reconciliation. Phases 1-2 remain critical solo-fleet path. Parallel tracks (settings, appearance, devices-storage) do not block Net 1-2. Full details + per-pillar PR breakdowns in the council archive + Appendix C.

Phase Name Duration (est.) Depends on
0 Pillar docs + library titles reconcile (shipped) Done + small
1 Net local (MVP: obs + merge + Player skip) 12 weeks Phase 0
1.5 Parallel: Settings split, appearance polish A/B, devices storage MVP overlaps 1-2
2 Net editions (single fleet, tx transport) 12 weeks Phase 1
3 Download hardening + infra (unblocks 4) external + 1-2w Seedbox/WG/launchd
4 Net subscribe (friend_mesh) 23 weeks Download stage 4 (F2F)
5 Net parts 25 (grouping first) ongoing Phase 4
6 UI + docs final + BTD extraction 1-2w Phase 1+
6a/b/c (see 1.5; watch appearance, settings, devices) parallel
7 Control-plane HTTP daemon on the always-on host (parallel long track) multi-sprint Operator priority
8 Discord planes + bridge (control/ops/announce + private availability bot; multi-identity / friendship root) multi-sprint Phase 4 (friend-mesh) + external bot tokens/servers (see .project/history/20260608_fleet-manager-mesh-design.md)

Phases 12 are pure v2 and do not require friends. Phases 34 align v2 Net with the existing fleet build order in docs/roadmap.md stages 24. Phase 8 is notify-surface only (Discord is never a custody/F2F/Net transport backbone).


6. Phase 0 — Pillar model

Goal: Everyone can classify any file/tab/daemon job by pillar.

Deliverables: v2/ tree (done), manifest.json, schemas, glossary.

Exit criteria:

  • correlation/components.md covers all major paths
  • docs/README.md and root README.md link to v2/
  • Optional: CI check that new TVAnarchyCore/ files declare pillar in header comment (lint script — low priority)

No code changes.


7. Phase 1 — Net local (single fleet, no torrent)

Goal: Net semantics work on one machine; Watch consumes merged intro markers without federation.

Historical baseline note (pre-2026-06 refresh): The subsections below (especially 7.0) describe work that is now largely shipped in the current codebase (see refreshed high-level phases in §5, library pillars, and council synthesis). The original detailed plan text is retained for provenance but should not be treated as current TODOs. Library reconciliation is Phase 0 work now. Net local (observation/merge/Player) remains the active Phase 1.

7.0 Title Library + display names (parallel — no Net dependency) [HISTORICAL / LARGELY SHIPPED]

Goal: Build a local episode-title dataset (contentKey rows); lookup before MLX on M2; canonical displayName in catalog.

Status per 2026-06 refresh: Core implementation (TitleLibrary store + merge + contentKey lookup, LibraryDisplayNames resolution chain, LocalLLMEpisodeRefiner + episode_refiner.py, scanner integration writing displayName/episodeTitle to CachedEpisode, tolerant models, some consumer updates, tests) is shipped and passing. Remaining: goldens, full dual-scanner (mcp TS) parity, complete consumer migration (no raw filenames), episode ingest pipelines, settings library section + UI, legacy title-refinements migration, re-resolve hooks.

Specs: library-titles.md, library-display.md.

PR Deliverable Current Status (refresh)
1 TitleLibrary store + lookup by contentKey Shipped (core)
2 episode_refiner.py + LocalLLMEpisodeRefiner (write on miss) Shipped + wired
3 LibraryDisplayNames chain; scanner denorms to library.json Shipped (scanner uses resolve)
4 Episode-level TVmaze/TMDB ingest → Title Library Remaining (one-shot)
5 Player / queue / iOS read displayName only Partial (core paths; bridge/MCP/iOS full migration needed)
6 Migrate title-refinements.json → library rows Remaining

Exit (refreshed): Second scan of known episodes uses DB (MLX ≤1 ever per contentKey); no raw release filenames in UI for cataloged episodes. See pillars + plan Appendix C for details. Old exit still valid as baseline.

7.1 Content keys [HISTORICAL — see refreshed §5 + library pillars for current]

Task Detail
Unify contentKey Extend ContentID.swift to emit stable keys for episodes (hash of normalized show+S+E+release fingerprint, not path)
Player binding PlayerController resolves current file → contentKey at play time
Tests ContentIDTests + new NetContentKeyTests

(Note: ContentID + ShowGrouping.canonical already central for TitleLibrary; any further unification is small and part of PR B in refreshed stack.)

7.2 Observation store

Task Detail
Path ~/.local/state/tv-anarchy/net/observations.jsonl
Swift ObservationStore Append NetObservation (matches v2/schema/net/observation.json)
Writers User skip intro → source: user-skip; optional ffmpeg detect → source: detect
Privacy Strip paths from persisted observations; store contentKey only

7.3 Merge (intro-markers only)

Task Detail
IntroMarkersMerge Cluster within ±3s; median introEndSeconds; weight user-skip > detect
Output ~/.local/state/tv-anarchy/net/merged/intro-markers.json
NetController Load merged view; expose introBounds(for: contentKey)
Gate minSources default 1 locally; minConfidence from subscription config later

7.4 Watch integration

Task Detail
Skip-intro precedence settings.skipIntroSecondsNet merged → per-file detect (future) → off
Skip button On skip, append observation at current position
UI No new tab; optional debug line in Player (“intro via Net”)

7.5 Tests

Test Asserts
NetObservationStoreTests Append, reload, no path leakage
IntroMarkersMergeTests Cluster, weight, median
PlayerIntroNetTests Skip uses merged bounds; skip writes observation

7.6 Exit criteria

  • Skip intro on a show uses merged JSON when present
  • User skip creates observation; second skip refines merge
  • No network calls; works offline
  • Schemas in v2/schema/net/ unchanged or version-bumped with note

7.7 Risks

Risk Mitigation
ContentID unstable across releases Include release group in fingerprint; document rotation
Duplicate skip-intro paths Single resolveIntroEnd() in PlayerController

8. Phase 2 — Net editions (single fleet, torrent transport)

Goal: Dogfood edition torrents on the always-on host; second device on the LAN can subscribe without friends.

8.1 Fleet config extension

Add to fleet.json (document in docs/data-model.md):

{
  "net": {
    "publisherFleetId": "example-fleet",
    "subscriptions": [
      { "part": "intro-markers", "trust": "friends", "edition": "latest" }
    ]
  }
}

Decoder: tolerant — missing net = no Net features.

8.2 Edition builder (governor)

Task Detail
governor/src/net/publish.ts Read observations + merged output → edition.jsonl + edition-manifest.json
Edition id intro-markers-YYYY-MM-DD or monotonic intro-markers-vN
Torrent create Shell to transmission add torrent (small files dir on the always-on host)
CLI portable-net-tv net publish [--part intro-markers] [--json]

8.3 Edition subscriber (governor)

Task Detail
governor/src/net/subscribe.ts Poll subscribed editions; verify sha256 from manifest
Import Append remote observations into local store; re-run merge
CLI portable-net-tv net sync [--json]
Daemon hook fleet daemon calls netSync() after custody tick

8.4 Swift read path

Task Detail
NetController.refresh() On library refresh or timer, read merged file mtime
No Swift torrent Governor owns torrent; Swift only reads merged JSON

8.5 mcp bridge (optional)

Task Detail
mcp/src/bridge/net.ts Expose merged intro bounds to iOS bridge
iOS Skip intro on phone when bridge returns bounds

8.6 Tests

Layer Tests
TS governor/test/net-publish.test.ts, net-merge-import.test.ts
Integration Publish on always-on host → sync on a client device → Player skip matches
Manifest Validate against v2/schema/net/edition-manifest.json

8.7 Exit criteria

  • net publish creates torrent; transmission shows seeding
  • Second machine net sync updates merged intro-markers
  • iOS benefits via bridge (if bridge task done)
  • Edition uses swarm_isolation: f2f_only even on LAN (habit for stage 4)

8.7 Risks

Risk Mitigation
Edition torrent confused with media torrent Separate download dir prefix net-editions/
Stale edition head edition: latest resolves via manifest pointer file

9. Phase 3 — Download hardening (v1 completion + mesh prep)

Goal: Finish Download stages 23 from docs/roadmap.md so Net stage 4 has transport. Runs in parallel with Phase 12 where possible.

9.1 Infrastructure (operator — may block)

Item Unblocks
Seedbox provisioned public_swarm_face, custody floor for laptop-only fleets
fleet.json mediaRoot + ssh on targets fleet repin --apply cross-host
launchd plist for fleet daemon Automated custody + Net sync
WG fabric decision (10.9.0.4) Stage 4 F2F

9.2 Code tasks (repo)

Task Pillar Detail
launchd template Download Document + sample plist in docs/operations.md
fleet serve on broadcast host Download Bearer token; client devices query peers_for over HTTP
Dead torrent re-search UI Download .project/objectives.md Part E — swap release
Bandwidth policy UI Download Surface BandwidthPolicy in Settings

9.3 Exit criteria

  • fleet daemon runs on schedule on the always-on host
  • peers_for reachable from client devices via bridge (optional)
  • Custody floor actuation tested on the central storage host + one target with mediaRoot

9.4 v2 correlation

No new pillar — strengthens Download so Net edition swarms can reuse friend_mesh infrastructure later.


10. Phase 4 — Net subscribe (friend_mesh)

Goal: Observations merge across fleets; trust boundaries enforced.

Prerequisite: Download design stage 4 — friend-mesh source + F2F relay (see .project/history/20260608_fleet-manager-mesh-design.md).

10.1 Transport

Task Detail
Edition swarms as friend_mesh Register in peers.ts source union for small infohashes only
f2f_only Forced for trust: friends parts
Relay Bytes hop through f2f_relay duty holder

10.2 Multi-fleet merge

Task Detail
minSources Default 2 for friends parts before Player trusts bounds
Provenance Track fleetId on each observation; UI never shows stranger identity on friends parts
Conflict UI Player uses best merge; Settings shows “3 fleets agree”

10.3 Observation gift economy

Task Detail
Auto-publish policy After local merge stabilizes, governor publishes edition (configurable)
Rate limit Max N observations per fleet per day per part (anti-spam)

10.4 signal part (network trust)

Task Detail
k-anon aggregator Governor rolls watchlog → anonymized counts per contentKey
Publish Only when distinct fleets ≥ K (default 5)
Consumer Reaper prioritizes dead torrents with high signal

10.5 Exit criteria

  • Friend fleet observation changes skip window on a client device
  • Stranger fleet cannot subscribe to friends edition without trust edge
  • signal edition reveals no single-fleet titles

10.6 Risks

Risk Mitigation
Adult content fingerprinting k-anon + content keys never include raw paths
Passkey leak via content share Keep Net editions separate from private media swarms

11. Phase 5 — Remaining Net parts

Ship after intro-markers path is proven. Order by consumer value:

Part Trust Phase Consumer
grouping friends 5a ShowGrouping — suggest merges
quality friends 5b Search — rank releases
peers-hints friends 5c peers_for — living infohash hints
signal network 5d Reaper priority (may start in 10.4)

Each part needs: schema in v2/schema/net/<part>.json, merge policy, publish/ subscribe support, and one Watch or Download consumer.


12. Phase 6 — UI and documentation alignment

Goal: Product surfaces match pillars without a redesign.

12.1 macOS sidebar (optional grouping)

Watch
  Home · Player · Adult
Library
  Library · Metadata
Download
  Search · Downloads
Devices
  This Mac · Devices
Net
  (Settings subsection + Player hooks)
Cross
  Logs · Settings (sectioned by pillar)

Implementation: RootView.Section enum grouping or section headers only — no rename of user-visible tab strings required in v2.0.

12.2 Settings → Net

Control Effect
Subscribe to intro-markers Toggle net.subscriptions[]
Contribute observations Master switch for publishing
Trust mode per part Advanced drawer

12.3 Doc migration

Doc Action
docs/architecture.md Lead with pillar diagram; link v2/
docs/data-model.md Add net block to fleet.json
docs/glossary.md Point to v2/glossary.md for pillar terms
docs/roadmap.md Add “v2 Net phases” cross-link

12.4 Watch appearance (Winamp .wsz)

Goal: Document and optionally extend classic Winamp skin support on macOS Player chrome. Not a Net feature — local preference only.

Full spec: pillars/watch-appearance.md.

Track Status Deliverable
Doc + correlation Phase 0 This plan + component/state/ui maps
Shipped base v1 Built-in themes, .wsz import, sprite Player
A — Polish Planned PLAYPAUS LED, MiniTransport sprites, AppLocalAPI
B — Validation UX Planned Compatibility report + preview on import
C — Extended chrome Optional SHUFREP, BALANCE (queue controls)
D — Sidebar tint Optional PLEDIT accent on nav when Winamp theme

Exit (track A): Skin settable via local API; Player shows skin play/pause indicator; MiniTransport uses sprites when skin active.

iOS: out of scope — stays Lilith / standard chrome.

12.5 Settings — per-pillar sections

Goal: Each pillar owns its prefs; cross owns theme. One Settings view, clear ownership.

Full spec: pillars/settings.md.

Task Pillar owner
Nested settings.json decode (tolerant) cross + all
SetupView section headers UI
Move notifyDownloads under Download section download
Move library types under Library section library
Net section (subscriptions) net
Devices section (mesh, VPN, pools) devices
AppLocalAPI section patches cross

Exit: Operator can answer “which pillar owns this toggle?” without reading Swift.

12.6 Devices — shared media volume (extension warmup)

Goal: Client spare disk extends central storage; watching on a client machine warms files to extension tier with alias paths — not offline-cache duplicates.

Full spec: pillars/devices-storage.md.

PR Deliverable
1 StoragePoolConfig + media-placement.json schema
2 PlacementController — resolve nearest tier for contentKey
3 MediaPaths + DownloadsIndex placement-aware
4 Extended warmup targets extension mount on active device
5 Devices UI — assign spare folder + quota
6 Deprecate duplicate rsync into offline-cache when pool member

Exit: Watch Show X on dev Mac → next episodes appear on spare volume; library shows one path; Player opens local alias.

Aligns with Download custody: canonical tier on always-on storage; extension is opportunistic — not a second custody floor.

12.7 Pillar split — Library + Devices (doc )

Was (v1 lump) Now (v2 pillar)
Library tab + LibraryController Library
Devices tab + DeviceConfig Devices
“Fleet” UX copy Install / Devices
Offline warmup duplicates Devices extension tier

Docs: library.md, devices.md.


13. Phase 7 — Parallel track: Control-plane HTTP daemon on the always-on host (universal client prep)

Not a pillar change — thins Watch control plane. Proceed when operator prioritizes Roku / web / retiring ssh-based client access.

Step Deliverable
7.1 HTTP daemon on the always-on host (player control, library index, media range streaming)
7.2 HTTP-based PlayerTarget implementation with SSH fallback for transition
7.3 Roaming clients talk directly to the always-on host; retire any intermediate bridge for playback paths
7.4 Web/PWA client parity (north star)

Pillar impact:

  • Watch becomes thin HTTP client.
  • Download + Net stay server-side on the always-on host / governor.
  • Net editions naturally live on the always-on host.

See docs/roadmap.md north star for tradeoffs (VLCKit offline vs PWA).

13.5 Phase 8 — Discord planes + bridge (parallel long track)

Goal: Provide Discord as a notification surface for control-plane ops, QA/community, release announcements, and (privately) mesh availability / custody signals. Establish discord_id as the root identity for multi-fleet ownership and signed friendships. Discord is never used as a transport or backbone for custody bytes, F2F relays, Net editions, or peers.

Prerequisite: Phase 4 (friend-mesh / F2F) so that all critical paths (custody, reaper, F2F, Net sync) are proven over WireGuard. Discord integration is pure surface; single-fleet value does not depend on it.

Design source of truth (verified): .project/history/20260608_fleet-manager-mesh-design.md (sections "Two graphs on one Discord identity layer", "Discord planes (keep separate)", build order, and "Networking — two independent planes"). Cross-refs in docs/roadmap.md, docs/glossary.md, v2/pillars/devices.md (meshAnchor duty), and DeviceConfig.swift.

13.5.1 Two planes — strict separation enforced

  • Control / ops / QA / community / release announcements plane: May target a project-owned "home" Discord server. Acceptable for ToS, distribution, and adult-content policy surface (operator-controlled).
  • Content / availability plane (magnets, custody signals, friend-trending, edition availability): MUST NEVER use the project/public home server. Operates exclusively via bots invited by the end user into their own personal Discord servers/guilds. Rides on top of the private friend-mesh only.

The same bridge process (on the broadcast anchor) may drive both with isolated clients/tokens.

13.5.2 Invariants (non-negotiable; match v2 principles + domain)

  • Notify surface, not backbone: All custody floor, reaper, F2F relay, Net observation/edition publish/subscribe, and peers_for execute exclusively over the fleet WireGuard overlay (or local). A Discord outage, rate-limit, or ban must leave the mesh, floor, and Net fully operational.
  • Watch parties (future): synchronized local playback only on each participant's devices. Discord carries voice + timestamps/sync signals at most — never streams or proxies copyrighted video.
  • Identity: One Identity = one discord_id + display_name + fleets[]. A person may own multiple installs (fleets); friendships are signed/revocable edges carrying the WG-key mesh identity + Discord context for human confirmation.
  • Privacy / adult: Never emit raw paths, full watchlog, or single-fleet attributable titles on the network plane. Use contentKey + displayName (if public) + k-anon / provenance rules from Net. Matches DE-regulated adult industry priors (red lines only CSAM/non-consent/trafficking).
  • Security surfaces: Bot tokens are secrets (FLEET-equivalent); never persisted in fleet.json/devices.json or app state. Least-privilege; inbound Discord commands (if any) strictly validated + no path execution. Outbound posts: contentKey only + generic titles; rate-limited per guild.
  • Pillar + package separation: Duty modeling (meshAnchor = "runs Discord bridge") lives in Devices; runtime + event subscription lives in governor (alongside fleet daemon / future net tick); Net and Watch may produce events for announce.

13.5.3 Modules + locations (when implemented)

Module Pillar/Location Notes
Discord client + bridge (discord.js or equiv) governor/src/discord/ (or fleet/discord-bridge.ts) Dual-plane (control vs private); multi-guild support; event-driven from custody/reaper/net.
Duty activation Devices + governor fleet (duties.ts, broadcast assign) meshAnchor duty starts/stops the bridge on the single anchor per install.
Event emitters Download (governor custody/reaper) + Net (edition publish) + Watch (if parties) Structured payloads (contentKey + minimal metadata); bridge formats for Discord.
Config / secrets Cross + Devices (settings + host env) Separate tokens per plane; channel allowlists; status exposed via AppLocalAPI / mcp.
UI surfaces Devices tab (broadcast device) + Settings (Devices section) Bot connection status, last-announce, enable toggles, guild picker (later).
Friendship / identity Devices (mesh join + future) + cross Discord context for human-readable friend invites/revokes (post Phase 4).

Update v2/correlation/components.md, manifest.json, state.md, and pillars/devices.md + glossary.md on landing (per pillar checklist).

13.5.4 Sequencing + exit criteria

Can land config stubs + event hooks + skeleton client (no real Discord calls) during Phase 4 prep. Full bot + posting + status UI only after mesh dogfood (Phase 4 exit).

Exit criteria:

  • A broadcast / meshAnchor device runs the bridge and posts a test control message and a private-availability message (magnets only) to correctly-separated guilds/channels.
  • All mesh operations (re-pin, Net edition sync, F2F) continue identically with bridge offline or Discord unreachable.
  • Zero raw paths or watch events ever appear in Discord posts (contentKey + displayName only where appropriate).
  • Tokens managed as secrets (no leakage in logs/state); mandatory validation on any inbound.
  • Pillar checklist answered; correlation/manifest/state updated; tests (unit for emitters, integration for bridge without real token in CI) green.

13.5.5 Risks + mitigations

Risk Mitigation
Discord ToS / adult policy violation on availability plane Enforce plane separation in code + docs; private plane only to user-controlled servers; magnets (no bytes) + k-anon; explicit warnings in settings.
Token exfil or abuse Env-only on anchor host; never in fleet/devices json or Swift state; support for rotation; least-priv bot permissions (send messages only; no admin).
Discord as de-facto dependency (user expects notifications) Best-effort only; all state authoritative in local governor watchlog / Net editions / custody records. Bridge failures are logged + non-fatal.
Multi-identity / friendship UX complexity Defer full flows until after Phase 4 mesh + Devices pools; start with bridge notify only.
Fingerprint via public announces Same protections as Net signal part (k-anon threshold, per-fleet dedup, contentKey).

13.5.6 North-star tie-in

Once Phase 7 control-plane HTTP daemon exists, the Discord bridge can be co-located or triggered from it (same anchor host). Availability notifications can include deep links to the thin client or the host daemon's /library + /media. The identity layer remains the same.

See also: docs/roadmap.md "After a working mesh", v2/pillars/devices.md broadcast type, glossary Identity/Fleet, and the two-graphs design in the 2026-06-08 mesh history.


14. v1 → v2 file map (what moves where)

Default: nothing moves in phases 12. Add new paths only.

New path Pillar Phase
Sources/TVAnarchyCore/Net/*.swift Net 1
Tests/TVAnarchyCoreTests/Net*.swift Net 1
governor/src/net/*.ts Net 2
governor/test/net*.ts Net 2
mcp/src/bridge/net.ts Net 2
~/.local/state/tv-anarchy/net/ Net 1
Unchanged v1 path Pillar
Sources/TVAnarchyCore/Library/ Watch
Sources/TVAnarchyCore/Torrents/ Download
governor/src/fleet/ Download
mcp/src/transmission/ Download

Future optional rename (phase 6+, not required): tag modules with // pillar: watch headers for generated correlation docs.


15. Testing strategy

Layer Approach
Net merge Pure unit tests (Swift + TS)
Observation store Round-trip JSONL fixtures
Edition manifest JSON Schema validation in CI
Player + Net Integration tests with temp state dir
Governor net bun test beside fleet tests
End-to-end Manual dogfood script in v2/scripts/dogfood-net.sh (create in phase 2)

Regression: Full TVAnarchyCoreTests + governor fleet tests must pass every phase.


16. Compatibility and rollback

Concern Policy
fleet.json without net Full v1 behavior
settings.skipIntroSeconds Always overrides when non-zero
Downgrade app Ignore ~/.local/state/tv-anarchy/net/; no crash
Edition schema bump edition-manifest.json carries formatVersion

Rollback per phase: feature flag net.enabled in settings.json (default true once stable).


17. Success metrics

Phase Metric
1 ≥1 show skip-intro correct from merge; 0 path keys in observations
2 Edition sync between the always-on host and a client device < 60s after publish
4 ≥2 fleets improve same intro marker within 3s cluster
5b Search ranks friend-preferred release first
6 New contributor classifies 10 random files by pillar correctly
8 Bridge posts a custody/availability event to correctly-gated Discord plane(s) with contentKey only; all mesh + Net functions continue when bridge is stopped

The original PR110 list below was stale relative to shipped library work and council refinements. Replaced with current recommended slices (from Senior Architect + Net/Library/Devices/QA experts).

MVP Slice (v2 local Net + Player value; ~2-3 PRs; replaces old PR1-3):

  • PR A: Observation + merge core (Net local only). Targets: Sources/TVAnarchyCore/Net/ObservationStore.swift, IntroMarkersMerge.swift, NetController.swift; PlayerController updates (precedence + write obs on skip); ContentID tweaks if needed. Test gates: NetObservationStoreTests, IntroMarkersMergeTests, PlayerIntroNetTests (temp state); full suite green; update correlation + manifest.
  • PR B: Library reconciliation + ContentID alignment + settings prep. Targets: Reconcile v2/ docs/manifest for shipped titles; SettingsStore tolerant nested sections (watch/net etc.); align any ContentID examples.
  • PR C: Integration + iOS bridge stub. Wire NetController; mcp bridge/net skeleton; minimal Player debug/badge.

Next (editions single-fleet):

  • PR D: Fleet config + governor publish/subscribe (phase 2).
  • PR E: Swift read + mcp + minimal Settings Net toggles.

Subsequent: friend_mesh when Download stage 4 ready; signal/quality/grouping incremental; full BTD faces extraction (post phase 2 proof); devices-storage full; settings full split; Discord planes/bridge (post Phase 4, notify-only).

Each slice must: pass full regression (./run test + governor bun test + mcp tests), update v2/correlation/* + manifest.json, answer pillar checklist, include dogfood/manual verif where applicable. PR A-C = current v2 local MVP. Discord bridge follows same (security surfaces mandatory; no real token in CI).

See full per-expert PR breakdowns and council archive for file targets + acceptance criteria.


19. Open decisions (resolve before phase 2)

# Question Options Recommendation
1 Swift vs TS owns merge Swift only / TS only / Swift read TS write Swift merge for Player latency; governor re-imports
2 Edition file format jsonl / sqlite jsonl first (matches watchlog pattern)
3 Publish trigger manual CLI / daemon auto CLI first; auto after dogfood
4 Net module path TVAnarchyCore/Net vs top-level net/ TVAnarchyCore/Net for Watch coupling
5 Pillar UI grouping headers / reorder tabs Settings only until phase 6

Discord / Phase 8 decisions (resolve before implementation, post Phase 4):

# Question Options Recommendation
D1 Bot library discord.js / discord.py / other discord.js (matches governor TS toolchain); Bun-compatible.
D2 Inbound surface status read-only first / limited control verbs (e.g. "status", "announce") Read-only status + health first; control verbs gated behind explicit opt-in and validation. Never path-bearing.
D3 Friendship UX Pure Discord invite flow / in-app + Discord confirmation token In-app Devices mesh join + Discord context display + bot DM confirm (human verifiable); signed edge on WG identity.
D4 Token storage for anchor env vars only / dedicated secret file on broadcast host Env vars (FLEET-style) + launchd unit example; never checked into repo.

20. Summary timeline (REFRESHED)

Now               Phase 0 reconciliation (docs + library titles shipped reality-sync)
Week 12          Phase 1 — Net local MVP (obs/merge/Player skip on single fleet)
Overlaps 12      Parallel 1.5: Settings split (tolerant), Watch appearance A/B polish, Devices storage MVP (pools + placement + alias warmup)
Week 34          Phase 2 — Net editions (single fleet, tx transport, dogfood on the always-on host / LAN)
Parallel          Phase 3 — Download hardening (infra checklist + custody multi-host + peers_for HTTP + net-editions dir + reaper signal stub + UI)
When stage 4 ready Phase 4 — Net subscribe (friend_mesh)
Ongoing           Phase 5 — Remaining Net parts (grouping first)
Phase 6+          UI/docs final + BTD extraction (internal/external faces)
Long track        Phase 7 — Control-plane HTTP daemon on the always-on host (parallel; thins Watch)
Long track        Phase 8 — Discord planes + bridge (notify surface only; control/ops + private availability; post Phase 4 mesh; identity for multi-fleet)

v1 remains fully operable throughout. v2 adds Net without breaking Watch or Download. The critical path to “v2 shipped” for a solo fleet is phases 12 only (plus parallel polish tracks); federation is phases 34 when infrastructure (seedbox, WG fabric, launchd, friend_mesh transport) exists. Discord (Phase 8) is a post-mesh surface layer only.

See §5 refined phase table and Appendix C for details. The old timeline above this note is retained only for historical reference.


Appendix A — Pillar checklist for new work

Before merging any PR, answer:

  1. Primary pillar? Watch / Download / Net / cross-cutting
  2. Touches watchlog? If yes, note Watch producer vs Download consumer
  3. Uses paths as keys? Forbidden in Net
  4. Needs friend_mesh? If yes, gate behind Download stage 4
  5. Docs updated? v2/correlation/components.md if new module
  6. Security surfaces (daemons/bridges/serves/HTTP/MCP)? Mandatory auth (no default-open like current serve 'OFF — open' or http TOKEN-empty=true; cite serve.ts/http.ts/peers.ts). Reusable PathGuard (model on bridge/library.ts:205-231 resolveStreamId realpath+roots+ext; implemented+wired for blacktv (sh+TS), model for Swift (prefix checks) / MCP / future Net; no raw paths in guarded flows). Treat untrusted (torrents/obs) hostile (strict validate + contentKey + generic errs). Least-privilege ssh/sudo + secrets mgmt (FLEET/BRIDGE_TOKEN; review blacktv.sh sudo). Update correlation/manifest/checklist + plan App C same PR. Privacy/path grep (0 raw paths) + auth tests in self-verif.
  7. New infra/HTTP/MCP (governor net/*, mcp bridge/net.ts, Phase 7 control-plane daemon, Phase 8 Discord bridge, fleet serve extensions)? Mandatory auth + PathGuard + generic errs from day 1 (per v1 review). Extend packages faces/BTD/TitleLibrary template for guards (pillar-specific policy/ACL). Full first-pass, tests gate, pillar checklist + manifest/correlation update.

These v1 items complement v2 but are not blockers:

  • Dead torrent re-search swap (Download)
  • Search collections UI (Download → Watch)
  • Control-plane HTTP daemon on the always-on host (Watch thin client)
  • Discord planes + bridge (notify surface; control + private availability; multi-identity) — post-mesh
  • Roku channel (Watch + media plane)
  • Watch-state rewatch button (Watch)

Prioritize Net phase 12 unless operator priority says otherwise.


Appendix C — Best Coding Practices, Pillar Separation (DRY), Switching, and UI Theming

This section was added 2026-06 during thorough doc build-out. It directly answers how v2 (and the supporting codebase) implements best practices, how pillars are separated while staying DRY, how "switching between pillars" works in the UI, and the theming architecture (custom Swift/AppKit hybrid + Winamp sprite system — explicitly not pure SwiftUI "legoblocks", and no Godot or Rust in this client; those appear in other parts of the operator's stack for games/tools).

All content cross-checked against live code (RootView.swift, Theme/, WinampSkin, SettingsStore.swift, Library*, PlayerController, DeviceConfig, correlation/, pillars/, packages/*, governor, mcp) + project instructions (SOLID, DRY, strong typing, explicit errors, zero debt, pillar checklist, etc.). See also v2/README.md (updated), correlation/ui.md, pillars/settings.md, pillars/watch-appearance.md, and the council archive for per-expert depth.

1. Overall Best Coding Practices Applied in v2

v2 follows the project's universal standards (loaded from instructions on architectural triggers):

  • SOLID: SRP (each pillar owns one job + its Settings section + state dir; packages own engines only). DIP (controllers depend on abstractions like ContentID, PlayerTarget protocol; future BTD faces). OCP (tolerant decode everywhere for new nested settings/net blocks; extension via packages not core forks).
  • DRY: See "separation while DRY" below. No duplication of merge logic (TitleLibrary pattern documented as template for Net ObservationStore/IntroMarkersMerge; not shared base class). Faces pattern for BTD (one engine, policy per pillar). Shared ContentID for all contentKey consumers.
  • Strong typing / no any: All new Net schemas map to typed Swift structs + TS interfaces. ContentKey is a stable string facade over canonical + optional fingerprint. Settings use enums (AppTheme, EpisodeDisplayStyle). Pillar tags are machine-readable in manifest.json + human in correlation/components.md.
  • Validate inputs, handle errors: Every store (ObservationStore, TitleLibraryStore, Settings) does tolerant decode + explicit path creation + atomic writes. No silent try? in prod paths (council flagged and requires fix in landed TitleLibrary appendJSONL). Governor uses warnings arrays + catch-per-tick (like existing daemon). Player targets degrade gracefully (the host daemon target will have ssh fallback).
  • Zero tech debt / no stubs / complete on first pass: New Net code goes in TVAnarchyCore/Net/ with full unit tests + integration (temp state) before use. No "TODO Net" in core paths. Legacy raw filename display paths must be removed (not left with fallbacks). Docs (this tree + correlation) updated in same PR as code.
  • Tests gate + self-verif: Per phase (see §15 + QA expert output). Full ./run test + bun test (governor/mcp) + dogfood script + manual pillar checklist + privacy grep (0 paths in net/ editions) before merge. Goldens for display names (library-display.md).
  • Anti-halluc + collective: Every claim in v2/ backed by tool exploration in the council process. Use v2/ to answer "which pillar owns X?" instantly.
  • Adult / trust-and-safety: Content keys + k-anon + minSources + f2f_only + provenance are structural (never export raw watchlog or paths). Matches domain priors (regulated industry in DE; red lines only exploitation vectors).
  • Infra/HTTP/MCP Security Surfaces (from v1 review): New infra/HTTP/MCP extensions (mcp/src/bridge/net.ts, governor/src/net/, TVAnarchyCore/Net/, Phase 7 control-plane daemon on always-on host, Phase 8 Discord bridge, extensions to fleet serve/bridge http) must land with security baked per v1 review lessons: mandatory auth (no optional/default-open), PathGuard reusable (cross blacktv play-file direct paths vs resolveStreamId strict guard in bridge/library.ts:205-231), least-priv ssh/sudo, secrets, untrusted torrents/obs (even contentKey-gated; validate + generic errs), no default-open. Extend TitleLibrary/BTD faces pattern to security (e.g. guard as cross package with pillar-specific policy/ACL). Always: full first pass (no stubs), tests gate + privacy/path grep, pillar checklist + manifest/correlation update same PR. See also cross-cutting.md transport layers + pillars/net.md trust model.

Migration principle #2 ("pillar tags, not folder chaos") + manifest.json as single source of truth enforce this without premature refactoring of TVAnarchyCore into 5 frameworks (or moving everything under a v1/ tree). Always enforce abstract naming for hosts and daemons per the "v2 is not" list above.

2. Separation of Pillars While Being DRY

Pillars are a lens, not a runtime boundary (explicit in README.md + plan §1 + correlation/components.md).

  • Ownership model (separation):

    • Primary pillar for every component (manifest.json + correlation/components.md table): e.g. TVAnarchyCore/Library/* primary=Library (was lumped under Watch); SearchController + Torrents/* = Download; new TVAnarchyCore/Net/* = Net; DeviceConfig + Mesh = Devices (or cross for mesh join); PlayerController + HomeView/PlayerView primary=Watch (even if it consumes Library data or Net bounds).
    • State segregation (correlation/state.md): ~/.local/state/tv-anarchy/library.json + titles/ (Library), net/observations.jsonl + merged/ (Net), devices/storage-pools.json + media-placement.json (Devices), settings.json (becoming namespaced cross/watch/library/...), governor fleet-state.json (Download primary + Net tick), watchlog (Watch producer, Download consumer).
    • UI surfaces (correlation/ui.md + RootView.Section enum): Dedicated tabs/sections map 1:1 (Home/Player/Adult=Watch; Library/Metadata=Library; Search/Downloads=Download; This Mac/Devices=Devices; Settings sections per pillar; Logs + theme = cross). Net has no dedicated tab (embedded in Settings + Player hooks) — deliberate to avoid "three apps" feel.
    • Settings (pillars/settings.md): One file, pillar-owned sections. Cross owns appTheme + winampSkin* + global chrome. Each pillar owns its toggles (library: episodeDisplayStyle + preferEnriched + useMLX; net: subscriptions + contribute; etc.). AppLocalAPI + MCP will expose /settings/<section>.
    • Product language: "install" (Devices pillar) not "fleet" in UI; "Devices" tab owns registry + pools.
  • DRY mechanisms (how we avoid duplication while separating ownership):

    • Packages (engines, not pillars): See packages/README.md + bittorrentdrive.md. mcp (CLI/bridge for all pillars), governor (24/7 daemon: watch+keeper for Watch/Download, fleet/* for Download, future net/* for Net), recommender (MLX + enrich for Library), search (Download), ContentID (keys for Library + Net obs + Devices placement + Download peers), PlayerTargets (Watch + Devices), SSHTransport (cross), planned BitTorrentDrive (one swarm/pin/manifest engine; three faces with different ACLs/namespaces/payload sizes — external editions for Net, internal pins for Devices storage, acquisition for Download media; Swift thin resolve for latency, governor for heavy tick).
    • Faces pattern (explicit in packages/README + bittorrentdrive.md): Same engine, pillar-specific policy. Net never touches Download custody logic; Devices extension warmup is opportunistic (not a second custody floor).
    • Pattern reuse without shared base: TitleLibrary (jsonl append + provenance-weighted merge + contentKey facade + lookup-before-expensive + always-persist + TV_ANARCHY_STATE_DIR + tests) is the documented template for Net's ObservationStore + IntroMarkersMerge (same shape, different pillar owner). No forced inheritance (SRP).
    • Correlation as living spec: components.md + manifest.json + state.md + ui.md are the "map". New code must update them (pillar checklist). This keeps separation visible without code changes. (Updated components.md tags for Library as own pillar + management/playback glue notes as part of this plan refresh.)
    • Tolerant + additive: Old flat settings or fleet.json without net: continue to work (v1 behavior). CachedEpisode has displayName + legacy label alias on encode. No breaking renames of modules or governor/mcp paths.
    • Controllers own pillar logic: LibraryController orchestrates scanner/store for Library pillar; PlayerController for Watch (even when it calls into Library or future NetController for bounds). Cross-cutting (theme, playlist popover, offline banner, build stamp) live in RootView + global services.
    • Media management vs. viewer client playback as two pieces (key for best code / SRP): The way media is managed (Library pillar + Download pillar: acquisition via torrents/search, storage/caching via OfflineCache + rsync/keeper, discovery via black index + LibraryScanner/TitleLibrary, policies, enrichment) is architecturally separate from how it is played on the viewer client (Watch pillar's PlayerController + PlayerTarget implementations: VLCTarget for VLC HTTP, MpvTarget + black-tv.sh/lua for mpv, QuickTime AppleScript, iOS VLCKit, Roku ECP, governor's VLC spawn, transport/queues/auto-advance/resume).
      • Management prepares the "what/where/when" (library model, cached files, watch state as input).
      • Playback piece owns the "how" (driving specific backends like VLC/Qt/mpv, per-client observation of finish/progress, client-specific playlist continuation).
      • Glue is narrow and explicit: watchlog (watched.jsonl as SSOT produced by playback, consumed by management for cull/prefetch/recommender), devices.json + playbackMode for choosing client + demanding cache, MediaPaths (toStreamURL/localCopy), PlaylistController (boundary: library data → viewer queues), on-demand fetch calls from launch paths, AppLocalAPI as live facade.
      • This split (documented in docs/architecture.md "Important architectural split" section) enables multiple viewer clients without duplicating management, follows SOLID (SRP for each piece, DIP via PlayerTarget protocol), DRY (shared watchlog + packages for engines), and zero tech debt. Future code (e.g. host daemon media plane) must respect it; do not leak management logic into Player* or vice-versa. See also Appendix D for prior art on layered media org + viewer.
      • Execution of the pieces at runtime: Management runs independently (scans, torrents, keeper prefetch, offline warmup can happen in background or via MCP without any viewer). Playback execution starts only when a viewer client is chosen (via HostSelector / active device) and launch/enqueue is called: the client-specific backend "executes" the media (VLC polls its HTTP for progress/finish; mpv IPC or black-tv script drives the TV; etc.). The watchlog is the async handoff: playback "executes" and records (via onEpisodeFinished etc.), management consumes for decisions. Glue calls (ensureLocalCopies) are on-demand from playback execution only. This keeps execution paths clean and testable separately.
      • v1 update for the split (2026-06): To embody the separation in current code (not just docs/plan), introduced LibraryProviding protocol in LibraryModels.swift. LibraryController conforms (management impl). PlayerController now depends on LibraryProviding? (via weak + attach) and updated static helpers + glue sites (DIP/SRP). Call sites (UI, tests) unchanged as conformer passed. Added/enhanced boundary comments in PlayerController, PlaylistController (the explicit bridge), LibraryController, mcp media tools. Enforces "management prepares, playback executes on clients" at code level for best practices. See also the protocol doc comment.
    • Result: Pillar code lives where the job lives (Library/ for catalog, Torrents/ for download, Net/ for observations). Shared mechanics extracted to packages or thin facades. Duplication is low and intentional only for pillar-specific merge policy or UI chrome. The management/playback split above is a prime example of pillar + cross-piece separation for maintainable, best-practice code.

Pillar checklist (Appendix A) + manifest.json + correlation/ updates are the enforcement mechanism for separation. In code, it's mostly comments + directory hints + state paths today (no runtime Pillar enum injected everywhere — that would be over-separation).

3. Switching Between Pillars (UI Navigation)

Single shell, declarative selection-driven routing (no "switch pillar" API; the shell owns chrome + nav; pillars own the detail content).

  • macOS (RootView.swift — NavigationSplitView + sidebar List):

    • enum Section (hardcoded cases: home/player/adult ≈ Watch; library/metadata ≈ Library; search/downloads ≈ Download; thisMac/devices ≈ Devices; logs/setup = cross). Icons + labels per case.
    • @State private var selection: Section.
    • Sidebar: ForEach(visibleSections) (adult compile-gated; library gets indented subnav categoryLinks that force selection=.library + filter).
    • Detail pane: @ViewBuilder switch on selection (case .library: LibraryView(...); .search: SearchView(...); .setup: SetupView(...) which itself has pillar-sectioned content; etc.).
    • Global cross elements always available: play queue popover (from any tab), offline banner (contextual), build stamp, adult eye toggle (compile-only), theme environment.
    • Visibility gating: applyVisibility() turns on detailed polling only for Player (transport) or Downloads (transfers). Library subnav auto-collapses when not on .library.
    • Controllers passed down (library, downloads, etc. created in init and shared where needed; playlist/ offline/ localAPI/ winampSkin are cross).
    • Adult reveal and selection reconcile (if adult hidden while selected, fall back to home).
  • iOS (simpler tabs via RootTabView or equivalent; from ui.md + code): Library (Watch+Library data), Downloads (Download + local play), Remote (Watch control), Settings (cross + bridge). No full pillar sidebar yet.

  • "Switching" cost: Near-zero. Selection change is instant (SwiftUI). Pillar-owned controllers do their own work (LibraryController.ingest, PlayerController.poll only when visible). Global services (Notifications, theme) unaffected. Net "switch" is just opening Settings Net section or letting Player consume merged bounds — no mode flip.

  • Future (phase 6+): Optional grouped sidebar Section("Watch") { ... } headers in the List for visual pillar clustering (user-visible tab strings unchanged). Settings can become a sub-nav list of pillar sections.

This keeps the "one app" feel while making pillar ownership obvious in sidebar + Settings + docs. No magic routing table; the enum + switch is the source (easy to audit against correlation/ui.md table).

4. UI Theming (Custom, Not Legoblocks; No Godot/Rust)

Hybrid custom chrome system for fidelity + extensibility. Explicitly not "SwiftUI legoblocks" (standard .button / .slider with .tint). No Godot or Rust here (this is the native Swift macOS/iOS media client; Godot/Rust live in sibling projects for other UI/game needs).

  • Core model (AppTheme enum in TVAnarchyCore + ThemePalette.swift):

    • .standard (dark minimal SwiftUI colors).
    • Winamp variants (.winampClassic, .winampModern, .winampLlama) — built-in palettes + full skin override.
    • usesWinampChrome flag gates sprite vs built-in paths.
  • ThemePalette (Equatable struct of Colors + vizColors + gradients + sidebarSelection). builtInPalette(for:) switch. palette(for:skin:) applies WinampSkinPackage merge (VISCOLOR for viz bars + PLEDIT for accents/LED/selection tint).

    • Environment: @Environment(\.themePalette). .themed() modifier on RootView (and consumers). RootView injects the environment based on SettingsStore + WinampSkinStore.
    • Sidebar selection tint can pull from skin when active (optional Phase 6).
  • Winamp skin system (real classic .wsz support — BMP + TXT files; see pillars/watch-appearance.md + watch-appearance.md for sprite coverage):

    • WinampSkinLoader (core, AppKit-free): zip extract, parse VISCOLOR.TXT / PLEDIT.TXT, validate sheets, SHA cache to ~/.local/state/tv-anarchy/skins/<id>/.
    • WinampSkinPackage + WinampSkinSprites (Webamp-compatible rects for CBUTTONS/POSBAR/VOLUME/NUMBERS/TITLEBAR/PLAYPAUS/etc.).
    • WinampSkinStore (@Observable, app layer): loads NSImage sprites.
    • Custom SwiftUI views (WinampSkin* in Theme/ + PlayerView/MiniTransport):
      • WinampSpriteImage (NSImage, .interpolation(.none), resizable).
      • Transport: WinampSkinTransportButton / WinampSkinTransportRow (CBUTTONS sprites + pressed state via DragGesture; fallback to symbols or WinampComponents).
      • Sliders: WinampSkinPositionSlider / Volume (POSBAR + VOLUME sprites + thumb).
      • LED: WinampSkinLEDDisplay / WinampLEDDisplay (NUMBERS digits + PLEDIT colors).
      • Title bar / spectrum: skin title + VISCOLOR bars.
      • WinampComponents (built-in bevels, buttons, LED, spectrum as fallback when no skin or standard theme).
    • PlayerView: conditional if appTheme.usesWinampChrome { WinampSkinTitleBar(...) } else { standard }; same for transport/scrubber/volume/LED. Backgrounds and tints from palette.
    • MiniTransport: sprite buttons only when skin active.
    • SetupView: theme picker + .wsz import (with validation/preview strip for Track A/B polish) + "Use Base Skin" (bundled base-2.91.wsz in Resources via project.yml).
    • WinampSkinStore + environment(.winampSkin) for sprite access.
  • Why this design (not pure SwiftUI controls or "legoblocks"):

    • Fidelity to Winamp 2.x (exact sprite coords from Webamp research, BMP nearest-neighbor, LED 7-seg style, bevel shadows, spectrum bars).
    • Extensibility: real user .wsz files (import, cache, preview, compatibility report for missing sheets).
    • Performance/retro: no vector scaling artifacts; sprite cache.
    • Fallbacks keep standard theme clean and iOS unaffected (iOS stays Lilith/standard chrome; Winamp is macOS Player-only).
    • Cross-pillar: theme affects whole shell (RootView) + accents on other tabs, but heavy Winamp chrome is Watch/Player only (per watch-appearance.md scope).
  • Implementation location: Core parsing/loader/sprites in TVAnarchyCore/Display/ (UI-agnostic data). App layer (Theme/, PlayerView, SetupView, MiniTransport, Winamp*Views) owns NSImage + SwiftUI consumption + environment. SettingsStore persists appTheme + winampSkinId/Name (cross section).

  • Polish remaining (per pillars/watch-appearance.md + plan 6a): PLAYPAUS indicator, MiniTransport sprites, AppLocalAPI skin patch roundtrip, Setup preview/compat banner (tracks A/B). Extended (SHUFREP etc.) optional.

  • No Godot/Rust: Confirmed via grep across workspace (false positives only on "trusted"/"deploy"). This client is native Swift (AppKit for skins + SwiftUI for modern surfaces) + TS governor/mcp + Python recommender/search. Theming is bespoke retro system, not engine-based or declarative component library.

How to extend theming in v2: New accent or skin sheet → update ThemePalette + WinampSkin* views + palette merging + tests (WinampSkinLoaderTests). New pillar chrome (e.g. Library grid tint) pulls from environment. Always update correlation + pillars/watch-appearance.md + manifest surfaces.

This keeps the "Swift legoblocks" (standard controls) available for standard theme while allowing pixel-perfect custom for the fun/retro path — exactly the balance documented in watch-appearance.md.

5. How the Council + This Build-Out Applied the Practices

  • Separation/DRY documented via explicit tables (correlation) + faces + pattern reuse (TitleLibrary → Net obs) + packages.
  • Switching explained from live RootView enum + switch + controller injection.
  • Theming reverse-engineered from actual files (no speculation).
  • All changes are additive docs + required code hygiene calls (e.g. fix silent errors, add goldens, schema CI).
  • Self-verif: edits preserve existing content; cross-refs consistent; no new top-level plan files created in v2/ (only enhanced the existing plan.md + supporting docs).

Update the pillar checklist (Appendix A) if new items (e.g. "Update v2/plan.md Appendix C + correlation if changing nav or theme chrome").

This makes v2/ the authoritative, up-to-date guide. Legacy docs/ stay for ops.

(End of Appendix C.)


Appendix D — Lessons from Previous TV Project (WatchTV-UWU)

We located the other TV project in the always-on host's last Linux backup (mounted locally at the equivalent of applications/src/@uwuapps/watchtv/) (a full monorepo TS/Node media/TV progress tracker and streamer, "WatchTV-UWU"). It is deep with monorepo structure (apps/, packages/, src/, docs/ with detailed architecture docs), and provides valuable prior art for architectural improvements that align with and enhance our v2 pillars, Net, Devices, the control-plane host daemon, and overall goals.

This project focused on network drive show progress tracking, intelligent media organization, progressive on-demand HLS transcoding/streaming, player integration, CLI/TUI/web UIs, SQLite for state, with clean separation. Many solutions directly address gaps or can be mined for v2 without duplicating effort (e.g. current LibraryScanner/TitleLibrary, watchlog jsonl, planned control-plane host daemon media plane, governor TS side).

Key Architectural Improvements to Mine / Adopt

  1. Progressive On-Demand Transcoding / Streaming for the control-plane host daemon (Phase 7 priority boost):

    • Adopt the "progressive transcoding" model: only transcode as watched (HLS 10s segments, just-in-time FFmpeg), look-ahead buffer (3-5 segments), adaptive quality, client-driven (HLS.js or native), automatic cleanup of old segments, smart pacing based on playback position.
    • Benefits: Efficient CPU/disk (no full file upfront), fast start, matches "on-demand HLS remux later" in the host daemon design.
    • See watchtv/docs/STREAMING_ARCHITECTURE.md for full flow, FFmpeg flags (hls_time, append_list+delete_segments+omit_endlist), StreamManager, options (segmentDuration, preset, codec, bitrate, resolution).
    • Integrate with current host daemon plan (player/index/media range) and Net (editions on always-on host benefit from efficient serving).
    • For Devices/Roku/RPi: enables thin receivers (Chromecast, smart TVs, RPi players) to consume HTTP streams from the always-on host without custom native targets.
  2. Monorepo + Shared Packages Structure for TS Side (governor, mcp, host daemon, future tools):

    • Current TS is somewhat flat (governor/src/* , mcp/src/* ). Adopt watchtv's monorepo (pnpm/turbo style, from its package.json/turbo) with clear separation:
      • packages/core, database (SQLite wrapper), media-utils (scanner, organization, titles, quality variants), config, types, api-client, service-discovery, ui (for TUI/CLI/web), utils, version.
      • apps/ for specific (e.g. orchestrator for fleet/net, media-manager, codec-service for transcoding).
    • Benefits: DRY (shared media logic, DB, config across governor/mcp/host daemon), better testing, faces-like policy per pillar, easier to extract for the host daemon (which reuses TS toolchain per roadmap).
    • Aligns with our packages/ (BTD) and v2 principles (packages for engines, not pillars). Update v2/packages/README.md and plan "v1 → v2 file map" to include this for TS.
    • Mine: packages/database for enhancing TitleLibrary/ObservationStore with SQLite indexes (current mentions optional sqlite in library-titles.md); service-discovery for fleet/mesh improvements.
  3. Advanced Media Organization & Library Enhancements (Library pillar + Download quality Net part):

    • Enhance current LibraryScanner, FilenameParser, ShowGrouping, TitleLibrary with watchtv's "enterprise-grade" system: quality-aware scoring (resolution, codec, source for duplicate/conflict resolution), duplicate detection, space optimization, quality variants handling (perfect for torrent multiple releases + Net quality part).
    • AutomatedMover with transaction support, rollback, backups before moves (valuable for re-pin in Devices/Download, or organization ops).
    • MediaOrganizer service with advanced caching (30m TTL), analytics, recommendations, progress tracking, performance monitoring.
    • ActivityLogger for real-time monitoring, alerts, export.
    • Benefits: better duplicate handling in torrents, safe ops, analytics for operator (current has some tests but this is more complete).
    • See watchtv/docs/ORGANIZATION_SYSTEM_SUMMARY.md , media-file-organization.md , quality-variant-organization.md for services (media-organizer, quality-organizer, automated-mover, activity-logger), DB schema v2 (multi-participant, watch history, indexes), performance gains (67% faster scans, 43% less memory).
    • Integrate with Net (share quality prefs, grouping observations), Devices (placement for organized media).
    • Update v2/pillars/library*.md and plan "v1 → v2 file map" / Phase 1b reconciliation.
  4. Layered Architecture + Event-Driven + Shared Types:

    • Adopt UI/Controllers/Services/Data Access layers (watchtv uses this for TUI/Web/CLI sharing backend).
    • Event emitters for real-time updates (e.g. fleet duties changes, net merge notifications, playback progress sync across devices via Net).
    • Single TypeScript interfaces/types across all (aligns with our strong typing, manifest for correlation).
    • See watchtv/docs/ARCHITECTURE_DECISION.md , UI_ARCHITECTURE.md , ARCHITECTURE.md for patterns (controller as intermediary, event bus, shared types).
    • For v2: improves pillar separation in TS (e.g. Net services eventing to Watch/Library consumers), real-time in governor daemon or host daemon.
  5. Database / State Persistence Improvements:

    • Complement jsonl (good for append-only watchlog, observations, titles) with SQLite for queryable data (progress per show/episode/user/fleet, titles with indexes, watch history immutable records, multi-participant support).
    • watchtv uses SQLite for progress persistence, enhanced schema v2 for watch history, multi-user, performance indexes.
    • Current TitleLibrary already plans "episodes.sqlite optional query index"; extend to Net merged views, Devices placement, Library metadata, WatchState.
    • Benefits: faster queries for "continue watching", search, Net merge, without losing append durability of jsonl.
    • Mine the DB layer package and schema decisions.
  6. CLI, TUI, Multi-UI with Shared Backend:

    • Expand governor CLI (current has good fleet/net commands) and add TUI (blessed/blessed-contrib for grid status, like watchtv's planned TUI for browsing/continue).
    • CLI for automation (watchtv has "watchtv play", "organize", etc.).
    • Optional web for remote (aligns with host daemon + thin client).
    • All share controllers/services (DRY, consistent).
    • Valuable for operator tools (fleet status, net publish/sync, devices pools, host daemon control) without full UI.
    • See watchtv TUI mock in README, UI_DESIGN.md, UI_IMPLEMENTATION.md.
  7. Player Integration, Resume/Position Tracking, Config:

    • Full player control with position saving/resume sync (current has WatchState, BlackWatchlog, resume in PlayerController; enhance with Net for cross-fleet sharing of watch progress as "signal" or new part).
    • Flexible config (json with categories, server integration optional like UPnP in watchtv current avoids heavy NFS reliance).
    • Streaming options, buffering strategy, FFmpeg config (directly for the host daemon).
    • Player integration (mpv/VLC with position tracking).
  8. Other:

    • Performance monitoring, activity logging, alerts (enhance governor logs, add for host daemon, net, custody).
    • Testing: Jest integration for scanner/organization/streaming (current has good XCTest for Swift, bun for TS; add more).
    • UPnP/ network features optional (current focuses on direct + cache/stream via the host daemon; mine the decision for optional integrations).
    • Multi-quality support (aligns with Download quality Net part and torrent variants).
    • Goals alignment: seamless network storage, efficient, reliable progress, zero-config for standard dirs, cross-platform.

How to Incorporate

  • Host daemon (Phase 7): Prioritize streaming arch from watchtv update plan Phase 7 and v2/pillars/devices.md / cross-cutting with the component diagram, flow, options.
  • Library Pillar: Enhance with organization services, quality handling, safe moves. Update pillars/library*.md , plan Phase 1b / 7.0 historical.
  • TS Modularity & Packages: For governor/mcp/host daemon. Update v2/packages/README.md , plan "v1 → v2 file map", correlation/components.md .
  • Net & Devices: Use for sharing progress facts, placement with organized media, SQLite for state.
  • Builds/Pkgs (Roku, RPi, etc.): The streaming helps thin pkgs for those devices (consume HTTP from central). Update the builds list in devices.md .
  • Overall v2: Improves DRY across pillars (shared media core), testability, operator UX (CLI/TUI), efficiency for the host daemon (key for thin Watch).
  • Add to pillar checklist: consider lessons from prior art for new modules.
  • Mine specific: the packages/database , media-utils , the services in apps/media-manager/src/features/organization/ , docs/STREAMING* for host daemon impl.

This project validates many v2 directions (progress tracking, organization, player sync, streaming efficiency) while providing battle-tested solutions we can adapt (with credit or as inspiration). The monorepo style and layered approach will help as we build the host daemon and expand Net/Devices in TS.

We can continue mining specific files (e.g. the episode-parser, quality scoring algorithm, StreamManager impl, DB schema) or port/adapt code into new packages if desired. The backup has tests, examples, configs to reference.

Additional Specific Improvements Mined (from deeper structure)

  • Dedicated Codec/Transcode Service with Worker Pools and Job Queues (for the host daemon media layer):

    • Their codec-service app uses Node worker_threads, fluent-ffmpeg, job queues (with workers, pools, isolated mode, docker for safety), monitoring, websocket for progress, routes for jobs.
    • Architecture: submit transcode job -> queue -> worker pool processes with FFmpeg (fluent-ffmpeg wrapper) -> report completed/failed with output, duration, size, metadata.
    • Valuable for the host daemon: instead of ad-hoc spawn per request, a robust queue + pool for on-demand transcoding (HLS segments, different qualities, formats for different clients like Roku needs certain codecs, RPi low power).
    • Supports isolation (for untrusted? but media is local), scaling workers.
    • Mine: apps/codec-service/src/workers/transcode-worker.ts, worker-pool.ts, queues/, routes/, websocket/, isolated.ts, Dockerfile.
    • For v2: make the host daemon or a companion "hostd-codec" service use this pattern. Update plan Phase 7, add to v2/pillars/devices.md or cross-cutting as "media services" package.
    • Ties to optimized pkgs: the service can serve to RPi/Roku clients efficiently.
  • Monorepo Workspaces with Separate Apps for Media Concerns:

    • apps/: codec-service (transcode/web), media-manager (org, dashboard, stats), media-scraper (enrichment/scrape), media-viewer (player UI with video.js/hls.js), orchestrator (start all, pipelines).
    • packages/: shared across.
    • Scripts: start-all, build-all, dev-all, organize, etc. (npm workspaces).
    • For tv-anarchy: the TS side (governor for fleet/custody/net, mcp for bridge/cli, host daemon for player/index/media, future net publish/sub) can be restructured as workspaces or separate apps under a "server" or "hostd" monorepo.
    • E.g., separate "media-scraper" app integrating recommender, "media-manager" for Library-like org on server, "codec-service" for host daemon transcode, "orchestrator" for daemon.
    • Benefits: clear separation, independent dev/deploy, shared packages for DRY (e.g., types, utils, DB across).
    • Mine: the root package.json workspaces, scripts/start-all.js, apps/*/package.json (some use vite for web UIs).
    • For builds: this structure inspires optimized "apps" or bundles for different targets (e.g., Roku channel as "media-viewer-roku", RPi as "media-manager-rpi" or headless).
  • Fluent-FFmpeg and Worker-Based Transcoding:

    • Use fluent-ffmpeg for easy chaining of options, progress events, error handling in workers.
    • Worker pool for concurrency, parentPort messaging for log/complete/failed.
    • Supports metadata, output stats.
    • For the host daemon: robust, observable transcoding jobs, can queue for different clients (Roku may need specific profiles, RPi for low res).
    • Mine the transcode-worker for patterns (start job, fluent-ffmpeg call, stats, messaging).
  • Other from structure:

    • Service discovery package: for finding media servers, peers (useful for Net, fleet, Devices mesh).
    • UI packages: shared components, perhaps for future web client to the host daemon or TUI tools.
    • Database package: SQLite with auth? (bcrypt, jwt in deps), but core is state.
    • Chokidar in deps: for live file watching (enhance LibraryScanner for real-time updates on the always-on host or extension tiers).
    • video.js + hls.js: for web player in thin client or host daemon viewer.
    • Tests per package/app: good coverage for org, scanner, db, config.
    • Config with pipelines, tools.
    • Examples like scanner-demo.
    • Redis? (dump.rdb in root) perhaps for queues or cache in full setup.

These fit the v2 "one app shell, pillars own policy, packages own mechanics", and "host daemon thins Watch".

Incorporate by:

  • For the host daemon: design with codec-service/worker pattern + progressive HLS from streaming docs.
  • Restructure TS into workspaces/monorepo if growing (governor + mcp + host daemon + shared).
  • Enhance Library with media-manager/org logic (quality, safe moves, caching).
  • For builds/optimized pkgs: use the monorepo apps idea to produce device-specific (e.g., "roki" channel from media-viewer, arm-optimized from headless orchestrator or manager).
  • Add packages like service-discovery, enhanced media-utils to v2/packages/.
  • Update correlation, plan file map, pillar docs.

This project is a treasure trove for making the host daemon production-ready and improving Library/Net/Devices completeness without reinventing.

(End of Appendix D. Update pillar checklist if adopting. We can port/adapt specific modules next.)