web-ui: карточка и страница обновляются, пока задачу может двигать фон
- условие самообновления — доменный предикат store.State.IsObservable() вместо фазы catched; один поллер на поверхность, интервалы 5 с и 15 с - отказ тика отвечает 200 и самозавершающимся фрагментом с корневым id цели вместо 404/500, который htmx не свопит - заведён ADR-2026-08-10-observability-is-not-terminality, переписан раздел «Живой поллинг» в конвенции веб-UI
This commit is contained in:
@@ -86,10 +86,21 @@ SHALL быть доступен для любой раздачи, присутс
|
||||
|
||||
### Requirement: Живой прогресс активных загрузок
|
||||
|
||||
Веб-UI SHALL обновлять прогресс активных (downloading) загрузок на главной без
|
||||
перезагрузки страницы — поллингом фрагмента через htmx. Обновление MUST NOT
|
||||
сбрасывать клиентские фильтр, поиск и прокрутку. Когда задача покидает
|
||||
состояние downloading, поллинг её прогресса SHALL прекращаться.
|
||||
Веб-UI SHALL показывать прогресс, скорость и ETA активных (`downloading`)
|
||||
загрузок на главной без перезагрузки страницы, обновляя их тем же
|
||||
самообновлением, которым обновляется сама карточка (см. `web-ui`,
|
||||
«Самообновление живой задачи»). Обновление MUST NOT сбрасывать клиентские
|
||||
фильтр, поиск и прокрутку.
|
||||
|
||||
Когда задача покидает состояние `downloading`, показ скорости и ETA SHALL
|
||||
прекращаться: вне качания эти величины смысла не имеют. Снимок при этом
|
||||
продолжает питать прочие живые значения карточки и страницы — размер и рейтинг
|
||||
раздачи, — и прекращение показа цифр качания MUST NOT означать прекращения
|
||||
обновления поверхности: она продолжает отражать смену состояния, пока задача
|
||||
наблюдаема.
|
||||
|
||||
Живые цифры MUST браться из снимка воркера; при отсутствии данных по задаче
|
||||
поверхность деградирует без них, не ломая остального отображения.
|
||||
|
||||
#### Scenario: Прогресс растёт без перезагрузки
|
||||
|
||||
@@ -99,13 +110,15 @@ SHALL быть доступен для любой раздачи, присутс
|
||||
|
||||
#### Scenario: Клиентское состояние сохраняется
|
||||
|
||||
- **WHEN** применён фильтр или поиск и происходит фоновое обновление прогресса
|
||||
- **WHEN** применён фильтр или поиск и происходит фоновое обновление
|
||||
- **THEN** выбранный фильтр, текст поиска и позиция прокрутки не сбрасываются
|
||||
|
||||
#### Scenario: Завершение останавливает поллинг
|
||||
#### Scenario: Завершение убирает цифры качания, но не обновление
|
||||
|
||||
- **WHEN** загрузка переходит из downloading в другое состояние
|
||||
- **THEN** фоновый поллинг прогресса для этой карточки прекращается
|
||||
- **WHEN** загрузка переходит из `downloading` в другое наблюдаемое состояние
|
||||
- **THEN** блок прогресса, скорости и ETA с карточки исчезает
|
||||
- **AND** карточка продолжает обновляться и приносит новое состояние
|
||||
- **AND** размер и рейтинг раздачи по-прежнему берутся из снимка
|
||||
|
||||
### Requirement: Секция раздачи на странице загрузки
|
||||
|
||||
@@ -114,6 +127,12 @@ SHALL быть доступен для любой раздачи, присутс
|
||||
чей торрент сидирует. Если живых данных по задаче нет, секция SHALL
|
||||
отсутствовать либо явно показывать «нет данных», не ломая остальную страницу.
|
||||
|
||||
Секция MUST NOT опрашивать сервер самостоятельно: она лежит внутри области,
|
||||
которую страница обновляет целиком, и собственный опрос секции подменял бы
|
||||
разметку страницы. Её цифры SHALL приходить с тиком самообновления страницы
|
||||
(см. `web-ui`, «Самообновление живой задачи»), а частота их обновления
|
||||
SHALL совпадать с частотой обновления страницы.
|
||||
|
||||
#### Scenario: Сидирующая задача показывает раздачу
|
||||
|
||||
- **WHEN** открыта страница задачи, торрент которой раздаётся
|
||||
@@ -125,3 +144,10 @@ SHALL быть доступен для любой раздачи, присутс
|
||||
- **THEN** секция «Раздача» отсутствует или показывает «нет данных», а
|
||||
распознавание, файлы и история отображаются нормально
|
||||
|
||||
#### Scenario: Секция обновляется тиком страницы
|
||||
|
||||
- **GIVEN** открыта страница наблюдаемой задачи, чья раздача сидирует
|
||||
- **WHEN** страница отрисована
|
||||
- **THEN** секция «Раздача» не несёт собственного опроса
|
||||
- **AND** её цифры обновляются вместе с остальной страницей
|
||||
|
||||
|
||||
@@ -339,6 +339,10 @@ MUST NOT показываться в карточке списка — он до
|
||||
отсутствии торрента в снимке — из суммарного размера разложенных файлов загрузки;
|
||||
если неизвестно ни то, ни другое — прочерк «—».
|
||||
|
||||
Карточка, пришедшая **самообновлением**, SHALL показывать те же значения, что и
|
||||
карточка в полном рендере списка: фоновое обновление MUST NOT подменять
|
||||
известное значение прочерком.
|
||||
|
||||
#### Scenario: Метка идентификатора
|
||||
|
||||
- **WHEN** рендерится карточка загрузки в списке
|
||||
@@ -365,6 +369,12 @@ MUST NOT показываться в карточке списка — он до
|
||||
- **AND** если торрента в снимке нет, но у загрузки есть разложенные файлы —
|
||||
размер берётся из суммарного размера этих файлов
|
||||
|
||||
#### Scenario: Самообновление не теряет размер
|
||||
|
||||
- **GIVEN** торрента нет в живом снимке, а файлы задачи разложены
|
||||
- **WHEN** карточка пришла самообновлением, а не полным рендером списка
|
||||
- **THEN** размер показан по тому же фолбэку, а не прочерком «—»
|
||||
|
||||
#### Scenario: Контекст не в карточке
|
||||
|
||||
- **WHEN** у загрузки есть переданный контекст
|
||||
@@ -470,6 +480,99 @@ PRG-редиректом, и действие исполняется тем же
|
||||
- **THEN** карточка подменяется на месте новым состоянием и остаётся видимой до
|
||||
следующей полной загрузки списка, без клиентского переупорядочивания
|
||||
|
||||
### Requirement: Самообновление живой задачи
|
||||
|
||||
Карточка списка и страница `/download/{id}` SHALL самообновляться, пока задача
|
||||
**наблюдаема**, и SHALL прекращать самообновление, как только она наблюдаемой
|
||||
быть перестала. Наблюдаемы все нетерминальные задачи, а из терминальных — те,
|
||||
которые фоновая сверка возвращает в поток сама: `failed`, `target_missing`,
|
||||
`orphaned`. Задача, которую с места двигает только человек (`done`, `cancelled`,
|
||||
`reverted`, `deleted`), наблюдаемой не является. Признак SHALL жить в домене
|
||||
рядом с признаком терминальности; второго перечня состояний веб-UI MUST NOT
|
||||
заводить.
|
||||
|
||||
Самообновление SHALL приносить смену состояния целиком — бейдж статуса,
|
||||
заголовок, набор доступных действий и живые цифры, если они есть, — и MUST NOT
|
||||
сбрасывать клиентские фильтр, поиск и прокрутку. Смена, произошедшая без участия
|
||||
этого браузера (переход воркера, действие из Telegram, фоновая сверка), MUST
|
||||
становиться видимой тем же способом, пока задача наблюдаема: интерфейс не знает,
|
||||
кто изменил состояние.
|
||||
|
||||
У одной поверхности SHALL быть **ровно один** источник самообновления. Вложенные
|
||||
живые регионы (прогресс качания в карточке, секция раздачи на странице) MUST NOT
|
||||
опрашивать сервер самостоятельно: своп корня уносит вложенный узел вместе с его
|
||||
поллером, поэтому два опроса на одну поверхность подменяют разметку друг друга и
|
||||
опрашивают одно и то же дважды.
|
||||
|
||||
Интервал самообновления SHALL зависеть от того, несёт ли поверхность блок живых
|
||||
цифр качания: у поверхности с таким блоком интервал SHALL быть **строго меньше**,
|
||||
чем у поверхности без него. Числовые значения интервалов живут в документации
|
||||
проекта, не в спеке.
|
||||
|
||||
Тик самообновления, не сумевший прочитать задачу (записи нет, хранилище
|
||||
отказало), SHALL отвечать успехом и фрагментом, который объясняет положение дел
|
||||
и **не несёт** самообновления: неуспешный ответ не заменяет разметку, поэтому
|
||||
поверхность осталась бы прежней, а опрос продолжался бы бесконечно.
|
||||
|
||||
#### Scenario: Завершение качания видно без перезагрузки
|
||||
|
||||
- **GIVEN** открыт список загрузок и в нём есть задача в `downloading`
|
||||
- **WHEN** qBittorrent довёл раздачу до конца и воркер увёл задачу в
|
||||
`recognizing` и дальше в `review`
|
||||
- **THEN** карточка без перезагрузки страницы показывает бейдж ревью и кнопку
|
||||
«Ревью →»
|
||||
- **AND** блок живого прогресса с неё исчезает
|
||||
|
||||
#### Scenario: Переход, сделанный не из этого браузера
|
||||
|
||||
- **GIVEN** открыт список загрузок и в нём есть задача в `review`
|
||||
- **WHEN** человек подтвердил план из Telegram и задача прошла `linking` в `done`
|
||||
- **THEN** карточка без перезагрузки страницы показывает бейдж `done` и действия
|
||||
терминальной задачи
|
||||
|
||||
#### Scenario: Ненаблюдаемая задача не опрашивается
|
||||
|
||||
- **WHEN** задача находится в `done`, `cancelled`, `reverted` или `deleted`
|
||||
- **THEN** её карточка и страница `/download/{id}` не несут самообновления, и
|
||||
фоновых запросов по ним не уходит
|
||||
|
||||
#### Scenario: Задача, оживлённая сверкой, видна без перезагрузки
|
||||
|
||||
- **GIVEN** открыт список, и в нём есть задача в `failed` (магнет не добрал
|
||||
метаданные за отведённое время)
|
||||
- **WHEN** источник ожил и фоновая сверка вернула задачу в `downloading`
|
||||
- **THEN** карточка без перезагрузки страницы показывает состояние качания
|
||||
|
||||
#### Scenario: Один источник обновления на поверхность
|
||||
|
||||
- **WHEN** отрисована карточка задачи в `downloading` или страница задачи, чья
|
||||
раздача сидирует
|
||||
- **THEN** самообновление объявлено ровно в одном месте поверхности, а вложенные
|
||||
живые регионы своего опроса не ведут
|
||||
|
||||
#### Scenario: Быстрее обновляется то, где есть живые цифры
|
||||
|
||||
- **WHEN** рядом отрисованы карточка задачи в `downloading` и карточка задачи в
|
||||
`review`
|
||||
- **THEN** объявленный интервал самообновления первой строго меньше, чем у второй
|
||||
|
||||
#### Scenario: Тик, который не смог прочитать задачу
|
||||
|
||||
- **GIVEN** открыта карточка наблюдаемой задачи
|
||||
- **WHEN** очередной тик самообновления не нашёл записи или получил отказ
|
||||
хранилища
|
||||
- **THEN** ответ успешен и несёт фрагмент с объяснением
|
||||
- **AND** фрагмент не несёт самообновления, поэтому опрос прекращается
|
||||
|
||||
#### Scenario: Группа и фильтр списка пересчитываются навигацией
|
||||
|
||||
- **GIVEN** открыт список и в нём есть задача в `downloading`
|
||||
- **WHEN** задача дошла до терминального состояния на глазах у смотрящего
|
||||
- **THEN** карточка показывает новое состояние и остаётся на своём месте в
|
||||
прежней группе списка
|
||||
- **AND** группа и фильтр пересчитываются при следующей навигации или
|
||||
перезагрузке — список целиком самообновлением не пересобирается
|
||||
|
||||
### Requirement: Отображение промежуточного состояния catched
|
||||
|
||||
Веб-UI SHALL отображать состояние `catched` как штатную промежуточную фазу
|
||||
@@ -483,11 +586,12 @@ PRG-редиректом, и действие исполняется тем же
|
||||
имени раздачи». Секция раздачи/живого прогресса для `catched` SHALL корректно
|
||||
отсутствовать (раздачи в qBittorrent ещё нет), не создавая ошибок отображения.
|
||||
|
||||
Карточка/страница загрузки в `catched` SHALL самообновляться самозавершающимся
|
||||
htmx-поллингом (см. конвенцию веб-UI): по переходе загрузки в `downloading`
|
||||
интерфейс SHALL отражать это без перезагрузки страницы (подхватить бейдж,
|
||||
выведенное имя и появившийся живой прогресс), а поллинг фазы `catched` SHALL
|
||||
завершаться, как только загрузка её покинула.
|
||||
Самообновление карточки и страницы в `catched` — частный случай требования
|
||||
«Самообновление живой задачи»: `catched` нетерминален, поэтому интерфейс
|
||||
подхватывает переход в `downloading` (бейдж, выведенное имя, появившийся живой
|
||||
прогресс) без перезагрузки страницы. Отдельного правила самообновления для этой
|
||||
фазы веб-UI MUST NOT иметь: фаза перестала быть единственной, где интерфейс
|
||||
обновляется сам.
|
||||
|
||||
#### Scenario: Бейдж и группа для catched
|
||||
|
||||
@@ -509,7 +613,7 @@ htmx-поллингом (см. конвенцию веб-UI): по перехо
|
||||
- **WHEN** worker перевёл загрузку в `downloading`
|
||||
- **THEN** интерфейс без перезагрузки показывает состояние `downloading`
|
||||
(бейдж, имя, живой прогресс)
|
||||
- **AND** поллинг фазы `catched` завершается
|
||||
- **AND** самообновление продолжается, потому что задача осталась наблюдаемой
|
||||
|
||||
### Requirement: Загрузка .torrent-файла на форме добавления
|
||||
|
||||
|
||||
Reference in New Issue
Block a user