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>
14 KiB
14 KiB
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-repomcp→searchverified returning live magnets. (Part A) - Hosts → Devices rename + 5 device types — cellphone · laptop · storage · seed ·
Broadcast Station — each with an overridable
DeviceServicesset (stream · offlineCache · ttlSeed · custody · publicSwarmFace · f2fRelay · meshAnchor), mirroring the fleet duties; legacyhosts.jsoninfers 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;
forwardMediaKeyssetting. (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.Identifierrows). 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 + UI —
WatchState(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 (mcptx-detail/tx-reannounce/tx-verify/tx-start/tx-stop;TorrentDetail/TrackerStatmodels;DownloadsControllerdetail+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
mergeSeriesByNameonly merges exact normalized name + same category + disjoint seasons, so e.g. Dandadan stays as 3–4 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.candidateClustersblocks variants by a canonical key (alphanumeric — "DAN DA DAN"/"DANDADAN"/"Dan.Da.Dan" → one cluster);ambiguousClustersis the cheap→reasoner hand-off; theShowGrouperseam (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 unfilledTitleRefinerMLX 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.pyreasons over a candidate cluster (correctly groups Dandadan's cross-category dupes); SwiftLocalLLMGroupershells into it likeenrich;ShowGroupCache/CachedGroupDeciderpersist decisions (once per cluster);ShowGrouping.combine+mergeapply 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 thecombineSplitShowssetting. - ✅ 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:
DeterministicGroupercombines same-canonical- key entries with close release years (dupes + split seasons) and excludes year-distant remakes — no model. It's the default incombineSplitShows; the MLXLocalLLMGrouperis gated behinduseLLMGrouper(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.
- ✅ Cheap stage + seam built:
- 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 infleet/(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; macOSwg-quickunder 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 + dedicatedtva0name. 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: is10.9.0.4general infra (additive) or just the tv-anarchy overlay (migration)? - Plane 2 — Commercial VPN via uploaded
.ovpn(public-swarm exit): thepublic_swarm_faceegress that keeps the home IP dark.- ✅ Import + manage UI BUILT (Settings → "VPN (public-swarm exit)"): import
individual
.ovpnOR a provider zip (dittounpack → harvest every.ovpn- its sidecar certs → group by zip name in
~/.config/tv-anarchy/vpn/),OVPNParsersurfaces 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.
- its sidecar certs → group by zip name in
- ⬜ Select active config per
public_swarm_facedevice + actuation = OpenVPN on the always-on Linux node, split-routed (only public-swarm torrent egress throughtun, not a full-device VPN). Coexists with plane-1 WG on a separate interface/route scope. (Needs the fleet duty engine.)
- ✅ Import + manage UI BUILT (Settings → "VPN (public-swarm exit)"): import
individual
- Plane 1 — Fleet WireGuard (internal mesh): auto-provisioned (package mints keys/
subnet/peers, thin per-OS actuator raises the interface — Linux
- Mesh registry + duty assignment —
peers_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 intopeers_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-pornsharing 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-idlepublic, 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.