Files
jellybit/openspec/changes/htmx-action-swap/specs/web-ui/spec.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

6.1 KiB
Raw Blame History

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 карточка подменяется на месте новым состоянием и остаётся видимой до следующей полной загрузки списка, без клиентского переупорядочивания