ENG-846 - Project delete: confirm, then cascade work items instead of a silent 409 - #223
Conversation
Drop the DELETE /api/ship/projects/{id} 409 reference guard and delete the
project's work items with it in the same session. Add each project's total
work-item count (resolved included) to the projects-list response so the FE
delete confirmation can state the project name and full destructive scope,
and surface any delete failure in a page-level error toast instead of a
silent no-op.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Verified against ENG-846 and the round-1 plan; all four acceptance criteria are satisfied.
AC1 — cascade delete, no 409 guard. delete_project (backend/druks/contrib/ship/routes.py) drops the referencing-count/409 branch and issues delete(WorkItem).where(WorkItem.project_id == project_id) in the same session before deleting the project, matching the plan's "explicit child delete" decision (FK left as a plain, non-cascading column, per the plan's "Ruled out" section). Covered by test_deleting_a_project_cascades_its_work_items_and_spares_others, which asserts the target project and both its work items (including a resolved one) are gone while a control project/work item survive.
AC2 — total work-item count including resolved. list_projects now selects a correlated COUNT subquery over WorkItem with no resolution filter, and ProjectListItem (a new subclass of ProjectSummary, keeping the detail/mutation responses unwidened as planned) carries work_item_count → serialized as workItemCount. Covered by test_projects_list_reports_total_work_item_count_including_resolved.
AC3 — confirmation states name + count, cancel is side-effect-free. ProjectCard's confirm() prompt now reads Delete project "{name}" and its {N work items}? …, gated before remove.mutate(). ProjectsPage.test.tsx exercises both the cancel path (no DELETE call, prompt text asserted) and the confirm path (DELETE called with the right id).
AC4 — failed delete surfaces as toast, card stays. remove mutation's onError routes to a page-level useFlashNote (the repo's existing transient-note pattern) rendered as role="alert"; on success it still just invalidates the query so the card lifecycle is unaffected by a failure. Covered by the third test, which asserts the toast text and that the card remains rendered.
CI is green for 9ebe27376b2c200eee902a7f932a5b5b4e5dd018 on both configured checks: "On Pull Request Backend / checks" (ruff check, ruff format, pytest) and "On Pull Request Frontend / checks" (lint, build/typecheck, frontend tests).
No blocking findings. Nothing carried over from a prior round (this is round 1).
Nothing above is final — happy to be pushed back on if I've misread any of the AC intent.
|
Code review: Swaps project delete from a 409 reference-guard to a work-item cascade, adds each project's total work-item count to the list, and refreshes the delete confirmation copy + error toast — cleanly factored and well tested. Non-blocking (low, no follow-up filed): the same "state a delete's full destructive scope" rationale is repeated across routes.py, schemas.py, and types.ts; and ProjectsPage.test.tsx asserts confirm() contains the literal "2 work items", which pins copy wording rather than the rendered count. |
…scope, not magnitude
Linear ticket: ENG-846
Plan
ENG-846 — Cascade project deletion with visible confirmation
Decisions
Backend
backend/druks/contrib/ship/routes.py:delete_project, remove the referencing-count query and 409 branch, delete matchingWorkItemrows in the same session, then delete and flush theProjectso its existing repo cascade also runs.backend/tests/ship/test_project_repo_routes.pywith focused coverage for deletion of a project containing work items (resolved ones included — the case that caused the live failure), isolated from another project's graph.Frontend
frontend/src/extensions/ship/projects/ProjectsPage.tsx:frontend/src/styles.css.frontend/src/extensions/ship/projects/ProjectsPage.test.tsxcovering confirmation/cancellation, confirmed deletion, and the failed-delete toast path.Scope
WorkItemrows requested by the ticket.Acceptance Criteria