Skip to content

feat(tasks): add Blocked task status with reason (#307) - #311

Merged
nevridge merged 6 commits into
mainfrom
feature/blocked-task-status
Oct 22, 2026
Merged

nevridge merged 6 commits into
mainfrom
feature/blocked-task-status

Conversation

@nevridge

Copy link
Copy Markdown
Owner

Summary

Adds a Blocked task status with an optional reason, so work waiting on something external is visibly separate from work that can be acted on today. Built on the PATCH endpoint and Status-only completion from #310.

Closes #307

Backend

  • Status.Blocked = 3. TaskItem gains BlockedReason (max 200, trimmed, blank stored as null), BlockedAt (UTC, serialized with Z) and PreviousStatus (set server-side only).
  • Migration AddTaskItemBlockedStatus adds three nullable columns with no backfill. Down maps Blocked rows back to PreviousStatus ?? Todo before dropping them, so the blocked data is lost on rollback.
  • Transition rules in ApplyUpdateAsync (shared by PUT and PATCH):
    • Only Todo can be blocked. Draft→Blocked, Completed→Blocked and create-as-Blocked return 422 with specific error codes, as does a non-empty reason on a task that is not Blocked.
    • Unblocking restores the previous status, or Todo when it is null or Draft. An explicit non-Todo status from Blocked wins.
    • Blocked→Completed clears the blocked fields and skips the past-day reopen guard.
  • PATCH blockedReason: omitted keeps it, null or "" clears it. PUT: null keeps it, "" clears it. The difference is documented in the DTOs.
  • History events: Blocked, Unblocked, BlockedReasonChanged, and a single Completed event for Blocked→Completed. Blocked transitions never write StatusChanged.
  • A Blocked parent is never auto-completed. Blocked tasks already carry forward unchanged, now covered by tests.
  • BlockedCount on projects (Blocked also stays in OpenTaskCount). The three response mapping sites emit the new fields.
  • Regenerated client: only types.gen.ts changed, and it only gained fields.

Frontend

  • New src/lib/task-status.ts is the single source of truth for statuses. normalizeStatus is case-insensitive and renders unknown values raw, not as "Draft".
  • Journal: Block button and inline reason input, an Unblock badge, a blocked checkbox glyph and label, a three-tier sort (open, blocked, completed) and "X/Y · M blocked" counts.
  • Optimistic block and unblock with rollback, focus return and a live region.
  • Tasks page: Block and Unblock on rows, a Blocked filter with count, a task-form Blocked option and reason field.
  • Typeahead and project badges, TaskHistoryPanel labels, and "N open (M blocked) · K completed" counts.
  • Colours derive from --danger-ink and --danger-bg. There is no --danger token, so no new token was added.
  • The journal toggle now passes throwOnError, so its existing error and rollback paths actually fire. This is a small fix beyond the story.

Tests

  • Backend: 540 passing, 94.46% line coverage (threshold 75%). Includes TaskItemsControllerBlockedTests, migration up/down tests, repository event tests and carry-forward tests.
  • Frontend: 810 passing, 92.94% line coverage (threshold 80%). type-check, build and dotnet format --verify-no-changes pass. Lint shows 0 errors and the pre-existing TasksPage.tsx useMemo warning.

Residual minor findings (awareness only)

  • TaskCard.tsx has no Blocked treatment, but it is unused (only its own test imports it).
  • The live regions don't announce "Task marked done".
  • The journal "Task unblocked" and Tasks-page unblock announcements have no test.
  • AC12's count format is "7 open (2 blocked) · 3 completed" (existing format plus the parenthetical), not the example's "/ 12".
  • Real-browser checks (light and dark contrast, narrow layout, hover reveal) need a manual pass. The checklist is on feat: add Blocked task status with blocked reason #307.

Release notes

  • Production schema change: back up the database before deploy. Migrations run on startup in Docker.
  • The taskflow-assistant MCP does not know about Blocked yet, so list_tasks, update_task and weekly_review may mishandle it until updated (nevridge/taskflow-assistant#12).

@nevridge nevridge left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: 💬 Comments

The PR adds the Blocked status end to end, with the transition rules enforced on the server and covered by tests. Two findings from review are fixed in e574da5, and nothing is left open.

# Severity Location Finding Status
1 🟠 Important TaskFlow.Web/src/components/journal/TodosSection.tsx:359 A task blocked with an empty reason had no control to add a reason later (journal and task list) ✅ Fixed in e574da5
2 ⚪ Nit TaskFlow.Web/src/lib/task-status.ts:57 isOpen was exported but unused outside its own test ✅ Fixed in e574da5

Fixes pushed

Commit e574da5 addresses: [1], [2]


Reviewed at e574da5 · findings from pr-review + code-review, verified and triaged by @nevridge with Claude Code

@nevridge
nevridge merged commit e09e7ab into main Oct 22, 2026
15 checks passed
@nevridge
nevridge deleted the feature/blocked-task-status branch October 22, 2026 19:02
Sign up for free to 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.

feat: add Blocked task status with blocked reason

1 participant