Skip to content

feat(board): drag-and-drop cards between columns to change state#230

Merged
martian56 merged 3 commits into
mainfrom
feat/board-dnd
Jun 25, 2026
Merged

feat(board): drag-and-drop cards between columns to change state#230
martian56 merged 3 commits into
mainfrom
feat/board-dnd

Conversation

@martian56

@martian56 martian56 commented Jun 25, 2026

Copy link
Copy Markdown
Member

What

Closes #175. The Board/Kanban layout was static — cards were plain links with no way to move a work item between columns. This adds native HTML5 drag-and-drop: dragging a card to another column changes its state.

How

  • IssueLayoutBoard — cards are draggable; columns are drop targets that highlight while a droppable card hovers over them. On drop the board resolves the column to a concrete state:

    • per-state columns → the column's own state,
    • group-by-state-group columns (workspace kanban) → a state in the card's own project that belongs to that group (default first).

    Dropping on the card's current column, or on the synthetic "No state" bucket, is a no-op.

  • IssueLayoutTypes — new optional onCardMove(issueId, targetStateId). When omitted the board stays non-draggable, so other layouts and read-only contexts are unaffected.

  • IssueListPage (project board) and WorkspaceViewsPage (workspace kanban) wire onCardMove to issueService.update({ state_id }) with an optimistic update and revert/refetch on failure.

No backend changes — this reuses the existing issue-update endpoint.

Testing

  • tsc -b + ESLint clean.
  • Browser-verified: on the project board, dragged a card from Todo → In Progress; the card moved columns, its state pill updated, the column counts updated, and after a full reload the new state persisted (server round-trip confirmed). Optimistic update + persistence both work; no console errors.

AI assistance

Produced with the help of Claude Code (Claude Opus 4.8); AI-assisted commits carry a Co-Authored-By trailer.

Summary by CodeRabbit

  • New Features
    • Work items can now be dragged between Kanban columns when card movement is enabled.
    • Columns visually highlight when they’re valid drop targets to clarify where you can move cards.
  • Bug Fixes
    • Dragging a card now applies an optimistic update immediately and saves in the background.
    • If a save fails (or requests arrive out of order), the board prevents stale moves from overwriting newer card positions and refreshes only when appropriate.

The Board/Kanban layout was static — cards were plain links with no way to
move a work item between columns. This adds native HTML5 drag-and-drop:

- IssueLayoutBoard: cards are draggable; columns are drop targets with a
  highlight while a droppable card hovers. On drop the board resolves the
  column to a concrete state — for per-state columns that's the column's state,
  for group-by-state-group columns it's a state in the card's own project that
  belongs to that group (default first) — and calls onCardMove. Dropping on the
  card's current column or the synthetic "No state" bucket is a no-op.
- IssueLayoutTypes: new optional onCardMove(issueId, targetStateId); when
  omitted the board stays non-draggable (other layouts unaffected).
- IssueListPage (project board) and WorkspaceViewsPage (workspace kanban) wire
  onCardMove to issueService.update({ state_id }) with an optimistic update and
  revert/refetch on failure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 999c3308-7d57-4d48-b62e-12fa425d3937

📥 Commits

Reviewing files that changed from the base of the PR and between af52ead and bf24ed8.

📒 Files selected for processing (1)
  • apps/web/src/pages/IssueListPage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/pages/IssueListPage.tsx

📝 Walkthrough

Walkthrough

Issue cards can now be dragged between board columns. The board resolves drop targets, highlights active columns, and the list and workspace views persist state changes with ordered per-card updates.

Changes

Board drag-and-drop flow

Layer / File(s) Summary
Drag contract and drop resolution
apps/web/src/components/work-item/layouts/IssueLayoutTypes.ts, apps/web/src/components/work-item/layouts/IssueLayoutBoard.tsx
IssueLayoutProps adds onCardMove, and IssueLayoutBoard tracks drag state and resolves grouped or direct column drops before calling it.
Card and column drag wiring
apps/web/src/components/work-item/layouts/IssueLayoutBoard.tsx
BoardCard and BoardColumn accept drag props, forward drag events, and change styling for active drop targets.
Page move persistence
apps/web/src/pages/IssueListPage.tsx, apps/web/src/pages/WorkspaceViewsPage.tsx
IssueListPage and WorkspaceViewsPage add optimistic move handlers, serialize per-card updates, and wire the handler into IssueLayoutBoard.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant IssueLayoutBoard
  participant IssueListPage as IssueListPage.handleCardMove
  participant WorkspaceViewsPage as WorkspaceViewsPage.handleCardMove
  participant issueService as issueService.update
  User->>IssueLayoutBoard: drop issue card
  IssueLayoutBoard->>IssueListPage: onCardMove(issueId, targetStateId)
  IssueLayoutBoard->>WorkspaceViewsPage: onCardMove(issueId, targetStateId)
  IssueListPage->>issueService: update state_id
  WorkspaceViewsPage->>issueService: update state_id
  alt update succeeds
    issueService-->>IssueListPage: success
    issueService-->>WorkspaceViewsPage: success
  else update fails
    issueService-->>IssueListPage: error
    issueService-->>WorkspaceViewsPage: error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I hopped the board with velvet feet,
