Files
jellybit/openspec/changes/htmx-action-swap/tasks.md
T
avandClaude Opus 4.8 2f8e6e3576 Веб-UI: htmx-своп действий на месте вместо PRG-редиректа
Мутирующие действия больше не уводят со страницы: undo/relink/retry/cancel
в списке свопят карточку (#card-{id}), на /download/{id} — #download-main;
петля ревью (refine/rerecognize) свопит #review-main и допалливает
recognizing до готового плана (fragment /fragments/downloads/{id}/review,
every 2s). Выходы ревью (apply/defer/cancel) остаются навигацией. Без htmx —
прежний PRG-редирект (деградация). Ошибка действия на htmx-пути — HTTP 200
с сообщением в фрагменте (ActionError), иначе htmx не свопит DOM.

Разметка вынесена в партиалы card/download_main/review_main (корень = элемент
с целевым id), различение поверхности — скрытым полем surface=list|download.
Извлечены buildCardView/buildDownloadView. handleSetProvider переведён на
reviewBlockAction (консистентность source-действий).

Реализация change htmx-action-swap (OpenSpec).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 14:37:51 +03:00

93 lines
7.5 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.
## 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`): откат из списка — остаёшься в списке
(фильтр/скролл целы); откат/relink со страницы загрузки — остаёшься на ней;
rerecognize/refine — экран ревью свопится в recognizing и сам обновляется до
готового плана без тыканья; выключенный JS — прежний редирект-поток работает
- [ ] 6.3 Ревью кода (второй чекпоинт), затем sync дельт в `openspec/specs/` и
архивирование change