Files
jellybit/openspec/specs/review/spec.md
T
avandClaude Opus 4.8 1f4267a046 Ревью: единый блок выбора источника, клик = выбор (review-unified-source-block)
Три секции экрана ревью (Догадка/Источник/Раскладка) слиты в один блок:
список вариантов (радио) → инфо о выбранном → предпросмотр раскладки. Клик
по варианту сразу выбирает и сохраняет источник и обновляет инфо+раскладку
частичным htmx-свопом блока, без полной перезагрузки и без кнопки «выбрать».

- httpapi: reviewBlockAction (htmx-aware, детект HX-Request) для
  candidate/nobase/source; вынос buildReviewView; поля SeasonSummary и
  BlockError; сводка сезонов (seasonSummary/seasonRanges)
- тип movie↔series убран из UI (read-only); удалён веб-роут /type и
  handleSetType, метод SetType из интерфейса httpapi (worker/Telegram не тронуты)
- шаблон: партиал review_source_block, ссылка «запись ↗» вне кликабельного
  label, фокус радио с клавиатуры; чистка мёртвого sourceView.Files/IsSeries
- тесты: htmx-своп выбора, htmx-путь ошибки, юнит-тесты сводки сезонов
- openspec: спеки review/web-ui синхронизированы, change заархивирован
- беклог: сложные сериальные раздачи; oob-обновление панели действий

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 08:12:28 +03:00

