Skip to content

ENG-846 - Project delete: confirm, then cascade work items instead of a silent 409 - #223

Merged
druks-operator[bot] merged 3 commits into
mainfrom
agent/ENG-846
Aug 9, 2026
Merged

ENG-846 - Project delete: confirm, then cascade work items instead of a silent 409#223
druks-operator[bot] merged 3 commits into
mainfrom
agent/ENG-846

Conversation

@druks-operator

@druks-operator druks-operator Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Linear ticket: ENG-846

Plan

ENG-846 — Cascade project deletion with visible confirmation

Decisions

  • Implement the cascade explicitly in the delete route. This keeps the change route-local and avoids a schema migration while preserving the existing 404 and 204 endpoint behavior.
  • Retain the projects page's existing browser confirmation step, but replace its stale refusal guidance with the project name and the delete's destructive scope: the project and every work item it owns. No per-project count is surfaced — the category is the informative part, so the list endpoint's response shape is untouched.
  • Reuse the existing transient flash-note mechanism for delete failures and render it as an error toast/alert.

Backend

  • Update backend/druks/contrib/ship/routes.py:
    • In delete_project, remove the referencing-count query and 409 branch, delete matching WorkItem rows in the same session, then delete and flush the Project so its existing repo cascade also runs.
  • Extend backend/tests/ship/test_project_repo_routes.py with 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

  • Update frontend/src/extensions/ship/projects/ProjectsPage.tsx:
    • Replace the confirmation's stale refusal guidance with the delete's actual scope.
    • Keep cancellation side-effect-free and invalidate the projects query after successful deletion.
    • Route mutation failures to a page-level transient error toast while leaving the project card present.
  • Add the minimal toast styling in frontend/src/styles.css.
  • Add frontend/src/extensions/ship/projects/ProjectsPage.test.tsx covering confirmation/cancellation, confirmed deletion, and the failed-delete toast path.

Scope

  • No database migration or foreign-key change.
  • No change to the projects-list response shape.
  • No cleanup of durable run/event history beyond deleting the project-owned WorkItem rows requested by the ticket.

Acceptance Criteria

  • AC1: DELETE /api/ship/projects/{id} returns 204 for a project that has work items and removes that project and all of its work-item rows without the former 409 reference guard; projects and work items belonging to other projects remain intact.
    • Verification: A focused backend route test creates target and control projects with work items, calls DELETE, and asserts the target graph is absent while the control graph remains.
  • AC2: The project delete action requires confirmation before issuing DELETE, and the confirmation states the selected project name and that every work item the project owns is deleted with it; canceling sends no delete request and confirming sends one.
    • Verification: A ProjectsPage component test exercises both confirmation outcomes and inspects the displayed project/scope information and DELETE calls.
  • AC3: When project deletion rejects, the project remains rendered and the failure message is surfaced in an error toast/alert rather than being silently ignored.
    • Verification: A ProjectsPage component test returns a failed DELETE response and asserts an error toast/alert containing the propagated API error.

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>

@druks-reviewer druks-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@druks-operator

druks-operator Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

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.
Scrutiny: plan critic ran (1) · evaluation rounds: 1 · line review ran

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.

@druks-operator
druks-operator Bot marked this pull request as ready for review August 9, 2026 20:12
@druks-operator
druks-operator Bot requested a review from czpython as a code owner August 9, 2026 20:12
@druks-operator
druks-operator Bot enabled auto-merge (squash) August 9, 2026 20:59
@druks-operator
druks-operator Bot merged commit 173c6c8 into main Aug 9, 2026
2 checks passed
@druks-operator
druks-operator Bot deleted the agent/ENG-846 branch August 9, 2026 21: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.

1 participant