myYouTube/UI_UX_spec.md
vrubelroman 0ed20bb838 Implement Phases 1-5: skeleton, OAuth, categories, video sync/feed, playback
- FastAPI + PostgreSQL + Alembic + React/Vite skeleton, Docker Compose, healthcheck
- Google OAuth (single allowed account), encrypted refresh token storage
- Subscriptions sync with pagination, uploads playlist batch fetch
- Categories CRUD, many-to-many channel assignment, category filtering
- Video sync (playlistItems + videos.list batching), cached feed with cursor
  pagination, background scheduler (APScheduler)
- Video detail page with YouTube embed player
- SPA fallback routing, optimistic UI updates, client-side query caching

40 backend tests covering OAuth allow-list, sync idempotency, cascade deletes,
cursor pagination, and category filtering.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 18:44:30 +00:00

1018 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UI/UX-спецификация: персональный YouTube-клиент
**Назначение:** передать coding agent вместе с основным техническим заданием.
**Формат:** single-user self-hosted web app, в будущем PWA/mobile client.
## 1. Концепция
Приложение должно ощущаться как персональная лента YouTube-подписок, но с нормальной категоризацией каналов и локальным хранением выбранных видео.
Пользовательский сценарий:
```text
Открыть приложение
→ выбрать категорию
→ увидеть последние видео каналов этой категории
→ смотреть через YouTube
→ при желании нажать "Скачать"
→ после загрузки видеть "На сервере"
→ дальше смотреть локальную копию через MeTube
```
Интерфейс не должен выглядеть как admin panel. Это consumer media UI.
---
## 2. Общий layout
Desktop:
```text
┌──────────────────────────────────────────────────────────────────────┐
│ Header │
├──────────────────────┬───────────────────────────────────────────────┤
│ Sidebar │ Main content │
│ │ │
│ Все видео │ Video feed │
│ Без категории │ │
│ На сервере │ │
│ │ │
│ КАТЕГОРИИ │ │
│ Linux │ │
│ Шахматы │ │
│ Научпоп │ │
│ │ │
│ Каналы │ │
│ Настройки │ │
└──────────────────────┴───────────────────────────────────────────────┘
```
Sidebar: 220–260 px.
Header: фиксированный сверху.
Main content: responsive grid.
Mobile: sidebar превращается в drawer/hamburger.
---
## 3. Визуальный стиль
Тёмная тема по умолчанию.
Рекомендуемые design tokens:
```text
Background main #0F0F0F
Surface #181818
Surface hover #272727
Border #303030
Primary text #F1F1F1
Secondary text #AAAAAA
Accent #3EA6FF
Success/local #2BA640
Warning/downloading #F5A623
Error #E53935
```
Использовать CSS variables, а не раскидывать цвета по компонентам.
Типографика:
```text
Inter / Roboto / system-ui
Page title 24px / 600
Section title 18px / 600
Video title 15–16px / 500
Metadata 12–14px
Buttons 14px / 500
```
---
## 4. Header
Пример:
```text
┌──────────────────────────────────────────────────────────────────────┐
│ ▶ MyTube 🔍 Поиск... ✓ 12 мин ⚙ │
└──────────────────────────────────────────────────────────────────────┘
```
Слева:
- логотип;
- рабочее название `MyTube` или configurable app name.
Центр:
- поиск по локальной БД: video title + channel title.
Справа:
- sync indicator;
- settings.
Sync states:
```text
✓ Обновлено 12 мин назад
↻ Обновление...
⚠ Ошибка синхронизации
```
По клику на sync indicator:
```text
Подписки: обновлены 20:11
Видео: обновлены 20:12
MeTube: доступен
```
---
## 5. Sidebar
Пример:
```text
● Все видео
Без категории
На сервере
КАТЕГОРИИ
🐧 Linux
♟ Шахматы
🔬 Научпоп
🎮 Игры
+ Новая категория
────────────
Каналы
Настройки
```
Активная категория должна иметь заметный, но спокойный selected state.
`Все видео`, `Без категории`, `На сервере` — виртуальные категории.
---
## 6. Feed
Route:
```text
/
```
или:
```text
/feed
```
Header выбранной категории:
```text
Linux
24 канала
Обновлено 12 минут назад [↻ Обновить]
```
Опциональные chips:
```text
[Все] [На сервере] [Не скачаны]
```
Сортировка видео:
```text
published_at DESC
```
---
## 7. Video grid
Responsive:
```text
>= 1800 px → 5 columns
>= 1400 px → 4 columns
>= 1000 px → 3 columns
>= 700 px → 2 columns
mobile → 1 column
```
Рекомендуемый CSS:
```css
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
```
---
## 8. Video card
Пример:
```text
┌────────────────────────────────────┐
│ │
│ THUMBNAIL │
│ 18:42 │
│ │
├────────────────────────────────────┤
│ Linux Kernel 7.0 — What's New │
│ The Linux Experiment │
│ 2 часа назад │
│ │
│ [▶ Смотреть] [⬇ Скачать] │
└────────────────────────────────────┘
```
Thumbnail:
- aspect ratio 16:9;
- duration badge справа снизу.
Карточка содержит:
- thumbnail;
- title;
- channel name;
- publish time;
- duration;
- download/local state;
- Watch;
- Download.
---
## 9. Состояния скачивания
### Не скачано
```text
[▶ Смотреть] [⬇ Скачать]
```
### В очереди
```text
[▶ Смотреть] [В очереди...]
```
### Скачивается
```text
[▶ Смотреть] [64%]
```
Дополнительно progress-line внизу thumbnail.
### Post-processing
```text
[▶ Смотреть] [Обработка...]
```
### На сервере
```text
[▶ Смотреть] [✓ На сервере]
```
### Ошибка
```text
[▶ Смотреть] [⚠ Повторить]
```
Не показывать traceback.
---
## 10. Video page
Route:
```text
/video/:youtubeVideoId
```
Desktop:
```text
┌──────────────────────────────────────────────────────────────────┐
│ │
│ VIDEO PLAYER │
│ │
└──────────────────────────────────────────────────────────────────┘
Linux Kernel 7.0 — What's New
The Linux Experiment
2 часа назад [⬇ Скачать]
Источник: YouTube
[Linux] [IT]
────────────────────────────────────────────────────────────────────
Описание видео...
```
Если local copy существует:
```text
Источник: ● Локальная копия · mediaVM
```
и вместо YouTube iframe используется HTML5 `<video>`.
Пользователь не выбирает источник вручную по умолчанию.
Логика:
```text
local available?
yes → local player
no → YouTube player
```
Если local player вернул ошибку/404:
```text
Локальная копия недоступна.
Переключено на YouTube.
```
---
## 11. Channels page
Route:
```text
/channels
```
Главная задача — быстро распределять подписки по категориям.
Пример:
```text
Каналы
[Поиск каналов...] [Только без категории]
┌────────────────────────────────────────────────────┐
│ ○ The Linux Experiment │
│ [Linux ×] [IT ×] [+ Категория] │
└────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ ○ GothamChess │
│ [Шахматы ×] [+ Категория] │
└────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ ○ Veritasium │
│ [Научпоп ×] [+ Категория] │
└────────────────────────────────────────────────────┘
```
Не делать отдельную страницу редактирования для каждого канала.
---
## 12. Category picker
По кнопке:
```text
+ Категория
```
показывать popover:
```text
Категории
☑ Linux
☑ IT
☐ Научпоп
☐ Игры
──────────────
+ Создать новую
```
Checkbox сохраняется сразу.
Отдельная кнопка `Сохранить` не нужна.
---
## 13. Массовая категоризация
Желательная функция.
Пользователь отмечает несколько каналов:
```text
☑ Channel A
☑ Channel B
☑ Channel C
```
Появляется action bar:
```text
Выбрано: 3
[Добавить в категорию] [Убрать из категории] [Отмена]
```
Если MVP затягивается, допускается реализовать после базовой версии.
---
## 14. Category management
Route:
```text
/settings/categories
```
Пример:
```text
Категории
≡ Linux 24 канала ✎ ⋮
≡ Шахматы 12 каналов ✎ ⋮
≡ Научпоп 8 каналов ✎ ⋮
[+ Новая категория]
```
Поддержать:
- create;
- rename;
- delete;
- reorder.
При удалении:
```text
Удалить категорию "Linux"?
Каналы удалены не будут.
Они просто перестанут относиться к этой категории.
[Отмена] [Удалить]
```
---
## 15. Virtual category "Без категории"
Показывает каналы/видео каналов, которые ещё не распределены.
Header:
```text
Без категории
17 каналов пока не распределены.
[Перейти к каналам]
```
---
## 16. Virtual category "На сервере"
Показывает видео:
```text
download_status = completed
```
Это простая локальная библиотека без отдельного сложного media-manager.
---
## 17. Empty states
### Нет категорий
```text
Категорий пока нет
Раздели подписки по темам:
Linux, Шахматы, Научпоп и т.д.
[Создать первую категорию]
```
### В категории нет видео
```text
Здесь пока нет видео
Добавь каналы в эту категорию
или обнови подписки.
[Добавить каналы]
```
### YouTube ещё не подключён
```text
Подключи свой YouTube
Мы импортируем список подписок и последние видео.
Доступ к YouTube только read-only.
[Подключить YouTube]
```
---
## 18. Loading states
Не использовать fullscreen spinner.
Использовать skeleton video cards.
Background sync не должен блокировать интерфейс.
---
## 19. Error UX
Global toast:
```text
Не удалось связаться с MeTube
```
или:
```text
YouTube временно недоступен
```
Critical banner:
```text
⚠ Требуется повторная авторизация YouTube
[Подключить заново]
```
Background sync не должен постоянно спамить toast notifications.
---
## 20. Settings
Route:
```text
/settings
```
Sections:
```text
YouTube
MeTube
Синхронизация
Интерфейс
О приложении
```
### YouTube
```text
Google account:
user@example.com
Статус: Подключено
Последняя синхронизация:
Сегодня, 20:15
[Обновить сейчас]
[Переподключить]
```
### MeTube
```text
MeTube
Статус: ● Доступен
Сервер: mediaVM
Адрес: 192.168.8.177:8081
[Проверить соединение]
```
Не показывать на основном UI filesystem path или internal job IDs.
### Sync
```text
Подписки: каждые 6 часов
Видео: каждые 60 минут
Последний запуск:
20:12
[Синхронизировать сейчас]
```
---
## 21. First-run onboarding
Flow:
```text
Open app
→ Подключить YouTube
→ Google OAuth
→ initial sync
→ channels imported
→ предложить категоризацию
```
После initial sync:
```text
Готово!
Импортировано:
143 канала
Теперь можно распределить их по категориям.
[Настроить категории]
[Сделать позже]
```
---
## 22. Mobile UX
Header:
```text
☰ MyTube ↻
```
Категории можно показывать horizontal chips:
```text
[Все] [Linux] [Шахматы] [Научпоп] →
```
Video card:
```text
┌───────────────────────────────┐
│ │
│ Thumbnail │
│ │
└───────────────────────────────┘
Video title
Channel · 2 часа назад
[▶ Смотреть] [⬇ Скачать]
```
Touch targets минимум ~44×44 px.
---
## 23. Navigation routes
Рекомендуемые routes:
```text
/ all feed
/category/:categoryId category feed
/channels channels
/video/:youtubeVideoId video detail/player
/local local videos
/uncategorized uncategorized
/settings settings
/settings/categories category management
```
Browser Back после просмотра видео должен возвращать:
- в ту же категорию;
- примерно на ту же scroll position.
---
## 24. Components
Frontend должен быть разделён на reusable components:
```text
AppShell
Header
Sidebar
CategoryNav
VideoGrid
VideoCard
VideoThumbnail
DownloadButton
DownloadStatus
Player
YouTubePlayer
LocalPlayer
ChannelRow
CategoryPicker
CategoryChip
SyncIndicator
EmptyState
ErrorBanner
Toast
ConfirmDialog
SkeletonVideoCard
```
Не делать весь UI одним React component.
---
## 25. Feed API object
Желательно, чтобы backend сразу отдавал frontend всё необходимое для одной карточки:
```json
{
"youtube_video_id": "abc123",
"title": "Linux Kernel 7.0",
"channel": {
"id": 12,
"youtube_channel_id": "UC...",
"title": "The Linux Experiment",
"thumbnail_url": "..."
},
"thumbnail_url": "...",
"published_at": "2026-09-16T18:00:00Z",
"duration_seconds": 1042,
"categories": [
{
"id": 2,
"name": "Linux"
}
],
"local": {
"available": true,
"status": "completed",
"progress_percent": 100,
"media_url": "http://..."
}
}
```
Запрещён frontend pattern:
```text
GET feed
→ отдельный request channel для каждой карточки
→ отдельный request download status для каждой карточки
```
Не должно быть N+1 API requests.
---
## 26. Desktop wireframe
```text
┌────────────────────────────────────────────────────────────────────────────┐
│ ▶ MyTube 🔍 Поиск ✓ 12 мин ⚙ │
├──────────────────────┬─────────────────────────────────────────────────────┤
│ │ │
│ ● Все видео │ Linux │
│ Без категории │ 24 канала [↻ Обновить] │
│ На сервере │ │
│ │ [Все] [На сервере] [Не скачаны] │
│ КАТЕГОРИИ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ 🐧 Linux │ │ thumbnail │ │ thumbnail │ │ thumbnail │ │
│ ♟ Шахматы │ │ 18:42│ │ 12:03│ │ 8:41│ │
│ 🔬 Научпоп │ └─────────────┘ └─────────────┘ └─────────────┘ │
│ 🎮 Игры │ Kernel 7.0... NixOS update... New GNOME... │
│ │ Linux Exp. Brodie R. DistroTube │
│ + Категория │ 2 часа назад 4 часа назад вчера │
│ │ [▶] [⬇] [▶] [64%] [▶] [✓ Local] │
│ ───────────── │ │
│ Каналы │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ Настройки │ │ │ │ │ │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │
└──────────────────────┴─────────────────────────────────────────────────────┘
```
---
## 27. Channels wireframe
```text
┌──────────────────────────────────────────────────────────────────────┐
│ Каналы │
│ │
│ 🔍 Поиск каналов... [Только без категории] │
│ │
├──────────────────────────────────────────────────────────────────────┤
│ ○ The Linux Experiment │
│ [Linux ×] [IT ×] [+ Категория] │
├──────────────────────────────────────────────────────────────────────┤
│ ○ GothamChess │
│ [Шахматы ×] [+ Категория] │
├──────────────────────────────────────────────────────────────────────┤
│ ○ Veritasium │
│ [Научпоп ×] [+ Категория] │
├──────────────────────────────────────────────────────────────────────┤
│ ○ Some Channel │
│ [+ Категория] │
└──────────────────────────────────────────────────────────────────────┘
```
---
## 28. Video page wireframe
```text
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ VIDEO PLAYER │
│ │
└──────────────────────────────────────────────────────────────────────┘
Linux Kernel 7.0 — What's New
The Linux Experiment
2 часа назад
● Локальная копия · mediaVM [✓ На сервере]
[Linux] [IT]
────────────────────────────────────────────────────────────────────────
Описание видео...
```
Когда local отсутствует:
```text
Источник: YouTube [⬇ Скачать]
```
---
## 29. UX-антипаттерны
Не делать:
- Bootstrap-like admin tables как основной UI;
- raw JSON;
- container names/job IDs на главном экране;
- filesystem paths;
- огромные формы;
- отдельный Save после каждого checkbox;
- модальное окно на каждое простое действие;
- fullscreen spinner при background sync;
- N+1 API requests;
- слишком много badges на thumbnail;
- сильные hover zoom animations.
---
## 30. Accessibility
Минимум:
- semantic HTML;
- keyboard navigation;
- visible focus ring;
- aria-label для icon buttons;
- alt у изображений;
- нормальный contrast;
- `<button>` для кнопок;
- состояние не должно кодироваться только цветом.
---
## 31. Icons
Использовать один icon set, например Lucide React.
Основные icons:
```text
Home
Folder
Download
Play
Check
RefreshCw
Settings
Search
MoreVertical
AlertTriangle
Server
Youtube
Menu
```
---
## 32. Анимации
Минимально:
- hover 150–200 ms;
- popover fade;
- drawer slide;
- progress;
- skeleton pulse.
Не делать сложные page transitions.
---
## 33. Рекомендуемый порядок реализации UI
Сначала сделать интерфейс на mock data:
```text
App shell
Sidebar
Video grid
Video card
Channels page
Category picker
Video page
Mobile responsive
```
Только после визуальной проверки подключать реальные backend API.
---
## 34. Definition of Done UI
UI MVP готов, если:
1. Пользователь может подключить YouTube.
2. После sync видит подписанные каналы.
3. Может создать категорию.
4. Может назначить каналу одну или несколько категорий.
5. Может выбрать категорию и увидеть соответствующую video feed.
6. Карточка показывает thumbnail/title/channel/time/duration.
7. Можно открыть video page.
8. Нескачанный ролик играет через YouTube.
9. Работает Download.
10. Отображаются queued/downloading/completed/failed.
11. Completed ролик имеет статус `На сервере`.
12. Completed ролик играет через MeTube local URL.
13. Есть loading/error/empty states.
14. UI usable на desktop и mobile.
15. Browser refresh сохраняет route.
16. UI не показывает secrets/internal paths/job IDs.
17. На feed нет N+1 requests.
18. Основной экран воспринимается как видеоприложение, а не административная панель.
---
## 35. Главное UX-правило
Пользователь должен воспринимать сервис так:
> Это моя лента YouTube-подписок, только каналы нормально разложены по темам, а понравившееся видео я могу одним кликом оставить у себя.
Технические детали:
```text
OAuth
YouTube Data API
PostgreSQL
MeTube
Socket.IO
mediaVM
filenames
```
должны быть скрыты за простым интерфейсом.