- условие самообновления — доменный предикат store.State.IsObservable() вместо фазы catched; один поллер на поверхность, интервалы 5 с и 15 с - отказ тика отвечает 200 и самозавершающимся фрагментом с корневым id цели вместо 404/500, который htmx не свопит - заведён ADR-2026-08-10-observability-is-not-terminality, переписан раздел «Живой поллинг» в конвенции веб-UI
8.4 KiB
8.4 KiB
1. Условие и частота самообновления
- 1.1 Завести в домене (
internal/store, рядом сIsTerminal) предикат наблюдаемости: нетерминальные плюсfailed,target_missing,orphaned;done,cancelled,reverted,deleted— не наблюдаемы. Перечень состояний вhttpapiне заводить - 1.2 Завести рядом с представлениями две константы интервала — быстрый 5 с
(вровень с
[worker].poll_interval, для поверхности с блоком живых цифр качания) и медленный 15 с — и поле вида, которое отдаёт шаблону выбранный интервал - 1.3
toView(internal/httpapi/httpapi.go):SelfPoll— по предикату наблюдаемости, интервал — быстрый только приIsDownloading - 1.4
buildDownloadView(internal/httpapi/download.go): тот же предикат, интервал всегда медленный — блока прогресса на странице нет
2. Шаблоны
- 2.1
card.html: интервал самополлинга берётся из вида, а не зашит в разметку - 2.2
progress.html: снять собственныйhx-get/hx-trigger— партиал остаётся вложенной разметкой карточки без своего опроса - 2.3
download_main.html: интервал самополлинга берётся из вида - 2.4
seeding.html: снять собственныйhx-get/hx-trigger— секция лежит внутри свопаемого#download-mainи едет с тиком страницы
3. Самообновление не теряет полей полного рендера
- 3.1
handleFragCardчитает размер раскладки черезLayoutSizeByDownload, как это делаетrenderCardFragment, вместо жёсткогоlayoutSize = 0 - 3.2 Отказ тика (
fragErrи путь «записи нет») отвечает200и фрагментом безhx-*: объяснение вместо молчаливого застывания и вечного опроса
4. Маршруты фрагментов
- 4.1
GET /fragments/downloads/{id}/progressи.../seedingоставить живыми: их партиалы теперь без поллера, поэтому ответ гасит разметку вкладок, открытых до деплоя. В комментарии назвать, что потребителей в новой разметке нет и обработчики убираются отдельной уборкой
5. Тесты
- 5.1 Карточка наблюдаемой задачи несёт самополлинг на
/fragments/downloads/{id}/card(включаяfailed,target_missing,orphaned); карточкаdone,cancelled,reverted,deleted— не несёт ниhx-get, ниhx-trigger - 5.2 Фрагмент карточки после смены состояния отдаёт новый бейдж и новый
набор действий: для
review— кнопку «Ревью →», дляdone— «Откатить» - 5.3 Карточка в
downloadingсодержит ровно одно объявление самополлинга, а вложенный блок прогресса — ни одного; страница сидирующей задачи — ровно одно, а секция «Раздача» — ни одного - 5.4 Интервал в разметке: быстрый у карточки в
downloading, медленный у прочих наблюдаемых карточек и у страницы в любом состоянии - 5.5
buildDownloadView:SelfPollистинен для наблюдаемых состояний и ложен для остальных - 5.6 Фрагмент карточки задачи без раздачи в снимке, но с разложенными файлами показывает размер, а не «—»
- 5.7 Тик по несуществующей задаче отвечает
200фрагментом безhx-*
6. Документация и приёмка
- 6.1
docs/database.md: обе константы интервала в таблицу настроек с числовым значением - 6.2 Поведенческая проверка на живом стенде: открыть список, довести раздачу до конца и убедиться, что карточка сама показала переход
- 6.3
openspec validate --strict card-live-refreshиtask gateзелёные
Критерии приёмки (из записи задачи)
- К1 Карточка нетерминальной загрузки самополлится, и первый ответ фрагмента
после смены состояния несёт новый бейдж (оракул: тест
internal/httpapi— рендер карточки вdownloadingсодержитhx-getна/fragments/downloads/{id}/card, а подставной читатель, сменивший состояние наreview, отдаёт карточку с бейджем ревью) - К2 Терминальная карточка себя не опрашивает: фоновых запросов после
done,cancelled,revertedиdeletedнет (оракул: тот же тест — в разметке такой карточки нет ниhx-get, ниhx-trigger). Уточнён на чекпоинте:failed,target_missingиorphanedиз перечня выведены — их возвращает в поток фоновая сверка, поэтому они наблюдаются медленным интервалом - К3 Появившееся действие видно без перезагрузки: карточка задачи,
перешедшей в
review, несёт кнопку «Ревью →» (оракул: тест фрагмента карточки) - К4 Страница
/download/{id}обновляет бейдж и блок действий по тому же правилу, что и карточка (оракул: тестinternal/httpapi/download.go—SelfPollистинен для наблюдаемых состояний и ложен для остальных) - К5 Правило записано в дельта-спеках обеих затронутых capability (оракул:
openspec validate --strictи шаг канона вtask gate)
Приёмочные критерии из рубрики (ревью дизайна)
- Р-1 Стоп-условие совпадает с «дальше само ничего не изменится»: для каждого состояния, где опрос прекращается, названо, что его не двигает ни воркер, ни сверка
- Р-2 Финальный тик доставляет новое содержимое до остановки: остановка — свойство уже отданного фрагмента, а не отдельное решение
- Р-3 Ровно один поллер на обновляемый корень; корень фрагмента-ответа несёт
тот же
id, что и цель свопа - Р-4 Все поля одного ответа посчитаны из одного чтения: бейдж, действия, цифры и интервал не расходятся между собой
- Р-5 Отказ тика определён: что видит человек, продолжается ли опрос, на каком уровне пишется лог
- Р-6 Стоимость тика посчитана: что делает один тик и сколько запросов даёт открытая страница и список из N карточек