OpenSpec: влить дельты htmx-action-swap в спеки, архив change

Sync новых требований в openspec/specs/: web-ui («Действия обновляют
интерфейс на месте») и review («Петлевые действия ревью обновляют экран на
месте»). Change перемещён в changes/archive/2026-07-04-htmx-action-swap.
Конвенция web-ui.md актуализирована: сняты маркеры «будем» по реализованному.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-04 14:42:06 +03:00
co-authored by Claude Opus 4.8
parent 2f8e6e3576
commit 576fc4e6c0
9 changed files with 147 additions and 30 deletions
@@ -0,0 +1,73 @@
## Why
Сейчас действия над загрузкой (откат, привязать заново, распознать заново,
уточнить, применить и т.д.) — это POST-формы с PRG-редиректом: после действия
браузер уходит на другую страницу и теряет контекст. Откат со страницы
загрузки `/download/{id}` и из карточки списка одинаково выкидывает на `/`, а
перераспознавание на `/review/{id}` перезагружает страницу с прыжком скролла
наверх. Пользователь хочет оставаться там, где действовал: действие со списка —
остаёшься в списке, со страницы загрузки/ревью — остаёшься на ней.
## What Changes
- Мутирующие UI-действия выполняются через **htmx-swap на месте** вместо
server-side PRG-редиректа: сервер отвечает обновлённым HTML-фрагментом той
области, которую затронуло действие, htmx подменяет её в DOM без навигации.
Паттерн уже применяется в `reviewBlockAction` (выбор источника) — расширяем
его на остальные действия.
- **Карточка в списке** (`/`): действия `undo`, `relink`, `retry`, `cancel`
подменяют карточку (`<article class="card">`) на месте — обновлённое
состояние, бейдж и набор кнопок. Фильтр/поиск/пагинация/прокрутка не
сбрасываются.
- **Страница загрузки** (`/download/{id}`): те же действия обновляют содержимое
страницы на месте (без перехода и без прыжка скролла), отражая новое
состояние.
- **Страница ревью** (`/review/{id}`): **петлевые** действия распознавания —
`rerecognize`, `refine` (и уже работающий выбор источника) — обновляют экран
на месте (без перезагрузки и прыжка скролла). Так как они асинхронны (переводят
загрузку в `recognizing`), своп отдаёт состояние `recognizing`, а экран сам
**допалливает** готовый план htmx-фрагментом (`GET /fragments/downloads/{id}/review`,
по образцу `progress`/`seeding`) и автоматически сменяется на план по
завершении — без ручного обновления. **Выходы** из ревью (`apply` → done,
`defer` → deferred, `cancel` → cancelled) уводят с экрана (загрузка покидает
ревью), поэтому остаются навигацией/редиректом. Отдельная кнопка
перераспознавания на `/download/{id}` НЕ добавляется — ревью считается частью
«страницы загрузки», действие остаётся в ревью-потоке.
- **Деградация без JS сохраняется**: формы остаются обычными POST; при
отсутствии htmx (нет заголовка `HX-Request`) сервер отвечает прежним
PRG-редиректом, поведение не ломается.
- **Ошибки действий** показываются на месте (в подменённом фрагменте), а не
только через `?err=` после редиректа.
Новых зависимостей нет: Alpine.js/SPA не вводятся, стек остаётся htmx-first
(см. решение `2026-06-30-web-ui-design-port`).
## Capabilities
### New Capabilities
<!-- Нет новых capability — меняется поведение существующих. -->
### Modified Capabilities
- `web-ui`: действия над карточкой/страницей загрузки выполняются htmx-swap'ом
фрагмента на месте (без навигации и сброса контекста списка), с graceful
degradation на PRG-редирект без htmx.
- `review`: петлевые действия ревью (`rerecognize`, `refine`) обновляют экран
на месте htmx-свопом, не перезагружая страницу и не сбрасывая прокрутку;
выходы из ревью (`apply`/`defer`/`cancel`) остаются навигацией.
## Impact
- **Код**: `internal/httpapi` — обработчики действий (`review.go`,
`httpapi.go`): вместо `redirect*` отвечать фрагментом (HTTP 200) при
`HX-Request`, сохранив редирект-ветку; извлечь `buildCardView`/
`buildDownloadView`; новый fragment-эндпоинт `handleFragReview` для
авто-поллинга recognizing (`live.go`).
- **Шаблоны** (`web/templates`): выделить переиспользуемые фрагменты —
карточка списка (в партиал), «главная область» страницы загрузки, тело
ревью; проставить `hx-post`/`hx-target`/`hx-swap` на формы действий, скрытое
поле `surface`, поллер на блок `recognizing`.
- **Зависимости**: без изменений (htmx уже вендорится).
- **Тесты**: обработчики действий — проверка ветвления `HX-Request`
фрагмент vs редирект.
- **Вне scope**: живой поллинг телеметрии (`live-status`) не трогаем;
клиентских фреймворков не добавляем.