Files
jellybit/docs/tasks/items/dismiss-marker-lost.md
T
av f42db0a275 sprint: набран спринт 2026-08-06 под цель распознавания
- в наборе шесть задач: локаль TVDB и confidence-гейт под цель, плюс баги и
  техдолг помимо неё
- взятым дописаны разделы своего типа: «Затрагивает», критерии с оракулами,
  воспроизведение
- решены развилки: оба расхождения код↔спека правятся спекой (и потому стали
  chore), опрос qBittorrent тормозится бэкоффом до минутного потолка
2026-08-06 13:57:33 +03:00

4.8 KiB
Raw Blame History

🧹 Зафиксировать в спеке разделение Cancel и Dismiss по состояниям

  • Тип: chore
  • Категория: Ядро продукта
  • Зачем: спека обещает dismiss из любого состояния, код осознанно даёт Cancel для активных и Dismiss для терминальных — расходится буква, а не поведение

Найдено аудитом capability state-reconciliation (сверка код↔спека). Пред-существующее, вне scope пачки lifecycle-задач.

Суть

Спека openspec/specs/state-reconciliation/spec.md (требование «Ручное закрытие»): команда dismiss доступна из любого состояния кроме deleted во всех транспортах, и переход SHALL помечаться error_code = user_dismiss.

В веб-UI danger-zone «Закрыть» (dismiss) гейтится только для терминальных состояний: Dismissable = IsTerminal() && !deleted && !cancelled (internal/httpapi/download.go:130, шаблон web/templates/partials/download_main.html:99-112). Для НЕ-терминальных (stuck, deferred, downloading, review) закрытие в UI идёт кнопкой «Отменить» → Cancel (internal/worker/worker.go:973), которая пишет пустой error_code, а не user_dismiss.

Насколько больно

Функционально сценарии проходят: Cancel тоже даёт cancelled и не трогает файлы/раздачу, семантика для пользователя идентична. Состояния без доступного «закрытия» нет (кроме deleted/cancelled). Теряется только маркер user_dismiss в error_code — расхождение с буквой спеки и небольшая потеря наблюдаемости (в аналитике/логах не отличить «пользователь закрыл активную» от «пользователь отменил»). Отсюда низкий приоритет.

Решение (2026-08-06): B — привести спеку к коду

Разделение осознанное: Cancel — стоп-кран для активных состояний, Dismiss — закрытие терминальных. Требование «Ручное закрытие» уточняется: на не-терминальных состояниях закрытие из интерфейса реализуется Cancel'ом, и называется, какой error_code при этом ожидается.

Вариант A (звать Dismiss из веб-UI на не-терминальных ради единого маркера) отклонён: он меняет рабочее поведение ради маркера в диагностике.

Кода задача не трогает: наблюдаемое поведение остаётся прежним, меняется заявленное.

Затрагивает

  • openspec/specs/state-reconciliation/spec.md — требование «Ручное закрытие»: доступность dismiss по состояниям и ожидаемый error_code;
  • дельта-спека change'а — сценарий закрытия не-терминальной загрузки из веб-UI;
  • internal/httpapi/download.go, web/templates/partials/download_main.html — только чтение, правок не предполагается.

Критерии приёмки

  • Требование спеки различает Cancel и Dismiss по состояниям и называет error_code для каждого пути закрытия (оракул: openspec validate --strict).
  • В спеке есть сценарий «пользователь закрывает не-терминальную загрузку из веб-UI» с исходом cancelled и названным error_code (оракул: тот же прогон).
  • Гейт Dismissable в коде и danger-zone шаблона остаются как есть (оракул: git diff --stat в отчёте ревью — файлов под internal/ и web/ нет).

Ссылки

  • internal/httpapi/download.go:130 — гейт Dismissable
  • web/templates/partials/download_main.html:99-112 — danger-zone
  • internal/worker/worker.go:973Cancel; :1001Dismiss
  • openspec/specs/state-reconciliation/spec.md — требование «Ручное закрытие»