B1 — Planner board: drop-to-unassign (close the loop) #409

Closed
opened 2026-04-27 00:01:33 +00:00 by claude-desktop · 0 comments
Collaborator

As an operator,
I want to drag a card back into the Unassigned column,
so that I can clear a wrong assignment in one gesture without flipping to the Forgejo UI.

Today the synthetic Unassigned column is read-only — apps/web/src/components/board/board-column.tsx:42 sets isDropTarget = column.type !== "unassigned", and apps/web/src/components/board/board.tsx:79 rejects drops onto it. Operators must either re-drag onto a different agent column or open Forgejo to clear the assignee. This story closes the loop.

Acceptance criteria

Frontend

  • Reverse the isDropTarget guard in board-column.tsx so the Unassigned column accepts drops.
  • handleDrop in board.tsx calls the existing assignCard mutation with assignee: null when the target column is the Unassigned synthetic.
  • Optimistic update moves the card immediately; the existing rollback path (TanStack Query mutation onError) restores the source column on failure.
  • Drag-from-Unassigned-onto-Unassigned is a no-op — early return before any API call, no visual flicker.
  • If the card has a running task on the previous agent, show a confirm dialog (AlertDialog) before completing the drop. Dialog copy: Cancel running task on @{previousAgent} and unassign? with Confirm / Keep assignment actions.
  • On confirm, the assignment mutation calls POST /board/assign { assignee: null, cancel_running: true } (extend body — see backend section).

Backend

  • POST /board/assign accepts assignee: null and clears the Forgejo assignee via PATCH /repos/:owner/:name/issues/:index with { assignees: [] }.
  • When body includes cancel_running: true, cancel the in-flight task on the previous agent (mirror /board/reroute's cancellation logic) and post an audit comment on the issue: Unassigned by @{actor} via board (running task cancelled).
  • When body omits cancel_running but the issue has a running task, return 409 { error: "running_task_present", running_agent: "<login>" } so the frontend can prompt.

Tests

  • board.test.tsx: drag from dev column to UnassignedassignCard called with assignee: null + cache patch.
  • board.test.tsx: same drag with running task → 409 → confirm dialog → second call with cancel_running: true.
  • board.test.tsx: drag-from-Unassigned-onto-Unassigned → no API call.
  • Server test for /board/assign: assignee: null clears Forgejo assignee + (when cancel_running) cancels task + posts audit comment.

Out of scope

  • Drop-to-unassign on issues that are not type:user-story (server filter unchanged).
  • Multi-card drop-to-unassign — covered by B3 (multi-select stack-drag).
  • Per-instance unassignment — out of scope until per-instance columns ship.

References

  • Spec: docs/specs/board-rework.md §5 B1.
  • Current board route: apps/web/src/routes/planner.board.tsx.
  • Drag handlers: apps/web/src/components/board/board-column.tsx, board.tsx.
  • Existing assign API: apps/server/src/domain/views/board.ts (POST /board/assign, POST /board/reroute).
  • Original board spec: M18-7 / #168.

Suggested first commit

feat(board): drop-to-unassign clears assignee in one gesture

**As an** operator, **I want** to drag a card back into the Unassigned column, **so that** I can clear a wrong assignment in one gesture without flipping to the Forgejo UI. Today the synthetic Unassigned column is read-only — `apps/web/src/components/board/board-column.tsx:42` sets `isDropTarget = column.type !== "unassigned"`, and `apps/web/src/components/board/board.tsx:79` rejects drops onto it. Operators must either re-drag onto a different agent column or open Forgejo to clear the assignee. This story closes the loop. ## Acceptance criteria ### Frontend - [ ] Reverse the `isDropTarget` guard in `board-column.tsx` so the Unassigned column accepts drops. - [ ] `handleDrop` in `board.tsx` calls the existing `assignCard` mutation with `assignee: null` when the target column is the Unassigned synthetic. - [ ] Optimistic update moves the card immediately; the existing rollback path (TanStack Query mutation `onError`) restores the source column on failure. - [ ] Drag-from-Unassigned-onto-Unassigned is a no-op — early return before any API call, no visual flicker. - [ ] If the card has a **running** task on the previous agent, show a confirm dialog (`AlertDialog`) before completing the drop. Dialog copy: `Cancel running task on @{previousAgent} and unassign?` with `Confirm` / `Keep assignment` actions. - [ ] On confirm, the assignment mutation calls `POST /board/assign { assignee: null, cancel_running: true }` (extend body — see backend section). ### Backend - [ ] `POST /board/assign` accepts `assignee: null` and clears the Forgejo assignee via `PATCH /repos/:owner/:name/issues/:index` with `{ assignees: [] }`. - [ ] When body includes `cancel_running: true`, cancel the in-flight task on the previous agent (mirror `/board/reroute`'s cancellation logic) and post an audit comment on the issue: `Unassigned by @{actor} via board (running task cancelled).` - [ ] When body omits `cancel_running` but the issue has a running task, return `409 { error: "running_task_present", running_agent: "<login>" }` so the frontend can prompt. ### Tests - [ ] `board.test.tsx`: drag from `dev` column to `Unassigned` → `assignCard` called with `assignee: null` + cache patch. - [ ] `board.test.tsx`: same drag with running task → 409 → confirm dialog → second call with `cancel_running: true`. - [ ] `board.test.tsx`: drag-from-Unassigned-onto-Unassigned → no API call. - [ ] Server test for `/board/assign`: `assignee: null` clears Forgejo assignee + (when `cancel_running`) cancels task + posts audit comment. ## Out of scope - Drop-to-unassign on issues that are not `type:user-story` (server filter unchanged). - Multi-card drop-to-unassign — covered by B3 (multi-select stack-drag). - Per-instance unassignment — out of scope until per-instance columns ship. ## References - Spec: `docs/specs/board-rework.md` §5 B1. - Current board route: `apps/web/src/routes/planner.board.tsx`. - Drag handlers: `apps/web/src/components/board/board-column.tsx`, `board.tsx`. - Existing assign API: `apps/server/src/domain/views/board.ts` (`POST /board/assign`, `POST /board/reroute`). - Original board spec: M18-7 / #168. ## Suggested first commit `feat(board): drop-to-unassign clears assignee in one gesture`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#409
No description provided.