Files
jellybit/openspec/specs/web-ui/spec.md
T
avandClaude Opus 4.8 ef75a0d302 Живые обновления прогресса и раздел «Раздача» (live-status)
Воркер ведёт in-memory снимок телеметрии раздач (прогресс, скорость, ETA,
рейтинг, сиды/пиры, отдано) под отдельным RWMutex, обновляя его на каждом
тике поллинга сразу после построения byHash — без лишних вызовов qBittorrent
и без хранения в БД (волатильно). qbt.Torrent дополнен полями телеметрии.

Веб-UI читает снимок через узкий контракт LiveStatus: карточки активных
загрузок показывают живой прогресс-бар (htmx-поллинг фрагмента every 3s,
точечно — без сброса фильтров), на странице загрузки появилась секция
«Раздача» для сидирующих задач. Начальный кадр рендерится сразу со
значениями; при отсутствии данных UI деградирует штатно.

Капабилити live-status (OpenSpec), web-ui дополнен. Change заархивирован.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:38:25 +03:00

10 KiB
Raw Blame History

web-ui Specification

Purpose

Презентационный слой веб-интерфейса: встроенная (go:embed) отдача статики и self-hosted шрифтов, единая дизайн-система (jellybit.css, тёмная тема по настройке ОС), рендеринг страниц (список загрузок, ревью, просмотр) с бейджами состояний и клиентскими взаимодействиями без сборки (копирование infohash, спойлер контекста). Превью раскладки берётся из единой логики internal/layout, а не дублируется в шаблонах. Тонкий транспорт над ядром (приём — ingest, команды — worker, чтение — store).

Requirements

Requirement: Встроенная отдача статики

Сервис SHALL отдавать статические ассеты (CSS, клиентский JS, вендорные библиотеки, шрифты) из встроенной (go:embed) файловой системы под префиксом /static/, без внешних зависимостей времени выполнения. Бинарь остаётся единым и самодостаточным.

Scenario: Запрос встроенного ассета

  • WHEN клиент запрашивает GET /static/css/jellybit.css
  • THEN сервис отвечает 200 с телом из встроенной FS и корректным Content-Type

Scenario: Кэширование статики

  • WHEN сервис отдаёт ответ на запрос под /static/
  • THEN ответ содержит заголовок Cache-Control, разрешающий кэширование ассета браузером

Scenario: Несуществующий ассет

  • WHEN клиент запрашивает несуществующий путь под /static/
  • THEN сервис отвечает 404 и не раскрывает структуру файловой системы

Requirement: Шрифты без внешних сетей

Веб-UI SHALL подключать шрифты (IBM Plex Sans, IBM Plex Mono) только из локально встроенных файлов через @font-face. Страницы MUST NOT обращаться к сторонним хостам (Google Fonts CDN и т. п.) для загрузки шрифтов или стилей.

Scenario: Нет внешних ссылок на шрифты

  • WHEN браузер открывает любую страницу веб-UI
  • THEN все используемые шрифты загружаются из-под /static/ того же origin, без обращений к внешним доменам

Requirement: Единая дизайн-система

Все страницы веб-UI SHALL использовать единственный CSS-файл jellybit.css с дизайн-токенами; страницы MUST NOT содержать инлайн-<style> или хардкод цветов вне токенов. Тема (светлая/тёмная) SHALL определяться настройкой ОС (color-scheme + prefers-color-scheme), без отдельного переключателя.

Scenario: Тёмная тема по настройке ОС

  • WHEN в ОS пользователя выбрана тёмная тема и открывается страница
  • THEN интерфейс отображается в тёмной палитре из токенов, без действий пользователя

Scenario: Нет инлайн-стилей

  • WHEN рендерится любая страница веб-UI
  • THEN оформление берётся из подключённого jellybit.css, в разметке нет блоков <style> с хардкодом цветов

Requirement: Бейдж статуса загрузки

