## MODIFIED Requirements ### Requirement: Команды ревью и их эффекты Экран ревью SHALL предоставлять команды: **Применить** (создать хардлинки по эффективному плану), **Уточнить** (добавить подсказку → перераспознать), **Распознать заново** (повторный прогон без новой подсказки), **Игнор файла**, **Позже** (`deferred`), **Отклонить** (`cancelled`), **Undo** (снять созданные ссылки → `reverted`) и **Привязать заново** (из `reverted`/`cancelled`/`target_missing` → перераспознавание с ручным подтверждением). Экран ревью MUST NOT содержать команду переключения типа movie↔series: тип показывается read-only, а его корректировка выполняется мягкой подсказкой через **Уточнить**. Команды из любого транспорта SHALL сериализоваться worker'ом под единой блокировкой; применяется последняя валидная команда. Команда **Позже** (`Defer`) SHALL парковать задачу в `deferred` из любого не-терминального состояния, у которого уже есть раздача в qBittorrent, и SHALL отклонять её из **пре-источникового** состояния `catched` (торрент ещё НЕ добавлен в qBittorrent) — конфликтом (`ErrConflict`) с понятным пользователю сообщением, НЕ меняя состояние загрузки. Пре-источниковое `catched` — единственное состояние без раздачи среди не-терминальных: откладывать в нём нечего (задача ещё не дошла до ревью), а `catched → deferred` уводил бы задачу в лимбо — `processCatched` листает только `catched` и больше её не подхватит, а последующие команды через отсутствие источника выводят необратимый `deleted`. Терминальные состояния Defer SHALL отклонять как и прежде (`ErrConflict`). Команды, которым нужен источник (**Применить**, **Уточнить**, **Распознать заново**, **Привязать заново**, а также фиксация типа), SHALL синхронно (без дебаунса) проверять перед действием, что источник не только присутствует в qBittorrent, но и **готов к раскладке** — раздача в готовом классе состояния (`uploading`/`stalledUP`/`pausedUP`/… с учётом различий имён qBit v4/v5), т.е. файлы докачаны. Если источник ещё качается (любое `downloading`-подобное или переходное `moving`/`checking` состояние), команда SHALL отказывать с конфликтом и причиной «торрент ещё качается», НЕ создавая хардлинки и НЕ меняя состояние загрузки (её нахождение в `review`/`deferred`/… легитимно, приводить к реальности нечего). Отсутствие источника в qBittorrent SHALL по-прежнему приводить состояние к реальности (`orphaned`/`deleted`) и отказывать. Так недокачанная задача не может пройти через перераспознавание в авто-раскладку или ручное применение и захардлинкать неполные файлы, обойдя финальность состояния `completed`. #### Scenario: Применение создаёт раскладку - **GIVEN** загрузка в `review` с эффективным планом - **WHEN** пользователь выбирает «Применить» - **THEN** создаются хардлинки по плану, задача переходит к раскладке #### Scenario: Отклонить и привязать заново - **GIVEN** загрузка в `review` - **WHEN** пользователь «Отклонить», затем «Привязать заново» - **THEN** задача уходит в `cancelled`, а затем снова на распознавание с ручным подтверждением (авто-раскладка не делается) #### Scenario: Тип не переключается кнопкой - **GIVEN** загрузка в `review` с распознанным типом - **WHEN** пользователь открывает экран ревью - **THEN** отдельной команды/кнопки переключения movie↔series на экране нет - **AND** тип показан read-only в инфо-части выбранного источника #### Scenario: Позже паркует задачу из ревью - **GIVEN** загрузка в `review` (раздача в qBittorrent уже есть) - **WHEN** пользователь выбирает «Позже» - **THEN** задача переходит в `deferred` и возвращается на поверхность ревью по любому последующему действию #### Scenario: Позже отклоняется для пре-источникового catched - **GIVEN** загрузка в `catched` (торрент ещё не добавлен в qBittorrent) - **WHEN** приходит команда «Позже» (`Defer`, напр. прямым POST на `/ui/downloads/{id}/defer`) - **THEN** команда отклоняется конфликтом с понятным сообщением, что отложить можно только после добавления торрента - **AND** загрузка остаётся в `catched` и штатно доходит до `downloading` через `processCatched` #### Scenario: Недокачанный источник отклоняет перераспознавание - **GIVEN** загрузка припаркована в `deferred`, а её раздача в qBittorrent ещё качается (`downloading`, файлы не докачаны) - **WHEN** пользователь выбирает «Распознать заново» (или «Уточнить»/«Привязать заново»/фиксацию типа) - **THEN** команда отклоняется с конфликтом и причиной «торрент ещё качается» - **AND** загрузка остаётся в `deferred`, хардлинки не создаются, авто-раскладка не запускается #### Scenario: Недокачанный источник отклоняет ручное применение - **GIVEN** загрузка в `review`, чья раздача в qBittorrent ещё качается - **WHEN** пользователь выбирает «Применить» - **THEN** команда отклоняется с конфликтом «торрент ещё качается», хардлинки на неполные файлы не создаются, состояние загрузки не меняется