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:
@@ -0,0 +1,101 @@
|
||||
## 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 карточек
|
||||
Reference in New Issue
Block a user