## 1. Ядро: эфемерный предпросмотр источника (worker) - [ ] 1.1 Ввести чистую деривацию «источник → полный самосогласованный набор overrides» (кандидат с title/year → пиннит; без — пустая строка в title/year; нейронка → provider=none + пустые title/year) и переиспользовать её в превью и в коммите — гарантия preview == apply (решение 1a design.md) - [ ] 1.2 Привести команды выбора к этой деривации: `ChooseCandidate` у безтайтлового кандидата и `ClearProvider` очищают `ovrTitle`/`ovrYear` (пустой строкой) — чинит латентный залипший override - [ ] 1.3 Выделить чистый расчёт «план + набор overrides источника → поля + `[]layout.Link`» из логики `ReviewData` (переиспользуя `applyOverrides` + `toLayoutPlan` + `layouter.BuildLinks`), без записи overrides в БД - [ ] 1.4 Расширить `ReviewData` срезом источников: нейронка (синтетическая строка `provider=none`) + каждый кандидат; для каждого — эффективные поля (тип/название/год), превью путей и признак «активен»; дедуп источников по `provider:id` (нейронка — отдельная строка) - [ ] 1.5 Тесты worker: превью для неактивного источника не пишет overrides и не создаёт ссылок; пути превью совпадают с результатом применения того же источника; **переключение с титульного кандидата на нейронку/ безтайтловый источник не тянет чужие название/год** (preview и apply); нейронка-строка = раскладка без тега провайдера ## 2. Ручное добавление источника (worker + store + httpapi) - [ ] 2.1 Парсер ручного ввода в `httpapi`: id или URL записи метабазы → `(provider, provider_id)` (обратный к `providerURL`: themoviedb.org movie/tv, thetvdb, imdb); валидация на входной границе - [ ] 2.2 Метод worker «добавить источник вручную»: сохранить `metadata_candidate` (provider/id, опц. url; title/year пустые) с дедупом по `provider:id`, затем выбрать его (как `ChooseCandidate`) - [ ] 2.3 Обработчик POST добавления ручного источника + маршрут; отклонять некорректный ввод сообщением, не меняя активный источник - [ ] 2.4 Тесты: URL → provider+id; невалидный ввод отклонён; дубль id не плодит строку, а выбирает существующую ## 3. Страница ревью: единый список и предпросмотр (httpapi + шаблон) - [ ] 3.1 Переработать блок «Источник совпадения» в единый список строк: нейронка + кандидаты баз + ручной ввод; ровно один активный - [ ] 3.2 Рендер строк с предпросмотром полей (тип/название/год + пустое место под режиссёра) и целевых путей для каждого источника - [ ] 3.3 Клиентский vanilla-JS: переключение видимости предпосчитанного превью по выбору строки; без доменного пересчёта на клиенте - [ ] 3.4 Действия строк формами-раундтрипами: выбрать кандидата (`ChooseCandidate`), снять в пользу нейронки (`ClearProvider`), добавить вручную; сохранить прежние действия ревью - [ ] 3.5 Убедиться, что матч-ссылка и заголовок корректны для ручного кандидата с пустыми title/year ## 4. Проверка и приёмка - [ ] 4.1 `task test` и `task lint` зелёные - [ ] 4.2 Ручная проверка сценариев спеки: нейронка-строка, переключение кандидатов, снятие в пользу нейронки, ручной ввод по URL, предпросмотр без фиксации - [ ] 4.3 `openspec validate review-source-selection --strict` - [ ] 4.4 Обновить `docs/specs/review-ux.md` (источник истины по review-домену до миграции): единый список источников, ручное добавление, предпросмотр, объём Ф3/Ф5 — обязательно (поведение экрана меняется); убрать пункт беклога после archive