Веб-UI SHALL отображать состояние каждой загрузки бейджем <span class="badge st-{STATE}"> с человекочитаемой русской подписью; класс определяет цвет группы. Маппинг SHALL покрывать все состояния домена (downloading, completed, recognizing, linking, review, deferred, done, stuck, target_missing, failed, orphaned, cancelled, reverted, deleted).

Scenario: Состояние отрисовано бейджем

  • WHEN загрузка находится в состоянии review
  • THEN в списке у неё бейдж с классом st-review и подписью «на ревью»

Scenario: Полнота покрытия

  • WHEN загрузка находится в любом из состояний домена
  • THEN для него существует класс бейджа и русская подпись (неизвестных состояний без оформления нет)

Requirement: Страницы веб-UI

Веб-UI SHALL предоставлять страницы: список загрузок с единым окном добавления и фильтром/поиском (/), экран ревью одной загрузки (/review/{id}) и страницу просмотра одной загрузки (/download/{id}) с распознаванием, файлами→раскладкой, историей и — для сидирующих задач — секцией живой статистики раздачи. Карточки активных (downloading) загрузок в списке SHALL содержать индикатор прогресса. Состояние deleted SHALL быть скрыто в списке по умолчанию (с переключателем «показать всё»). Механика живого обновления прогресса и наполнение секции раздачи определяются capability live-status.

Scenario: Просмотр одной загрузки

  • WHEN клиент открывает GET /download/{id} существующей загрузки
  • THEN отрисовывается страница с её распознаванием, файлами, раскладкой и историей

Scenario: Прогресс активной загрузки в списке

  • WHEN в списке есть загрузка в состоянии downloading
  • THEN её карточка содержит индикатор прогресса (прогресс-бар со скоростью и ETA)

Scenario: Удалённые скрыты по умолчанию

  • WHEN в списке есть загрузки в состоянии deleted и фильтр «показать всё» не включён
  • THEN они не отображаются, но доступны при включённом переключателе

Requirement: Действия соответствуют состоянию

Каждая страница SHALL показывать только действия, допустимые в текущем состоянии загрузки, и каждое действие-кнопка SHALL отправлять форму с полями, имена которых совпадают с ожидаемыми обработчиком (internal/httpapi).

Scenario: Набор действий по состоянию

  • WHEN загрузка в состоянии done
  • THEN доступно действие отката (undo), но недоступны «применить»/«позже»

Scenario: Имена полей формы

  • WHEN пользователь отправляет форму действия (например, «уточнить»)
  • THEN поля формы имеют name, которые обработчик читает без переименования, и действие исполняется

Requirement: Превью раскладки через единую логику именования

Превью целевых путей раскладки в веб-UI SHALL вычисляться той же логикой именования, что и реальная раскладка (internal/naming), а не дублировать правила в шаблоне. Показанные пути MUST совпадать с теми, что создались бы при применении.

Scenario: Превью совпадает с реальной раскладкой

  • WHEN на экране ревью отображается превью целевых путей для текущей догадки
  • THEN эти пути идентичны тем, что создаст применение (те же правила имён, спецвыпусков, мультифайла, запрещённых символов и коллизий)

Requirement: Клиентские взаимодействия без сборки

Веб-UI SHALL реализовывать клиентскую логику без шага сборки и без реактивных фреймворков: копирование infohash (vanilla JS) и раскрытие контекста нативным <details>. Все действия над загрузкой SHALL выполняться через формы/htmx (раундтрип на сервер), без клиентского пересчёта доменного состояния.

Scenario: Копирование infohash

  • WHEN пользователь нажимает кнопку копирования рядом с infohash
  • THEN значение infohash копируется в буфер обмена без перезагрузки страницы

Scenario: Раскрытие контекста без JS

  • WHEN пользователь раскрывает спойлер переданного контекста
  • THEN контекст показывается нативным <details>, без скриптов