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>
6.6 KiB
v2 roadmap
See the dedicated v2/features.md for the comprehensive feature state comparison table (v1 vs v2).
Phased checklist for the Watch · Download · Net pillar model. For the full migration plan (tasks, PR stack, tests, risks, timelines), see plan.md.
Phase 0 is documentation only (this v2/ tree).
Phase 0 — Pillar model (now)
- Define Watch / Download / Net scopes
- Correlate all components → correlation/components.md
- Net JSON schemas → schema/net/
- Machine index → manifest.json
- Link from
docs/README.mdtov2/ - Full build plan → plan.md
- Watch appearance spec → pillars/watch-appearance.md
No code moves. No path renames.
Phase 1 — Net local (single fleet)
Ship Net semantics without federation.
| Task | Pillar | Target |
|---|---|---|
contentKey helper unify |
Watch | ContentID.swift |
| Local observation queue | Net | ~/.local/state/tv-anarchy/net/observations.jsonl |
| Intro observation on skip | Net | PlayerController |
| Merge policy for intro-markers | Net | v2/net/ or TVAnarchyCore/Net/ |
| Player reads merged view | Watch | fallback: settings → merged → detect |
Phase 2 — Net edition publish (dogfood)
| Task | Pillar | Target |
|---|---|---|
| Edition builder (jsonl + manifest) | Net | governor or net/ package |
| Single-fleet edition torrent | Net + Download | always-on host |
fleet.json → net.subscriptions[] |
Cross-cutting | extend data model |
Phase 3 — Net subscribe (friend_mesh)
Requires Download stage 4 (F2F relay).
| Task | Pillar | Target |
|---|---|---|
| Subscribe + verify editions | Net | friend_mesh source |
k-anon merge for signal part |
Net | network trust |
Governor net/ tick alongside fleet daemon |
Net | governor/src/net/ |
Phase 1b — Library titles + display names (parallel, largely shipped)
| Task | Target | Status |
|---|---|---|
| Title Library spec | pillars/library-titles.md | ✅ + shipped impl |
| Display spec | pillars/library-display.md | ✅ + shipped impl |
TitleLibrary store |
~/.local/state/tv-anarchy/titles/ by contentKey |
Shipped (TitleLibrary.swift + Store) |
| Lookup before MLX | Database hit → skip model | Shipped (DisplayNames + refiner gate) |
episode_refiner.py + LocalLLMEpisodeRefiner |
M2 MLX, episode-aware, write library row | Shipped + wired |
| TVmaze/TMDB ep ingest | One-shot → library, not per-browse | Remaining (Phase 1b polish) |
LibraryDisplayNames + scanner |
Denorm into library.json |
Shipped (scanner calls resolve; tolerant CachedEpisode) |
| Player / queue / iOS / bridge / MCP | Read displayName only |
Partial (core paths updated; dual-scanner TS mcp + full consumer migration remaining) |
| Goldens + settings (library section) + legacy migration + dual-scanner parity | Per library-display.md | Remaining (see plan.md Appendix C) |
Exit: Known episodes never re-hit MLX or APIs; no raw filenames in UI for indexed catalog episodes. Reconcile v2/manifest.json + plan.md + correlation/ on any further work. See plan.md §5 refined phases + Appendix C.
Phase 6b — Settings per pillar (parallel)
| Task | Target |
|---|---|
| Spec | pillars/settings.md ✅ |
| Nested settings decode | SettingsStore |
| SetupView sections | Watch · Library · Download · Net · Devices · General |
Phase 6c — Devices shared media volume
| Task | Target |
|---|---|
| Spec | pillars/devices-storage.md ✅ |
| Storage pool config | devices.json or storage-pools.json |
| Placement index | media-placement.json |
| Extension warmup (alias) | Replace duplicate offline-cache rsync |
| UI | Spare folder + quota on This Mac / Devices |
Use case: client machine spare space extends the central storage; watch on client → smart copy to extension tier on the client extension tier; warmup aliases extension path.
Phase 6d — Library + Devices pillar split (doc)
| Task | Status |
|---|---|
| library.md | ✅ |
| devices.md | ✅ |
| Correlation + manifest | ✅ |
| UI copy fleet → install | ✅ Done (string sweep) |
Phase 6a — Watch appearance (parallel, macOS)
Shipped base: built-in Winamp themes + .wsz import on Player. See
pillars/watch-appearance.md.
| Task | Pillar | Target |
|---|---|---|
| Doc + v2 correlation | Watch | v2/pillars/watch-appearance.md ✅ |
PLAYPAUS status LED |
Watch | PlayerView |
| MiniTransport skin sprites | Watch | MiniTransport.swift |
AppLocalAPI skin fields |
Watch | settings patch |
| Import validation preview | Watch | SetupView |
Does not block Net phases 1–3.
Phase 4 — Docs & UI alignment (optional)
| Task | Notes |
|---|---|
| Settings → Net subscriptions | Toggle parts |
| Sidebar grouping labels | Watch / Download / Net sections |
Migrate docs/architecture.md lead with pillar diagram |
Keep ops detail in place |
Phase 5 — Universal client (north star)
Unchanged from docs/roadmap.md: the control-plane HTTP daemon on the always-on host thins Watch;
pillars remain valid — Net and Download live server-side, Watch becomes thin HTTP.
What not to do in v2
- Rename
TVAnarchyCore→ three frameworks (premature) - Move
governor/underv2/(operational path stays stable) - Call VPN/bridge/mesh transport "Net"
- Use path-based Net keys
- New infra/daemons/bridges/serves/HTTP/MCP without mandatory auth + PathGuard + generic errs + least-priv (per plan.md expanded checklist 6-7 + App C infra security + pillars/net.md). Default-open (like current bridge/serve) or ad-hoc paths forbidden. Update correlation/manifest/checklist + plan App C same PR.
Success criteria
| Milestone | Signal |
|---|---|
| Phase 0 | Operator can answer "which pillar?" for any file via v2/ |
| Phase 1 | Skip intro uses merged local intro-markers |
| Phase 2 | Another device can pull edition torrent from the always-on host |
| Phase 3 | Friend fleet contributes observation; merge updates skip window |
| Phase 8 | Discord bridge posts availability/control events to separated planes (contentKey only); mesh unaffected by Discord state |