tv-anarchy/.project/objectives.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

14 KiB
Raw Permalink Blame History

TV Anarchy — Objectives (living backlog)

Running list of agreed objectives. Status: done (built+tested+staged) · 🟡 in progress · planned · 💭 designed (fleet package, future). Full specs in ~/.claude/plans/woolly-tumbling-pelican.md; fleet design in .project/history/.

Done — built, tested, staged (green)

  • Consolidate governor/mcp/recommender/search into the single repo; repoint via RepoPaths; in-repo mcp→search verified returning live magnets. (Part A)
  • Hosts → Devices rename + 5 device types — cellphone · laptop · storage · seed · Broadcast Station — each with an overridable DeviceServices set (stream · offlineCache · ttlSeed · custody · publicSwarmFace · f2fRelay · meshAnchor), mirroring the fleet duties; legacy hosts.json infers type from kind. (Part B1)
  • Offline cache — pull next-Y-episodes-of-recent-Z-shows to local disk (continue-watching / recently-added rules), rsync from black, capped. (Part B2)
  • Media-control forwarding — Mac media keys / Control Center / lock screen / AirPods drive the active device + Now Playing; forwardMediaKeys setting. (Part B3)
  • Unified playlist — playing a series episode queues the rest of the show from there (resume the first, rest from 0). Continue Watching + Home rails now route through it too (was the "S3 didn't queue after S2 Daria" bug — those paths single-launched one episode and queued nothing).
  • Season 0 = specials/movies, ordered LAST (Daria's two movies after the run); shown as "Specials & Movies"; the unified queue puts them at the end.
  • Continue Watching rethink — was severely broken (one row per watched episode, never advanced, junk :Zone.Identifier rows). Now per-show, pointing at the next episode (library-ordered → crosses season boundaries, specials last), most-recent sustained play wins (fly-by filter, mirrors the governor), finished shows drop off, junk filtered.
  • Watch-state core + UIWatchState (unwatched/inProgress/watched) + nextUnwatched + isWatched; watched badges on the Library grid + the thumbnail-tap = "watch next" / title-tap = open detail split (movie plays, series plays its next unwatched episode and queues the rest). (Still pending was: rewatch button = reset watch state; searchable top tags; indicators on the other list formats — Home rails, detail.) 2026-06-21.

Planned — specified, not yet built

  • Download manager (Part E): TransferHealth → smart sort (attention + ETA) (downloadingSorted + "needs attention" count + health pills) → system+in-app surfacing (ready-to-watch / stuck) → per-torrent debug detail + reannounce/verify/pause actions (mcp tx-detail/tx-reannounce/tx-verify/ tx-start/tx-stop; TorrentDetail/TrackerStat models; DownloadsController detail+act; "Inspect / fix…" sheet showing error/peers/per-tracker announce + Reannounce/Verify/Pause-Resume — verified live against black) → dead-torrent re-search swap ("Find a better release": search title → rank by seeders → swap) 2026-06-21.
  • Settings UI — media keys, offline (Y/Z/source), combine + local-LLM, hover previews, adult, all in the (renamed) Settings pane, one-persist on change.
  • Sidebar subnav — Library → category links, auto-collapsed (shown only while Library is the active nav).
  • Search results as collections (Part E): per-title summary cards (not flat rows), anchored to library/TMDB; collection page with resolution selector — movie = pick resolution → auto-pick best-seeded (one tap); series = resolution × season matrix showing owned/available/downloading. Wired 2026-06-21 (basic grouping + best-add cards + alt menu in SearchView; full matrix/res selector UI scaffolded).
  • Search robustness (Part E): launch-time warmup, per-source partial results, dependency health row, retry-after-warmup, prune dead sources. Core landed 2026-06-21 (D1 probes+health in Deps/Setup + better errors; D2 launch warmup wired + cli; D5 prune EXCLUDE; D3/D4 partial via existing python union + budgets; spec history followed; see SearchController + mcp/search.ts + Setup).
  • Settings hub (Part F): summary/home page + dedicated pages; everything toggleable/tuneable via AppSettings; device editing lives in Settings (Devices view deep-links to it).
  • Auto-preview pre-warm (Part F): generate hover previews ahead of time for likely-previewed content — rules: recently-added · visible-on-screen · continue-watching · recommended; capped per pass, low-priority.
  • Watch-state + card interaction (new):
    • Watched indicators in every list format (grid/rows/collection) — unwatched / in-progress / watched.
    • Card tap split: tapping the thumbnail = "watch next" (play the next unwatched episode straight away); tapping the text/title area = open the detail page (breadcrumb). Two targets on one card.
    • Rewatch button per collection/item — resets watch state so "watch next" starts over.
    • Searchable top tags — surface top tags/genres as chips that search on tap.
  • Dynamic show combination (new): merge the split/duplicate library entries of one show into a single combined show/collection. Today mergeSeriesByName only merges exact normalized name + same category + disjoint seasons, so e.g. Dandadan stays as 34 entries: spacing variants ("DAN DA DAN" vs "DANDADAN"), cross-category (anime vs cartoons), and duplicate S1 releases (REPACK/Remux/BDRip) whose overlapping seasons make the merge treat them as different shows. Rethink: metadata-anchored grouping — resolve each entry to a canonical TMDB/enrich work (title+year+id); same work → combine + dedup episodes per season×episode (alt releases reachable via the quality switcher); different work → stay separate (so an anime + its live-action remake don't fuse). Same anchor model as search collections; feeds the shared catalog's "media definitions."
    • Cheap stage + seam built: ShowGrouping.candidateClusters blocks variants by a canonical key (alphanumeric — "DAN DA DAN"/"DANDADAN"/"Dan.Da.Dan" → one cluster); ambiguousClusters is the cheap→reasoner hand-off; the ShowGrouper seam (provider-agnostic: local LLM / TMDB) makes the final same-work call. Two- stage per the design: easy algorithm for the bulk + LLM light reasoning on the ambiguous tail (the same role the unfilled TitleRefiner MLX seam was built for).
    • Local-LLM reasoner BUILT + wired (no TMDB): installed MLX (mlx-lm + Qwen2.5-1.5B-Instruct-4bit) into the recommender uv env — plum now has a working local LLM. recommender/media_rec/grouper.py reasons over a candidate cluster (correctly groups Dandadan's cross-category dupes); Swift LocalLLMGrouper shells into it like enrich; ShowGroupCache/ CachedGroupDecider persist decisions (once per cluster); ShowGrouping.combine+merge apply them (dedup episodes per season×episode, a year-gap guard against a small model over-merging a remake); LibraryController.combineSplitShows() runs it off-main after load/scan, gated by the combineSplitShows setting.
    • Shipped default is now deterministic (zero MB), LLM optional. The measured 1.5B model is 839 MB — too big to ship (no git-lfs; repo .git is 2.5 MB). The same-work signal is structural: DeterministicGrouper combines same-canonical- key entries with close release years (dupes + split seasons) and excludes year-distant remakes — no model. It's the default in combineSplitShows; the MLX LocalLLMGrouper is gated behind useLLMGrouper (off) for the rare same-year ambiguous tail. Future tiny-ship option: a Model2Vec static embedding (~30 MB, CPU-instant) for fuzzy title recall ("Frieren" ≈ "Sousou no Frieren") — beats a generative model for this task and is genuinely shippable.
  • Sidebar subnav (Part G): Library → category links, Settings → its pages, with auto-collapse per nav.
  • Cleanup (Part C, LAST): remove the stale worktrees + delete the external repos (plum-control-mcp, portable-net-tv, media-recommender, torrent-search-mcp) once the port is verified. Nothing irreversible until then.

💭 Designed — fleet PACKAGE (future, not app core)

Keep the mesh engine out of TVAnarchyCore; the app consumes its outputs. Lives in fleet/ (package).

  • Networking — two planes (spec: .project/history/20260608_fleet-manager-mesh-design.md → "Networking — two independent planes"). Separate interfaces, never conflated:
    • Plane 1 — Fleet WireGuard (internal mesh): auto-provisioned (package mints keys/ subnet/peers, thin per-OS actuator raises the interface — Linux wg-quick+systemd; macOS wg-quick under sudo over a utun; iOS manual/out-of-path), tv-anarchy-only (AllowedIPs=fleet subnet, Table=off, no default route — a scoped mesh, NOT a VPN), never-conflict via a runtime route/interface/port collision-probe + dedicated tva0 name. Promoted to an early foundational stage because one stable fleet IP per device (home-or-away) kills today's LAN-vs-VPN juggling (black-endpoint-lan-vpn; the transmission LAN→VPN fallback is the interim). Single-fleet trust = trivial (own all devices, no M-of-N). Open Q: is 10.9.0.4 general infra (additive) or just the tv-anarchy overlay (migration)?
    • Plane 2 — Commercial VPN via uploaded .ovpn (public-swarm exit): the public_swarm_face egress that keeps the home IP dark.
      • Import + manage UI BUILT (Settings → "VPN (public-swarm exit)"): import individual .ovpn OR a provider zip (ditto unpack → harvest every .ovpn
        • its sidecar certs → group by zip name in ~/.config/tv-anarchy/vpn/), OVPNParser surfaces remote/proto/needs-login/inline-vs-external-certs, per-provider login stored in the Keychain (VPNCredentialStore, never on disk), list/group/delete. VPNController + VPNConfigStore; parser/scan/zip tested (OVPNParserTests, 8). Management only — no actuation yet.
      • Select active config per public_swarm_face device + actuation = OpenVPN on the always-on Linux node, split-routed (only public-swarm torrent egress through tun, not a full-device VPN). Coexists with plane-1 WG on a separate interface/route scope. (Needs the fleet duty engine.)
  • Mesh registry + duty assignmentpeers_for(infohash) / custodians_of(title), auto-built from activity (watch = auto-seed, completion = custody).
  • Shared catalog / "public data drive" — registry lists + media/collection definitions (e.g. "The Matrix collection") + optional cover art; written only by mesh anchors (Broadcast Stations), read by all, distributed AS a torrent; a signed, versioned catalog pointer.
  • Anchor trust — M-of-N existing anchors co-sign a new writer's key (vouching); reuse WireGuard peer identity; signed revocation list; founder-key bootstrap.
  • Custody floor + reaper — ≥N copies per wanted title; re-pin before the last copy vanishes; reaper resurrects dead titles from the mesh first, public re-search fallback. (System-level "improve health".)
  • Cover-art sharing across the mesh (a payload of the shared catalog).
  • Friend subscription + mutual promotion — A invites B, B accepts → a bidirectional, signed, revocable Friendship (on the WireGuard-key identity + Discord) carrying a per-pair share/promote policy: serve each other custody/streaming (bandwidth tier 2), union each other into peers_for, relay each other's F2F requests, and boost each other's content in discovery/recs. Either side revokes.
  • Anonymized adult sharing — help friends watch porn you hold without it being attributable to you: serve it content-addressed (by ContentID/infohash, not "my files"), via F2F relay (source identity stripped hop-by-hop), with a raised k-anonymity threshold (never surfaced below K distinct holders). The friend gets the bytes by hash; the mesh never reveals who has it. A per-porn sharing mode on top of the data boundaries.
  • Content hashing (ContentID) — BUILT. Canonical, library-agnostic episode id (canonicalKey(work)/sNNeNN, quality opt-in) so the same episode across libraries (different rips/folders) normalizes to one id; + a SHA-256 short digest that leaks no title. The keystone for dedup, peers_for(id), k-anonymity counting, and the anonymized content-addressed serving above. (Byte-exact swarm identity stays the torrent infohash at the transmission layer.)
  • Data boundaries + bandwidth tiers pure models built (DataDomain: tvanarchy/user/publicMedia + shareability; BandwidthPolicy: you > friends-when-idle

    public, Travel-Mode = all-to-you). Actuation: mcp upload-control verbs (transmission-remote -u/-U, per-torrent), laptop magnet-add for swarm augmentation, governor enforcement.

💭 For later — recommendation engine

  • "Collection ideas like this" — per-collection "more like this" recommendations (recommender / media-recommender). Surfaced from a collection item.

💭 2026-06-23 Council Expansions (from session 019ef387... planning)

  • Net "signal" part (light recent-watch aggregates, k-anon, local MVP in Phase 1.5) for Library badges + Download hints.
  • Consensual anonymous adult sharing ("porn relay"): raised-K contentKey-only part, F2F relay stripping, never surfaced below threshold (aligns operator trust-and-safety domain).
  • Install health dashboard (per-device last-seen/quota/net-contrib) + "synced" skip badge in Player when marker from merged Net.
  • Net peers-hints boost "Find better" re-search in Downloads detail.
  • Exhaustive "fleet" → "install" terminology sweep as explicit pre-Phase-1 gate (product term; internal fleet.json/WG stay).
  • All grounded in current shipped (library titles+fix, Winamp, mesh join), respect contentKey-only / k-anon / adult redlines / one-shell / pillar DRY. Full synthesis + handoff in .project/history/20260623_v2-council-expansions-planning.md.

Standing directives

  • Keep this list current as objectives are added/finished.
  • Keep building while waiting for responses; don't stall on questions.
  • Separate core app code from ideas that should be packages.
  • Thoroughly test each feature; keep the tree green at every boundary.