Files
pipu-music/docs/web.md
T
mrleo1nidandClaude Opus 5.5 ab092def54
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 50s
ci / ts / ts (packages + web) (push) Successful in 43s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 27s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m22s
feat(web): sign-in, first-run setup, app shell and user admin (M1)
- core: AuthSession keeps the access token in memory, refreshes it
  once for all parallel requests (single-flight) and retries them;
  refresh races between tabs are retried, reuse signs out; ApiError
  carries problem+json codes and field errors
- web: TanStack Router with guards — guests go to /login, the bootstrap
  admin to /setup/admin, temporary passwords to /setup/password, admin
  pages by role
- login, setup screens, Classic shell (top bar, role-based sidebar,
  empty player), settings: name, theme, language, password, devices
- users admin: search, role filter, create with a one-time temporary
  password, reset, block, role change, delete as the policy allows;
  audit log for admins
- ru/en strings for auth, admin and error codes; language and theme
  are saved in the profile

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 06:33:46 +03:00

104 lines
10 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.
# Веб-клиент (`apps/web`)
## Стек
| Задача | Выбор |
|---|---|
| Сборка | Vite, TypeScript (strict) |
| UI | React 19 |
| Роутинг | TanStack Router (типобезопасные пути и search-параметры) |
| Серверное состояние | TanStack Query (кэш, инвалидация, бесконечные списки) |
| Клиентское состояние | Zustand (плеер, очередь, UI) |
| Стили | CSS Modules + CSS-переменные из `packages/design-tokens`; тёмная и светлая темы ([design.md](design.md#2-токены)) |
| Шрифты и иконки | `@fontsource` Onest / Unbounded / JetBrains Mono; иконки — SVG Material Symbols Rounded (`@material-symbols/svg-400`), импортируются поштучно в `shared/ui/icons.ts` и рисуются CSS-маской (`currentColor`), поэтому в сборку попадают только используемые — всё встроено в сборку |
| Компоненты-примитивы | Radix UI (доступные меню, диалоги, слайдеры, popover поиска); в M1 — собственные `Menu` / `Dialog` в `shared/ui`, переход на Radix вместе с поиском (M2) |
| Анимация | Motion (переходы UI), Canvas 2D для кнопки волны (порт `wave.js` из прототипа) |
| Формы | React Hook Form + Zod (в M1 формы простые — контролируемые поля, валидация на сервере с ошибками полей) |
| i18n | i18next + react-i18next, словари из `packages/i18n` |
| API | `packages/api-client` (сгенерирован из OpenAPI, `openapi-typescript` + `openapi-fetch`) |
## Структура (feature-sliced)
```
apps/web/src/
├── app/ # провайдеры, роутер, глобальные стили, гарды
├── pages/ # login, setup-admin, setup-password, home, search, artist, album,
│ # collection/{history,likes,dislikes}, settings,
│ # admin/{users,sources,library,upload,upload-queue,settings}
├── widgets/ # app-shell (топбар, сайдбар), player-bar, wave-button, search-box, track-list
├── features/ # like-track, change-password, mood-picker, create-user, pattern-editor, ...
├── entities/ # track, album, artist, user — модели и маленькие UI-карточки
└── shared/ # ui-kit, lib, hooks, config
```
Правило: слой может импортировать только из нижележащих (`pages → widgets → features → entities → shared`),
проверяется `eslint-plugin-boundaries`.
## Ключевые компоненты
### App shell
Макет — `Pipu Web.dc.html`, каркас **Classic** ([design.md](design.md#4-каркас-и-экраны)).
- Сетка: топбар 60 px (логотип, поиск, меню профиля) / сайдбар 240 px (Главная, Коллекция → История / Лайки / Дизлайки,
Админка по роли) / контент / плеер.
- Плеер в двух видах, выбор в профиле: `bar` — полоса 88 px внизу (по умолчанию), `island` — плавающий остров.
- Ambient-фон за всем интерфейсом: три размытых дрейфующих пятна, одно — в цвете обложки текущего трека.
- Адаптивность (макетов нет): < 1024 px — сайдбар из иконок, < 768 px — нижняя навигация, как в мобильном.
### Поиск
- `SearchBox` — Radix Popover + combobox по WAI-ARIA (стрелки, Enter, Esc).
- Debounce 150 мс, отмена предыдущего запроса, `GET /search/suggest`.
- Enter без выбранного элемента → `/search?q=…`; Enter / клик по элементу → исполнитель / альбом / запуск трека.
- Горячая клавиша `/` или `Ctrl+K` — фокус в поиск.
### Плеер
- Движок: `HTMLAudioElement` → Web Audio граф (`MediaElementSource → Gain (нормализация) → Analyser → destination`).
`Analyser` даёт спектр для анимации кнопки волны (тот же origin — CORS не мешает).
- Два элемента `<audio>` для предзагрузки следующего трека и бесшовного перехода (опционально — кроссфейд 0–6 с в настройках).
- Media Session API: метаданные, обложка, кнопки ОС / наушников.
- Горячие клавиши: пробел — пауза, ←/→ — перемотка 10 с, Shift+←/→ — трек, L — лайк.
- Логика очереди, событий, сохранения позиции — из `packages/core` (общая с мобильным); веб реализует
только адаптер `AudioEngine`.
### Кнопка «Моя волна»
- Большой круг в центре главной (раскладка **Center**). Два вида на выбор пользователя: **сфера с шумом** (`orb`,
по умолчанию) и **жидкая форма** (`liquid`). Реализация — порт `wave.js` на TS, Canvas 2D; API и поведение —
[design.md](design.md#3-кнопка-моя-волна).
- В покое — медленное «дыхание». При воспроизведении — пульс с частотой BPM трека и силой от energy,
дополнительно амплитуда от RMS спектра (`AnalyserNode`); цвета — палитра обложки текущего трека.
- Hover — волны ускоряются и «тянутся» к курсору; нажатие — сжатие / отскок (spring).
- Та же кнопка, увеличенная и неинтерактивная, — декоративный фон экрана входа.
- Под кнопкой — `MoodPicker`: чипы занятий и настроений (одно занятие + одно настроение, повторный клик снимает выбор).
Смена во время игры волны — без остановки музыки.
- `prefers-reduced-motion` — замедленная версия без пульса, ambient-фон статичен.
### Профиль и настройки
Пароль, язык, тема (тёмная / светлая / системная), вид волны, вид плеера, ambient-фон, кроссфейд (0–6 с),
выравнивание громкости (EBU R128), выход. Настройки внешнего вида сохраняются в `ui_prefs` (`PATCH /me`).
### Админка
- Пользователи: таблица с поиском и фильтром по ролям, создание (диалог: логин, имя, роль; пароль генерируется сервером
и показывается один раз), сброс пароля (тот же диалог «Временный пароль» с кнопкой «копировать»), блокировка, смена роли (только админу видно).
- Хранилища: список с статусом, форма NFS / local, кнопка «Проверить»; редактор шаблонов — список регэкспов
с drag-and-drop порядка, подсветкой синтаксиса и ошибок компиляции, таблица предпросмотра с подсветкой групп.
- Библиотека: счётчики, прогресс скана и анализа (live), список ошибок.
- Хранилища → «Шаблон раскладки»: поле `write_template` с кнопками «Подобрать автоматически» и «Проверить»
(таблица расхождений реальный путь ↔ сгенерированный).
- **Предложить треки** (moderator, admin): зона drag-and-drop для файлов и папок, индикатор очереди «X из Y ГБ».
Перед загрузкой — Web Worker считает SHA-256 и читает теги (`music-metadata`), `POST /uploads/precheck` →
дубли сразу показываются как «уже есть: <трек>» и не загружаются. Загрузка — `tus-js-client`, до 4 параллельно,
пауза / возобновление. Ниже — «Мои предложения» со статусами и причинами отклонения.
- **Очередь на добавление** (admin): группы по альбомам (вариант `grouped` из прототипа), мини-плеер для прослушивания, inline-редактирование
метаданных (поле на весь альбом), live-превью итогового пути, бейджи «возможный дубль» / «лучшее качество»,
массовые одобрение / отклонение с причиной. Бейдж с числом ожидающих — в сайдбаре.
- **Настройки** (admin): лимит очереди (ГБ), лимит на модератора, макс. размер файла, хранилище по умолчанию.
## Производительность
- Длинные списки (история, лайки, треки альбома) — виртуализация (TanStack Virtual).
- Код-сплиттинг по страницам; админка — отдельный чанк, грузится только для ролей admin / moderator.
- Обложки — `srcset` 64 / 300 / 1200, `loading="lazy"`.
- Бюджет: первый JS-чанк < 200 КБ gzip, LCP < 1.5 с в локальной сети.
## Доступность
- Вся навигация с клавиатуры, видимый фокус, ARIA-роли у плеера и поиска, контраст AA.