where cards and columns met so neat.
A drop, a twitch, a state_id cheer,
then saved and settled, bright and clear.
🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, Conventional Commits compliant, and accurately summarizes the main change: board card drag-and-drop to change state.
Description check ✅ Passed The description covers summary, linked issue, implementation, testing, and AI disclosure; only noncritical template sections are left incomplete.
Linked Issues check ✅ Passed The PR adds board drag-and-drop, maps drops to concrete states, and persists optimistic updates through issueService.update as requested by #175.
Out of Scope Changes check ✅ Passed The changes stay focused on board drag-and-drop and related persistence/rollback behavior, with no unrelated feature or refactor additions.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/board-dnd

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/pages/IssueListPage.tsx`:
- Around line 590-600: Serialize issue state updates in handleCardMove so only
the latest drag for a given issue can win. Add the same per-issue
ordering/staleness guard pattern used by handleReorder around
issueService.update(...) and refetchIssues(), keyed by issueId, so older
in-flight PATCH responses or rollback refetches cannot overwrite a newer
optimistic state_id change.

In `@apps/web/src/pages/WorkspaceViewsPage.tsx`:
- Around line 506-521: The rollback in handleCardMove is unconditional and can
overwrite a newer successful move when an older issueService.update request
fails later. Add a per-issue request token/version inside WorkspaceViewsPage so
each move records the latest attempt for that issue, and in the catch handler
only restore prevStateId if the failing request is still the newest one. Use
handleCardMove, setIssues, and issueService.update to locate the logic and guard
the rollback against stale responses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0802854a-400b-499f-ac98-ab7f65d35b6e

📥 Commits

Reviewing files that changed from the base of the PR and between b04404d and 7c1a1c0.

📒 Files selected for processing (4)
  • apps/web/src/components/work-item/layouts/IssueLayoutBoard.tsx
  • apps/web/src/components/work-item/layouts/IssueLayoutTypes.ts
  • apps/web/src/pages/IssueListPage.tsx
  • apps/web/src/pages/WorkspaceViewsPage.tsx

Comment thread apps/web/src/pages/IssueListPage.tsx Outdated
Comment thread apps/web/src/pages/WorkspaceViewsPage.tsx Outdated
Addresses CodeRabbit review: dragging the same card repeatedly fired
independent issueService.update calls, so a late older PATCH (or its
rollback/refetch) could clobber a newer move.

Both board consumers now chain PATCHes per issue id (commit in order) and tag
each move with a sequence token, so only the latest move's failure rolls back
(WorkspaceViewsPage) or refetches (IssueListPage). Mirrors the existing
handleReorder serialization.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/pages/IssueListPage.tsx`:
- Around line 614-616: The rollback refetch in handleCardMove can apply stale
data after a route change because refetchIssues() still uses the current page
state. Capture the initiating workspaceSlug/projectId in handleCardMove and pass
them into the fallback refetch path, and in refetchIssues or the setIssues
caller discard results when they no longer match the active route. Use the
existing cardMoveSeq.current check plus the route identifiers to ensure only the
move from the current workspace/project can update issues.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6a3301aa-789c-44c0-a8a8-977356954037

📥 Commits

Reviewing files that changed from the base of the PR and between 7c1a1c0 and af52ead.

📒 Files selected for processing (2)
  • apps/web/src/pages/IssueListPage.tsx
  • apps/web/src/pages/WorkspaceViewsPage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/pages/WorkspaceViewsPage.tsx

Comment thread apps/web/src/pages/IssueListPage.tsx
Addresses CodeRabbit: the board move's rollback refetch could replace a
different project's list if the user navigated away before the failed PATCH
settled. Track the latest route key and only refetch when still on the project
that initiated the move.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@martian56 martian56 merged commit 6ec8479 into main Jun 25, 2026
8 checks passed
@martian56 martian56 deleted the feat/board-dnd branch June 25, 2026 22:03
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.

[FEAT] Add drag-and-drop to the Board/Kanban layout

1 participant