Files
jellybit/openspec/specs/live-status/spec.md
T
av a5d873b62d web-ui: карточка и страница обновляются, пока задачу может двигать фон
- условие самообновления — доменный предикат store.State.IsObservable() вместо
  фазы catched; один поллер на поверхность, интервалы 5 с и 15 с
- отказ тика отвечает 200 и самозавершающимся фрагментом с корневым id цели
  вместо 404/500, который htmx не свопит
- заведён ADR-2026-08-10-observability-is-not-terminality, переписан раздел
  «Живой поллинг» в конвенции веб-UI
2026-08-10 14:02:38 +03:00

154 lines
11 KiB
Markdown

# live-status Specification
## Purpose
Живая телеметрия раздач: единственный сэмплер — воркер, снимок держится в
памяти на тике поллинга и в БД не персистится. Состав телеметрии (прогресс,
скорость, ETA, размер; для сидирующих — рейтинг, сиды и пиры, отдано), узкий
контракт чтения транспортом и живое обновление интерфейса поллингом фрагментов.
Что именно рисуется на странице и в карточке — `web-ui`; откуда берутся сами
состояния загрузки — `download-tracking`.
## Requirements
### Requirement: Снимок живой телеметрии
Воркер — единственный сэмплер qBittorrent — SHALL на каждом тике поллинга
обновлять in-memory снимок телеметрии всех известных раздач. Снимок MUST NOT
персиститься в БД: он волатилен и переживает только до рестарта процесса.
#### Scenario: Обновление снимка на тике
- **WHEN** воркер завершает успешный тик поллинга qBittorrent
- **THEN** снимок телеметрии содержит актуальные данные по каждой раздаче,
присутствующей в ответе qBittorrent
#### Scenario: Снимок волатилен
- **WHEN** процесс только что перезапущен и первый тик поллинга ещё не прошёл
- **THEN** снимок пуст, а UI отображает задачи без живых значений, не падая
### Requirement: Состав телеметрии
Телеметрия одной раздачи SHALL включать прогресс (доля 0..1), скорость загрузки,
ETA и общий размер раздачи (total size); а для сидирующих раздач дополнительно —
рейтинг, число сидов и пиров, объём отданного и скорость отдачи. Общий размер
SHALL быть доступен для любой раздачи, присутствующей в снимке (не только
сидирующей). Значения SHALL извлекаться из ответа qBittorrent `/torrents/info`
без дополнительного сетевого вызова.
#### Scenario: Телеметрия качающейся задачи
- **WHEN** торрент задачи находится в состоянии загрузки
- **THEN** в снимке для неё доступны прогресс, скорость загрузки и ETA
#### Scenario: Общий размер доступен для любой раздачи в снимке
- **WHEN** торрент задачи присутствует в снимке в любом состоянии
- **THEN** в телеметрии для неё доступен общий размер раздачи
#### Scenario: Телеметрия сидирующей задачи
- **WHEN** торрент задачи завершён и раздаётся
- **THEN** в снимке для неё доступны рейтинг, число сидов/пиров, объём
отданного и скорость отдачи
### Requirement: Чтение телеметрии транспортом
Сервис SHALL предоставлять чтение снимка телеметрии по задаче через
изолированный контракт, не зависящий от способа доставки в браузер (поллинг
сейчас, SSE в будущем). Если для задачи нет записи в снимке (соответствующий
торрент отсутствовал в qBittorrent на последнем тике), чтение SHALL сообщать
об отсутствии данных, а UI MUST деградировать без живых значений, не падая.
#### Scenario: Данные есть
- **WHEN** транспорт читает телеметрию задачи, чей торрент был в последнем тике
- **THEN** он получает живые значения этой задачи
#### Scenario: Данных нет
- **WHEN** транспорт читает телеметрию задачи, торрента которой нет в qBittorrent
- **THEN** он получает признак отсутствия данных и рендерит страницу без живых
значений
### Requirement: Свежесть не выше тика поллинга
Живые значения, видимые в браузере, SHALL быть не свежее последнего тика
поллинга воркера; браузер MUST NOT опрашивать qBittorrent напрямую. Любой
запрос UI за телеметрией SHALL обслуживаться из in-memory снимка, не порождая
обращения к qBittorrent — поэтому частота обновления UI может быть выбрана
свободно (в т.ч. чаще тика для плавности), не нагружая qBittorrent.
#### Scenario: Браузер не обгоняет воркер
- **WHEN** браузер запрашивает фрагмент телеметрии чаще, чем длится тик
поллинга
- **THEN** он получает значения последнего тика, и обращения к qBittorrent при
этом не происходит
### Requirement: Живой прогресс активных загрузок
Веб-UI SHALL показывать прогресс, скорость и ETA активных (`downloading`)
загрузок на главной без перезагрузки страницы, обновляя их тем же
самообновлением, которым обновляется сама карточка (см. `web-ui`,
«Самообновление живой задачи»). Обновление MUST NOT сбрасывать клиентские
фильтр, поиск и прокрутку.
Когда задача покидает состояние `downloading`, показ скорости и ETA SHALL
прекращаться: вне качания эти величины смысла не имеют. Снимок при этом
продолжает питать прочие живые значения карточки и страницы — размер и рейтинг
раздачи, — и прекращение показа цифр качания MUST NOT означать прекращения
обновления поверхности: она продолжает отражать смену состояния, пока задача
наблюдаема.
Живые цифры MUST браться из снимка воркера; при отсутствии данных по задаче
поверхность деградирует без них, не ломая остального отображения.
#### Scenario: Прогресс растёт без перезагрузки
- **WHEN** загрузка качается и пользователь смотрит на главную
- **THEN** её прогресс-бар, скорость и ETA обновляются на месте без
перезагрузки страницы
#### Scenario: Клиентское состояние сохраняется
- **WHEN** применён фильтр или поиск и происходит фоновое обновление
- **THEN** выбранный фильтр, текст поиска и позиция прокрутки не сбрасываются
#### Scenario: Завершение убирает цифры качания, но не обновление
- **WHEN** загрузка переходит из `downloading` в другое наблюдаемое состояние
- **THEN** блок прогресса, скорости и ETA с карточки исчезает
- **AND** карточка продолжает обновляться и приносит новое состояние
- **AND** размер и рейтинг раздачи по-прежнему берутся из снимка
### Requirement: Секция раздачи на странице загрузки
Страница `/download/{id}` SHALL показывать секцию «Раздача» с живой статистикой
(рейтинг, число сидов и пиров, объём отданного, скорость отдачи) для задач,
чей торрент сидирует. Если живых данных по задаче нет, секция SHALL
отсутствовать либо явно показывать «нет данных», не ломая остальную страницу.
Секция MUST NOT опрашивать сервер самостоятельно: она лежит внутри области,
которую страница обновляет целиком, и собственный опрос секции подменял бы
разметку страницы. Её цифры SHALL приходить с тиком самообновления страницы
(см. `web-ui`, «Самообновление живой задачи»), а частота их обновления
SHALL совпадать с частотой обновления страницы.
#### Scenario: Сидирующая задача показывает раздачу
- **WHEN** открыта страница задачи, торрент которой раздаётся
- **THEN** в секции «Раздача» видны рейтинг, сиды/пиры, отдано и скорость отдачи
#### Scenario: Нет живых данных — секция деградирует
- **WHEN** открыта страница задачи, торрента которой нет в qBittorrent
- **THEN** секция «Раздача» отсутствует или показывает «нет данных», а
распознавание, файлы и история отображаются нормально
#### Scenario: Секция обновляется тиком страницы
- **GIVEN** открыта страница наблюдаемой задачи, чья раздача сидирует
- **WHEN** страница отрисована
- **THEN** секция «Раздача» не несёт собственного опроса
- **AND** её цифры обновляются вместе с остальной страницей