Replaces the five novelty themes with one professional dark theme built
on a shared token set (surfaces, 6-step type scale, semantic colours,
44px tap target) used across dashboard, login and settings.
Layout now matches the cache tiers in app.py: a "Right now" strip with
live streams, the download queue and array usage sits above the fold,
with library, discovery, requests and watch insights below. Previously
the page opened on rarely-actionable disk stats and buried active
streams under twenty recommendation rows.
Also from the design review:
- Discover lists show 5 with a Show more toggle instead of 10 each
- RT descriptions render inline and clamped, not hover-only tooltips
that touch devices could never reach
- Adding a title is now two-step (tap to arm, tap to confirm) since it
queues a real download
- role=alert on errors, aria-live on polled regions, semantic dl/dt/dd
stats, labelled search inputs
- every text/surface pair verified at >= 5:1 contrast
Drops the dead delete-modal JS, which threw a TypeError on every load,
and the stale root-level template copies that Flask never rendered.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SABnzbd and Tautulli embed their API key in the request URL, so it
was showing up verbatim in the raw exception text under "Technical
details" on the dashboard — visible to anyone with the study-room
password. Added a redact_secrets filter that strips apikey/token/
password query params before the raw string is rendered.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Settings lived inside .header-right, which is hidden below 600px,
leaving no way to reach it on mobile. It's now a standalone 44x44px
button next to the title.
Dashboard error cards showed raw Python/urllib3 exception strings.
Added a friendly_error() filter that classifies the failure (timeout,
connection refused, DNS, auth, 4xx/5xx, TLS) into plain-English copy,
with the raw string still available behind a details disclosure.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>