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>
7.6 KiB
7.6 KiB
1. Извлечение переиспользуемых view-builder'ов
- 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 - 1.2 Вынести сборку страницы загрузки в
buildDownloadView(id, rd)— перенести инлайн-сборку изhandleDownload(download.go:86-142);handleDownloadзовёт её. Добавить в view отдельное полеActionError - 1.3 Добавить в view карточки отдельное поле
ActionError(НЕ переиспользоватьcard-meta/.Error— они несутNote/error_msg)
2. Партиалы и фрагменты (единый источник разметки)
- 2.1 Выделить карточку в
partials/card.html({{define "card"}}) сid="card-{{.ID}}"на корневом<article>;index.html— цикл через{{template "card" .}}. Инвариант: кореньdefine== элемент с целевымid - 2.2 Обернуть
<main>страницы загрузки в{{define "download_main"}}сid="download-main";download.htmlвключает его - 2.3 Обернуть тело ревью в
{{define "review_main"}}сid="review-main"(внутри остаётсяreview_source_block);review.htmlвключает его - 2.4 В обеих карточка/
download_mainдобавить разметку слотаActionError(видим только при непустом значении), не конфликтуя сNote/error-баннером - 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-*(намеренно: единственный маркер «это выход из ревью») - 2.6 В формы общих действий (
undo/relink/retry/cancel), доступных и в списке, и на/download/{id}, добавить скрытое полеsurface=list|download— им обработчик выбирает фрагмент ответа
3. Авто-поллер recognizing на экране ревью
- 3.1 Добавить fragment-эндпоинт
GET /fragments/downloads/{id}/review→handleFragReview, рендеритreview_mainпо текущему состоянию (по образцуhandleFragProgress/handleFragSeedingвlive.go) - 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 → фрагмент
- 4.1 Ввести помощник
cardAction(по образцуreviewBlockAction): доменный вызов; наisHTMXперечитать состояние, отрендеритьcardдля одной загрузки; на ошибке — тот же фрагмент с заполненнымActionErrorи HTTP 200 (иначе htmx не свопит); без htmx — прежний редирект - 4.2 Ввести помощник
downloadActionаналогично, рендерdownload_main(тоже 200 +ActionErrorна ошибке) - 4.3 Перевести
handleUndo,handleRelink,handleUIRetryна выбор помощника/фрагмента по полюsurface(list→cardAction, download→downloadAction); без htmx — прежний редирект - 4.4 Переписать единый
handleUICancel: наisHTMX— своп поsurface(list→card, download→download_main); на не-htmx (в т.ч. форма выхода из ревью и режим без JS) → редирект на/, как сейчас. Убрать деление «в контексте ревью не трогать» — обработчик один - 4.5 Перевести петлевые
handleRefine,handleRerecognizeна рендерreview_mainприisHTMX(200 +.Errorна ошибке), сохранив редирект-ветку на/review/{id}без htmx. Выходы ревью (handleApply/handleDefer) не трогать - 4.6 Ошибки везде переводить существующими
userErr/classifyErr(наружу только нейтральный текст); секреты в логи не попадают — паритет сохранить
5. Тесты
- 5.1 htmx-запрос (
HX-Request: true) действия из списка (surface=list) → 200 + фрагментcardс новым состоянием, не редирект - 5.2 htmx-запрос действия со страницы (
surface=download) → 200 +download_main; безHX-Request→ прежний 303-редирект - 5.3 Петля ревью:
rerecognize/refineприHX-Request→ 200 +review_mainв состоянииrecognizingс поллером; без htmx → 303 на/review/{id} - 5.4
handleFragReview: покаrecognizing— фрагмент содержит поллер; после перехода вreview— фрагмент без поллера (опрос прекращается) - 5.5 Ошибка действия: htmx-ответ — 200 + сообщение в теле (в
ActionError/.Error), состояние не меняется; выход ревью (cancelбезhx-*) — по-прежнему навигация - 5.6 retry из списка при htmx → карточка
downloadingсprogress-поллером
6. Проверка и оформление
- 6.1
task lintиtask testзелёные - 6.2 Ручная проверка (
task run) — ОТЛОЖЕНА до окружения с qBittorrent/LLM/конфигом. Рендер всех страниц и фрагментов и ветвление htmx/redirect покрыты хендлер-тестами через реальные шаблоны (action_swap_test.go); живой браузерный прогон (сохранение скролла, живой своп) — за пользователем. - 6.3 Ревью кода (второй чекпоинт, сабагентом — критичных багов нет,
minor-замечания закрыты), sync дельт в
openspec/specs/, архивирование change