Files
jellybit/openspec/changes/archive/2026-08-10-card-live-refresh/proposal.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

67 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Why
Загрузка докачалась, воркер увёл её в распознавание и дальше в ревью — а в
списке она по-прежнему «Загружается», без кнопки «Ревью →». Верное состояние
появляется только после того, как человек сам перезагрузит страницу.
Живое обновление сегодня привязано к двум отдельным фазам: карточка опрашивает
себя, пока задача в `catched`, а прогресс — пока она в `downloading`. Выйдя из
`downloading`, задача не опрашивается ничем, хотя сменить состояние ей предстоит
ещё не раз (распознавание, ревью, раскладка) и часть этих смен идёт вообще без
участия того, кто смотрит на список: их делает воркер или человек из Telegram.
## What Changes
- Самообновление карточки списка привязывается к **нетерминальности** задачи, а
не к фазе `catched`: карточка обновляется, пока задача жива, и перестаёт —
когда та встала окончательно.
- У карточки остаётся **один** источник обновления. Сейчас в `downloading` их
было бы два (сама карточка и вложенный фрагмент прогресса), и они опрашивали
бы одно и то же дважды, подменяя разметку друг друга. Живые цифры прогресса
приходят вместе с карточкой.
- Страница `/download/{id}` живёт по тому же правилу: самообновляется, пока
задача нетерминальна.
- Частота обновления перестаёт быть одинаковой: карточка с живыми цифрами
(скорость, ETA) обновляется чаще, чем карточка, у которой меняется только
состояние. Цена тика у второй поверхности выше — сборка страницы загрузки
считает предпросмотр раскладки и ходит в файловую систему.
- Секция «Раздача» на странице загрузки перестаёт опрашивать сервер сама — она
лежит внутри области, которую страница обновляет целиком, и два опроса на одну
поверхность мешали бы друг другу.
- Тик самообновления, не сумевший прочитать задачу, перестаёт быть молчаливым:
он объясняет положение дел и прекращает опрос вместо бесконечного стука в
сервер.
- **BREAKING** для внутреннего контракта фрагментов: фрагменты прогресса и
раздачи перестают быть самостоятельными поллерами. Наружного API это не
касается — `/api/**` не меняется.
## Capabilities
### New Capabilities
Новых нет.
### Modified Capabilities
- `web-ui`: требование «Отображение промежуточного состояния catched»
обобщается — самообновление интерфейса перестаёт быть свойством одной фазы и
становится свойством живой задачи; условие остановки — терминальное
состояние.
- `live-status`: требование «Живой прогресс активных загрузок» — сценарий
«Завершение останавливает поллинг» сегодня описывает наблюдаемый дефект как
норму. Прекращаться должен показ живых цифр, а не обновление карточки.
## Impact
- `internal/httpapi`: `toView` и `buildDownloadView` (условие самообновления),
обработчики фрагментов карточки и прогресса;
- `web/templates/partials/card.html`, `progress.html`, `download_main.html`;
- нагрузка: число фоновых запросов на открытую страницу меняется — считается в
`design.md`;
- вне scope: переход на SSE (задача `sse-live-updates`), любые изменения
`/api/**` и состава живой телеметрии;
- вне scope и названо сознательно: экран `/review/{id}` сохраняет фазовое
самообновление, заказанное спекой `review` («пока загрузка в `recognizing`»).
Третья поверхность приводится к общему правилу отдельной задачей — иначе
change тянет за собой ещё одну capability.