Files
avandClaude Opus 4.8 0c9421f4c1 Имя: восстановление display_name после распознавания + гард пустого входа
Голый magnet без dn/контекста заставлял namer звать LLM на пустом входе,
и модель галлюцинировала мусорное имя («Unknown»), которое писалось и в
display_name, и в rename qBittorrent, а заодно ломало UI-фолбэк на
распознанное название. Верное каноническое имя, вычисляемое позже при
распознавании, никуда не переливалось.

- naming: гард пустого входа в DeriveName (нет контекста и подсказки → ""
  без вызова LLM) + детерминированный форматтер FormatTitleYear.
- qbt: операция RenameTorrent (переименование существующей раздачи).
- store: SetDisplayName — обновление имени постфактум без гарда состояния.
- worker: refreshDisplayNameLocked/RefreshDisplayName — перелив канонического
  имени (эффективный план) в display_name + best-effort rename раздачи по
  реальному t.Hash; авто-триггер при подтверждении матча (choose/manual add).
- web-ui: кнопка «Обновить имя» на странице загрузки (htmx-своп заголовка,
  деградация без JS), видимая при наличии распознавания (вкл. done/orphaned).

Спека: дельты ingest/review/web-ui влиты в openspec/specs; change
refresh-display-name заархивирован.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 16:51:04 +03:00

5.5 KiB
Raw Permalink Blame History

1. Гард пустого входа в naming (корень «Unknown»)

  • 1.1 В internal/naming/naming.go DeriveName: если strings.TrimSpace(contextText) и strings.TrimSpace(hint) оба пусты — вернуть "" до вызова LLM и фолбека
  • 1.2 Тест: пустой контекст + пустой hint → "", провайдер LLM не вызван (фейковый provider фиксирует отсутствие вызова)
  • 1.3 Тест регрессии: непустой hint (или контекст) при пустом другом — прежнее поведение сохранено

2. Операция переименования раздачи в qBittorrent

  • 2.1 В internal/qbt добавить Client.RenameTorrent(ctx, hash, name)POST /api/v2/torrents/rename (form hash, name), логирование внешнего вызова как ext.service=qbittorrent, ext.operation=torrents/rename
  • 2.2 Тест клиента qBittorrent на форму запроса и разбор ответа/ошибки (по образцу существующих тестов Add/Delete)

3. Store: обновление display_name пост-фактум

  • 3.1 В internal/store добавить SetDisplayName(ctx, id, name) — UPDATE display_name, updated_at, без гарда состояния (не двигает FSM)
  • 3.2 Тест store: имя обновляется в review/done; updated_at продвинут

4. Форматтер и помощник обновления имени

  • 4.1 В пакете naming: экспортировать sanitize/truncate (или добавить публичную обёртку) и добавить детерминированный форматтер FormatTitleYear(title string, year int) string (Title (Year), год опционален; очистка/обрезка) — без LLM. НЕ унифицировать с render() (у refresh нет режиссёра/сезона)
  • 4.2 Добавить в интерфейсы worker методы RenameTorrent (QBittorrent) и SetDisplayName (Store)
  • 4.3 Реализовать refreshDisplayNameLocked(ctx, id) (вызывается ПОД w.mu): прочитать загрузку и эффективное распознанное название (пины title/year поверх recognition.title/year), собрать имя через naming.FormatTitleYear, при пустом — no-op; записать display_name через SetDisplayName; затем резолвить раздачу через torrentByInfohash и best-effort RenameTorrent по её реальному t.Hash (не по сырому primary infohash) — сбой/отсутствие раздачи логировать WARN, операцию не проваливать
  • 4.4 Публичная обёртка RefreshDisplayName(ctx, id): берёт w.mu и вызывает refreshDisplayNameLocked (для внешнего вызова из httpapi)
  • 4.5 Тесты worker: перелив имени в display_name + вызов RenameTorrent с реальным t.Hash; недоступность/отсутствие раздачи qBittorrent → display_name обновлён, ошибки нет; отсутствие распознавания → no-op

5. Авто-триггер при подтверждённом матче (review)

  • 5.1 В chooseCandidateLocked после успешного SetCandidateChosen вызвать refreshDisplayNameLocked(ctx, id) (общий путь покрывает и ChooseCandidate, и AddManualSource)
  • 5.2 Тесты: выбор кандидата (ChooseCandidate) и ручное добавление источника (AddManualSource) обновляют display_name каноническим именем и переименовывают раздачу; ошибка qBittorrent не ломает команду

6. Ручное действие «Обновить имя» (web-ui)

  • 6.1 Эндпоинт POST /ui/downloads/{id}/refresh-name в internal/httpapi: вызвать RefreshDisplayName (обёртка с w.mu), вернуть htmx-партиал заголовка (ветвление по isHTMX), деградация без JS — редирект/страница загрузки с новым заголовком
  • 6.2 Кнопка «Обновить имя» в шаблоне страницы загрузки, видимая при наличии распознавания (rd.Recognition != nil/наличие плана), включая done/orphaned — НЕ гейтить по Reviewable; htmx-обновление фрагмента заголовка на месте
  • 6.3 Тест httpapi: POST на загрузку с распознаванием возвращает партиал с новым заголовком; идемпотентность (повтор даёт то же имя); загрузка без распознавания → без изменений; кнопка видна на done

7. Финализация

  • 7.1 task test и task lint зелёные
  • 7.2 openspec validate refresh-display-name --strict проходит
  • 7.3 Ревью кода (чекпоинт перед archive): сверка со спеками + инварианты/конвенции