web-ui: карточка и страница обновляются, пока задачу может двигать фон
- условие самообновления — доменный предикат store.State.IsObservable() вместо фазы catched; один поллер на поверхность, интервалы 5 с и 15 с - отказ тика отвечает 200 и самозавершающимся фрагментом с корневым id цели вместо 404/500, который htmx не свопит - заведён ADR-2026-08-10-observability-is-not-terminality, переписан раздел «Живой поллинг» в конвенции веб-UI
This commit is contained in:
+20
-12
@@ -114,11 +114,12 @@ func NewRouter(d Deps) (http.Handler, error) {
|
||||
r.Get("/", s.handleIndex)
|
||||
r.Get("/download/{id}", s.handleDownload)
|
||||
|
||||
// Живые фрагменты телеметрии (htmx-поллинг; читают снимок воркера).
|
||||
// Партиалы телеметрии без потребителя в новой разметке: оставлены гасителями
|
||||
// вкладок, отрисованных прошлой версией (см. handleFragProgress).
|
||||
r.Get("/fragments/downloads/{id}/progress", s.handleFragProgress)
|
||||
r.Get("/fragments/downloads/{id}/seeding", s.handleFragSeeding)
|
||||
// Карточка целиком: самополлинг catched до перехода в downloading (бейдж,
|
||||
// имя и появившийся прогресс обновляются без перезагрузки).
|
||||
// Карточка целиком — тик самообновления списка: пока задача наблюдаема,
|
||||
// карточка приносит текущее состояние без перезагрузки страницы.
|
||||
r.Get("/fragments/downloads/{id}/card", s.handleFragCard)
|
||||
// Тело ревью для поллинга recognizing (htmx-своп до готового плана).
|
||||
r.Get("/fragments/downloads/{id}/review", s.handleFragReview)
|
||||
@@ -205,8 +206,9 @@ type downloadView struct {
|
||||
State string
|
||||
Error string
|
||||
Terminal bool
|
||||
IsDownloading bool // активная загрузка → живой прогресс-бар + поллинг
|
||||
SelfPoll bool // catched → карточка сама опрашивает себя до перехода
|
||||
IsDownloading bool // активная загрузка → живой прогресс-бар
|
||||
SelfPoll bool // задача наблюдаема → карточка сама опрашивает себя
|
||||
PollEvery string // интервал самообновления карточки (pollFast/pollSlow)
|
||||
Progress progressView // живой прогресс (заполняется в handleIndex из снимка)
|
||||
Reviewable bool // review/deferred — есть экран ревью
|
||||
Undoable bool // done — можно откатить раскладку
|
||||
@@ -311,10 +313,11 @@ func (s *server) handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// buildCardView собирает представление карточки списка из доменных данных и
|
||||
// живого снимка. Общий для полной страницы (handleIndex) и htmx-свопа карточки
|
||||
// после действия (renderCardFragment): чтобы htmx-ветка не дублировала обвязку
|
||||
// (рейтинг/размер/прогресс). Для retry→downloading карточка обязана нести
|
||||
// прогресс-поллер — поэтому Progress заполняется здесь.
|
||||
// живого снимка. Общий для полной страницы (handleIndex), тика самообновления
|
||||
// (handleFragCard) и htmx-свопа после действия (renderCardFragment): чтобы
|
||||
// htmx-ветки не дублировали обвязку (рейтинг/размер/прогресс). Живые цифры едут
|
||||
// вместе с карточкой — своего опроса у блока прогресса нет, поэтому Progress
|
||||
// заполняется здесь на каждом пути.
|
||||
func (s *server) buildCardView(d store.Download, now time.Time, layoutSize int64) downloadView {
|
||||
v := s.toView(d, now)
|
||||
// Живой снимок читаем для всех карточек (map-lookup, без сети/БД): рейтинг
|
||||
@@ -494,7 +497,7 @@ func (s *server) surfaceAction(w http.ResponseWriter, r *http.Request, id string
|
||||
func (s *server) renderCardFragment(w http.ResponseWriter, r *http.Request, id string, actionErr error) {
|
||||
d, err := s.deps.Reader.GetDownload(r.Context(), id)
|
||||
if err != nil {
|
||||
s.fragErr(w, err, id)
|
||||
s.fragActionErr(w, err, id, "card-"+id)
|
||||
return
|
||||
}
|
||||
sizes, err := s.deps.Reader.LayoutSizeByDownload(r.Context(), []string{id})
|
||||
@@ -514,7 +517,7 @@ func (s *server) renderCardFragment(w http.ResponseWriter, r *http.Request, id s
|
||||
func (s *server) renderDownloadFragment(w http.ResponseWriter, r *http.Request, id string, actionErr error) {
|
||||
rd, err := s.deps.Reviewer.ReviewData(r.Context(), id)
|
||||
if err != nil {
|
||||
s.fragErr(w, err, id)
|
||||
s.fragActionErr(w, err, id, "download-main")
|
||||
return
|
||||
}
|
||||
v := s.buildDownloadView(id, rd)
|
||||
@@ -661,7 +664,8 @@ func (s *server) toView(d store.Download, now time.Time) downloadView {
|
||||
Error: d.ErrorMsg.String,
|
||||
Terminal: d.State.IsTerminal(),
|
||||
IsDownloading: d.State == store.StateDownloading,
|
||||
SelfPoll: d.State == store.StateCatched,
|
||||
SelfPoll: d.State.IsObservable(),
|
||||
PollEvery: pollSlow,
|
||||
Reviewable: d.State == store.StateReview || d.State == store.StateDeferred,
|
||||
Undoable: d.State == store.StateDone,
|
||||
Relinkable: d.State == store.StateReverted || d.State == store.StateCancelled ||
|
||||
@@ -669,6 +673,10 @@ func (s *server) toView(d store.Download, now time.Time) downloadView {
|
||||
Retriable: d.State == store.StateFailed || d.State == store.StateStuck,
|
||||
Note: desyncNote(d.State),
|
||||
}
|
||||
// Быстрый интервал — только там, где на поверхности бегут цифры качания.
|
||||
if v.IsDownloading {
|
||||
v.PollEvery = pollFast
|
||||
}
|
||||
// Дата добавления в карточке — всегда (source_added_at → фолбэк created_at,
|
||||
// как в порядке списка); неразбираемое время просто опускаем.
|
||||
if t, ok := addedTime(d); ok {
|
||||
|
||||
Reference in New Issue
Block a user