UI/UX списка и карточки загрузки: серверные фильтр/поиск/пагинация, матч-ссылка, имя раздачи (web-ui-list-detail)

- Список: серверные фильтр по группе состояний, поиск и пагинация (GET
  f/q/page/all, по 25), сортировка по времени добавления в qBittorrent
  (added_on) с фолбеком на created_at и tie-break по id.
- Заголовок загрузки = имя раздачи (display_name) → распознанное название →
  усечённый источник; сырой magnet вынесен в блок «Информация о торренте».
- Матч метабазы показан ссылкой на запись (страница загрузки и ревью);
  URL берётся у выбранного кандидата либо строится по provider+id и типу.
- Полировка вёрстки; клиентская JS-фильтрация убрана (всё серверное, без JS).
- Миграция 0005 (display_name, source_added_at); воркер однократно
  фиксирует source_added_at при поллинге/усыновлении; ER-схема обновлена.
- OpenSpec: дельты влиты в specs/{web-ui,ingest}, change заархивирован.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-01 09:50:24 +03:00
co-authored by Claude Opus 4.8
parent 2a9bf7efb0
commit c0b5ab7295
33 changed files with 1679 additions and 110 deletions
-17
View File
@@ -73,23 +73,6 @@ func badgeLabel(state string) string {
}
}
// stateGroup — группа фильтра на главной (чипы): ждут меня / в работе / готово
// / проблемы / прочее. Завязано на смысл состояния, не на цвет.
func stateGroup(state string) string {
switch state {
case "review", "deferred":
return "review"
case "downloading", "completed", "recognizing", "linking":
return "active"
case "done":
return "done"
case "stuck", "failed", "target_missing", "orphaned":
return "problem"
default:
return "other"
}
}
// render отрисовывает именованный шаблон в буфер и только затем пишет ответ —
// при ошибке шаблона клиент не получит «полустраницу».
func (s *server) render(w http.ResponseWriter, name string, data any) {