An axe-core audit of every screen in both themes found low contrast on white text over the accent and on caption text, track rows using row roles without cells, and unlabelled progress bars. Accent fills now use a darker token, caption text meets 4.5:1, track lists are lists and progress bars are labelled. The queue drawer slides in from the right as designed, takes focus, closes on Escape and notes that the wave never ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 KiB
12 KiB
Веб-клиент (apps/web)
Стек
| Задача | Выбор |
|---|---|
| Сборка | Vite, TypeScript (strict) |
| UI | React 19 |
| Роутинг | TanStack Router (типобезопасные пути и search-параметры) |
| Серверное состояние | TanStack Query (кэш, инвалидация, бесконечные списки) |
| Клиентское состояние | Zustand (плеер, очередь, UI) |
| Стили | CSS Modules + CSS-переменные из packages/design-tokens; тёмная и светлая темы (design.md) |
| Шрифты и иконки | @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).
- Сетка: топбар 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. - В покое — медленное «дыхание». При воспроизведении — пульс с частотой 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.
- Обложки —
srcset64 / 300 / 1200,loading="lazy". - Бюджет: первый JS-чанк < 200 КБ gzip, LCP < 1.5 с в локальной сети.
Доступность
- Вся навигация с клавиатуры, видимый фокус, ARIA-роли у плеера и поиска, контраст AA.
Доступность
Цель — WCAG 2.1 AA. Проверено axe-core на всех экранах в тёмной и светлой темах (с играющим треком и открытой очередью) — нарушений нет. Правила, которые держим:
- текст на заливке акцента — только
--accent-fill(белый текст ≥ 4.5:1), вторичный текст--text3— ≥ 4.5:1 на фоне и поверхностях; - у каждого экрана есть
h1; у кнопок-иконок —aria-label, у переключателей —aria-pressed; - списки треков —
role="list"/listitem(строка фокусируется, Enter запускает трек, лайк и ссылки — отдельные элементы); прогресс-бары подписаны черезaria-labelledby; - выезжающие панели (очередь) забирают фокус при открытии, закрываются по Escape и возвращают фокус на кнопку;
:focus-visible— обводка акцентом;prefers-reduced-motion— анимации и Ambient выключены, волна замедлена.