- условие самообновления — доменный предикат store.State.IsObservable() вместо фазы catched; один поллер на поверхность, интервалы 5 с и 15 с - отказ тика отвечает 200 и самозавершающимся фрагментом с корневым id цели вместо 404/500, который htmx не свопит - заведён ADR-2026-08-10-observability-is-not-terminality, переписан раздел «Живой поллинг» в конвенции веб-UI
4.5 KiB
Наблюдаемость поверхности не выводится из терминальности задачи
- Дата: 2026-08-10
- Источник: openspec/changes/archive/2026-08-10-card-live-refresh/design.md
Решение
Веб-UI обновляет себя, пока задача наблюдаема — то есть её состояние ещё
может измениться без участия человека, — а не пока она нетерминальна.
Предикат store.State.IsObservable() живёт в домене рядом с IsTerminal() и
даёт: все нетерминальные плюс failed, target_missing, orphaned. Замолкают
done, cancelled, reverted, deleted.
Почему
Очевидный предикат — «обновляемся, пока задача не терминальна» — оказался неверным, и это выяснилось на ревью дизайна, до кода. Цитата из источника:
Терминальность в проекте значит «не активна», а не «навсегда»: фоновая сверка двигает часть терминальных сама —
ListRecoverableвозвращает в потокfailed/stuckс кодамиmagnet_timeoutиstalled, аdesyncStatesпереоцениваетdone,target_missingиorphaned. Карточка, застывшая поIsTerminal, показывала бы «Ошибка» у задачи, которая уже качается, — ровно тот дефект, ради которого затеян change.
done в перечень наблюдаемых не вошёл, и это отдельное решение с ценой:
Переход
done → target_missing/orphanedозначает, что файлы удалили руками мимо сервиса, — событие редкое, а карточекdoneв списке больше всех. Платить за редкий случай постоянным фоновым запросом на каждую разложенную задачу дороже, чем показать её новое состояние при следующем заходе.
Рассмотренные варианты
- Наблюдать только нетерминальные (как задумывалось изначально) — проще
всего и не заводит второго предиката. Отвергнут: задача, оживлённая сверкой из
failed, висела бы на экране с надписью «Ошибка» до перезагрузки, причём соседние карточки при этом обновлялись бы — застывшая читалась бы как достоверная. - Наблюдать всё, терминальные — редким тиком — снимает вопрос целиком. Отвергнут: список из сотни разложенных задач слал бы пустые запросы вечно, а критерий приёмки «завершённая карточка себя не опрашивает» пришлось бы отменить.
Последствия
+смена состояния становится видимой независимо от того, кто её сделал: воркер, веб-UI, Telegram или фоновая сверка.+условие обновления выражено одним доменным предикатом; второго перечня состояний в транспорте нет, и завести его нельзя не заметив.−в домене стало два перечня состояний вместо одного, и второй выведен из поведения воркера (desyncStates,ListRecoverable) вручную. Расширение сверки новым состоянием молча вернёт застывшую карточку — связки, которая бы это ловила, нет.−карточкаfailed,target_missingилиorphanedопрашивает сервер, пока открыта вкладка: эти состояния живут долго и копятся (срока хранения нет).