UI/UX списка и карточки загрузки: серверные фильтр/поиск/пагинация, матч-ссылка, имя раздачи (web-ui-list-detail)
- Список: серверные фильтр по группе состояний, поиск и пагинация (GET
f/q/page/all, по 25), сортировка по времени добавления в qBittorrent
(added_on) с фолбеком на created_at и tie-break по id.
- Заголовок загрузки = имя раздачи (display_name) → распознанное название →
усечённый источник; сырой magnet вынесен в блок «Информация о торренте».
- Матч метабазы показан ссылкой на запись (страница загрузки и ревью);
URL берётся у выбранного кандидата либо строится по provider+id и типу.
- Полировка вёрстки; клиентская JS-фильтрация убрана (всё серверное, без JS).
- Миграция 0005 (display_name, source_added_at); воркер однократно
фиксирует source_added_at при поллинге/усыновлении; ER-схема обновлена.
- OpenSpec: дельты влиты в specs/{web-ui,ingest}, change заархивирован.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Отображаемое имя торрента из контекста
|
||||
|
||||
При добавлении загрузки в qBittorrent система SHALL выводить из контекста
|
||||
загрузки человекочитаемое отображаемое имя и передавать его в qBittorrent
|
||||
(параметр `rename` API `/torrents/add`), чтобы задача в списке qBit не
|
||||
показывалась безликим `dn` magnet-ссылки. Это же имя система SHALL **сохранять
|
||||
у загрузки** (`download.display_name`) для последующего показа заголовком в
|
||||
веб-UI.
|
||||
|
||||
Имя SHALL быть коротким читаемым ярлыком (название, опционально режиссёр и
|
||||
год; для сериала — номер сезона, если он определён), а не куском сырого
|
||||
контекста. Имя SHALL очищаться от управляющих символов и переводов строк и
|
||||
SHALL обрезаться по ограничению длины.
|
||||
|
||||
Вывод имени SHALL выполняться синхронно перед отдачей источника в
|
||||
qBittorrent (параметр `rename` действует только в момент добавления).
|
||||
|
||||
Отображаемое имя SHALL влиять только на отображение (в qBittorrent и как
|
||||
заголовок в веб-UI) и SHALL NOT влиять на пути файлов на диске, распознавание
|
||||
или раскладку — реальные пути система по-прежнему читает из qBit API.
|
||||
|
||||
#### Scenario: Имя из контекста передаётся в qBittorrent
|
||||
|
||||
- **WHEN** загрузку добавляют с непустым контекстом, из которого удалось
|
||||
получить имя
|
||||
- **THEN** система передаёт это имя в qBittorrent в параметре `rename`
|
||||
- **AND** имя — короткий читаемый ярлык вида «название (режиссёр, год)»,
|
||||
где режиссёр и год опциональны
|
||||
|
||||
#### Scenario: Имя сохраняется у загрузки
|
||||
|
||||
- **WHEN** при приёме получено непустое отображаемое имя
|
||||
- **THEN** система сохраняет его в `download.display_name` вместе с созданием
|
||||
загрузки
|
||||
- **AND** веб-UI использует его заголовком карточки и страницы загрузки
|
||||
|
||||
#### Scenario: Контекст пуст или имя не получено
|
||||
|
||||
- **WHEN** контекста нет либо ни один способ вывода не дал непустого имени
|
||||
- **THEN** система добавляет загрузку без параметра `rename`
|
||||
- **AND** qBittorrent оставляет собственное имя (из `dn`/торрента)
|
||||
- **AND** `download.display_name` остаётся пустым, а веб-UI берёт заголовок из
|
||||
фолбека (распознанное название или усечённый источник)
|
||||
@@ -0,0 +1,125 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Страницы веб-UI
|
||||
|
||||
Веб-UI SHALL предоставлять страницы: список загрузок с единым окном
|
||||
добавления, **серверными фильтром по группе состояний, поиском и постраничной
|
||||
выдачей (пагинацией)** (`/`), экран ревью одной загрузки (`/review/{id}`)
|
||||
и страницу просмотра одной загрузки (`/download/{id}`) с распознаванием,
|
||||
файлами→раскладкой, историей, блоком информации о торренте и — для сидирующих
|
||||
задач — секцией живой статистики раздачи. Карточки активных (downloading)
|
||||
загрузок в списке SHALL содержать индикатор прогресса. Фильтр, поиск и номер
|
||||
страницы SHALL передаваться GET-параметрами запроса (например `f`, `q`, `page`)
|
||||
и SHALL работать без клиентского JavaScript. Состояние `deleted` SHALL быть
|
||||
скрыто в списке по умолчанию (с переключателем «показать всё»). Механика живого
|
||||
обновления прогресса и наполнение секции раздачи определяются capability
|
||||
`live-status`.
|
||||
|
||||
#### Scenario: Просмотр одной загрузки
|
||||
|
||||
- **WHEN** клиент открывает `GET /download/{id}` существующей загрузки
|
||||
- **THEN** отрисовывается страница с её распознаванием, файлами, раскладкой и
|
||||
историей
|
||||
|
||||
#### Scenario: Прогресс активной загрузки в списке
|
||||
|
||||
- **WHEN** в списке есть загрузка в состоянии `downloading`
|
||||
- **THEN** её карточка содержит индикатор прогресса (прогресс-бар со скоростью
|
||||
и ETA)
|
||||
|
||||
#### Scenario: Удалённые скрыты по умолчанию
|
||||
|
||||
- **WHEN** в списке есть загрузки в состоянии `deleted` и фильтр «показать
|
||||
всё» не включён
|
||||
- **THEN** они не отображаются, но доступны при включённом переключателе
|
||||
|
||||
#### Scenario: Пагинация списка
|
||||
|
||||
- **WHEN** загрузок под текущим фильтром больше, чем помещается на одну
|
||||
страницу, и клиент запрашивает `GET /?page=N`
|
||||
- **THEN** возвращается N-я страница результатов и элементы навигации по
|
||||
страницам, сохраняющие текущие фильтр и поисковый запрос
|
||||
|
||||
#### Scenario: Серверный фильтр и поиск
|
||||
|
||||
- **WHEN** клиент запрашивает список с параметрами фильтра по состоянию и/или
|
||||
строкой поиска (`GET /?f=review&q=дюна`)
|
||||
- **THEN** сервер возвращает только подходящие загрузки (по группе состояний и
|
||||
совпадению строки в названии/infohash/контексте), отфильтрованные на стороне
|
||||
БД, а не на клиенте
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Порядок списка загрузок
|
||||
|
||||
Список загрузок SHALL сортироваться по времени добавления торрента в источник
|
||||
(qBittorrent `added_on`), от новых к старым. Если время добавления в источник
|
||||
неизвестно, для сортировки SHALL использоваться время создания загрузки в
|
||||
jellybit (`created_at`). Порядок MUST быть согласован между страницами
|
||||
пагинации.
|
||||
|
||||
#### Scenario: Сортировка по времени добавления в источник
|
||||
|
||||
- **WHEN** отрисовывается список загрузок, у которых известно время добавления
|
||||
в qBittorrent
|
||||
- **THEN** загрузки идут от недавно добавленных к более старым по этому времени
|
||||
|
||||
#### Scenario: Фолбек порядка без времени источника
|
||||
|
||||
- **WHEN** у загрузки нет сохранённого времени добавления в источник
|
||||
- **THEN** для её позиции в списке используется время создания загрузки в
|
||||
jellybit
|
||||
|
||||
### Requirement: Заголовок загрузки из имени раздачи
|
||||
|
||||
Веб-UI SHALL показывать заголовком загрузки (в карточке списка и в шапке
|
||||
страницы `/download/{id}`) сохранённое отображаемое имя раздачи (`display_name`,
|
||||
переданное в qBittorrent при приёме). Если имя пусто, заголовок SHALL брать
|
||||
распознанное название из плана; если и его нет — сырой источник (`source_ref`),
|
||||
усечённый до одной строки как обычный заголовок. Заголовок MUST NOT занимать
|
||||
несколько строк сырым magnet.
|
||||
|
||||
#### Scenario: Заголовок из имени раздачи
|
||||
|
||||
- **WHEN** у загрузки сохранено отображаемое имя раздачи
|
||||
- **THEN** карточка и страница показывают это имя заголовком
|
||||
|
||||
#### Scenario: Фолбек до распознавания и без имени
|
||||
|
||||
- **WHEN** отображаемого имени нет, но есть распознанное название
|
||||
- **THEN** заголовком служит распознанное название
|
||||
- **AND** если нет ни того, ни другого — заголовком служит усечённый до одной
|
||||
строки сырой источник, а не многострочный magnet
|
||||
|
||||
### Requirement: Матч с записью метабазы ссылкой
|
||||
|
||||
Веб-UI SHALL показывать подтверждённый матч с записью метабазы (TMDB/TVDB/IMDb)
|
||||
как ссылку на эту запись — на странице просмотра `/download/{id}` (блок
|
||||
распознавания) и в блоке «Источник совпадения» экрана ревью. Ссылка SHALL
|
||||
открываться в новой вкладке с `rel="noopener"`. Рядом со ссылкой SHALL быть
|
||||
видны провайдер, идентификатор записи и (при наличии) год.
|
||||
|
||||
#### Scenario: Матч виден ссылкой на странице просмотра
|
||||
|
||||
- **WHEN** у загрузки подтверждён матч с записью метабазы и известен URL записи
|
||||
- **THEN** в блоке распознавания на `/download/{id}` матч показан ссылкой на
|
||||
запись с провайдером и id
|
||||
|
||||
#### Scenario: URL записи неизвестен
|
||||
|
||||
- **WHEN** матч подтверждён (например, id задан вручную), но канонический URL
|
||||
записи построить нельзя
|
||||
- **THEN** матч показывается текстом (провайдер и id) без ссылки, страница не
|
||||
ломается
|
||||
|
||||
### Requirement: Блок информации о торренте
|
||||
|
||||
Страница `/download/{id}` SHALL содержать блок «Информация о торренте» с полным
|
||||
исходным источником (magnet/URL/`.torrent`-ссылка) и infohash с кнопкой
|
||||
копирования. Сырой источник MUST выноситься в этот блок, а не в заголовок.
|
||||
|
||||
#### Scenario: Полный источник в отдельном блоке
|
||||
|
||||
- **WHEN** клиент открывает `GET /download/{id}`
|
||||
- **THEN** полный источник (magnet) и infohash показаны в блоке «Информация о
|
||||
торренте», а заголовок страницы остаётся человекочитаемым именем
|
||||
Reference in New Issue
Block a user