- Detect shorts by duration (SHORTS_MAX_DURATION_SECONDS, default 180)
and expose is_short in feed, video and channel-activity DTOs.
- Feed gains type=all|long|short and anchor; lists show a two-mode
'Обычные | Shorts' filter (no 'Все') persisted in the URL.
- Clicking a short opens /shorts/🆔 a vertical scroll-snap feed with
autoplay for the active slide, context-aware endpoints and infinite
loading.
- Keep the sound choice across swipes, syncing with the player's own
mute control and guarding against the widget's stale isMuted() reads.
- 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>