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,56 @@
|
||||
# Наблюдаемость поверхности не выводится из терминальности задачи
|
||||
|
||||
- **Дата:** 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` опрашивает сервер, пока
|
||||
открыта вкладка: эти состояния живут долго и копятся (срока хранения нет).
|
||||
Reference in New Issue
Block a user