Files
avandClaude Opus 4.8 c0b5ab7295 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>
2026-07-01 09:50:24 +03:00

7.1 KiB
Raw Permalink Blame History

1. Персистентность имени раздачи и времени добавления (БД + ingest + worker)

  • 1.1 Миграция 0005 (goose): download.display_name TEXT NOT NULL DEFAULT '' и download.source_added_at (nullable timestamp), обратная миграция (down)
  • 1.2 Добавить поля DisplayName и SourceAddedAt в store.Download; писать display_name в CreateDownload; метод однократной записи source_added_at с SQL-гардом WHERE source_added_at IS NULL; экспортировать хелпер форматирования времени в формате sqliteTimeLayout (UTC)
  • 1.3 В internal/ingest заполнять DisplayName выведенным именем rename при создании загрузки; в discover.adopt()display_name = t.Name для усыновлённых торрентов
  • 1.4 В internal/worker персистить source_added_at из qbt.Torrent.AddedOn в цикле Poll сразу после byHash-сопоставления (до reconcile, для любого класса состояния) и в adopt(); формат — time.Unix(...).UTC().Format(sqliteTimeLayout); однократно (гард по d.SourceAddedAt + SQL IS NULL); под w.mu
  • 1.5 Обновить ER-схему docs/specs/database.md (колонки display_name, source_added_at)
  • 1.6 Тесты: ingest сохраняет display_name; adopt пишет display_name из имени торрента; worker пишет source_added_at однократно и в формате created_at; пустой контекст → пустое имя

2. Серверные фильтр, поиск, сортировка и пагинация списка (store)

  • 2.1 Вынести маппинг состояние↔группа фильтра в единый источник истины (используется и в UI, и в запросе); учесть all с исключением deleted и осознанно — группу other (cancelled/reverted/deleted) без своего чипа
  • 2.2 Метод store.ListDownloadsPage(ctx, ListFilter) → страница + общий count: WHERE по группе состояний, deleted только при «показать всё», поиск LIKE/COLLATE NOCASE по source_ref/display_name/context/infohash, сортировка ORDER BY COALESCE(source_added_at, created_at) DESC, id DESC, LIMIT/OFFSET
  • 2.3 Тесты store: фильтр по группе, поиск, скрытие deleted по умолчанию, порядок по времени добавления с фолбеком + тай-брейк по id при равных метках, границы пагинации и count

3. Список на главной (httpapi + шаблоны)

  • 3.1 Разбор GET-параметров f/q/page/all в handleIndex (клэмп page: <1/нечисловой → 1, за пределом → пустая страница), константа размера страницы (25), вызов ListDownloadsPage; расширить интерфейс Reader методом ListDownloadsPage
  • 3.2 View-модель списка: элементы страницы + данные пагинации + текущие фильтр/поиск; удалить ставшие мёртвыми SearchText и (если больше не нужен) Group из downloadView
  • 3.3 index.html: чипы-фильтр и «показать всё» как ссылки (сохраняют q), форма поиска (GET), навигация по страницам (сохраняет f/q); серверный рендер пустого состояния («ничего не найдено» при активных фильтре/поиске, иначе «пока пусто»)
  • 3.4 Убрать клиентскую JS-фильтрацию из app.js (applyFilters/setFilter + DOMContentLoaded-хук), оставить copyHash и <details>; сохранить живой прогресс активных карточек
  • 3.5 Заголовок карточки — фолбек-цепочка display_name → распознанное → усечённый источник (одна строка)
  • 3.6 Тесты httpapi: параметры списка → корректная выборка/пагинация; заголовок из display_name и фолбеки

4. Страница загрузки: имя, матч-ссылка, блок торрента (httpapi + шаблоны)

  • 4.1 Заголовок/шапка /download/{id} — та же фолбек-цепочка заголовка
  • 4.2 Хелпер providerURL(provider, id, mediaType) (tmdb: movie/tv; tvdb: медиазависимый путь; imdb: /title) + выбор URL матча: URL выбранного кандидата только если его provider+id == эффективным rd.Provider/rd.ProviderID, иначе построенный; нет надёжного URL → без ссылки
  • 4.3 «Распознано как»: показать матч ссылкой на запись (провайдер, id, год); при отсутствии URL — текст без ссылки
  • 4.4 Блок «Информация о торренте»: полный источник (magnet) + infohash с копированием
  • 4.5 Тесты httpapi: матч-ссылка при наличии/отсутствии URL; расхождение выбранного кандидата и эффективного provider_id (ссылка ведёт на эффективную запись); блок торрента

5. Ревью: матч-ссылка (httpapi + шаблон)

  • 5.1 В блоке «Источник совпадения» строку «Выбрано» показывать ссылкой на запись (та же логика выбора URL, что в 4.2)

6. Полировка вёрстки (CSS/шаблоны)

  • 6.1 «Распознано как»: снять max-width у списка полей — блок на всю ширину
  • 6.2 Layout-виджет: приклеить стрелку к строке файла-источника (источник и цель строго друг под другом)
  • 6.3 Проверить отсутствие инлайн-<style> с хардкодом цветов в изменённых шаблонах (правки — в jellybit.css)

7. Проверка и ревью

  • 7.1 task test и task lint зелёные
  • 7.2 openspec validate web-ui-list-detail --strict
  • 7.3 Ручная проверка: список с фильтром/поиском/пагинацией, живой прогресс не сбрасывается; заголовки; матч-ссылки; блок торрента; полировка
  • 7.4 Ревью кода перед archive