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

8.4 KiB
Raw Blame History

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.goSelfPoll истинен для наблюдаемых состояний и ложен для остальных)
  • К5 Правило записано в дельта-спеках обеих затронутых capability (оракул: openspec validate --strict и шаг канона в task gate)

Приёмочные критерии из рубрики (ревью дизайна)

  • Р-1 Стоп-условие совпадает с «дальше само ничего не изменится»: для каждого состояния, где опрос прекращается, названо, что его не двигает ни воркер, ни сверка
  • Р-2 Финальный тик доставляет новое содержимое до остановки: остановка — свойство уже отданного фрагмента, а не отдельное решение
  • Р-3 Ровно один поллер на обновляемый корень; корень фрагмента-ответа несёт тот же id, что и цель свопа
  • Р-4 Все поля одного ответа посчитаны из одного чтения: бейдж, действия, цифры и интервал не расходятся между собой
  • Р-5 Отказ тика определён: что видит человек, продолжается ли опрос, на каком уровне пишется лог
  • Р-6 Стоимость тика посчитана: что делает один тик и сколько запросов даёт открытая страница и список из N карточек