Голый 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>
5.5 KiB
5.5 KiB
1. Гард пустого входа в naming (корень «Unknown»)
- 1.1 В
internal/naming/naming.goDeriveName: если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(formhash,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)— UPDATEdisplay_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-effortRenameTorrentпо её реальному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): сверка со спеками + инварианты/конвенции