Мутирующие действия больше не уводят со страницы: 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>
6.1 KiB
ADDED Requirements
Requirement: Действия обновляют интерфейс на месте
Мутирующие действия над загрузкой в списке (/) и на странице /download/{id} SHALL выполняться htmx-запросом и обновлять затронутую область HTML на месте (partial swap), без навигации на другую страницу и без сброса контекста списка (фильтр, поиск, страница пагинации, позиция прокрутки).
Сервер SHALL отвечать на такой запрос HTML-фрагментом обновлённой области, а не
редиректом.
Область свопа SHALL соответствовать поверхности действия: в списке — карточка
загрузки (<article class="card">) целиком, отражающая новое состояние, бейдж
и допустимый набор действий; на странице /download/{id} — содержимое
страницы, отражающее новое состояние загрузки. После свопа набор показанных
действий MUST соответствовать новому состоянию (см. «Действия соответствуют
состоянию»).
Поведение MUST деградировать без htmx: если запрос действия пришёл без признака
htmx (нет заголовка HX-Request), обработчик SHALL отвечать прежним
PRG-редиректом, и действие исполняется тем же доменным вызовом. Формы действий
остаются обычными POST-формами.
Ошибка действия (доменная или валидации) SHALL показываться на месте — в
подменённом фрагменте той же области, — а не только через параметр ?err=
после редиректа; при ошибке активное состояние загрузки не меняется молча.
Ответ на htmx-запрос действия SHALL иметь статус 200 даже при ошибке действия
(иначе htmx не подменит фрагмент): сообщение об ошибке несёт сам фрагмент.
После свопа карточка SHALL оставаться на своём месте в списке, даже если её новое состояние уже не подходит под активный фильтр; согласованность списка с фильтром восстанавливается при следующей полной загрузке. Клиентского переупорядочивания или пересчёта доменного состояния не выполняется.
Scenario: Откат из карточки списка обновляет карточку на месте
- GIVEN в списке есть загрузка в состоянии
doneс действием отката - WHEN пользователь нажимает «Откатить» (htmx активен)
- THEN карточка этой загрузки подменяется на месте на её новое состояние
(
reverted) с соответствующим бейджем и набором действий - AND список не перезагружается: фильтр, поиск, страница и позиция прокрутки сохраняются
Scenario: Действие со страницы загрузки оставляет на странице
- GIVEN открыта страница
GET /download/{id}загрузки в состоянииdone - WHEN пользователь нажимает «Откатить» или «Привязать заново» (htmx активен)
- THEN содержимое страницы обновляется на месте под новое состояние загрузки, без перехода на список и без прыжка прокрутки наверх
Scenario: Деградация без htmx — прежний редирект
- WHEN действие над загрузкой приходит POST-запросом без заголовка
HX-Request(htmx недоступен) - THEN обработчик исполняет то же доменное действие и отвечает PRG-редиректом, как раньше; поведение без JavaScript не ломается
Scenario: Ошибка действия показана на месте
- GIVEN пользователь запускает действие через htmx
- WHEN доменный вызов возвращает ошибку (например, состояние уже изменилось)
- THEN ответ имеет статус
200, а сообщение об ошибке показывается в подменённом фрагменте той же области, а не только на отдельной странице после редиректа - AND активное состояние загрузки не меняется
Scenario: Свопнутая карточка остаётся вне фильтра
- GIVEN список отфильтрован по группе состояний (например,
review) и в нём есть карточка загрузки - WHEN действие через htmx переводит загрузку в состояние вне этого фильтра
(например,
cancelled) - THEN карточка подменяется на месте новым состоянием и остаётся видимой до следующей полной загрузки списка, без клиентского переупорядочивания