feat(pinchflat): Retention & quality-upgrade per Library source

On-demand maintenance panel (SourceRetention) on the source detail —
not a persisted schedule (roadmap's niche-for-desktop scope).

Prune: keep the newest N downloaded files, delete the rest's files
(download archive prevents silent re-download); dry-run count + two-step
confirm before any deletion. Upgrade: re-enqueue items whose recorded
downloadedQuality ranks below the source's current target (profile or
global). Selection is pure + injected-now in @shared/retention.ts
(selectPruneCandidates/selectUpgradeCandidates + qualityRank), unit-
tested; pruneMediaItems (main) deletes best-effort and clears state.
MediaItem.downloadedQuality now recorded through the completion path.
Live-verified the panel renders. 366 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 16:14:23 -04:00
parent 15cc96dd10
commit 0956bd536a
13 changed files with 487 additions and 17 deletions
+12 -2
View File
@@ -199,8 +199,18 @@ Re-running a channel should grab only what's new — the everyday media-manager
backed by the existing **`--download-archive`** setting (`Settings.downloadArchive`,
`getDownloadArchivePath()`) as a second, yt-dlp-level dedup guard so even a stale index
can't re-download.
- [ ] **Retention / quality-upgrade (stretch, from Pinchflat).** Optional re-download to upgrade
quality/metadata, and optional pruning of old items. Defer — niche for a desktop app.
- [x] **Retention / quality-upgrade (stretch, from Pinchflat).** Optional re-download to upgrade
quality/metadata, and optional pruning of old items. Implemented as an on-demand per-source
maintenance panel (`SourceRetention` on the Library source detail), not a persisted schedule
(matching the "niche for a desktop app" scope). **Prune:** keep the newest N downloaded files,
delete the older ones' files (the download archive stops them silently re-downloading); shows a
dry-run count and a two-step confirm before any deletion. **Upgrade:** re-enqueue items whose
recorded `downloadedQuality` ranks below the source's current target (its profile's quality or
the global default). The selection logic is pure + injected-now (`@shared/retention.ts`:
`selectPruneCandidates`/`selectUpgradeCandidates`, `qualityRank` in `@shared/ipc`), unit-tested
in `test/retention.test.ts`; the impure prune (`pruneMediaItems` in `src/main/sources.ts`)
deletes files best-effort and clears `downloaded`. `MediaItem.downloadedQuality` is now recorded
through the completion path. Live-verified the panel renders on an expanded source.
## Phase J — Subscriptions & scheduled auto-download ✅ COMPLETE (OS wiring needs smoke test)