Files
avandClaude Opus 4.8 576fc4e6c0 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>
2026-07-04 14:42:06 +03:00

94 lines
7.6 KiB
Markdown
Raw Permalink 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.
## 1. Извлечение переиспользуемых view-builder'ов
- [x] 1.1 Вынести сборку карточки списка в `buildCardView(d, now, live,
layoutSize)` — перенести обвязку из `handleIndex` (`httpapi.go:291-304`:
`liveFor`, `ratioText`, `sizeText`, `layoutSizes[id]`, `buildProgress`);
`handleIndex` теперь зовёт её в цикле. Билдер MUST выставлять
`IsDownloading`/`Progress` (для retry→downloading карточка обязана нести
`progress`-партиал с поллером) и `Ratio`/`Size`
- [x] 1.2 Вынести сборку страницы загрузки в `buildDownloadView(id, rd)` —
перенести инлайн-сборку из `handleDownload` (`download.go:86-142`);
`handleDownload` зовёт её. Добавить в view отдельное поле `ActionError`
- [x] 1.3 Добавить в view карточки отдельное поле `ActionError` (НЕ
переиспользовать `card-meta`/`.Error` — они несут `Note`/`error_msg`)
## 2. Партиалы и фрагменты (единый источник разметки)
- [x] 2.1 Выделить карточку в `partials/card.html` (`{{define "card"}}`) с
`id="card-{{.ID}}"` на корневом `<article>`; `index.html` — цикл через
`{{template "card" .}}`. Инвариант: корень `define` == элемент с целевым `id`
- [x] 2.2 Обернуть `<main>` страницы загрузки в `{{define "download_main"}}` с
`id="download-main"`; `download.html` включает его
- [x] 2.3 Обернуть тело ревью в `{{define "review_main"}}` с `id="review-main"`
(внутри остаётся `review_source_block`); `review.html` включает его
- [x] 2.4 В обеих карточка/`download_main` добавить разметку слота `ActionError`
(видим только при непустом значении), не конфликтуя с `Note`/error-баннером
- [x] 2.5 Проставить `hx-post`/`hx-target`/`hx-swap="outerHTML"` на свопимые
формы: карточка (`undo`/`relink`/`retry`/`cancel` → `#card-{id}`), страница
загрузки (те же → `#download-main`), петля ревью (`refine`/`rerecognize` →
`#review-main`). Выходы ревью (`apply`/`defer`/`cancel`) — обычные POST-формы
БЕЗ `hx-*` (намеренно: единственный маркер «это выход из ревью»)
- [x] 2.6 В формы общих действий (`undo`/`relink`/`retry`/`cancel`), доступных и
в списке, и на `/download/{id}`, добавить скрытое поле
`surface=list|download` — им обработчик выбирает фрагмент ответа
## 3. Авто-поллер recognizing на экране ревью
- [x] 3.1 Добавить fragment-эндпоинт `GET /fragments/downloads/{id}/review` →
`handleFragReview`, рендерит `review_main` по текущему состоянию (по образцу
`handleFragProgress`/`handleFragSeeding` в `live.go`)
- [x] 3.2 В `review_main` блок состояния `recognizing` (`review.html:33-38`)
оснастить поллером `hx-get="/fragments/downloads/{id}/review"
hx-trigger="every 2s" hx-target="#review-main" hx-swap="outerHTML"`; ручную
ссылку «Обновить» оставить как fallback без JS. Когда состояние выходит из
`recognizing`, `review_main` возвращается без поллера — опрос сам прекращается
## 4. Обработчики: ветка htmx → фрагмент
- [x] 4.1 Ввести помощник `cardAction` (по образцу `reviewBlockAction`):
доменный вызов; на `isHTMX` перечитать состояние, отрендерить `card` для
одной загрузки; на ошибке — тот же фрагмент с заполненным `ActionError` и
**HTTP 200** (иначе htmx не свопит); без htmx — прежний редирект
- [x] 4.2 Ввести помощник `downloadAction` аналогично, рендер `download_main`
(тоже 200 + `ActionError` на ошибке)
- [x] 4.3 Перевести `handleUndo`, `handleRelink`, `handleUIRetry` на выбор
помощника/фрагмента по полю `surface` (list→`cardAction`,
download→`downloadAction`); без htmx — прежний редирект
- [x] 4.4 Переписать единый `handleUICancel`: на `isHTMX` — своп по `surface`
(list→`card`, download→`download_main`); на не-htmx (в т.ч. форма выхода из
ревью и режим без JS) → редирект на `/`, как сейчас. Убрать деление «в
контексте ревью не трогать» — обработчик один
- [x] 4.5 Перевести петлевые `handleRefine`, `handleRerecognize` на рендер
`review_main` при `isHTMX` (200 + `.Error` на ошибке), сохранив
редирект-ветку на `/review/{id}` без htmx. Выходы ревью
(`handleApply`/`handleDefer`) не трогать
- [x] 4.6 Ошибки везде переводить существующими `userErr`/`classifyErr` (наружу
только нейтральный текст); секреты в логи не попадают — паритет сохранить
## 5. Тесты
- [x] 5.1 htmx-запрос (`HX-Request: true`) действия из списка (`surface=list`)
→ 200 + фрагмент `card` с новым состоянием, не редирект
- [x] 5.2 htmx-запрос действия со страницы (`surface=download`) → 200 +
`download_main`; без `HX-Request` → прежний 303-редирект
- [x] 5.3 Петля ревью: `rerecognize`/`refine` при `HX-Request` → 200 +
`review_main` в состоянии `recognizing` с поллером; без htmx → 303 на
`/review/{id}`
- [x] 5.4 `handleFragReview`: пока `recognizing` — фрагмент содержит поллер;
после перехода в `review` — фрагмент без поллера (опрос прекращается)
- [x] 5.5 Ошибка действия: htmx-ответ — **200 + сообщение в теле** (в
`ActionError`/`.Error`), состояние не меняется; выход ревью (`cancel` без
`hx-*`) — по-прежнему навигация
- [x] 5.6 retry из списка при htmx → карточка `downloading` с `progress`-поллером
## 6. Проверка и оформление
- [x] 6.1 `task lint` и `task test` зелёные
- [ ] 6.2 Ручная проверка (`task run`) — ОТЛОЖЕНА до окружения с
qBittorrent/LLM/конфигом. Рендер всех страниц и фрагментов и ветвление
htmx/redirect покрыты хендлер-тестами через реальные шаблоны
(`action_swap_test.go`); живой браузерный прогон (сохранение скролла, живой
своп) — за пользователем.
- [x] 6.3 Ревью кода (второй чекпоинт, сабагентом — критичных багов нет,
minor-замечания закрыты), sync дельт в `openspec/specs/`, архивирование change