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

1124 lines
No EOL
79 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](./features.md) for the comprehensive feature state comparison table (v1 vs v2).**
Companion docs: [README.md](./README.md) (overview + practices), [roadmap.md](./roadmap.md)
(phase checklist), [correlation/](./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](./pillars/library.md).
Companion docs: [README.md](./README.md) (overview + practices), [roadmap.md](./roadmap.md)
(phase checklist), [correlation/](./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](./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](./pillars/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](./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](./pillars/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](./packages/bittorrentdrive.md).
6. **Tests gate promotion.** Each phase adds tests before the next phase starts.
7. **Docs follow code.** Update `v2/` and `docs/data-model.md` when schemas land.
---
## 4. Architecture delta (v1 → v2)
```mermaid
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:**
- [x] `correlation/components.md` covers all major paths
- [x] `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](./pillars/library-titles.md),
[library-display.md](./pillars/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.skipIntroSeconds`**Net 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`):
```jsonc
{
"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](./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](./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](./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](./pillars/library.md), [devices.md](./pillars/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](../.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/roadmap.md), [docs/glossary.md](../docs/glossary.md), [v2/pillars/devices.md](./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 |
---
## 18. Execution order (recommended PR stack) — REFRESHED
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.
## Appendix B — Related v1 backlog
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](./README.md) (updated), [correlation/ui.md](./correlation/ui.md), [pillars/settings.md](./pillars/settings.md), [pillars/watch-appearance.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](./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.)