- 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>
1018 lines
28 KiB
Markdown
1018 lines
28 KiB
Markdown
# 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
|
||
```
|
||
|
||
должны быть скрыты за простым интерфейсом.
|