tasks: заведены баг устаревшей карточки и цель группового удаления
- fix card-stale-after-download-finish: карточка списка не обновляется после выхода задачи из downloading - goal bulk-download-management + feature bulk-delete-page: удаление раздач с файлами пачкой на отдельной странице
This commit is contained in:
@@ -27,6 +27,8 @@
|
|||||||
- [✨ Переделать веб-UI в устанавливаемое PWA](items/web-ui-pwa.md) — текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер
|
- [✨ Переделать веб-UI в устанавливаемое PWA](items/web-ui-pwa.md) — текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер
|
||||||
- [✨ Править на ревью маппинг «файл → серия» и раскладывать вручную при провале LLM](items/review-mapping-editor.md) — правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом
|
- [✨ Править на ревью маппинг «файл → серия» и раскладывать вручную при провале LLM](items/review-mapping-editor.md) — правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом
|
||||||
- [✨ Заказать спекой крайние случаи именования: многофайловый фильм, редакции, двойная серия](items/naming-edge-cases.md) — стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена
|
- [✨ Заказать спекой крайние случаи именования: многофайловый фильм, редакции, двойная серия](items/naming-edge-cases.md) — стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена
|
||||||
|
- [🐞 Обновлять карточку списка после завершения загрузки](items/card-stale-after-download-finish.md) — завершённая загрузка висит в списке с бейджем «Загружается» до ручной перезагрузки: карточка самополлится только в catched, а прогресс перестаёт поллить, выйдя из downloading — обновлять карточку становится некому
|
||||||
|
- [✨ Удалять выбранные загрузки с файлами на отдельной странице](items/bulk-delete-page.md) — удаление с файлами живёт только в danger-секции страницы одной загрузки: чтобы снести десять раздач, надо десять раз пройти путь список → карточка → подтверждение
|
||||||
- [🔬 Канон нумерации серий и порядок у провайдера тега](items/episode-numbering-canon.md) — Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега
|
- [🔬 Канон нумерации серий и порядок у провайдера тега](items/episode-numbering-canon.md) — Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега
|
||||||
- [🔬 Тексты и формат уведомлений в Telegram](items/telegram-messages-audit.md) — зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи
|
- [🔬 Тексты и формат уведомлений в Telegram](items/telegram-messages-audit.md) — зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи
|
||||||
- [🔬 guessit как сервис-спутник](items/guessit-sidecar.md) — go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём
|
- [🔬 guessit как сервис-спутник](items/guessit-sidecar.md) — go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
- [🎯 Раскладывается не только типовая раздача](items/complex-releases.md) — типовая раздача раскладывается, а всё, что сложнее одного сезона одного тайтла, упирается в ручной разбор
|
- [🎯 Раскладывается не только типовая раздача](items/complex-releases.md) — типовая раздача раскладывается, а всё, что сложнее одного сезона одного тайтла, упирается в ручной разбор
|
||||||
- [🎯 Раздача приносится и подтверждается из любого транспорта](items/ingest-and-review-interfaces.md) — путь «принести раздачу и подтвердить догадку» упирается в незакрытые куски интерфейсов, а не в логику
|
- [🎯 Раздача приносится и подтверждается из любого транспорта](items/ingest-and-review-interfaces.md) — путь «принести раздачу и подтвердить догадку» упирается в незакрытые куски интерфейсов, а не в логику
|
||||||
- [🎯 По записи загрузки видно, как она сюда попала](items/state-integrity.md) — известные окна рассинхрона и потери маркеров: каждое по отдельности самоисцеляется, вместе — источник необъяснимых состояний
|
- [🎯 По записи загрузки видно, как она сюда попала](items/state-integrity.md) — известные окна рассинхрона и потери маркеров: каждое по отдельности самоисцеляется, вместе — источник необъяснимых состояний
|
||||||
|
- [🎯 Загрузки удаляются пачкой, а не по одной](items/bulk-download-management.md) — удаление раздачи с файлами доступно только по одной кнопке на странице одной загрузки — уборка десятка раздач превращается в десяток заходов
|
||||||
|
|
||||||
## Сопровождение
|
## Сопровождение
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# ✨ Удалять выбранные загрузки с файлами на отдельной странице
|
||||||
|
|
||||||
|
- **Тип:** feature
|
||||||
|
- **Категория:** Ядро продукта
|
||||||
|
- **Зачем:** удаление с файлами живёт только в danger-секции страницы одной загрузки: чтобы снести десять раздач, надо десять раз пройти путь список → карточка → подтверждение
|
||||||
|
- **Теги:** goal:bulk-download-management
|
||||||
|
|
||||||
|
Отдельная страница веб-UI, доступная из шапки: список загрузок, у которых
|
||||||
|
удаление разрешено (`done`, `orphaned`, `target_missing` —
|
||||||
|
`internal/worker/review.go:631`), чекбокс у каждой строки и одна кнопка
|
||||||
|
«Удалить выбранные». Кнопка ведёт на подтверждение, где выбранные раздачи
|
||||||
|
названы поимённо, и только оттуда уходит удаление.
|
||||||
|
|
||||||
|
Страница отдельная, чтобы не вешать режим выбора на основной список: там
|
||||||
|
карточки живые, самополлятся и перерисовываются, а выбор пользователя такое
|
||||||
|
перерисовывание переживать не обязан.
|
||||||
|
|
||||||
|
Речь только о полном удалении (снять хардлинки + снести раздачу с файлами из
|
||||||
|
qBittorrent). Групповой `Dismiss` (смена статуса без файлов) в эту задачу не
|
||||||
|
входит.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- новая страница веб-UI и ссылка на неё в `web/templates/partials/header.html`;
|
||||||
|
новый шаблон страницы и её партиалы;
|
||||||
|
- новый POST-эндпоинт группового удаления в `internal/httpapi` (рядом с
|
||||||
|
`POST /ui/downloads/{id}/delete`, `internal/httpapi/httpapi.go:143`);
|
||||||
|
- `Reviewer.Delete` воркера (`internal/worker/review.go:619`) — зовётся по
|
||||||
|
каждой выбранной загрузке;
|
||||||
|
- qBittorrent `torrents/delete` с `deleteFiles=true` — внешний сервис,
|
||||||
|
необратимая операция;
|
||||||
|
- спека `web-ui`, требования «Страницы веб-UI» и «Действия соответствуют
|
||||||
|
состоянию»;
|
||||||
|
- спека `state-reconciliation` в части `Delete` — групповой вызов не меняет
|
||||||
|
условий поштучного, но подтверждение человека теперь одно на пачку.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Страница открывается из шапки и показывает только те загрузки, для которых
|
||||||
|
удаление разрешено поштучно (оракул: тест `internal/httpapi` — подставной
|
||||||
|
читатель отдаёт задачи во всех состояниях, в разметке строки есть у
|
||||||
|
`done`/`orphaned`/`target_missing` и нет у остальных).
|
||||||
|
- Удаление уходит только после явного подтверждения, и подтверждение называет
|
||||||
|
каждую выбранную раздачу поимённо (оракул: тест — POST без признака
|
||||||
|
подтверждения отвечает отказом и не делает ни одного вызова `Delete` у
|
||||||
|
подставного воркера; ответ подтверждения содержит заголовки всех выбранных).
|
||||||
|
- Отказ на одной загрузке не отменяет остальных, а результат называет
|
||||||
|
удалённые и отказавшие поимённо с причиной (оракул: тест, где второй `Delete`
|
||||||
|
возвращает ошибку — первая и третья удалены, страница результата называет
|
||||||
|
вторую и её причину).
|
||||||
|
- Групповой путь не расширяет прав поштучного: попытка удалить задачу в
|
||||||
|
состоянии, где кнопка недоступна, отклоняется с тем же отказом
|
||||||
|
(оракул: тест — `Delete` для `downloading` возвращает `ErrConflict`, страница
|
||||||
|
показывает отказ, остальные выбранные не затронуты).
|
||||||
|
- Поведение страницы записано дельта-спекой и проходит валидацию (оракул:
|
||||||
|
`openspec validate --strict` и `task gate`).
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Необратимое действие: `deleteFiles=true` сносит файлы раздачи, гард последней
|
||||||
|
копии в `Delete` выключен сознательно. Ослаблять подтверждение ради удобства
|
||||||
|
пачки нельзя — оно остаётся обязательным и поимённым. Групповой `Dismiss` и
|
||||||
|
автоматическая чистка по сроку хранения (`db-retention-cleanup`) — не эта
|
||||||
|
задача.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# 🎯 Загрузки удаляются пачкой, а не по одной
|
||||||
|
|
||||||
|
- **Тип:** goal
|
||||||
|
- **Секция:** Направления
|
||||||
|
- **Зачем:** удаление раздачи с файлами доступно только по одной кнопке на странице одной загрузки — уборка десятка раздач превращается в десяток заходов
|
||||||
|
|
||||||
|
Ради чего: разложенные раздачи копятся, и убирают их обычно скопом — после
|
||||||
|
просмотра сезона, при чистке диска, после серии неудачных заливок. Сегодня
|
||||||
|
удаление живёт только в danger-секции страницы одной загрузки, поэтому уборка
|
||||||
|
десяти раздач стоит десяти проходов «список → карточка → подтверждение».
|
||||||
|
|
||||||
|
При этом удаление с файлами необратимо: оно зовёт `torrents/delete` с
|
||||||
|
`deleteFiles=true`, а гард последней копии там выключен сознательно
|
||||||
|
(инвариант «Источник неприкосновенен», исключение 1). Групповой режим обязан
|
||||||
|
сделать уборку дешевле, не сделав ошибку дешевле.
|
||||||
|
|
||||||
|
## Завершение
|
||||||
|
|
||||||
|
Достигнута, когда человек убирает любое число раздач одним проходом: выбирает
|
||||||
|
их в списке, один раз подтверждает удаление по перечню, где каждая раздача
|
||||||
|
названа поимённо, и видит поимённый результат — что снесено, что отказало и
|
||||||
|
почему. Ни одна раздача не сносится без того, чтобы человек увидел её в
|
||||||
|
подтверждении.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# 🐞 Обновлять карточку списка после завершения загрузки
|
||||||
|
|
||||||
|
- **Тип:** fix
|
||||||
|
- **Категория:** Ядро продукта
|
||||||
|
- **Зачем:** завершённая загрузка висит в списке с бейджем «Загружается» до ручной перезагрузки: карточка самополлится только в catched, а прогресс перестаёт поллить, выйдя из downloading — обновлять карточку становится некому
|
||||||
|
|
||||||
|
Карточка списка показывает состояние, которого уже нет: качание кончилось,
|
||||||
|
задача ушла в распознавание и дальше в ревью, а на экране прежний бейдж и
|
||||||
|
прежний набор действий.
|
||||||
|
|
||||||
|
Причина видна в коде. Самополлинг карточки включает поле `SelfPoll`, а оно
|
||||||
|
истинно только в `catched` (`internal/httpapi/httpapi.go:664`,
|
||||||
|
`web/templates/partials/card.html:2`). Внутренний фрагмент прогресса поллится,
|
||||||
|
пока состояние `downloading` (`internal/httpapi/live.go:88`,
|
||||||
|
`web/templates/partials/progress.html:1`). Задача, покинувшая `downloading`, не
|
||||||
|
опрашивается уже ничем. На странице `/download/{id}` то же самое:
|
||||||
|
`web/templates/partials/download_main.html:2` самополлится только в `catched`
|
||||||
|
(`internal/httpapi/download.go:116`).
|
||||||
|
|
||||||
|
Спека нынешнее поведение не запрещает: `live-status` → «Живой прогресс активных
|
||||||
|
загрузок» прямо требует прекращать поллинг прогресса на выходе из
|
||||||
|
`downloading`, а `web-ui` → «Отображение промежуточного состояния catched»
|
||||||
|
заказывает самообновление только для перехода `catched` → `downloading`.
|
||||||
|
Поэтому починка несёт дельту спеки, а не только правку кода.
|
||||||
|
|
||||||
|
## Воспроизведение
|
||||||
|
|
||||||
|
1. Открыть главную `/`, добавить раздачу, дождаться состояния `downloading`.
|
||||||
|
2. Страницу не перезагружать. Дождаться, пока qBittorrent доведёт раздачу до
|
||||||
|
100% и воркер уведёт задачу из `downloading` (в `recognizing`, дальше в
|
||||||
|
`review` или `done`).
|
||||||
|
3. Наблюдается: бейдж карточки остаётся «Загружается», прогресс-бар замирает на
|
||||||
|
последнем значении, кнопка «Ревью →» не появляется. Фоновых запросов от этой
|
||||||
|
карточки в панели сети больше нет.
|
||||||
|
4. Ожидается: карточка сама показывает новое состояние — бейдж, набор действий,
|
||||||
|
исчезнувший прогресс.
|
||||||
|
5. Перезагрузить страницу руками: карточка сразу показывает верное состояние —
|
||||||
|
расходится отображение, а не данные.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- фрагменты `GET /fragments/downloads/{id}/card` и
|
||||||
|
`GET /fragments/downloads/{id}/progress` (`internal/httpapi/live.go`);
|
||||||
|
- поле `SelfPoll` вида карточки и вида страницы загрузки
|
||||||
|
(`internal/httpapi/httpapi.go`, `internal/httpapi/download.go`);
|
||||||
|
- шаблоны `web/templates/partials/card.html`, `progress.html`,
|
||||||
|
`download_main.html`;
|
||||||
|
- спека `web-ui`, требование «Отображение промежуточного состояния catched» —
|
||||||
|
условие самообновления перестаёт быть привязанным к одной фазе;
|
||||||
|
- спека `live-status`, требование «Живой прогресс активных загрузок» и его
|
||||||
|
сценарий «Завершение останавливает поллинг» — прекращение поллинга прогресса
|
||||||
|
перестаёт означать прекращение обновления карточки.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Карточка нетерминальной загрузки самополлится, и первый ответ фрагмента после
|
||||||
|
смены состояния несёт новый бейдж (оракул: тест `internal/httpapi` — рендер
|
||||||
|
карточки в `downloading` содержит `hx-get` на `/fragments/downloads/{id}/card`,
|
||||||
|
а подставной читатель, сменивший состояние на `review`, отдаёт карточку с
|
||||||
|
бейджем ревью).
|
||||||
|
- Терминальная карточка себя не опрашивает: фоновых запросов после `done`,
|
||||||
|
`failed`, `cancelled` и `deleted` нет (оракул: тот же тест — в разметке такой
|
||||||
|
карточки нет ни `hx-get`, ни `hx-trigger`).
|
||||||
|
- Появившееся действие видно без перезагрузки: карточка задачи, перешедшей в
|
||||||
|
`review`, несёт кнопку «Ревью →» (оракул: тест фрагмента карточки).
|
||||||
|
- Страница `/download/{id}` обновляет бейдж и блок действий по тому же правилу,
|
||||||
|
что и карточка (оракул: тест `internal/httpapi/download.go` — `SelfPoll`
|
||||||
|
истинен для нетерминальных состояний и ложен для терминальных).
|
||||||
|
- Правило записано в дельта-спеках обеих затронутых capability (оракул:
|
||||||
|
`openspec validate --strict` и шаг канона в `task gate`).
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Чинится поллингом фрагментов: переход на SSE — отдельная задача
|
||||||
|
`sse-live-updates`, и эта починка её не делает и не отменяет. Расширение
|
||||||
|
самополлинга на все нетерминальные состояния добавляет фоновых запросов к БД —
|
||||||
|
частота и её потолок решаются в предложении, а не молча.
|
||||||
Reference in New Issue
Block a user