feat(feed): make episode cache bound user-configurable (date window or count)

Add episodeCacheMode/count/days preferences (default: date, 60 days).
Apply the bound when reading instead of writing, so a preference change
takes effect without a refetch; the full parse cache stays intact so
fetch-more can page beyond the bound. Thread the window through
load/saveFeedsToFile and update tests and task docs.
This commit is contained in:
2026-08-12 15:41:22 -04:00
parent 4127fd1181
commit 26729fa5e6
16 changed files with 431 additions and 160 deletions

View File

@@ -1,4 +1,4 @@
import type { Episode } from "../types/episode"
import type { Episode } from "../types/episode";
/** Sort key for an episode's pubDate — missing/invalid dates sort as NEWEST
* (Infinity) so undated episodes float to the top instead of dropping into
@@ -10,17 +10,21 @@ const ts = (ep: Episode): number => {
/**
* Union of two episode lists keyed by id — on collision the fetched copy
* wins (fresh metadata). Result is sorted newest-first by pubDate and capped
* at `cap` entries (oldest dropped). Never mutates either input.
* wins (fresh metadata). Result is sorted newest-first by pubDate and pruned
* by the supplied `keep` predicate: episodes outside the configured cache
* bound (date window or count) are dropped. Never mutates either input.
*
* The caller supplies `keep` so this module stays free of the preference
* types — the feed store passes a closure bound to the user's mode/count/days.
*/
export function mergeEpisodes(
export function mergeEpisodesBounded(
existing: Episode[],
fetched: Episode[],
cap: number,
keep: (ep: Episode, index: number) => boolean,
): Episode[] {
const byId = new Map<string, Episode>()
for (const ep of existing) byId.set(ep.id, ep)
for (const ep of fetched) byId.set(ep.id, ep)
const sorted = [...byId.values()].sort((a, b) => ts(b) - ts(a))
return sorted.slice(0, cap)
return sorted.filter((ep, i) => keep(ep, i))
}