feat(feed): bound feed lifecycle to 30-day window with nonblocking refresh

Persisted feeds keep only episodes from the last 30 days (plus completed
downloads); older episodes live in volatile memory and survive refreshes
via union merge, with per-feed in-memory caches capped at 500. Refresh
batches run at FETCH_CONCURRENCY=4 with per-feed incremental apply (no
Promise.all barrier), config.json writes are trailing-edge debounced
(250ms, immediate flushPendingSave for unsubscribes), and cold
fetch-more refetches abort at FETCH_TIMEOUT_MS. A shared activity store
powers a global top-right indicator covering refresh, fetch-more,
subscribe, search, and downloads.

Also includes the in-flight incremental RSS parsing (chunked with
event-loop yields) and refresh spinner work this tree already carried.
This commit is contained in:
2026-08-12 10:13:20 -04:00
parent e09ae15e32
commit deac6081ca
23 changed files with 2226 additions and 169 deletions

View File

@@ -27,6 +27,7 @@ import { TABS } from "@/utils/navigation";
import { createDispatcher } from "@/utils/dispatch";
import { TabListPane } from "@/components/TabPanel";
import { PaneRow } from "@/components/PaneRow";
import { GlobalActivityIndicator } from "@/components/GlobalActivityIndicator";
export function Shell() {
const theme = useTheme();
@@ -396,6 +397,8 @@ export function Shell() {
theme={t as any}
/>
</Show>
{/* ── Global activity indicator (top-right overlay) ─────────────────────── */}
<GlobalActivityIndicator />
</box>
);
}