review: убрать явный переключатель типа movie↔series из всех транспортов

Кнопка «Тип» была только в Telegram (доменная SetType = жёсткий override
media_type + хинт + перераспознавание), в вебе её нет. Это создавало
расхождение поверхностей и внутреннюю противоречивость спеки review.
Решение: смена типа — редкий случай, для него достаточно «Уточнить»
(перераспознавание с явным указанием типа). Явный переключатель не нужен
ни на одной поверхности.

Снято: Telegram-кнопка и callback type:, worker.SetType, ставший мёртвым
override-плумбинг media_type (ovrMediaType, ветка applyOverrides, хелпер
oppositeType) и стейл-хвосты в тестах httpapi. Спека review — три MODIFIED
требования (запрет на все поверхности, «фиксация типа» убрана из команд и
из быстрых действий Telegram, иллюстрация override заменена на закрепление
источника). Синхронизирован docs/specs/review-ux.md.

Change заархивирован: openspec/changes/archive/2026-07-18-review-remove-type-switch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-18 16:50:43 +03:00
co-authored by Claude Opus 4.8
parent 023fdacda5
commit 1d6f8f3449
13 changed files with 252 additions and 123 deletions
+4 -41
View File
@@ -23,7 +23,6 @@ import (
// Поля override.
const (
ovrMediaType = "media_type"
ovrIgnoredFiles = "ignored_files"
ovrProvider = "provider" // выбранная база ("none" = без базы)
ovrProviderID = "provider_id" // id в выбранной базе
@@ -475,38 +474,6 @@ func (w *Worker) Refine(ctx context.Context, id string, hint string) (err error)
return nil
}
// SetType фиксирует тип (override) и перезапускает распознавание с подсказкой
// — чтобы LLM пересобрал роли файлов под новый тип.
func (w *Worker) SetType(ctx context.Context, id string, mediaType string) (err error) {
defer func() { w.logCmd(ctx, "set_type", id, err) }()
if mediaType != string(recognize.MediaMovie) && mediaType != string(recognize.MediaSeries) {
return fmt.Errorf("set type: invalid type %q: %w", mediaType, ErrInvalidInput)
}
w.mu.Lock()
defer w.mu.Unlock()
d, err := w.requireReviewable(ctx, id, "set type")
if err != nil {
return err
}
if err := w.ensureSourceReady(ctx, d, "set type"); err != nil {
return err
}
ctx = w.scoped(ctx, capReview, id, d.PrimaryInfohash())
if err := w.store.SetOverride(ctx, id, ovrMediaType, mediaType); err != nil {
return fmt.Errorf("set type: %w", err)
}
label := "фильм"
if mediaType == string(recognize.MediaSeries) {
label = "сериал"
}
if err := w.store.AddHint(ctx, id, "Тип точно: "+label+"."); err != nil {
return fmt.Errorf("set type: %w", err)
}
w.transition(ctx, *d, store.StateRecognizing, "", "")
return nil
}
// IgnoreFile помечает файл к игнорированию (не линкуем). Остаёмся в review;
// превью пересчитается с учётом правки.
func (w *Worker) IgnoreFile(ctx context.Context, id string, src string) (err error) {
@@ -1148,8 +1115,7 @@ func (w *Worker) sourceOption(ctx context.Context, downloadID string, kind Sourc
}
// mergeSourceOverrides накладывает пины источника (provider/id/title/year/
// director) на неисточниковые overrides (media_type, ignored_files, force_review,
// ...).
// director) на неисточниковые overrides (ignored_files, force_review, ...).
func mergeSourceOverrides(base, pins map[string]string) map[string]string {
m := make(map[string]string, len(base)+len(pins))
for k, v := range base {
@@ -1307,13 +1273,10 @@ func (w *Worker) resolveFolderBase(ctx context.Context, downloadID, provider, pr
// --- Хелперы преобразования ---
// applyOverrides применяет ручные правки к плану: форсит тип, каноническое
// имя/год (из выбранного кандидата базы) и помечает игнорируемые файлы ролью
// ignore (их раскладка пропустит).
// applyOverrides применяет ручные правки к плану: каноническое имя/год (из
// выбранного кандидата базы) и помечает игнорируемые файлы ролью ignore (их
// раскладка пропустит).
func applyOverrides(plan recognize.Plan, overrides map[string]string) recognize.Plan {
if mt := overrides[ovrMediaType]; mt == string(recognize.MediaMovie) || mt == string(recognize.MediaSeries) {
plan.Type = recognize.MediaType(mt)
}
if t := overrides[ovrTitle]; t != "" {
plan.Title = t
}