## ADDED Requirements ### Requirement: Действия обновляют интерфейс на месте Мутирующие действия над загрузкой в списке (`/`) и на странице `/download/{id}` SHALL выполняться htmx-запросом и обновлять затронутую область HTML на месте (partial swap), без навигации на другую страницу и без сброса контекста списка (фильтр, поиск, страница пагинации, позиция прокрутки). Сервер SHALL отвечать на такой запрос HTML-фрагментом обновлённой области, а не редиректом. Область свопа SHALL соответствовать поверхности действия: в списке — карточка загрузки (`
`) целиком, отражающая новое состояние, бейдж и допустимый набор действий; на странице `/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** карточка подменяется на месте новым состоянием и остаётся видимой до следующей полной загрузки списка, без клиентского переупорядочивания