## 1. qbt — удаление раздачи - [x] 1.1 В `internal/qbt/qbt.go` добавить `Delete(ctx, hashes []string, deleteFiles bool) error`: `POST /api/v2/torrents/delete`, форма `hashes=

|

|…` + `deleteFiles`. Логирование `ExtCall{Operation:"torrents/delete"}` как у `Add`; 2xx/пустой ответ — успех, отсутствие хеша ошибкой не считать. - [x] 1.2 Тест `internal/qbt/qbt_test.go`: форма запроса delete (hashes через `|`, `deleteFiles=true`), обработка ответа. ## 2. layout — снятие ссылок без гарда последней копии - [x] 2.1 Добавить в `Layouter` и реализацию `internal/layout/layout.go` путь снятия целевых ссылок **без** `ErrLastCopy` (напр. `Remove(ctx, links)`): безусловный `unlink` только своих ссылок строго под `paths.movies`/`series`; санитизация/проверка «под библиотекой» сохраняется, `paths.downloads` не трогаем. ## 3. worker — команда Delete - [x] 3.1 Расширить интерфейс `QBittorrent` воркера методом `Delete(...)` (`internal/worker/worker.go`); проброс реализации `qbt.Client`. - [x] 3.2 В `internal/worker/review.go` добавить `Delete(ctx, id string) error`: под `w.mu`; `GetDownload`; предусловие `d.State ∈ {done, orphaned, target_missing}`, иначе `ErrConflict`. Порядок: (а) снять цель как в `Undo` (`LatestBatchID`/`ListFileLinksByBatch`/фильтр `isLaidOut`, `superseded` пропускаем) через новый `layouter.Remove`, затем `DeleteFileLinksByBatch`; (б) `qbt.Delete(d.Infohashes, deleteFiles=true)` (хеши уже загружены с задачей, отдельный store-геттер не нужен) — ошибку присутствующей раздачи пробросить (не метить `deleted`), отсутствие раздачи ошибкой не считать; (в) `transition(ctx, d, StateDeleted, "user_delete", <причина>)`. Delete source-preflight (`ensureSourceReady`/`reconcileToReality`) НЕ вызывает — толерантен к отсутствию источника. - [x] 3.3 Логировать удаление как осознанное пользовательское снятие источника (`slog`, без секретов): id, состояние-источник, снятые ссылки, `code=user_delete`. ## 4. Транспорт HTTP/веб + подтверждение - [x] 4.1 Роут `POST /ui/downloads/{id}/delete` → хендлер (по образцу `handleUndo`), финализация через `surfaceAction`; трансляция `ErrConflict` → 409/фрагмент как у прочих действий, а ошибка qBittorrent (не `ErrConflict`) — внятный отказ пользователю (не тихий успех), не 200-без-эффекта. - [x] 4.2 Флаг `Deletable` (состояние ∈ {done, orphaned, target_missing}) в расчётах кнопок (`httpapi.go:648`, `download.go:108`). - [x] 4.3 Danger-секция в самом низу `web/templates/partials/download_main.html`: кнопка «Удалить» → confirm (htmx-своп фрагмента «Точно удалить? … [Да, удалить] [Отмена]») → `POST …/delete`. Деградация без htmx (`
`/отдельный шаг), ошибка на htmx-пути = 200 + фрагмент. - [ ] 4.4 (Опц., НЕ в этом change) JSON-роут `POST /api/downloads/{id}/delete`. Осознанно отложено: REST в спеке помечен опциональным, а деструктивный endpoint без естественного шага подтверждения заводим только по реальной нужде. ## 5. Транспорт Telegram + подтверждение - [x] 5.1 В `internal/tgbot` добавить действие `delete`: callback `delete:` показывает confirm-keyboard (`delete_confirm:` / «Отмена»); удаление — на подтверждающем callback (`Reviewer.Delete`). Кнопка «Удалить» — в карточке задачи для состояний done/orphaned/target_missing. - [x] 5.2 Ветка ошибок как у прочих действий (`ErrConflict` → понятный отказ; ошибка qBittorrent → отдельное сообщение об отказе, не тихий успех), после успеха — `answer` + `refreshCard`. ## 6. Тесты воркера - [x] 6.1 Delete из `done`: сняты ссылки + вызван `qbt.Delete(deleteFiles=true)` + переход `deleted`/`user_delete`. - [x] 6.2 Delete из `orphaned`: последняя копия снята (гард выключен), отсутствие раздачи не ошибка, переход `deleted`. - [x] 6.3 Delete из `target_missing`: снятие цели идемпотентно (нет живых ссылок), раздача снесена, переход `deleted`. - [x] 6.4 Delete из `review`/иного — отказ `ErrConflict`, состояние не меняется. - [x] 6.5 Ошибка `qbt.Delete` при присутствующей раздаче — задача остаётся, в `deleted` не уходит; повторный delete идемпотентен. ## 7. Ревью и сверка - [x] 7.1 `task test` и `task lint` зелёные. - [x] 7.2 Ревью кода (второй чекпоинт) перед archive. - [x] 7.3 `openspec validate download-delete --strict` зелёный. - [x] 7.4 Синк нарратива `docs/specs/workflow.md`: явные пользовательские рёбра `done/orphaned/target_missing → deleted` (delete) и различие Undo vs delete; удалить `docs/backlog/udalenie-edinoe-okno.md` и строку из индекса беклога.