Files
jellybit/openspec/changes/archive/2026-08-10-card-live-refresh/tasks.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

102 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. Условие и частота самообновления
- [x] 1.1 Завести в домене (`internal/store`, рядом с `IsTerminal`) предикат
наблюдаемости: нетерминальные плюс `failed`, `target_missing`, `orphaned`;
`done`, `cancelled`, `reverted`, `deleted` — не наблюдаемы. Перечень состояний
в `httpapi` не заводить
- [x] 1.2 Завести рядом с представлениями две константы интервала — быстрый 5 с
(вровень с `[worker].poll_interval`, для поверхности с блоком живых цифр
качания) и медленный 15 с — и поле вида, которое отдаёт шаблону выбранный
интервал
- [x] 1.3 `toView` (`internal/httpapi/httpapi.go`): `SelfPoll` — по предикату
наблюдаемости, интервал — быстрый только при `IsDownloading`
- [x] 1.4 `buildDownloadView` (`internal/httpapi/download.go`): тот же предикат,
интервал всегда медленный — блока прогресса на странице нет
## 2. Шаблоны
- [x] 2.1 `card.html`: интервал самополлинга берётся из вида, а не зашит в
разметку
- [x] 2.2 `progress.html`: снять собственный `hx-get`/`hx-trigger` — партиал
остаётся вложенной разметкой карточки без своего опроса
- [x] 2.3 `download_main.html`: интервал самополлинга берётся из вида
- [x] 2.4 `seeding.html`: снять собственный `hx-get`/`hx-trigger` — секция лежит
внутри свопаемого `#download-main` и едет с тиком страницы
## 3. Самообновление не теряет полей полного рендера
- [x] 3.1 `handleFragCard` читает размер раскладки через `LayoutSizeByDownload`,
как это делает `renderCardFragment`, вместо жёсткого `layoutSize = 0`
- [x] 3.2 Отказ тика (`fragErr` и путь «записи нет») отвечает `200` и фрагментом
без `hx-*`: объяснение вместо молчаливого застывания и вечного опроса
## 4. Маршруты фрагментов
- [x] 4.1 `GET /fragments/downloads/{id}/progress` и `.../seeding` оставить
живыми: их партиалы теперь без поллера, поэтому ответ гасит разметку вкладок,
открытых до деплоя. В комментарии назвать, что потребителей в новой разметке
нет и обработчики убираются отдельной уборкой
## 5. Тесты
- [x] 5.1 Карточка наблюдаемой задачи несёт самополлинг на
`/fragments/downloads/{id}/card` (включая `failed`, `target_missing`,
`orphaned`); карточка `done`, `cancelled`, `reverted`, `deleted` — не несёт ни
`hx-get`, ни `hx-trigger`
- [x] 5.2 Фрагмент карточки после смены состояния отдаёт новый бейдж и новый
набор действий: для `review` — кнопку «Ревью →», для `done` — «Откатить»
- [x] 5.3 Карточка в `downloading` содержит ровно одно объявление самополлинга, а
вложенный блок прогресса — ни одного; страница сидирующей задачи — ровно одно,
а секция «Раздача» — ни одного
- [x] 5.4 Интервал в разметке: быстрый у карточки в `downloading`, медленный у
прочих наблюдаемых карточек и у страницы в любом состоянии
- [x] 5.5 `buildDownloadView`: `SelfPoll` истинен для наблюдаемых состояний и
ложен для остальных
- [x] 5.6 Фрагмент карточки задачи без раздачи в снимке, но с разложенными
файлами показывает размер, а не «—»
- [x] 5.7 Тик по несуществующей задаче отвечает `200` фрагментом без `hx-*`
## 6. Документация и приёмка
- [x] 6.1 `docs/database.md`: обе константы интервала в таблицу настроек с
числовым значением
- [x] 6.2 Поведенческая проверка на живом стенде: открыть список, довести
раздачу до конца и убедиться, что карточка сама показала переход
- [x] 6.3 `openspec validate --strict card-live-refresh` и `task gate` зелёные
## Критерии приёмки (из записи задачи)
- [x] К1 Карточка нетерминальной загрузки самополлится, и первый ответ фрагмента
после смены состояния несёт новый бейдж (оракул: тест `internal/httpapi`
рендер карточки в `downloading` содержит `hx-get` на
`/fragments/downloads/{id}/card`, а подставной читатель, сменивший состояние на
`review`, отдаёт карточку с бейджем ревью)
- [x] К2 Терминальная карточка себя не опрашивает: фоновых запросов после `done`,
`cancelled`, `reverted` и `deleted` нет (оракул: тот же тест — в разметке такой
карточки нет ни `hx-get`, ни `hx-trigger`). **Уточнён на чекпоинте:** `failed`,
`target_missing` и `orphaned` из перечня выведены — их возвращает в поток
фоновая сверка, поэтому они наблюдаются медленным интервалом
- [x] К3 Появившееся действие видно без перезагрузки: карточка задачи,
перешедшей в `review`, несёт кнопку «Ревью →» (оракул: тест фрагмента карточки)
- [x] К4 Страница `/download/{id}` обновляет бейдж и блок действий по тому же
правилу, что и карточка (оракул: тест `internal/httpapi/download.go`
`SelfPoll` истинен для наблюдаемых состояний и ложен для остальных)
- [x] К5 Правило записано в дельта-спеках обеих затронутых capability (оракул:
`openspec validate --strict` и шаг канона в `task gate`)
## Приёмочные критерии из рубрики (ревью дизайна)
- [x] Р-1 Стоп-условие совпадает с «дальше само ничего не изменится»: для каждого
состояния, где опрос прекращается, названо, что его не двигает ни воркер, ни
сверка
- [x] Р-2 Финальный тик доставляет новое содержимое до остановки: остановка —
свойство уже отданного фрагмента, а не отдельное решение
- [x] Р-3 Ровно один поллер на обновляемый корень; корень фрагмента-ответа несёт
тот же `id`, что и цель свопа
- [x] Р-4 Все поля одного ответа посчитаны из одного чтения: бейдж, действия,
цифры и интервал не расходятся между собой
- [x] Р-5 Отказ тика определён: что видит человек, продолжается ли опрос, на
каком уровне пишется лог
- [x] Р-6 Стоимость тика посчитана: что делает один тик и сколько запросов даёт
открытая страница и список из N карточек