Имя: восстановление 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>
This commit is contained in:
av
2026-07-10 16:51:04 +03:00
co-authored by Claude Opus 4.8
parent e2ea1840c9
commit 0c9421f4c1
26 changed files with 1179 additions and 21 deletions
+15
View File
@@ -719,6 +719,21 @@ WHERE id = ? AND state = ?`,
return nil
}
// SetDisplayName обновляет отображаемое имя загрузки постфактум — перелив
// канонического имени после распознавания (см. capability ingest). В отличие от
// PromoteCatched не двигает FSM и не завязан на состояние (гарда state нет):
// обновление имени валидно и в review, и в терминальных done/orphaned. Имя —
// косметика (заголовок в UI + ярлык раздачи), пути на диске не затрагивает.
func (s *Store) SetDisplayName(ctx context.Context, id, name string) error {
if _, err := s.DB.ExecContext(ctx, `
UPDATE download
SET display_name = ?, updated_at = ?
WHERE id = ?`, name, FormatTime(Now()), id); err != nil {
return fmt.Errorf("set display name %s: %w", id, err)
}
return nil
}
// setState выполняет UPDATE состояния. reviveOK=true — вызов из гарда
// (ActivateIfNoOtherActive), которому переход терминал→активное разрешён;
// иначе предикат в UPDATE не даёт молча оживить терминальную задачу.