## Context Экран ревью (`web/templates/review.html` + `internal/httpapi/review.go`) сейчас состоит из трёх секций: - **Догадка** — тип (переключатель movie↔series, POST `/type`), название, год; - **Источник совпадения** — единый список вариантов (`.Sources`); у каждого не-активного варианта — нативный `
` с предпросмотром раскладки, и отдельная кнопка «выбрать» (POST `/candidate` или `/nobase`); ниже — форма ручного добавления (POST `/source`); - **Раскладка** — та же таблица предпросмотра, но для активного источника. Все действия — обычные `
` c PRG-редиректом (`reviewAction` → `redirectReview`, `303`). htmx подключён (`review.html:8`), но на странице ревью не используется; на других страницах он уже применяется для фрагментов (`hx-get .../progress`, `every 3s`). Домен уже отдаёт всё нужное: `worker.ReviewData` строит `Sources []SourceOption` (нейронка + кандидаты), у каждого — эффективные поля и эфемерный предпросмотр; верхнеуровневые поля `reviewView` (`Title/Year/Files/...`) уже соответствуют **активному** источнику. Инвариант «превью == применённое» обеспечивается тем, что выбор источника пишет те же пины, что показаны в превью. ## Goals / Non-Goals **Goals:** - Слить три секции в один блок: список вариантов (радио) → инфо о выбранном → предпросмотр раскладки выбранного. - Выбор варианта — одним кликом/тапом по строке; инфо и предпросмотр обновляются немедленно, без полной перезагрузки (htmx частичный своп блока). - Тип показывать read-only; убрать переключатель типа с веб-экрана. - Сохранить: ручное добавление источника, инвариант «превью == применённое», тонкость транспорта (доменная логика в `worker` не трогается). **Non-Goals:** - Менять доменный слой `internal/worker/review.go` (выбор источника, построение плана/превью остаются как есть). - Менять команду `SetType` в домене и её доступность в Telegram (убираем только веб-контрол). - Клиентский рефреймворк/сборка. Оптимизация «не считать превью для не-активных источников» — отдельная будущая задача, не входит сюда. ## Decisions ### Решение 1: htmx частичный своп единого блока (не полная перезагрузка) Выделяем единый блок в партиал `web/templates/partials/review_source_block.html` с корневым контейнером `id="source-block"`. Партиал рендерится из того же `reviewView`: список радио из `.Sources`, инфо — из верхнеуровневых полей активного источника (`.Title/.OriginalTitle/.Year/.IsSeries/.SeasonSummary`), предпросмотр — из `.Files`. Радиокнопка варианта несёт htmx-атрибуты: `hx-trigger="change"`, `hx-target="#source-block"`, `hx-swap="outerHTML"` и `hx-post` на эндпоинт выбора. Клик по строке (label оборачивает кликабельную зону строки) переключает радио → `change` → POST → сервер сохраняет выбор и возвращает **свежий партиал блока** → htmx подменяет блок. Инфо и предпросмотр в новом партиале уже относятся к новому активному источнику. Все радио вариантов имеют **общий `name`** для взаимной эксклюзивности; кандидатские несут `value`=`candidate_id` и постят на `/candidate`, нейронка — пустое `value` и постит на `/nobase` (тот `candidate_id` игнорирует). **Внешняя ссылка «запись ↗»** у кандидата (открывается в новой вкладке) НЕ должна попадать в кликабельную зону label — иначе клик по ссылке заодно переключит источник. Выносим ссылку из `