Backend: cache Google access tokens (drop dead access_token_expires_at,
migration 0007), handle MeTube cleared/canceled events by URL, return
email from /auth/status only when authenticated, move Google base URLs
into settings, run container as non-root.
Frontend: include local feed filters in the query key, remove dead
Saved page and unused assets, drop stale CategoryNav props and classes,
send Content-Type only with a body, remove Uncategorized from sidebar.
Same "channels per category" counts as the categories management page,
now next to each button in the sidebar used across all three pages.
Categories already carry channel_count; "Все"/"Без категории" counts are
derived client-side from one extra unfiltered channels query, reused
across the three pages via a shared ['channels', 'all-for-counts'] key.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- GET /api/channels called with subscribed=true from the Channels page, so
a channel disappears from the list as soon as it's unsubscribed instead
of lingering with a badge
- GET /api/feed gains a `downloaded` filter (this was already anticipated
in the original TZ's feed query params but left unimplemented until
download_jobs existed) -- matches on each video's *latest* job only, so
a redownload/delete history doesn't leave stale matches
- New /saved route: same category filtering, video grid and download
actions as the main feed, scoped to downloaded videos only
2 new backend tests (76 total).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>