## MODIFIED Requirements ### Requirement: Приём источника и заведение загрузки Приём SHALL быть единым **быстрым** use-case, общим для всех транспортов (HTTP, Telegram, CLI): по источнику (Ф1 — magnet) и текстовому контексту система SHALL синхронно извлечь инфохэши, синтезировать контекст из полей ссылки (без сети), дедуплицировать по активной задаче и при отсутствии дубля завести загрузку (`download` в состоянии **`catched`** + записи `download_infohash`), после чего **сразу вернуть ответ** транспорту. Заведение загрузки и запись её хешей SHALL выполняться атомарно (см. «Атомарность возврата загрузки в активное состояние»). Синхронный путь приёма SHALL NOT обращаться к qBittorrent и SHALL NOT выводить отображаемое имя (потенциально медленный LLM): и добавление источника в qBittorrent, и вывод имени выполняются отдельным асинхронным шагом машины состояний (worker) — см. `download-tracking` «Добавление пойманной загрузки в qBittorrent». `catched` — нетерминальное активное состояние: оно участвует в инварианте «не более одной активной загрузки на infohash» наравне с прочими активными. #### Scenario: Быстрый приём magnet - **GIVEN** валидная magnet-ссылка и контекст - **WHEN** вызывается приём - **THEN** создаётся `download` в состоянии `catched` с записями `download_infohash` - **AND** ответ транспорту отдан без обращения к qBittorrent и без вывода имени #### Scenario: Дубль по активной задаче на быстром пути - **GIVEN** уже есть активная (в т.ч. `catched`) загрузка с тем же infohash - **WHEN** вызывается приём - **THEN** новая загрузка не создаётся, возвращается существующая ### Requirement: Отображаемое имя торрента из контекста На шаге добавления пойманной загрузки в qBittorrent (worker) система SHALL выводить из контекста загрузки человекочитаемое отображаемое имя и передавать его в qBittorrent (параметр `rename` API `/torrents/add`), чтобы задача в списке qBit не показывалась безликим `dn` magnet-ссылки. Это же имя система SHALL **сохранять у загрузки** (`download.display_name`) для последующего показа заголовком в веб-UI. Имя SHALL быть коротким читаемым ярлыком (название, опционально режиссёр и год; для сериала — номер сезона, если он определён), а не куском сырого контекста. Имя SHALL очищаться от управляющих символов и переводов строк и SHALL обрезаться по ограничению длины. Вывод имени SHALL выполняться на шаге добавления, непосредственно перед вызовом `add` (параметр `rename` действует только в момент добавления), а НЕ в синхронном пути ответа приёма. В состоянии `catched` (до добавления) `download.display_name` ещё пуст — веб-UI берёт заголовок из фолбека. Отображаемое имя SHALL влиять только на отображение (в qBittorrent и как заголовок в веб-UI) и SHALL NOT влиять на пути файлов на диске, распознавание или раскладку — реальные пути система по-прежнему читает из qBit API. #### Scenario: Имя из контекста передаётся в qBittorrent - **WHEN** на шаге добавления получен непустой контекст, из которого удалось вывести имя - **THEN** система передаёт это имя в qBittorrent в параметре `rename` - **AND** имя — короткий читаемый ярлык вида «название (режиссёр, год)», где режиссёр и год опциональны #### Scenario: Имя сохраняется у загрузки - **WHEN** на шаге добавления выведено непустое отображаемое имя - **THEN** система сохраняет его в `download.display_name` (обновлением записи загрузки) - **AND** веб-UI использует его заголовком карточки и страницы загрузки #### Scenario: Контекст пуст или имя не получено - **WHEN** контекста нет либо ни один способ вывода не дал непустого имени - **THEN** система добавляет загрузку без параметра `rename` - **AND** qBittorrent оставляет собственное имя (из `dn`/торрента) - **AND** `download.display_name` остаётся пустым, а веб-UI берёт заголовок из фолбека (распознанное название или усечённый источник)