From 9993ebb2b4af91677c441169b88e315753d5c5f8 Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Tue, 16 Jun 2026 22:03:14 +0330 Subject: [PATCH] Delete tasks from the board Adds DELETE /api/orgboard/tasks/{id} (WorkTasks permission) and a "Delete task" button in the task drawer (with confirm). Children are detached (kept as top-level) rather than deleted; status-transition history is dropped. There was previously no way to remove a task. Co-Authored-By: Claude Opus 4.8 --- client/src/pages/BoardPage.tsx | 17 +++++++++- .../Domain/WorkItem.cs | 3 ++ .../Endpoints/OrgBoardEndpoints.cs | 31 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/client/src/pages/BoardPage.tsx b/client/src/pages/BoardPage.tsx index 5080f34..39dab93 100644 --- a/client/src/pages/BoardPage.tsx +++ b/client/src/pages/BoardPage.tsx @@ -8,7 +8,7 @@ import { useSensors, type DragEndEvent, } from '@dnd-kit/core' -import { Bot, Plus } from 'lucide-react' +import { Bot, Plus, Trash2 } from 'lucide-react' import { toast } from 'sonner' import { AppShell } from '@/components/AppShell' import { Badge } from '@/components/ui/badge' @@ -554,6 +554,21 @@ function TaskDrawer({ )} + +
+ +
) diff --git a/src/Modules/TeamUp.Modules.OrgBoard/Domain/WorkItem.cs b/src/Modules/TeamUp.Modules.OrgBoard/Domain/WorkItem.cs index a147fb0..0c7ec41 100644 --- a/src/Modules/TeamUp.Modules.OrgBoard/Domain/WorkItem.cs +++ b/src/Modules/TeamUp.Modules.OrgBoard/Domain/WorkItem.cs @@ -69,6 +69,9 @@ internal sealed class WorkItem : Entity UpdatedAtUtc = nowUtc; } + /// Detach from a parent (used when the parent is deleted, so the child stays on the board). + public void ClearParent() => ParentId = null; + /// Appends an approved agent artifact (spec / test plan) to the task. public void AttachArtifact(string content, DateTimeOffset nowUtc) { diff --git a/src/Modules/TeamUp.Modules.OrgBoard/Endpoints/OrgBoardEndpoints.cs b/src/Modules/TeamUp.Modules.OrgBoard/Endpoints/OrgBoardEndpoints.cs index bdac069..11d064a 100644 --- a/src/Modules/TeamUp.Modules.OrgBoard/Endpoints/OrgBoardEndpoints.cs +++ b/src/Modules/TeamUp.Modules.OrgBoard/Endpoints/OrgBoardEndpoints.cs @@ -30,6 +30,7 @@ internal static class OrgBoardEndpoints group.MapGet("/board", GetBoard).RequireAuthorization(); group.MapPatch("/tasks/{id:guid}/move", MoveTask).RequireAuthorization(); group.MapPatch("/tasks/{id:guid}/assign", AssignTask).RequireAuthorization(); + group.MapDelete("/tasks/{id:guid}", DeleteTask).RequireAuthorization(); group.MapGet("/cartable", Cartable).RequireAuthorization(); group.MapPost("/seats", CreateSeat).RequireAuthorization(); @@ -273,6 +274,36 @@ internal static class OrgBoardEndpoints return Results.Ok(ToResponse(item)); } + // Remove a task from the board. Its children are detached (kept as top-level) rather than deleted, + // and its status-transition history is dropped. Any agent runs/reviews it spawned are left as history. + private static async Task DeleteTask( + Guid id, ICurrentUser user, IPermissionService permissions, + IAuditLog audit, OrgBoardDbContext db, CancellationToken ct) + { + var item = await db.WorkItems.FirstOrDefaultAsync(w => w.Id == id, ct); + if (item is null) + { + return Results.NotFound(); + } + + var team = await db.Teams.FirstOrDefaultAsync(t => t.Id == item.TeamId, ct); + if (team is null || !permissions.Has(Capability.WorkTasks, ScopeRef.Team(item.TeamId), ScopeRef.Org(team.OrganizationId))) + { + return Results.Forbid(); + } + + foreach (var child in await db.WorkItems.Where(w => w.ParentId == id).ToListAsync(ct)) + { + child.ClearParent(); + } + + db.Transitions.RemoveRange(await db.Transitions.Where(t => t.WorkItemId == id).ToListAsync(ct)); + db.WorkItems.Remove(item); + await db.SaveChangesAsync(ct); + await audit.WriteAsync(new AuditEvent("task.deleted", "WorkItem", id, user.MemberId, item.Title), ct); + return Results.NoContent(); + } + private static async Task GetBoard( Guid teamId, IPermissionService permissions, OrgBoardDbContext db, CancellationToken ct) {