Files
kandev/docs/plans/subtask-detachment/plan.md
T
Clément Fiere 01b0dedd6c fix: harden task archive lifecycle recovery (#3651)
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
2026-09-14 16:34:25 +01:00

5.1 KiB

spec, created, status
spec created status
docs/specs/tasks/requirements/subtask-detachment.md 2026-07-18 complete

Implementation Plan: Subtask detachment

Overview

Add one canonical backend detach operation that atomically clears hierarchy and normalizes inherited workspace policy, then expose it through the shared task-menu surfaces. Backend and frontend implementation can proceed independently after the HTTP contract is fixed; integrated desktop/mobile E2E follows both.

Backend

Canonical service operation

  • Add Store.DetachTask(ctx, actor, command) in internal/task/archivecascade; it owns hierarchy, conditional workspace stewardship, lifecycle revision, and outbox atomically.
  • Service.DetachTask and Office's empty-parent path invoke only that aggregate command. No service or dashboard repository method writes detachment fields.
  • A non-empty reparent uses the narrow Store.SetTaskParent command; it owns parent and mode changes without direct SQL writers.

HTTP contract and Office parity

  • Route the Office dashboard's empty-parent mutation through Store.DetachTask; non-empty reparenting uses the narrow Store.SetTaskParent command.
  • Return the updated task DTO, map missing tasks to 404, and surface persistence errors consistently.

Frontend

API and action state

  • Add detachTask(taskId) to apps/web/lib/api/domains/kanban-api.ts and its barrel export.
  • Add a focused detach action hook that calls the endpoint, reports failure through the existing toast pattern, and prevents duplicate confirmation submissions.
  • Ensure the task mapper/store retains the workspace mode needed to render the shared-workspace warning.

Menus and confirmation

  • Add a reusable TaskDetachConfirmDialog under apps/web/components/task/ with concise hierarchy-only copy and a conditional shared-workspace warning.
  • Add Detach from parent with the existing unlink icon to apps/web/components/task/task-switcher-context-menu.tsx for single subtasks only.
  • Route the Office parent picker's No parent selection through detachTask; non-empty parent changes use the SetTaskParent aggregate command.
  • Add the same entry to apps/web/components/kanban-card-menu-items.tsx; the shared entry builder keeps card right-click and three-dot menus aligned.

Tests

  • Canonical detach: table-driven service tests cover root no-op, ordinary child, inherit_parent, shared_group, and new_workspace modes in apps/backend/internal/task/service/service_detachment_test.go.
  • Relationship preservation: service tests prove descendants, blockers, repositories, sessions, and workspace-group rows are not mutated by the task-row operation.
  • Event clearing: service/event tests assert task.updated.parent_id is present and empty after detach.
  • HTTP integration: handler tests exercise success, root idempotency, missing task, and persistence failure.
  • Menu visibility: component tests assert the action is present only for a subtask and hidden for root and multi-selection menus.
  • Card parity: apps/web/components/kanban-card-menu-items.test.tsx asserts both menu renderers receive the detach entry and invoke the action.
  • Mapper/state: frontend tests assert a cleared parent_id removes cached nesting and workspace mode is retained for confirmation copy.
  • Office picker: component tests assert No parent calls the detach endpoint while selecting a parent uses the existing update endpoint.

E2E Tests

  • Add apps/web/e2e/tests/task/subtask-detachment.spec.ts for sidebar right-click, confirmation, live root promotion, card three-dot parity, root-menu absence, and descendant preservation.
  • Add apps/web/e2e/tests/task/mobile-subtask-detachment.spec.ts for the touch-accessible task action and confirmation flow on mobile-chrome.
  • Seed an inherited-workspace subtask and assert the confirmation warning and post-detach workspace context remain shared.

Implementation Waves

Wave 1 (parallel):

Wave 2:

Verification

From apps/backend:

rtk go test ./internal/task/service ./internal/task/handlers ./internal/office/dashboard

From apps/web:

rtk pnpm test -- components/task/task-switcher.test.tsx components/kanban-card-menu-items.test.tsx components/task/simple/components/parent-picker.test.tsx lib/kanban/map-task.test.ts
rtk pnpm run typecheck
rtk pnpm run lint
rtk pnpm e2e:run e2e/tests/task/subtask-detachment.spec.ts e2e/tests/task/mobile-subtask-detachment.spec.ts

From the repository root after make fmt:

rtk make typecheck
rtk make test
rtk make lint

Risks

  • Omitting a cleared parent_id from WebSocket payloads would leave existing clients visually nested until reload.
  • Leaving inherit_parent after clearing the parent would silently provision a separate workspace on a future launch.
  • Mobile context menus are not assumed to be touch-accessible; the action must be wired through the existing mobile action presentation.