web-ui: добавлена страница группового удаления загрузок

- выбор → поимённое подтверждение → отчёт: пачка до 20 загрузок, гарды входа
  на обеих границах, потолок времени и остановка после трёх подряд отказов
  внешнего сервиса
- допуск полного удаления сведён в единую точку store.State.CanDelete() —
  worker, страница загрузки и Telegram больше не держат своих перечней
This commit is contained in:
av
2026-08-10 17:43:36 +03:00
parent a7c1efd8eb
commit 288be8ec34
35 changed files with 3185 additions and 31 deletions
@@ -455,6 +455,12 @@ SHALL задевать раскладку в полёте.
целевые библиотечные хардлинки И раздачу с файлами в qBittorrent — и переводить
задачу в терминальное `deleted`. Из прочих состояний команда доступна SHALL NOT.
Перечень состояний, из которых удаление допустимо, SHALL иметь **единственный
дом** — предикат состояния в модели данных. Транспорты, решающие, показывать
действие или нет, SHALL опираться на него, а не на собственный список. Проверку
допуска в ядре это SHALL NOT отменять: транспорт решает, что показать, ядро — что
допустить, и допуск SHALL держаться без транспорта.
Снятие цели SHALL идти по механике снятия ссылок последнего батча (как в `Undo`:
`superseded` пропускаются как забранные другой загрузкой), но **отдельным путём с
выключенным** гардом последней копии — не переиспользуя guarded-`Undo`: в отличие
@@ -467,6 +473,13 @@ SHALL задевать раскладку в полёте.
`paths.downloads` система сама трогать SHALL NOT — их удаляет qBittorrent по
вызову API с `deleteFiles=true`.
Подтверждение SHALL быть допустимо **одно на пачку** загрузок, когда транспорт
даёт групповое удаление: ослаблением требования это не является, если
подтверждение называет каждую загрузку пачки поимённо. Условия допустимости
групповой путь смягчать SHALL NOT — каждая загрузка пачки проходит те же
проверки и тот же отказ по конфликту, что и при поштучном удалении, а отказ на
одной остальных отменять SHALL NOT.
В отличие от прочих команд, требующих источника, delete синхронный source-preflight
выполнять SHALL NOT и под требование «Принудительная проверка источника/цели перед
действием» не подпадает: цель delete — снять источник, поэтому его отсутствие
@@ -538,6 +551,16 @@ SHALL задевать раскладку в полёте.
- **AND** пользователю сообщается причина отказа (ошибка qBittorrent, не тихий успех)
- **AND** повторный delete идемпотентно дожимает удаление
#### Scenario: Одно подтверждение на пачку не смягчает допуска
- **GIVEN** транспорт даёт групповое удаление, и человек подтвердил пачку, где
каждая загрузка названа поимённо
- **WHEN** одна из загрузок пачки находится в состоянии, из которого удаление
недоступно
- **THEN** по ней приходит тот же отказ по конфликту, что и при поштучном
удалении
- **AND** остальные загрузки пачки удаляются
### Requirement: Ручное закрытие загрузки (стоп-кран)
Система SHALL давать пользователю возможность **закрыть** загрузку — перевести