Веб-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>
This commit is contained in:
av
2026-07-04 14:37:51 +03:00
co-authored by Claude Opus 4.8
parent 5d704059ee
commit 2f8e6e3576
16 changed files with 1140 additions and 248 deletions
+21 -14
View File
@@ -13,19 +13,20 @@ import (
// --- Страница просмотра одной загрузки ---
type downloadDetailView struct {
ID string
Title string
SourceType string // тип источника (magnet/torrent/url) — блок «Информация о торренте»
SourceFull string // полный источник (magnet) — блок «Информация о торренте»
Infohashes []string // все хеши загрузки (блок «Информация о торренте»)
Context string
State string
Error string
Note string
Added string // дата добавления (source_added_at → created_at), как в списке
AddedAgo string // относительная давность («5 дней назад»); пусто — если не распарсить
CreatedAt string
UpdatedAt string
ID string
Title string
SourceType string // тип источника (magnet/torrent/url) — блок «Информация о торренте»
SourceFull string // полный источник (magnet) — блок «Информация о торренте»
Infohashes []string // все хеши загрузки (блок «Информация о торренте»)
Context string
State string
Error string
ActionError string // ошибка действия на htmx-пути (своп download_main), не error_msg
Note string
Added string // дата добавления (source_added_at → created_at), как в списке
AddedAgo string // относительная давность («5 дней назад»); пусто — если не распарсить
CreatedAt string
UpdatedAt string
// Распознавание (если есть план).
HasPlan bool
@@ -82,7 +83,13 @@ func (s *server) handleDownload(w http.ResponseWriter, r *http.Request) {
http.Error(w, "внутренняя ошибка", http.StatusInternalServerError)
return
}
s.render(w, "download.html", s.buildDownloadView(id, rd))
}
// buildDownloadView собирает представление страницы загрузки из доменных данных
// и живого снимка. Общий для полной страницы (handleDownload) и htmx-свопа
// главной области после действия (renderDownloadFragment).
func (s *server) buildDownloadView(id string, rd *worker.ReviewData) downloadDetailView {
d := rd.Download
view := downloadDetailView{
ID: id,
@@ -141,5 +148,5 @@ func (s *server) handleDownload(w http.ResponseWriter, r *http.Request) {
l, ok := s.liveFor(d)
view.Seeding = buildSeeding(id, l, ok)
s.render(w, "download.html", view)
return view
}