Swaps v-select for v-autocomplete on the Source filter so users can
type-to-filter instead of scrolling a long list. Same data source, same
selection semantics.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces two stacked filter rows (4 fields + superseded switch) with a
single "Filters" button that opens a popover containing all controls.
Active filters surface as dismissable chips on the bar alongside a
"Clear all" button, so what's currently applied stays visible without
eating vertical space above the table.
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>
The anyio_backend fixture is only consumed by pytest-anyio, which is not a
dependency of this project (we use pytest-asyncio with asyncio_mode=auto).
Also drop the unused pytest import from test_sanity.py.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Four bite-sized tasks: pytest bootstrap, resolver service, downloads.py
pure helpers, retry-hook integration. Full TDD with 35 tests.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Auto-heal "Failed to extract campaign ID" failures by resolving via
Patreon's campaigns API, caching the ID in Source.metadata_, and retrying
once within the same task run.
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>
Defines "failing source" as error_count >= configurable threshold
(default 5), adds per-platform health bars and a strict failing-source
list to the Dashboard, surfaces the threshold on the Settings page.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Earlier container rolled 1.31.5, which fails Patreon fetches with
HTTP 426 Upgrade Required. 1.31.10 restores the OAuth flow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a new filter toggle to the Downloads view that allows users to hide
superseded failed downloads. The filter is enabled by default and passes
exclude_superseded=true to the API when active.
Changes:
- Add filterExcludeSuperseded ref initialized to true
- Add filterExcludeSuperseded to watch dependencies
- Include exclude_superseded param in loadDownloads() when true
- Add v-switch toggle UI control below date filters
Co-Authored-By: Claude Sonnet 4.6 <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>
- Add .claude/ to .gitignore and untrack settings.local.json
- Create .dockerignore to exclude docs, extension, analysis, scripts,
node_modules, and other noise from the Docker build context
- Dockerfile: Node 20→22-alpine, Python 3.11→3.13-slim, DEBIAN_FRONTEND
set, pip upgrade before install
- Track frontend/package-lock.json for reproducible builds
- Update summary.md: date, db.py in project structure, superseded column
in schema, two new key patterns (12/13), full 2026-03-19 changelog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added missing 'from sqlalchemy.ext.asyncio import AsyncSession' import in downloads.py
This fixes the NameError at module load time that was breaking all download tasks
- Replaced regex patterns with plain-string equivalents in NOT_FOUND_PATTERNS:
* 'account.*deleted' → 'account deleted' (. and * are literal in substring search)
* 'profile.*not.*exist' → 'profile does not exist' (same issue)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all bare `session = AsyncSession(bind=conn)` assignments with
`async with AsyncSession(bind=conn) as session:` in downloads.py,
credentials.py, settings.py, sources.py, and subscriptions.py to
ensure sessions are properly closed on all code paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract identical get_async_session() and cleanup_engine() functions
from downloads.py and maintenance.py into a shared db.py module.
This eliminates code duplication and improves maintainability.
db.py imports only from app.config and SQLAlchemy to avoid circular
dependencies with the task modules that import from it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>