Единое окно: полное пользовательское удаление загрузки (delete)

Вторая половина «единого окна»: команда «Удалить» снимает наши библиотечные
хардлинки (гард последней копии осознанно выключен, в отличие от Undo) и сносит
раздачу с файлами из qBittorrent (deleteFiles=true) → терминальный deleted.
Доступна из done/orphaned/target_missing, идемпотентна к отсутствующей стороне;
инициатор различается через error_code=user_delete. Подтверждение обязательно:
веб — danger-секция внизу страницы (hx-confirm + details), Telegram — двухшаговый
inline-confirm. qbt.Delete + layout.Remove (unlink без ErrLastCopy, только свои
ссылки под movies/series). Граф переходов не менялся — рёбра уже были.

OpenSpec: state-reconciliation +1 требование; синк workflow.md; беклог закрыт.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-10 12:17:51 +03:00
co-authored by Claude Opus 4.8
parent e68f96df9e
commit 3f1a928000
25 changed files with 893 additions and 76 deletions
+14 -4
View File
@@ -255,10 +255,12 @@ func (f *fakeStore) GetCandidate(_ context.Context, _ string) (*store.MetadataCa
func (f *fakeStore) SetCandidateChosen(_ context.Context, _, _ string) error { return nil }
type fakeQbt struct {
torrents []qbt.Torrent
added []qbt.AddRequest
addErr error
files []qbt.File
torrents []qbt.Torrent
added []qbt.AddRequest
addErr error
files []qbt.File
deleted [][]string // хеши каждого вызова Delete
deleteErr error
}
// Torrents имитирует /torrents/info: пустая категория — все торренты, иначе
@@ -290,6 +292,14 @@ func (f *fakeQbt) Files(_ context.Context, _ string) ([]qbt.File, error) {
return f.files, nil
}
func (f *fakeQbt) Delete(_ context.Context, hashes []string, _ bool) error {
if f.deleteErr != nil {
return f.deleteErr
}
f.deleted = append(f.deleted, hashes)
return nil
}
func newTestWorker(st *fakeStore, qb *fakeQbt) *Worker {
w := New(st, qb, nil, nil, Config{
Category: "jellybit",