203 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# review Specification
## Purpose
Ревью раскладки человеком после распознавания и матча: петля «догадка →
подсказка → перераспознавание», команды (Применить/Уточнить/Распознать заново/
Игнор/Позже/Отклонить/Undo/Привязать заново; тип — read-only, корректируется
через «Уточнить»), мягкие подсказки vs жёсткие
`override`, единый список источников совпадения с ручным добавлением и
предпросмотром (превью = применение), разделение труда транспортов (веб —
точные правки, Telegram — быстрые действия и эскалация в веб).
## Requirements
### Requirement: Вход в review с явной причиной
Когда модель уверенности не разрешает авто-раскладку, система SHALL переводить
загрузку в `review` и SHALL показывать **конкретную причину** (низкая самооценка
LLM; нет матча в базе или несколько кандидатов; предупреждение структурной
валидации; неразобранный ответ LLM), а не обобщённое «не уверен». Поверхность
решения SHALL быть единой для всех транспортов и содержать источник (имя, контекст,
дерево файлов), догадку системы (тип, название, год, матч) и превью целевой
раскладки.
#### Scenario: Причина видна в интерфейсе
- **GIVEN** загрузка ушла в `review` из-за отсутствия матча в базе
- **WHEN** пользователь открывает ревью
- **THEN** показана конкретная причина (напр. «нет в TMDB · уверенность 0.46»)
### Requirement: Команды ревью и их эффекты
Экран ревью SHALL предоставлять команды: **Применить** (создать хардлинки по
эффективному плану), **Уточнить** (добавить подсказку → перераспознать),
**Распознать заново** (повторный прогон без новой подсказки), **Игнор файла**,
**Позже** (`deferred`), **Отклонить** (`cancelled`), **Undo** (снять созданные
ссылки → `reverted`) и **Привязать заново** (из
`reverted`/`cancelled`/`target_missing` → перераспознавание с ручным
подтверждением). Экран ревью MUST NOT содержать команду переключения типа
movie↔series: тип показывается read-only, а его корректировка выполняется
мягкой подсказкой через **Уточнить**. Команды из любого транспорта SHALL
сериализоваться worker'ом под единой блокировкой; применяется последняя валидная
команда. Команды, которым нужен источник, SHALL проверять его наличие синхронно
перед действием.
#### Scenario: Применение создаёт раскладку
- **GIVEN** загрузка в `review` с эффективным планом
- **WHEN** пользователь выбирает «Применить»
- **THEN** создаются хардлинки по плану, задача переходит к раскладке
#### Scenario: Отклонить и привязать заново
- **GIVEN** загрузка в `review`
- **WHEN** пользователь «Отклонить», затем «Привязать заново»
- **THEN** задача уходит в `cancelled`, а затем снова на распознавание с ручным
подтверждением (авто-раскладка не делается)
#### Scenario: Тип не переключается кнопкой
- **GIVEN** загрузка в `review` с распознанным типом
- **WHEN** пользователь открывает экран ревью
- **THEN** отдельной команды/кнопки переключения movie↔series на экране нет
- **AND** тип показан read-only в инфо-части выбранного источника
### Requirement: Подсказка мягкая, override жёсткий
Подсказка (`hint`) SHALL быть мягким сигналом — её интерпретирует LLM при
перераспознавании. Ручная правка поля SHALL быть жёстким **override**: система
берёт значение как есть и «пиннит» его; перераспознавание НЕ SHALL затирать уже
поправленное поле. Накопленные подсказки и правки SHALL переживать
перераспознавание и накладываться на новый план.
#### Scenario: Override переживает перераспознавание
- **GIVEN** пользователь зафиксировал тип `series` как override
- **WHEN** запускается перераспознавание по новой подсказке
- **THEN** в новом эффективном плане тип остаётся `series`
### Requirement: Единый список источников совпадения на ревью
Экран ревью (`/review/{id}`) SHALL показывать совпавшие источники **единым
списком**, в котором распознавание нейронкой (без базы) — такая же строка,
как кандидаты метабаз (TMDB/TVDB/TVMaze), а не отдельный режим сверху.
Ровно один источник в списке SHALL быть отмечен активным (эффективный
матч). Активный источник SHALL выбираться **одним кликом/тапом по строке
варианта** (радиокнопка), без отдельной кнопки подтверждения выбора. Выбор
источника SHALL сохранять его как эффективный матч (persist) и SHALL
выполняться через раундтрип на сервер (форма/htmx), без клиентского пересчёта
доменного состояния; при этом инфо-часть и предпросмотр раскладки SHALL
немедленно обновляться под выбранный источник (частичный своп блока, без полной
перезагрузки страницы). Экран SHALL позволять операции над этим списком:
выбрать кандидата базы, переключиться на другого кандидата и снять матч с базы
обратно на нейронку («без базы»). Список источников SHALL показываться только
при наличии плана распознавания.
#### Scenario: Нейронка — строка в общем списке
- **GIVEN** загрузка в `review` с распознаванием нейронкой и одним или
несколькими кандидатами метабаз
- **WHEN** пользователь открывает `GET /review/{id}`
- **THEN** источники показаны единым списком, где строка «распознано
нейронкой» стоит наравне с кандидатами баз
- **AND** активным отмечен ровно один источник (текущий эффективный матч)
#### Scenario: Выбор кандидата одним кликом
- **GIVEN** на экране ревью выбран один кандидат метабазы
- **WHEN** пользователь кликает/тапает строку другого кандидата
- **THEN** выбранный кандидат сохраняется активным, прочие — неактивны, без
отдельного нажатия кнопки «выбрать»
- **AND** инфо-часть и предпросмотр раскладки сразу обновляются под выбранного
кандидата без полной перезагрузки страницы
#### Scenario: Снятие матча в пользу нейронки
- **GIVEN** на экране ревью активен кандидат метабазы с названием «Fargo»
- **WHEN** пользователь кликает строку «распознано нейронкой»
- **THEN** матч с базой снимается (источник — нейронка, «без базы»), тег
папки провайдера не проставляется
- **AND** поля источника — из распознавания нейронкой, без унаследованных
от прежнего кандидата название/год
### Requirement: Ручное добавление источника по id или URL
Когда автопоиск по базам промахнулся, экран ревью SHALL позволять добавить
источник вручную — по идентификатору записи метабазы или, где применимо, по
её URL. Ввод SHALL разбираться и валидироваться в пару
`(provider, provider_id)` на входной границе (`internal/httpapi`); допустимые
провайдеры — `tmdb`, `tvdb`, `imdb`. Добавленный источник SHALL появляться в
списке как выбираемая строка; при совпадении `provider:id` с уже присутствующим
источником новая строка NOT создаётся, а выбирается существующая.
Некорректный ввод SHALL отклоняться с сообщением, не меняя текущий активный
источник.
#### Scenario: Добавление кандидата по URL TMDB
- **GIVEN** загрузка в `review`, где нужной записи нет среди автокандидатов
- **WHEN** пользователь вводит URL записи TMDB и подтверждает добавление
- **THEN** из URL извлекаются провайдер и id, источник добавляется в список
выбираемой строкой
#### Scenario: Дубль id выбирает существующую строку
- **GIVEN** в списке уже есть кандидат с данным `provider:id`
- **WHEN** пользователь добавляет вручную тот же `provider:id`
- **THEN** новая строка не создаётся, активным становится существующий
кандидат
#### Scenario: Некорректный ввод отклонён
- **WHEN** пользователь вводит нераспознаваемый id/URL
- **THEN** экран показывает сообщение об ошибке и не меняет текущий активный
источник
### Requirement: Инфо и предпросмотр выбранного источника
В едином блоке выбора источника экран ревью SHALL показывать для **выбранного
(активного)** источника две части: **инфо** — тип (read-only, movie/series),
название, оригинальное название, год, для сериала — сводку сезонов (один сезон,
диапазон/список для многосезонного пака или «Спецвыпуски»), с
зарезервированным местом под режиссёра; и **предпросмотр раскладки** — целевые
пути хардлинков этого источника. Обе части SHALL относиться именно к активному
источнику и SHALL обновляться при смене выбора. Отрисовка блока (показ инфо и
предпросмотра) MUST NOT создавать хардлинки: раскладка создаётся только явным
действием «Применить». Совпадение целевых путей предпросмотра с результатом
применения регулируется требованием «Превью раскладки через единую логику
именования» (`web-ui`).
#### Scenario: Инфо и предпросмотр относятся к активному источнику
- **GIVEN** в списке активен кандидат метабазы
- **WHEN** пользователь смотрит инфо-часть и предпросмотр раскладки
- **THEN** показаны тип, название, ориг. название, год (и сводка сезонов для
сериала) именно этого источника и предпросмотр его целевых путей
#### Scenario: Просмотр блока не создаёт раскладку
- **GIVEN** экран ревью с показанным блоком выбора источника
- **WHEN** пользователь только просматривает инфо и предпросмотр, не нажимая
«Применить»
- **THEN** хардлинки не создаются, файлы под `paths.movies`/`series` не
меняются
#### Scenario: Зарезервированное место под режиссёра
- **GIVEN** режиссёр из метабазы пока не загружается
- **WHEN** отображается инфо-часть выбранного источника
- **THEN** в ней присутствует место под режиссёра, показанное пустым (или
прочерком), не ломая вёрстку
### Requirement: Разделение труда транспортов в ревью
Состояние ревью SHALL быть единым (в SQLite) для всех транспортов. Веб-UI SHALL
быть поверхностью точных правок (маппинг файлов, выбор/ввод источника,
предпросмотр). Telegram SHALL давать быстрые действия (одобрить, подсказать,
переключить тип, «Позже»/«Отклонить») и эскалацию в веб deep-link'ом на ту же
страницу; точечные правки, не помещающиеся в чат, SHALL делаться в вебе.
#### Scenario: Эскалация из Telegram в веб
- **GIVEN** загрузка в `review`, требующая точечного маппинга файлов
- **WHEN** пользователь в Telegram выбирает «В вебе»
- **THEN** бот даёт deep-link на страницу ревью той же загрузки