Replace the four stat cards with a compact three-card strip: 7-day activity
with inline SVG sparkline (new ActivitySparkline), Running Now / Next Check
with per-source countdown list, and System with credential health + disk
usage bar.
- Add GET /downloads/activity-timeline — per-day completed/failed/files
counts, pre-filled with zero buckets so the sparkline always has N points.
- Report filesystem-level usage via shutil.disk_usage in storage rollup,
plus a live fallback in GET /settings so the capacity bar works before
the first Celery rollup runs and for cached rows that predate the field.
- Extract Download Details into a reusable DownloadDetailsModal component
and wire Recent Activity rows to open it (previously Downloads-page only).
- Compute next scheduled check per source from global schedule_interval;
surface credential expiration/missing alerts scoped to platforms that
actually have enabled sources.
- Two-speed polling (5s when downloads are active, 30s idle) with Page
Visibility awareness so background tabs don't churn the API.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Dashboard's "Retry All Failed" button used to fetch every non-superseded
failure, then fire one retry HTTP call per row. Two missing guards:
- No per-source dedup — a source with N historical failures enqueued N jobs
for the same URL.
- No "recent success" check — a source that succeeded two hours ago could
still have stale un-superseded failures from last week that got re-queued.
Adds POST /api/downloads/retry-failed-bulk:
• Selects non-superseded failures, ordered (source_id ASC, created_at DESC)
• Per-source dedup keeps only the most recent failure per source
• Skips sources whose last_success is within the configurable recent window
(default 24h, via recent_window_hours body param)
• Resets + enqueues in a single DB transaction, single Celery dispatch loop
• Returns a per-reason skip breakdown: duplicate_source, recent_success,
no_source (orphaned rows)
The Dashboard button now calls the bulk endpoint and surfaces the full
skip breakdown in the toast, so the user knows exactly what was (and
wasn't) queued. Logic is extracted into a pure _plan_bulk_retry helper
and unit-tested against SimpleNamespace fixtures — 7 new tests covering
the dedup, window, and orphan paths.
Both cards now use h-100 with a flex column layout so v-row stretches
them to match, and their v-card-text grows to fill so their footers
line up. Move Active's "+N more queued" out of the list body into a
proper v-card-actions footer mirroring Recent Activity's "View all
downloads" button.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Both lists could grow long enough to dominate the viewport (21 queued
downloads was common). Limit each to 5 entries with a "+N more"
affordance: Active shows a "+N more queued" button that deep-links to
/downloads?status=queued, and Recent Activity folds the overflow count
into the existing "View all downloads" button. Seed Downloads.vue's
status filter from the URL query param so the deep-link actually filters
on arrival.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The dual meaning of the progress bar (red = failing share, green = all
healthy) was not self-explanatory. Added a small legend beside the
section header and a hover tooltip on each bar that states "N of M
<platform> sources failing".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Converts the Source Health last-check column and the Downloads created-at
column from locale date strings to human-relative times ("6 hours ago").
The absolute timestamp is preserved as a native browser tooltip so full
precision is still one hover away; detail modals keep the absolute form.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Consecutive failures on the same source no longer spam the feed as
separate rows. Failed downloads are now bucketed per subscription and
rendered as a single row with a failure count and the latest
timestamp, while successful/running entries still render individually.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v-model.number on the Settings input returns an empty string when cleared,
which persists through the PATCH and then coerces error_count >= '' to
error_count >= 0 — flagging every source as failing. Coerce to Number and
fall back to 5 unless the value is a finite number >= 1.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The platform-health row icon binds to getPlatformColor(row.platform) but the
helper was only present in Sources.vue/Subscriptions.vue/Credentials.vue/
Settings.vue — not copied into Dashboard.vue when the template was introduced,
so the icons next to each platform row rendered in Vuetify's default color.
Also collapse the double blank line left where sourcesWithErrors was removed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Updated empty state messages to include descriptive icons, main text (body-1), and helper text (caption)
- Active Downloads: shows idle status with check-circle icon
- Recent Activity: shows history icon with context about failures/new downloads
- Sources Needing Attention: shows check-all icon confirming 7-day health check
- Added pulse animation to running downloads with left border animation
- Consistent styling across all empty states with py-8 padding and secondary color icons
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>