# 🐞 Обновлять страницу загрузки при выходе из «распознаётся»

- **Тип:** fix
- **Категория:** Ядро продукта
- **Зачем:** на боевом umbar последней версии страница держит бейдж «распознаётся» после того, как задача ушла в review или done — состояние видно только по F5

Спека `web-ui` («Самообновление живой задачи») заказывает: страница наблюдаемой
задачи обновляет себя сама, пока задачу может двигать фон. `recognizing`
наблюдаем (`State.IsObservable`), тик страницы объявлен `every 15s`, и разметка
покрыта тестом `TestDownloadPageSelfPoll` — а на боевом стенде страница
застывает. Расходится не разметка, а поведение живой страницы, и причина
неизвестна: тик либо не уходит, либо уходит и не свопит, либо гасит сам себя.

Речь только о странице `/download/{id}`. Список загрузок ведёт свой поллер
(карточка опрашивает `/fragments/downloads/{id}/card`) и в эту задачу не входит:
на нём застывания не заявлено и оно не проверялось.

## Воспроизведение

1. Открыть `/download/{id}` на боевом umbar, пока задача в `recognizing`.
2. Дождаться, когда фон уведёт её дальше — в `review` или `done`.
3. Смотреть на страницу, не трогая её.

Видно вместо ожидаемого: бейдж и вся главная область остаются на «распознаётся»
дольше одного тика (15 с) — сколько именно, не замерено. Нажатие F5 показывает
настоящее состояние сразу. Наблюдалось на umbar на последней выложенной версии,
то есть уже с самообновлением из коммита `a5d873b`.

Замера «через сколько секунд перестало обновляться», журнала сети из браузера и
проверки на локальном запуске нет — первым делом их и надо снять.

## Затрагивает

- `web/templates/partials/download_main.html` — объявление тика:
  `hx-get="/download/{id}"`, `hx-trigger="every …"`, `hx-select="#download-main"`,
  `hx-swap="outerHTML"` и `hx-preserve` на «Опасной зоне»;
- `handleDownload` и `buildDownloadView` (`internal/httpapi/download.go`) — ответ
  на тик, ветка `isHTMX` → `fragTickErr` и вычисление `SelfPoll`/`PollEvery`;
- `fragNote` (`internal/httpapi/live.go`) — самозавершающийся фрагмент, который
  снимает поллер с поверхности;
- `State.IsObservable` (`internal/store/download.go`) — предикат, на котором
  стоит тик;
- спеки `web-ui` («Самообновление живой задачи») и `live-status`;
- боевой рантайм: версия развёрнутого бинаря, раздача статики и заголовки
  кэширования страницы `/download/{id}`.

## Критерии приёмки

- Причина названа и подтверждена: показано, на каком шаге тик теряется — запрос
  не уходит, ответ не свопится или поллер снят (оракул: журнал сети браузера или
  лог сервера с боевого стенда, приложенный к задаче).
- Страница, открытая в `recognizing`, показывает новое состояние без F5 не позже
  двух тиков после перехода (оракул: ручной прогон на umbar с засечкой времени
  перехода по логу сервера и времени смены бейджа).
- Найденная причина закрыта проверкой, которая падала бы до починки (оракул:
  тест в `internal/httpapi`; причина не ловится тестом — сказано прямо, чем она
  закрыта вместо теста).
- Тик по-прежнему замолкает на ненаблюдаемом состоянии (оракул: существующий
  `TestDownloadPageSelfPoll` зелёный).
- `task gate` зелёный (оракул: сам гейт).

## Рамки

На SSE не переезжать — это отдельная задача `sse-live-updates`; чинится
существующий htmx-поллинг. Ветку `fragTickErr` не убирать: она стоит там, чтобы
отказ чтения не оставлял вкладку стучать вечно (ADR-2026-08-10-observability-is-not-terminality).
