Skip to content

feat(task): автозакрытие задачи при выполнении всех помидоров - #21

Merged
MegaBadCoder merged 5 commits into
mainfrom
feat/auto-complete-task-on-pomodoros-done
Aug 13, 2026
Merged

feat(task): автозакрытие задачи при выполнении всех помидоров#21
MegaBadCoder merged 5 commits into
mainfrom
feat/auto-complete-task-on-pomodoros-done

Conversation

@MegaBadCoder

Copy link
Copy Markdown
Owner

Когда накопленное число помидоров достигает pomodoroCount, задача автоматически становится выполненной — и в календаре сразу отображается как выполненная.

Как устроено

Правило живёт на бэкенде: фронтовый timer-store не знает накопленного pomodoroCompleted, у него только настройки сессии. TaskService.incrementPomodoro снимает счётчик до инкремента, применяет атомарный инкремент, перечитывает авторитетное значение и при пересечении порога вызывает существующийthis.update(..., { completed: true }). За счёт этого повторяющиеся задачи закрываются ровно как от ручной галочки — второй ветки логики завершения не появилось.

Триггер — переход через порог (before < target <= after), а не факт «счётчик выше цели». Это даёт бесплатный escape-hatch: снял галочку вручную → продолжай работать, задача сама не перезакроется.

PATCH /tasks/:id/pomodoro теперь возвращает UpdateTaskResponse — ту же форму, что и PATCH /tasks/:id.

Побочно починилось

  • Бейдж X/Y стал живым. Раньше timer-store дёргал API сам и выбрасывал ответ, а taskStore.incrementPomodoro был мёртвым кодом — счётчик не двигался до перезагрузки списка.
  • E2E были сломаны до этой ветки: проходили 4 из 31.AppModule вычисляет isTelegramEnabled() на этапе импорта модуля, а test/helpers/test-app.ts выставлял ENABLE_TELEGRAM=false уже внутри createTestApp() — то есть после. Telegraf стартовал и валил каждый suite с 401: Bot Token is required. Перенёс переменные в setupFiles. Стало 31 из 31.
  • Три ассерта в tasks.e2e-spec.ts читали body.title вместо body.task.title — эндпоинт отдаёт обёртку ещё с задачи про recurring, просто тесты никогда не доходили до ассертов.

Проверено

  • backend: 349 unit, 31 e2e
  • frontend: 321 тест, vue-tsc чисто
  • живой сервер + реальные curl: цель 2 → закрылась на втором помидоре; после снятия галочки третий не перезакрыл; 0.5 + 0.5 при цели 1 закрыло; recurring дала инстанс на следующий день со сброшенным счётчиком; задача без конфига не закрылась

Осознанно не сделано

  • Бэкфилла нет: задачи, уже стоящие на 4/4 и незакрытые, ретроспективно не закрываются.
  • Таймер после автозакрытия продолжает цикл фаз — так решили при дизайне.
  • alfy-mcp/src/tools/tasks.ts:128,142 отдают клиенту обёртку {task:{...}} вместо задачи, и у PATCH /tasks/:id/pomodoro нет DTO (increment не валидируется). Обе находки пре-существуют и лежат в Future work — инвариант задачи запрещал трогать alfy-mcp.

Дизайн, план и разбор — в docs/tasks/auto-complete-task-on-pomodoros-done.md.

i.seliverstov added 5 commits August 13, 2026 12:08
PATCH /tasks/:id/pomodoro now decides completion on the backend: it snapshots
the counter, applies the atomic increment, re-reads the authoritative value and
routes through the existing update({ completed: true }) when the task crossed
its pomodoroCount. Recurring tasks therefore behave exactly like a manual
checkbox — no second completion path.
The trigger is a transition (before < target <= after), not "counter is above
target", so unchecking an auto-completed task lets the user keep working on it
without it re-closing.
The endpoint now returns UpdateTaskResponse, the same shape as PATCH /tasks/:id.
Also fixes the e2e harness: AppModule resolves isTelegramEnabled() at import
time, so createTestApp() set ENABLE_TELEGRAM too late and telegraf launched for
real, failing every suite with "401: Bot Token is required" (4/31 passing).
Moved the env setup into jest setupFiles and corrected three stale assertions
that read PATCH /tasks/:id as a flat task. Now 31/31.
The timer used to PATCH /tasks/:id/pomodoro itself and drop the response, so
the X/Y badge never moved until a refetch and taskStore.incrementPomodoro was
dead code. It now delegates to the store, which applies the backend response —
including the auto-completion and any recurring nextInstance the backend
produced when the pomodoro target was reached.
Extracts applyUpdateResponse from updateTask so both callers share one parser
for { task, nextInstance?, deletedInstanceId? } instead of duplicating it.
Drops the `!task || !task.isPomodoroTask` guard: the timer can restore its
session before fetchTasks resolves, and swallowing the increment because the
task is not in the store yet would lose it. Only the optimistic bump is now
conditional, and the rollback restores the snapshot instead of subtracting.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@MegaBadCoder