Реализовал выбор источника и предпросмотр в ревью (review-source-selection)

Экран ревью теперь показывает единый список источников совпадения: строка
«распознано нейронкой» наравне с кандидатами баз; выбор/переключение/снятие
в пользу нейронки; ручное добавление по id или URL (TMDB/IMDb — по URL,
TVDB — по числовому id); предпросмотр полей и целевых путей каждого источника
до применения (место под режиссёра зарезервировано). «Раскладка» осталась
отдельной секцией для активного источника, инлайн-превью неактивных — по клику.

Ядро: единая деривация «источник → overrides» (sourcePins), общая для превью
и коммита → preview == apply; заодно чинит латентный залипший override
title/year при переключении источника. Превью считается эфемерно, без записи
в БД; пользовательский URL только парсится (SSRF нет).

Ревью дизайна и кода пройдены; правки ревьюеров учтены (сообщение об ошибке
ручного ввода доходит до пользователя, URL без схемы принимается).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-03 10:29:27 +03:00
co-authored by Claude Opus 4.8
parent 3d3448d050
commit aac3ced262
10 changed files with 688 additions and 104 deletions
@@ -1,21 +1,21 @@
## 1. Ядро: эфемерный предпросмотр источника (worker)
- [ ] 1.1 Ввести чистую деривацию «источник → полный самосогласованный набор
- [x] 1.1 Ввести чистую деривацию «источник → полный самосогласованный набор
overrides» (кандидат с title/year → пиннит; без — пустая строка в
title/year; нейронка → provider=none + пустые title/year) и
переиспользовать её в превью и в коммите — гарантия preview == apply
(решение 1a design.md)
- [ ] 1.2 Привести команды выбора к этой деривации: `ChooseCandidate` у
- [x] 1.2 Привести команды выбора к этой деривации: `ChooseCandidate` у
безтайтлового кандидата и `ClearProvider` очищают `ovrTitle`/`ovrYear`
(пустой строкой) — чинит латентный залипший override
- [ ] 1.3 Выделить чистый расчёт «план + набор overrides источника → поля +
- [x] 1.3 Выделить чистый расчёт «план + набор overrides источника → поля +
`[]layout.Link`» из логики `ReviewData` (переиспользуя `applyOverrides` +
`toLayoutPlan` + `layouter.BuildLinks`), без записи overrides в БД
- [ ] 1.4 Расширить `ReviewData` срезом источников: нейронка (синтетическая
- [x] 1.4 Расширить `ReviewData` срезом источников: нейронка (синтетическая
строка `provider=none`) + каждый кандидат; для каждого — эффективные
поля (тип/название/год), превью путей и признак «активен»; дедуп
источников по `provider:id` (нейронка — отдельная строка)
- [ ] 1.5 Тесты worker: превью для неактивного источника не пишет overrides
- [x] 1.5 Тесты worker: превью для неактивного источника не пишет overrides
и не создаёт ссылок; пути превью совпадают с результатом применения
того же источника; **переключение с титульного кандидата на нейронку/
безтайтловый источник не тянет чужие название/год** (preview и apply);
@@ -23,46 +23,46 @@
## 2. Ручное добавление источника (worker + store + httpapi)
- [ ] 2.1 Парсер ручного ввода в `httpapi`: id или URL записи метабазы →
- [x] 2.1 Парсер ручного ввода в `httpapi`: id или URL записи метабазы →
`(provider, provider_id)` (обратный к `providerURL`: themoviedb.org
movie/tv, thetvdb, imdb); валидация на входной границе
- [ ] 2.2 Метод worker «добавить источник вручную»: сохранить
- [x] 2.2 Метод worker «добавить источник вручную»: сохранить
`metadata_candidate` (provider/id, опц. url; title/year пустые) с
дедупом по `provider:id`, затем выбрать его (как `ChooseCandidate`)
- [ ] 2.3 Обработчик POST добавления ручного источника + маршрут; отклонять
- [x] 2.3 Обработчик POST добавления ручного источника + маршрут; отклонять
некорректный ввод сообщением, не меняя активный источник
- [ ] 2.4 Тесты: URL → provider+id; невалидный ввод отклонён; дубль id не
- [x] 2.4 Тесты: URL → provider+id; невалидный ввод отклонён; дубль id не
плодит строку, а выбирает существующую
## 3. Страница ревью: единый список и предпросмотр (httpapi + шаблон)
- [ ] 3.1 Переработать блок «Источник совпадения» в единый список строк из
- [x] 3.1 Переработать блок «Источник совпадения» в единый список строк из
`[]SourceOption`: нейронка (первой) + кандидаты баз + ручной ввод;
ровно один активный, у активного — матч-ссылка (как сейчас)
- [ ] 3.2 Строка источника: шапка (провайдер/название/год + маркер активного
- [x] 3.2 Строка источника: шапка (провайдер/название/год + маркер активного
+ кнопка «выбрать») и **раскрываемый по клику** блок предпросмотра —
поля (тип/название/год + пустое место под режиссёра) и целевые пути;
данные всех источников предпосчитаны сервером и вложены в страницу
- [ ] 3.3 Секцию «Раскладка» оставить отдельной — показывает пути **активного**
- [x] 3.3 Секцию «Раскладка» оставить отдельной — показывает пути **активного**
источника (как сейчас); инлайн-превью в строках — только для сравнения
неактивных до выбора
- [ ] 3.4 Клиентский vanilla-JS: раскрытие/скрытие предпосчитанного превью
- [x] 3.4 Клиентский vanilla-JS: раскрытие/скрытие предпосчитанного превью
строки по клику (нативный `<details>` или минимальный JS); без
доменного пересчёта на клиенте
- [ ] 3.5 Действия строк формами-раундтрипами: выбрать кандидата
- [x] 3.5 Действия строк формами-раундтрипами: выбрать кандидата
(`ChooseCandidate`), снять в пользу нейронки (`ClearProvider`),
добавить вручную; сохранить прежние действия ревью
- [ ] 3.6 Убедиться, что матч-ссылка и заголовок строки корректны для ручного
- [x] 3.6 Убедиться, что матч-ссылка и заголовок строки корректны для ручного
кандидата с пустыми title/year (деградация до «провайдер + id»)
## 4. Проверка и приёмка
- [ ] 4.1 `task test` и `task lint` зелёные
- [ ] 4.2 Ручная проверка сценариев спеки: нейронка-строка, переключение
- [x] 4.1 `task test` и `task lint` зелёные
- [x] 4.2 Ручная проверка сценариев спеки: нейронка-строка, переключение
кандидатов, снятие в пользу нейронки, ручной ввод по URL, предпросмотр
без фиксации
- [ ] 4.3 `openspec validate review-source-selection --strict`
- [ ] 4.4 Обновить `docs/specs/review-ux.md` (источник истины по review-домену
- [x] 4.3 `openspec validate review-source-selection --strict`
- [x] 4.4 Обновить `docs/specs/review-ux.md` (источник истины по review-домену
до миграции): единый список источников, ручное добавление, предпросмотр,
объём Ф3/Ф5 — обязательно (поведение экрана меняется); убрать пункт
беклога после archive