feat(downloads): live per-file progress on running events — #709
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 17s
CI / backend-lint-and-test (push) Successful in 25s
CI / integration (push) Failing after 3m1s

Now that we own the walk, surface live counts on the in-flight download in the
Downloads view. ingest_core.run takes an event_id and does a TIME-THROTTLED
write (~5s, decoupled from page boundaries so it ticks steadily regardless of
how big/slow a page is) of {downloaded, skipped, errors, quarantined, posts} to
the running download_event's metadata.live (jsonb_set; short session; status
guard so a finalized event isn't clobbered). download_backends threads
event_id from ctx; the /api/downloads list surfaces `live`; ActiveDownloadsPanel
renders it beside the elapsed timer. Native (Patreon) only — gallery-dl is an
opaque subprocess; the row only shows when `live` is present. Phase 3 overwrites
metadata with run_stats on finish, dropping `live`.

Test: _write_live_progress updates a running event's metadata.live and leaves a
finalized (status != running) event alone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 15:48:31 -04:00
parent 89dfa42e18
commit 62cca64dce
5 changed files with 93 additions and 0 deletions
@@ -23,6 +23,18 @@
<span class="fc-active__dot" />
<PlatformChip :platform="e.platform" size="x-small" />
<span class="fc-active__artist">{{ e.artist_name || '—' }}</span>
<!-- #709: live per-file progress for native (Patreon) downloads. -->
<span v-if="e.live" class="fc-active__counts">
<span class="fc-active__count" title="downloaded"> {{ e.live.downloaded }}</span>
<span v-if="e.live.skipped" class="fc-active__count" title="skipped">
{{ e.live.skipped }}</span>
<span
v-if="e.live.errors" class="fc-active__count fc-active__count--err"
title="errors"
> {{ e.live.errors }}</span>
<span class="fc-active__count fc-active__count--posts" title="posts scanned">
{{ e.live.posts }} posts</span>
</span>
<v-spacer />
<span class="fc-active__timer" :title="`started ${e.started_at}`">
{{ elapsed(e.started_at) }}
@@ -123,6 +135,13 @@ function elapsed (startedIso) {
font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em;
color: rgb(var(--v-theme-on-surface-variant));
}
.fc-active__counts {
display: inline-flex; align-items: center; gap: 8px;
font-variant-numeric: tabular-nums; font-size: 0.78rem;
}
.fc-active__count { color: rgb(var(--v-theme-on-surface-variant)); }
.fc-active__count--err { color: rgb(var(--v-theme-error)); }
.fc-active__count--posts { opacity: 0.7; }
@keyframes fc-active-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.35; transform: scale(0.7); }