feat(podcasts): real-time episode list — byte progress bars, direct swaps (issues #1/#3)
WHY: downloads needed manual refreshes to see progress; the 204 + HX-Trigger event indirection missed its listener, and the worker buffered whole enclosures so no progress existed to show. - Worker streams enclosures to disk through a counting loop (64KiB chunks, scheme/redirect/size guards preserved) with an in-memory per-episode progress snapshot (received/total; total from Content-Length, seeded by the feed's declared length). Deliberately transient — the DB row state remains the durable truth. - Episode-list renderer seam: the router injects a closure into PodcastHandler (handlers can't import templates — MediaDetail cycle) so htmx download/retry/Download All/refresh/order/policy actions answer with the REFRESHED partial directly (200, hx-target= #episode-list) — no events to miss. Non-HX callers keep JSON; tests without a renderer keep 204+trigger. - Polling tightens to 2s while downloads are active; downloading rows render a percentage bar + MB counter (quote-free inline style per templ gotcha #8) or an indeterminate bar when the server sends no length. - Issue #3: Download All (button AND the add-feed checkbox) flips the CALLER's subscription policy to 'all' — per-user, never global (D6: files shared, policies not) — and the auto-download select out-of-band-swaps so the dropdown shows what the user is getting. They can turn it down afterwards; the flip is honest communication, not coercion (user-approved 2026-10-29). PINs: Download All with HX answers 200 + episode-list + oob select and flips newest → all; add-feed download_all sets policy all.
This commit is contained in:
1 parent
ffc80f7ba7
commit
30766020c8
8 files changed
+545
-57
No files matched your search
+5
-4
@@ -18,10 +18,11 @@ Alpine.data("podcastsPage", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// The detail page's actions are pure htmx (hx-post/hx-put/hx-delete +
|
||||
// HX-Trigger-driven list refresh); Alpine only hosts the x-data the
|
||||
// body declares so CSP-bound Alpine stays happy.
|
||||
Alpine.data("podcastDetail", () => ({}));
|
||||
// The detail page's actions are pure htmx; Alpine hosts the x-data the
|
||||
// body declares (modal visibility) so CSP-bound Alpine stays happy.
|
||||
Alpine.data("podcastDetail", () => ({
|
||||
unsubOpen: false,
|
||||
}));
|
||||
|
||||
// Add-feed form: surface the server's error text inline (the JSON error
|
||||
// body arrives on non-2xx; htmx swaps nothing). Success redirects via
|
||||
|
||||
Reference in new issue
Block a user