- условие самообновления — доменный предикат store.State.IsObservable() вместо фазы catched; один поллер на поверхность, интервалы 5 с и 15 с - отказ тика отвечает 200 и самозавершающимся фрагментом с корневым id цели вместо 404/500, который htmx не свопит - заведён ADR-2026-08-10-observability-is-not-terminality, переписан раздел «Живой поллинг» в конвенции веб-UI
67 lines
5.4 KiB
Markdown
67 lines
5.4 KiB
Markdown
## 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.
|