Skip to content

feat: make cross-column dragged card sortable - #1894

Merged
andrejsshell merged 15 commits into
usekaneo:mainfrom
VictorOnwukwe:feat/cross-column-card-sorting
Oct 5, 2026
Merged

andrejsshell merged 15 commits into
usekaneo:mainfrom
VictorOnwukwe:feat/cross-column-card-sorting

Conversation

@VictorOnwukwe

Copy link
Copy Markdown
Contributor

Description

This PR enables the sorting of a card when dragging from one status column to another.

UX notes:

When a card is dragged into another column, an overlay appears, prompting them to hold the "⌘" key if they want to sort the added card

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement
  • Test addition or update
  • Other (please describe):

How Has This Been Tested?

  • Unit tests
  • Integration tests
  • Manual testing
  • Other (please describe):

Screenshots (if applicable)

Task.placement.sorting.PR.video.mov

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I understand and take responsibility for every change, and I wrote this pull request description in my own words
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Additional Notes

None

@github-actions github-actions Bot added the size/l label Oct 2, 2026
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

PR Summary by Qodo

Preview and sort cross-column Kanban card drops

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Preview cross-column card placement and save the position shown when dropped.
• Preserve append-only moves on automatically sorted boards and explain their sorting with a
 localized hint.
• Improve collision targeting, drag cleanup, and card rendering; add regression tests.
Diagram

graph TD
  A["Drag events"] --> B["Collision targeting"] --> C{"Sort mode?"} --> D["Derived preview"] --> E["Drop placement"] --> F["Persist reorder"]
  C -->|Automatic| G["Append and hint"] --> F
  C -->|Manual| D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Mutate the optimistic board on every hover
  • ➕ Could reuse the existing rendered board and sortable placement directly.
  • ➖ Would require rollback on cancellation and reconciliation with incoming board updates.
  • ➖ Risks treating an uncommitted preview as persisted task state.

Recommendation: Keep the derived preview and persist only on drop. It separates transient drag feedback from canonical board state while preserving the existing reorder path; hover-time store mutation adds avoidable rollback and synchronization complexity.

Files changed (48) +1088 / -273

Enhancement (14) +316 / -74
board-collision-detection.tsTarget the column and card beneath a drag +57/-0

Target the column and card beneath a drag

• Chooses a nearby column from pointer coordinates, then its nearest visible card or column drop target. Falls back to closest-corners detection when pointer coordinates are unavailable.

apps/web/src/components/kanban-board/board-collision-detection.ts

column-dropzone.tsxRender sortable cards without preview exit artifacts +32/-48

Render sortable cards without preview exit artifacts

• Disables sortable layout shifts on automatically sorted boards and suppresses entry and exit animations for the active card during cross-column previews. Column droppable registration moves to the parent component.

apps/web/src/components/kanban-board/column/column-dropzone.tsx

column-sort-hint.tsxDisplay an accessible automatic-sort hint +10/-0

Display an accessible automatic-sort hint

• Adds a themed, non-interactive status message for the hovered automatically sorted column.

apps/web/src/components/kanban-board/column/column-sort-hint.tsx

index.tsxMake whole columns droppable and expose sort hints +19/-5

Make whole columns droppable and expose sort hints

• Registers the column container as the drop target, passes preview and sorting flags to its cards, and displays the optional sort hint. Memoizes the column component.

apps/web/src/components/kanban-board/column/index.tsx

drag-hover.tsDefine the active drag-hover identifiers +1/-0

Define the active drag-hover identifiers

• Adds the active-card and hovered-target pair used to derive a temporary board preview.

apps/web/src/components/kanban-board/drag-preview/drag-hover.ts

find-task-column.tsResolve a card or column to its board column +7/-0

Resolve a card or column to its board column

• Adds a shared lookup for deciding whether a hover crosses columns and where a card is displayed.

apps/web/src/components/kanban-board/drag-preview/find-task-column.ts

get-hovered-other-column-id.tsIdentify destination columns for cross-column hovers +12/-0

Identify destination columns for cross-column hovers

• Returns a destination column only when the hovered target belongs to a different column from the active card.

apps/web/src/components/kanban-board/drag-preview/get-hovered-other-column-id.ts

get-preview-drop-placement.tsResolve the final slot from the displayed preview +17/-0

Resolve the final slot from the displayed preview

• Settles a final hover against the preview board and converts the resulting visual position into a target and insertion direction.

apps/web/src/components/kanban-board/drag-preview/get-preview-drop-placement.ts

get-visual-task-placement.tsExpress a visual slot as a drop target +19/-0

Express a visual slot as a drop target

• Finds a neighboring card and insertion direction, or uses the column itself when the active card has no neighbors.

apps/web/src/components/kanban-board/drag-preview/get-visual-task-placement.ts

move-into-hovered-column.tsDerive a cross-column hover preview +12/-0

Derive a cross-column hover preview

• Uses the board move helper to create a temporary destination-column placement without changing canonical state.

apps/web/src/components/kanban-board/drag-preview/move-into-hovered-column.ts

use-drag-preview.tsManage transient drag previews and drop placement +48/-0

Manage transient drag previews and drop placement

• Tracks the current cross-column hover, derives its preview from the latest project, and resolves placement at drop time. Exposes cleanup for cancelled, blocked, or completed drags.

apps/web/src/components/kanban-board/drag-preview/use-drag-preview.ts

index.tsxWire drag previews into Kanban reorder handling +67/-15

Wire drag previews into Kanban reorder handling

• Uses custom collision detection and drag-over events to render cross-column previews, then persists the resolved slot on drop. Clears transient state on end or cancel and shows localized hints instead of previews on automatically sorted boards.

apps/web/src/components/kanban-board/index.tsx

move-task.tsSupport explicit insertion direction on board moves +11/-5

Support explicit insertion direction on board moves

• Allows preview-derived moves to insert before or after a target while retaining append-only behavior for automatically sorted boards.

apps/web/src/components/kanban-board/move-task.ts

board.tsxAllow cross-column moves on priority-sorted boards +4/-1

Allow cross-column moves on priority-sorted boards

• Permits drag-and-drop when sorting by priority and passes that mode to the Kanban board for append-only drops and hints.

apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx

Refactor (5) +237 / -164
drag-listeners.tsShare the sortable listener type +3/-0

Share the sortable listener type

• Defines the listener type passed from the draggable card wrapper to shared click and keyboard handling.

apps/web/src/components/kanban-board/task-card/drag-listeners.ts

index.tsxSeparate sortable card behavior from card content +63/-0

Separate sortable card behavior from card content

• Adds a lightweight wrapper for sortable refs, transforms, drag styles, and keyboard activation while delegating card details to a memoized content component.

apps/web/src/components/kanban-board/task-card/index.tsx

task-card-content.tsxMemoize card content and defer auxiliary UI +60/-130

Memoize card content and defer auxiliary UI

• Moves sortable behavior into the wrapper and reuses extracted click handling. Mounts context-menu content only after first opening and the delete dialog only after it has been requested.

apps/web/src/components/kanban-board/task-card/task-card-content.tsx

use-task-card-click.tsExtract card selection and keyboard behavior +68/-0

Extract card selection and keyboard behavior

• Preserves task navigation, range and modifier selection, and drag-key forwarding for the split card components.

apps/web/src/components/kanban-board/task-card/use-task-card-click.ts

task-row.tsxDefer list-row menus and delete dialogs +43/-34

Defer list-row menus and delete dialogs

• Memoizes task rows and mounts context-menu content and the delete dialog only when first needed, matching the card-content rendering change.

apps/web/src/components/list-view/task-row.tsx

Tests (8) +493 / -14
board-collision-detection.test.tsTest pointer-based board collision targeting +93/-0

Test pointer-based board collision targeting

• Covers empty columns, cards, gaps, headers, scrolled-out cards, out-of-board pointers, and the pointerless fallback.

apps/web/src/components/kanban-board/board-collision-detection.test.ts

get-hovered-other-column-id.test.tsTest cross-column hover detection +16/-0

Test cross-column hover detection

• Checks hovers over destination cards and columns, same-column targets, and unknown identifiers.

apps/web/src/components/kanban-board/drag-preview/get-hovered-other-column-id.test.ts

get-preview-drop-placement.test.tsTest preview-to-drop placement resolution +29/-0

Test preview-to-drop placement resolution

• Verifies dropping onto the active card preserves its previewed slot, while a final hover can change its position or destination column.

apps/web/src/components/kanban-board/drag-preview/get-preview-drop-placement.test.ts

get-visual-task-placement.test.tsTest visual task-position anchors +32/-0

Test visual task-position anchors

• Covers placement before a next card, after a previous card, into a lone-card column, and an unknown card.

apps/web/src/components/kanban-board/drag-preview/get-visual-task-placement.test.ts

move-into-hovered-column.test.tsTest temporary cross-column moves +23/-0

Test temporary cross-column moves

• Checks insertion beside a hovered card, append to an empty column, and delegation of same-column hovers to sortable behavior.

apps/web/src/components/kanban-board/drag-preview/move-into-hovered-column.test.ts

test-board.tsProvide board fixtures for drag-preview tests +34/-0

Provide board fixtures for drag-preview tests

• Defines a three-column test board and a helper for asserting each column's task order.

apps/web/src/components/kanban-board/drag-preview/test-board.ts

filtered-drag.test.tsxExercise preview, cancellation, and sorted-board drops +257/-14

Exercise preview, cancellation, and sorted-board drops

• Adds board-level tests for previewed placement, return and cancellation, blocked drags, concurrent project updates, priority preservation, and append-only number or priority sorting.

apps/web/src/components/kanban-board/filtered-drag.test.tsx

move-task.test.tsTest insertion before a cross-column target +9/-0

Test insertion before a cross-column target

• Adds coverage for placing a moved card before the hovered card to match its previewed position.

apps/web/src/components/kanban-board/move-task.test.ts

Documentation (21) +42 / -21
az-AZ.jsonTranslate automatic-sort hint into Azerbaijani +2/-1

Translate automatic-sort hint into Azerbaijani

• Adds the interpolated Kanban hint for the active sort field.

i18n/az-AZ.json

de-DE.jsonTranslate automatic-sort hint into German +2/-1

Translate automatic-sort hint into German

• Adds the interpolated Kanban hint for the active sort field.

i18n/de-DE.json

el-GR.jsonTranslate automatic-sort hint into Greek +2/-1

Translate automatic-sort hint into Greek

• Adds the interpolated Kanban hint for the active sort field.

i18n/el-GR.json

en-US.jsonAdd English automatic-sort hint +2/-1

Add English automatic-sort hint

• Adds the interpolated Kanban message explaining the active sort field.

i18n/en-US.json

es-ES.jsonTranslate automatic-sort hint into Spanish +2/-1

Translate automatic-sort hint into Spanish

• Adds the interpolated Kanban hint for the active sort field.

i18n/es-ES.json

fr-FR.jsonTranslate automatic-sort hint into French +2/-1

Translate automatic-sort hint into French

• Adds the interpolated Kanban hint for the active sort field.

i18n/fr-FR.json

hi-IN.jsonTranslate automatic-sort hint into Hindi +2/-1

Translate automatic-sort hint into Hindi

• Adds the interpolated Kanban hint for the active sort field.

i18n/hi-IN.json

id-ID.jsonTranslate automatic-sort hint into Indonesian +2/-1

Translate automatic-sort hint into Indonesian

• Adds the interpolated Kanban hint for the active sort field.

i18n/id-ID.json

it-IT.jsonTranslate automatic-sort hint into Italian +2/-1

Translate automatic-sort hint into Italian

• Adds the interpolated Kanban hint for the active sort field.

i18n/it-IT.json

ja-JP.jsonTranslate automatic-sort hint into Japanese +2/-1

Translate automatic-sort hint into Japanese

• Adds the interpolated Kanban hint for the active sort field.

i18n/ja-JP.json

ko-KR.jsonTranslate automatic-sort hint into Korean +2/-1

Translate automatic-sort hint into Korean

• Adds the interpolated Kanban hint for the active sort field.

i18n/ko-KR.json

mk-MK.jsonTranslate automatic-sort hint into Macedonian +2/-1

Translate automatic-sort hint into Macedonian

• Adds the interpolated Kanban hint for the active sort field.

i18n/mk-MK.json

nl-NL.jsonTranslate automatic-sort hint into Dutch +2/-1

Translate automatic-sort hint into Dutch

• Adds the interpolated Kanban hint for the active sort field.

i18n/nl-NL.json

pl-PL.jsonTranslate automatic-sort hint into Polish +2/-1

Translate automatic-sort hint into Polish

• Adds the interpolated Kanban hint for the active sort field.

i18n/pl-PL.json

pt-BR.jsonTranslate automatic-sort hint into Brazilian Portuguese +2/-1

Translate automatic-sort hint into Brazilian Portuguese

• Adds the interpolated Kanban hint for the active sort field.

i18n/pt-BR.json

ru-RU.jsonTranslate automatic-sort hint into Russian +2/-1

Translate automatic-sort hint into Russian

• Adds the interpolated Kanban hint for the active sort field.

i18n/ru-RU.json

tr-TR.jsonTranslate automatic-sort hint into Turkish +2/-1

Translate automatic-sort hint into Turkish

• Adds the interpolated Kanban hint for the active sort field.

i18n/tr-TR.json

uk-UA.jsonTranslate automatic-sort hint into Ukrainian +2/-1

Translate automatic-sort hint into Ukrainian

• Adds the interpolated Kanban hint for the active sort field.

i18n/uk-UA.json

vi-VN.jsonTranslate automatic-sort hint into Vietnamese +2/-1

Translate automatic-sort hint into Vietnamese

• Adds the interpolated Kanban hint for the active sort field.

i18n/vi-VN.json

zh-CN.jsonTranslate automatic-sort hint into Simplified Chinese +2/-1

Translate automatic-sort hint into Simplified Chinese

• Adds the interpolated Kanban hint for the active sort field.

i18n/zh-CN.json

zh-TW.jsonTranslate automatic-sort hint into Traditional Chinese +2/-1

Translate automatic-sort hint into Traditional Chinese

• Adds the interpolated Kanban hint for the active sort field.

i18n/zh-TW.json

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Hidden cards can accept off-board drops ✓ Resolved
Description
boardCollisionDetection accepts the first pointerWithin hit without checking whether a task is
visible inside its column's scroll viewport. After scrolling a column, a clipped task can retain a
rectangle in the padding above the column, so releasing a dragged card there produces a task target
instead of cancelling the off-board drop.
Code

apps/web/src/components/kanban-board/board-collision-detection.ts[R23-25]

+  const [hit] = pointerWithin(args);
+  const target = hit
+    ? droppableContainers.find((container) => container.id === hit.id)
Relevance

●●● Strong

Directly matches the PR’s stated off-board cancellation intent; hidden pointerWithin hits can bypass
cancellation.

PR-#1816

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Tasks sit inside an overflow-clipped scrolling region, while collision detection accepts their
measured rectangles before checking any column bounds. A documented dnd-kit collision case confirms
that pointerWithin can register droppables scrolled out of view; a non-null target then reaches
the drop handler rather than its cancellation guard.

apps/web/src/components/kanban-board/column/index.tsx[70-95]
apps/web/src/components/kanban-board/task-card/index.tsx[19-30]
apps/web/src/components/kanban-board/board-collision-detection.ts[23-40]
apps/web/src/components/kanban-board/index.tsx[228-254]
🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.: 🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A task scrolled out of view can still be selected by its measured rectangle when the pointer is outside every visible column.
## Fix Focus Areas
- apps/web/src/components/kanban-board/board-collision-detection.ts[23-40]
- apps/web/src/components/kanban-board/column/index.tsx[70-95]
- apps/web/src/components/kanban-board/board-collision-detection.test.ts[67-77]
## Recommended Fix
Filter task collisions against the visible bounds of their column and scroll viewport before accepting a pointer hit. Return no collision when the pointer is outside every column and its intended gap allowance, and test a scrolled-out task whose rectangle extends into the board padding.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Some priority drags do not stick 🐞 Bug ≡ Correctness
Description
handleDragEnd computes positional changes against the canonical board and returns on an empty
moved.tasks before reaching the new priority update. If canonical order is [high, low] but
ascending-priority display order is [low, high], dragging the high-priority card above the
low-priority card previews a change yet reproduces canonical order, so neither its position nor its
priority is saved.
Code

apps/web/src/components/kanban-board/index.tsx[R379-383]

+    const priorityTarget = shouldAllowSortedReorder
+      ? canonical.columns
+          .flatMap((column) => column.tasks)
+          .find((task) => task.id === overId)
+      : null;
Evidence
The displayed board is priority-sorted, but the commit uses the unsorted cache; the move helper
reports only position and status changes, and the guard precedes the priority mutation.

apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[216-226]
apps/web/src/lib/sort-tasks.ts[25-42]
apps/web/src/components/kanban-board/select-reorder-board.ts[9-20]
apps/web/src/components/kanban-board/move-task.ts[76-88]
apps/web/src/components/kanban-board/index.tsx[365-400]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A priority-sorted drag can require a priority change even when its canonical positional changes are empty.
## Fix Focus Areas
- apps/web/src/components/kanban-board/index.tsx[365-400]
## Recommended Fix
Determine whether the target priority changes before returning for an empty position reorder, and persist that priority-only result without submitting an empty reorder.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Holding ⌘ while dragging overwrites a card's priority ✓ Resolved
Description
handleDragEnd sets priorityTarget whenever shouldAllowSortedReorder is true without checking
sortedByPriority, so updatePriority copies the target card’s priority onto the dragged task. A
⌘-drop onto a card on a position- or number-sorted board, or during a same-column reorder, reaches
the server update and publishes a mutation even when the priority is unchanged.
Code

apps/web/src/components/kanban-board/index.tsx[R379-383]

+    const priorityTarget = shouldAllowSortedReorder
+      ? canonical.columns
+          .flatMap((column) => column.tasks)
+          .find((task) => task.id === overId)
+      : null;
Evidence
The drag route permits position, number, and priority sorts, but priorityTarget depends only on
Command-assisted sorting; sortedByPriority is not checked in that block. The server controller
writes the priority and calls publishTaskMutation unconditionally, which explains why even an
unchanged priority reaches both operations.

apps/web/src/components/kanban-board/index.tsx[379-401]
apps/api/src/task/controllers/update-task-priority.ts[16-38]
apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[330-339]
apps/web/src/components/kanban-board/index.tsx[379-400]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Command-assisted drags onto cards can copy the target card’s priority on position- or number-sorted boards and can issue an update when the priority is unchanged.
## Fix Focus Areas
- apps/web/src/components/kanban-board/index.tsx[379-401]
## Recommended Fix
Set a priority target and call `updatePriority` only when `shouldAllowSortedReorder && sortedByPriority`, preserving position-only behavior for other sorts. Skip both `updatePriority` and the immer patch when `priorityTarget.priority === activeTask.priority`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Board updates disappear during a drag 🐞 Bug ≡ Correctness
Description
useDragPreview retains its preview when the project prop changes, and KanbanBoard renders that
preview instead of the updated project. If a filter or task refresh changes the board after a
cross-column hover, cards and columns remain as they were before the update until the drag ends,
while drop placement is calculated from the stale snapshot.
Code

apps/web/src/components/kanban-board/index.tsx[334]

+            {(dragPreview.preview ?? project).columns.map((column) => (
Relevance

●●● Strong

Accepted history flags stale retained UI across project changes; this directly risks losing
refreshed board updates during drag.

PR-#1686
PR-#1905

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hook stores a derived board in state and a ref without reconciling either when its project
argument changes; hover and placement prefer the ref. The new render path prefers that stored
preview over project, while the board route recomputes and passes a filtered or sorted project
when its inputs change.

apps/web/src/components/kanban-board/use-drag-preview.ts[6-30]
apps/web/src/components/kanban-board/index.tsx[69-70]
apps/web/src/components/kanban-board/index.tsx[334-334]
apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[217-229]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A cross-column preview masks project updates made during a drag and supplies stale data for drop placement.
## Fix Focus Areas
- apps/web/src/components/kanban-board/use-drag-preview.ts[6-30]
- apps/web/src/components/kanban-board/index.tsx[334-334]
## Recommended Fix
Invalidate or rebuild the preview when the underlying project changes, and ensure placement calculations use the current board. Cover a filter or task update between hover and drop.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Empty priority columns miss card drops ✓ Resolved
Description
The newly enabled priority drag path relies on ColumnDropzone for collisions, but its
content-sized droppable element has no height when a column contains no cards. On a priority-sorted
board with neighboring cards lower down, closestCorners can select a neighboring target rather
than the empty column, preventing the intended status change.
Code

apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[R333-335]

+                  (sort.field !== "position" &&
+                    sort.field !== "number" &&
+                    sort.field !== "priority")
Relevance

●●● Strong

Accepted history consistently addresses empty or filtered UI states; this concrete empty-droppable
collision bug affects the new drag path.

PR-#1686
PR-#1524

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The route newly permits priority-sorted dragging. The droppable ref is on an inner wrapper whose
task list supplies its content height; its parent is not a flex container, so the wrapper's flex-1
does not fill the column. The board uses closestCorners to choose targets.

apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[330-338]
apps/web/src/components/kanban-board/column/column-dropzone.tsx[21-47]
apps/web/src/components/kanban-board/column/index.tsx[69-83]
apps/web/src/components/kanban-board/index.tsx[319-325]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Priority-sorted boards now allow dragging, but an empty column's droppable area collapses and may lose collision detection to neighboring cards.
## Fix Focus Areas
- apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[330-338]
- apps/web/src/components/kanban-board/column/index.tsx[69-83]
- apps/web/src/components/kanban-board/column/column-dropzone.tsx[21-47]
## Recommended Fix
Make the droppable area fill the available column body even with no tasks, and test dragging into an empty priority-sorted column beside a populated one.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Cards jump to the bottom on release ✓ Resolved
Description
getPreviewDropPlacement moves the card to the end when the final target is its previewed
destination column, but useDragPreview.hover does not update the preview for a hover within that
same column. If a user previews a card before another card and then releases it over blank space
lower in the destination column, the saved position is at the bottom rather than the position shown.
Code

apps/web/src/components/kanban-board/drag-preview/get-preview-drop-placement.ts[R11-15]

+  const settled =
+    moveIntoHoveredColumn(board, activeId, overId) ??
+    (overId === activeId
+      ? board
+      : (moveBoardTask(board, activeId, overId)?.project ?? board));
Relevance

●●● Strong

Clear correctness bug: release over destination blank space saves at bottom despite the earlier
previewed position.

PR-#1816

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hover handler only changes the preview for a target in another column. For a final hover on the
destination column itself, the placement helper instead calls moveBoardTask; that function uses
the destination column ID to splice the card at the end. The column remains a droppable target below
its cards.

apps/web/src/components/kanban-board/drag-preview/use-drag-preview.ts[33-44]
apps/web/src/components/kanban-board/drag-preview/get-preview-drop-placement.ts[11-16]
apps/web/src/components/kanban-board/move-task.ts[37-48]
apps/web/src/components/kanban-board/column/column-dropzone.tsx[31-33]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Dropping over the destination column's blank space appends a card even though its preview still shows an earlier slot.
## Fix Focus Areas
- apps/web/src/components/kanban-board/drag-preview/get-preview-drop-placement.ts[11-16]
- apps/web/src/components/kanban-board/drag-preview/use-drag-preview.ts[33-44]
## Recommended Fix
When the final target is the column already containing the previewed card, derive placement from the shown board instead of appending it again. Add a test that previews a card before a destination task, then drops over that destination column.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (4)
7. Header drops miss or misplace cards ✓ Resolved
Description
boardCollisionDetection returns no collision when pointerWithin misses, even when the pointer is
over a column header rather than its droppable content. A direct release over that header is
ignored; after a preview in another column, the same release commits the card to the previously
previewed column instead.
Code

apps/web/src/components/kanban-board/board-collision-detection.ts[R20-21]

+  if (data?.type !== "column" || !data.column?.tasks.length)
+    return hit ? [hit] : [];
Relevance

●●● Strong

Clear drag-and-drop correctness regression; recent board-move fixes show the team accepts missing
target behavior affecting status changes.

PR-#1816
PR-#1594

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The header is a sibling of the content containing ColumnDropzone, so it is outside the registered
droppable rectangle. The new collision detector returns an empty list for pointer positions outside
droppables; the drag-end handler then either has no target or substitutes the active card when an
earlier preview remains.

apps/web/src/components/kanban-board/column/index.tsx[78-96]
apps/web/src/components/kanban-board/board-collision-detection.ts[13-21]
apps/web/src/components/kanban-board/index.tsx[213-255]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Pointer drags over a column header produce no collision because only the content is droppable. Without a prior preview the drop is ignored; with one, the drop can commit to the previously hovered column.
## Fix Focus Areas
- apps/web/src/components/kanban-board/board-collision-detection.ts[13-21]
- apps/web/src/components/kanban-board/column/index.tsx[78-96]
- apps/web/src/components/kanban-board/index.tsx[213-255]
## Recommended Fix
Make the intended column target detectable over its header, while keeping pointers outside the board invalid. Ensure a null collision cannot commit a stale preview to a different column, and add tests for both direct and previously previewed header drops.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Gap drops favor the left column ✓ Resolved
Description
boardCollisionDetection expands each column by COLUMN_GAP and uses find() to select the first
match, even when the pointer is closer to the next column. Because the board's gap is also 16
pixels, both adjacent columns match throughout that gap; a release in its right half can still
target the left column.
Code

apps/web/src/components/kanban-board/board-collision-detection.ts[R26-27]

+    : droppableContainers.find((container) => {
+        const rect = droppableRects.get(container.id);
Relevance

●●● Strong

Expanded adjacent columns overlap across the gap, and first-match ordering contradicts
nearest-column drop behavior.

PR-#1816

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The fallback returns the first matching expanded column. Columns are rendered with gap-4 (16
pixels), equal to the expansion on each side, so both columns match every point in their shared gap;
the new test checks only its left half.

apps/web/src/components/kanban-board/board-collision-detection.ts[8-9]
apps/web/src/components/kanban-board/board-collision-detection.ts[23-37]
apps/web/src/components/kanban-board/index.tsx[329-340]
apps/web/src/components/kanban-board/board-collision-detection.test.ts[71-73]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Both columns match a pointer in the inter-column gap, so registration order selects the left one even when the right one is nearer.
## Fix Focus Areas
- apps/web/src/components/kanban-board/board-collision-detection.ts[24-37]
- apps/web/src/components/kanban-board/board-collision-detection.test.ts[71-73]
## Recommended Fix
Evaluate all columns whose expanded bounds contain the pointer and choose the one with the shortest horizontal distance to its actual rectangle. Add a test for a pointer in the right half of the gap.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Board cards repeat workspace subscriptions 🐞 Bug ➹ Performance
Description
TaskCard and TaskCardContent each call useTaskCardClick, which subscribes to workspace and
selection state, while TaskCardContent also calls useActiveWorkspace directly. Every displayed
card now runs the workspace hook three times instead of once, multiplying subscriptions and update
work on boards with many cards.
Code

apps/web/src/components/kanban-board/task-card/index.tsx[31]

+  const { handleKeyDown } = useTaskCardClick(task, listeners);
Evidence
The column renders a TaskCard for every task. The new wrapper and its content both invoke the
click hook; that hook invokes useActiveWorkspace, which in turn invokes two organization hooks.
The content also retains its own workspace-hook call.

apps/web/src/components/kanban-board/column/column-dropzone.tsx[35-47]
apps/web/src/components/kanban-board/task-card/index.tsx[26-31]
apps/web/src/components/kanban-board/task-card/task-card-content.tsx[81-81]
apps/web/src/components/kanban-board/task-card/task-card-content.tsx[139-139]
apps/web/src/components/kanban-board/task-card/use-task-card-click.ts[8-18]
apps/web/src/hooks/queries/workspace/use-active-workspace.ts[4-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The card wrapper and content independently call `useTaskCardClick`, and the content separately calls `useActiveWorkspace`. This triples workspace-hook calls per displayed card.
## Fix Focus Areas
- apps/web/src/components/kanban-board/task-card/index.tsx[26-31]
- apps/web/src/components/kanban-board/task-card/task-card-content.tsx[81-81]
- apps/web/src/components/kanban-board/task-card/task-card-content.tsx[139-139]
- apps/web/src/components/kanban-board/task-card/use-task-card-click.ts[8-18]
## Recommended Fix
Create the click handlers once per card and pass them to the content, reusing the workspace value needed there rather than establishing duplicate hook subscriptions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Cross-column drags fail whenever a board filter is active 🐞 Bug ≡ Correctness
Description
disableDragDrop now allows dragging whenever sort.field is position, number, or priority,
while sortedProject passed to KanbanBoard is still derived from filteredProject, which drops
tasks that don't match the active filters. moveBoardTask builds its expectedTasks snapshot only
from the visible (filtered) source/destination columns, but the reorder endpoint compares that
snapshot against every task currently in those statuses on the server, so any hidden task causes the
request to fail with a 409 and the optimistic move to roll back for priority-sorted boards exactly
as it already did for number-sorted boards.
Code

apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[R333-338]

+                  (sort.field !== "position" &&
+                    sort.field !== "number" &&
+                    sort.field !== "priority")
      }
      sortedByNumber={sort.field === "number"}
+                sortedByPriority={sort.field === "priority"}
Evidence
The route enables dragging on priority-sorted boards by adding sort.field !== "priority" to the
disable condition and passing sortedByPriority, but the project it hands to KanbanBoard is
sortedProject, built from filteredProject which strips non-matching tasks. moveBoardTask
(apps/web/src/components/kanban-board/move-task.ts) and the reorder mutation only see the filtered
columns, while the backend reorder endpoint validates against the complete, unfiltered set of tasks
in each status, so a reorder submitted while any filter hides tasks in the source or destination
column is rejected and the UI snaps back.

apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[207-215]
apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[330-339]
apps/web/src/components/kanban-board/move-task.ts[1-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Enabling cross-column drag for priority-sorted boards (in addition to number-sorted) extends a pre-existing conflict: `sortedProject` fed to `KanbanBoard` is built from `filteredProject`, which omits tasks that don't match active filters. The reorder mutation's `expectedTasks` snapshot (built from the filtered, visible columns) will not match the server's unfiltered view of the same statuses whenever a filter is active, causing the reorder request to be rejected and the optimistic move rolled back.
## Fix Focus Areas
- apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[330-339]
- apps/web/src/components/kanban-board/move-task.ts[55-90]
## Recommended Fix
Extend `disableDragDrop` to also account for `hasActiveFilters` (or any other condition that hides tasks within a status), disabling drag-and-drop reordering while a filter is active, for both the number- and priority-sorted cases. Alternatively, pass the full unfiltered project's columns (not just the visible subset) into `moveBoardTask` so `expectedTasks` includes every task in the affected statuses.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 6 rules
✅ Cross-repo context — repo relationships
Review mode: Auto: 🧠 Deep: Large, bug-dense drag-and-drop feature spans many independent behavioral paths.

Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread apps/web/src/components/kanban-board/index.tsx Outdated
Comment thread apps/web/src/components/kanban-board/index.tsx Outdated
Comment thread apps/web/src/components/kanban-board/index.tsx Outdated
Comment thread apps/web/src/components/kanban-board/index.tsx Outdated
Comment thread apps/web/src/components/kanban-board/index.tsx Outdated
Comment thread apps/web/src/components/kanban-board/index.tsx Outdated
Comment thread apps/web/src/components/kanban-board/index.tsx Outdated
Comment thread apps/web/src/components/kanban-board/index.tsx Outdated
Comment thread apps/web/src/components/kanban-board/index.tsx Outdated
@VictorOnwukwe
VictorOnwukwe force-pushed the feat/cross-column-card-sorting branch from 6c29df3 to e7a167f Compare October 2, 2026 14:11
VictorOnwukwe and others added 4 commits October 2, 2026 18:13
…d-sorting

# Conflicts:
#	apps/web/src/components/kanban-board/column/column-dropzone.tsx
#	apps/web/src/components/kanban-board/filtered-drag.test.tsx
#	apps/web/src/components/kanban-board/index.tsx
#	apps/web/src/components/kanban-board/task-card.tsx
Dragging a card into another column now shows it in place while hovering,
and the drop lands where it was shown. This works with mouse, touch and
keyboard, so the hold-to-sort modifier and its key listeners are gone.

- Stop copying the neighbouring card's priority onto ⌘-dropped cards
- Replace the white full-column overlay with a themed hint, shown only
  on number- or priority-sorted boards where drops are append-only
- Drop onDragMove, the always-on layout animation, the sortable
  transform override and the unused moveBoardTask parameter
While previewing a cross-column move, the dragged card left a fading
copy behind in the column it moved out of, which overlapped the cards
around it. The dragged card now skips its enter and exit animations.
@andrejsshell

Copy link
Copy Markdown
Member

/review

@qodo-code-review

qodo-code-review Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Hidden cards can accept off-board drops ✓ Resolved
Description
boardCollisionDetection accepts the first pointerWithin hit without checking whether a task is
visible inside its column's scroll viewport. After scrolling a column, a clipped task can retain a
rectangle in the padding above the column, so releasing a dragged card there produces a task target
instead of cancelling the off-board drop.
Code

apps/web/src/components/kanban-board/board-collision-detection.ts[R23-25]

+  const [hit] = pointerWithin(args);
+  const target = hit
+    ? droppableContainers.find((container) => container.id === hit.id)
Relevance

●●● Strong

Directly matches the PR’s stated off-board cancellation intent; hidden pointerWithin hits can bypass
cancellation.

PR-#1816

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Tasks sit inside an overflow-clipped scrolling region, while collision detection accepts their
measured rectangles before checking any column bounds. A documented dnd-kit collision case confirms
that pointerWithin can register droppables scrolled out of view; a non-null target then reaches
the drop handler rather than its cancellation guard.

apps/web/src/components/kanban-board/column/index.tsx[70-95]
apps/web/src/components/kanban-board/task-card/index.tsx[19-30]
apps/web/src/components/kanban-board/board-collision-detection.ts[23-40]
apps/web/src/components/kanban-board/index.tsx[228-254]
🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A task scrolled out of view can still be selected by its measured rectangle when the pointer is outside every visible column.
## Fix Focus Areas
- apps/web/src/components/kanban-board/board-collision-detection.ts[23-40]
- apps/web/src/components/kanban-board/column/index.tsx[70-95]
- apps/web/src/components/kanban-board/board-collision-detection.test.ts[67-77]
## Recommended Fix
Filter task collisions against the visible bounds of their column and scroll viewport before accepting a pointer hit. Return no collision when the pointer is outside every column and its intended gap allowance, and test a scrolled-out task whose rectangle extends into the board padding.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Gap drops favor the left column ✓ Resolved
Description
boardCollisionDetection expands each column by COLUMN_GAP and uses find() to select the first
match, even when the pointer is closer to the next column. Because the board's gap is also 16
pixels, both adjacent columns match throughout that gap; a release in its right half can still
target the left column.
Code

apps/web/src/components/kanban-board/board-collision-detection.ts[R26-27]

+    : droppableContainers.find((container) => {
+        const rect = droppableRects.get(container.id);
Relevance

●●● Strong

Expanded adjacent columns overlap across the gap, and first-match ordering contradicts
nearest-column drop behavior.

PR-#1816

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The fallback returns the first matching expanded column. Columns are rendered with gap-4 (16
pixels), equal to the expansion on each side, so both columns match every point in their shared gap;
the new test checks only its left half.

apps/web/src/components/kanban-board/board-collision-detection.ts[8-9]
apps/web/src/components/kanban-board/board-collision-detection.ts[23-37]
apps/web/src/components/kanban-board/index.tsx[329-340]
apps/web/src/components/kanban-board/board-collision-detection.test.ts[71-73]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Both columns match a pointer in the inter-column gap, so registration order selects the left one even when the right one is nearer.
## Fix Focus Areas
- apps/web/src/components/kanban-board/board-collision-detection.ts[24-37]
- apps/web/src/components/kanban-board/board-collision-detection.test.ts[71-73]
## Recommended Fix
Evaluate all columns whose expanded bounds contain the pointer and choose the one with the shortest horizontal distance to its actual rectangle. Add a test for a pointer in the right half of the gap.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Header drops miss or misplace cards ✓ Resolved
Description
boardCollisionDetection returns no collision when pointerWithin misses, even when the pointer is
over a column header rather than its droppable content. A direct release over that header is
ignored; after a preview in another column, the same release commits the card to the previously
previewed column instead.
Code

apps/web/src/components/kanban-board/board-collision-detection.ts[R20-21]

+  if (data?.type !== "column" || !data.column?.tasks.length)
+    return hit ? [hit] : [];
Relevance

●●● Strong

Clear drag-and-drop correctness regression; recent board-move fixes show the team accepts missing
target behavior affecting status changes.

PR-#1816
PR-#1594

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The header is a sibling of the content containing ColumnDropzone, so it is outside the registered
droppable rectangle. The new collision detector returns an empty list for pointer positions outside
droppables; the drag-end handler then either has no target or substitutes the active card when an
earlier preview remains.

apps/web/src/components/kanban-board/column/index.tsx[78-96]
apps/web/src/components/kanban-board/board-collision-detection.ts[13-21]
apps/web/src/components/kanban-board/index.tsx[213-255]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Pointer drags over a column header produce no collision because only the content is droppable. Without a prior preview the drop is ignored; with one, the drop can commit to the previously hovered column.

## Fix Focus Areas
- apps/web/src/components/kanban-board/board-collision-detection.ts[13-21]
- apps/web/src/components/kanban-board/column/index.tsx[78-96]
- apps/web/src/components/kanban-board/index.tsx[213-255]

## Recommended Fix
Make the intended column target detectable over its header, while keeping pointers outside the board invalid. Ensure a null collision cannot commit a stale preview to a different column, and add tests for both direct and previously previewed header drops.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Cards jump to the bottom on release ✓ Resolved
Description
getPreviewDropPlacement moves the card to the end when the final target is its previewed
destination column, but useDragPreview.hover does not update the preview for a hover within that
same column. If a user previews a card before another card and then releases it over blank space
lower in the destination column, the saved position is at the bottom rather than the position shown.
Code

apps/web/src/components/kanban-board/drag-preview/get-preview-drop-placement.ts[R11-15]

+  const settled =
+    moveIntoHoveredColumn(board, activeId, overId) ??
+    (overId === activeId
+      ? board
+      : (moveBoardTask(board, activeId, overId)?.project ?? board));
Relevance

●●● Strong

Clear correctness bug: release over destination blank space saves at bottom despite the earlier
previewed position.

PR-#1816

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hover handler only changes the preview for a target in another column. For a final hover on the
destination column itself, the placement helper instead calls moveBoardTask; that function uses
the destination column ID to splice the card at the end. The column remains a droppable target below
its cards.

apps/web/src/components/kanban-board/drag-preview/use-drag-preview.ts[33-44]
apps/web/src/components/kanban-board/drag-preview/get-preview-drop-placement.ts[11-16]
apps/web/src/components/kanban-board/move-task.ts[37-48]
apps/web/src/components/kanban-board/column/column-dropzone.tsx[31-33]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Dropping over the destination column's blank space appends a card even though its preview still shows an earlier slot.

## Fix Focus Areas
- apps/web/src/components/kanban-board/drag-preview/get-preview-drop-placement.ts[11-16]
- apps/web/src/components/kanban-board/drag-preview/use-drag-preview.ts[33-44]

## Recommended Fix
When the final target is the column already containing the previewed card, derive placement from the shown board instead of appending it again. Add a test that previews a card before a destination task, then drops over that destination column.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (2)
5. Empty priority columns miss card drops ✓ Resolved
Description
The newly enabled priority drag path relies on ColumnDropzone for collisions, but its
content-sized droppable element has no height when a column contains no cards. On a priority-sorted
board with neighboring cards lower down, closestCorners can select a neighboring target rather
than the empty column, preventing the intended status change.
Code

apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[R333-335]

+                  (sort.field !== "position" &&
+                    sort.field !== "number" &&
+                    sort.field !== "priority")
Relevance

●●● Strong

Accepted history consistently addresses empty or filtered UI states; this concrete empty-droppable
collision bug affects the new drag path.

PR-#1686
PR-#1524

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The route newly permits priority-sorted dragging. The droppable ref is on an inner wrapper whose
task list supplies its content height; its parent is not a flex container, so the wrapper's flex-1
does not fill the column. The board uses closestCorners to choose targets.

apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[330-338]
apps/web/src/components/kanban-board/column/column-dropzone.tsx[21-47]
apps/web/src/components/kanban-board/column/index.tsx[69-83]
apps/web/src/components/kanban-board/index.tsx[319-325]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Priority-sorted boards now allow dragging, but an empty column's droppable area collapses and may lose collision detection to neighboring cards.
## Fix Focus Areas
- apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[330-338]
- apps/web/src/components/kanban-board/column/index.tsx[69-83]
- apps/web/src/components/kanban-board/column/column-dropzone.tsx[21-47]
## Recommended Fix
Make the droppable area fill the available column body even with no tasks, and test dragging into an empty priority-sorted column beside a populated one.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Board updates disappear during a drag ✓ Resolved
Description
useDragPreview retains its preview when the project prop changes, and KanbanBoard renders that
preview instead of the updated project. If a filter or task refresh changes the board after a
cross-column hover, cards and columns remain as they were before the update until the drag ends,
while drop placement is calculated from the stale snapshot.
Code

apps/web/src/components/kanban-board/index.tsx[334]

+            {(dragPreview.preview ?? project).columns.map((column) => (
Relevance

●●● Strong

Accepted history flags stale retained UI across project changes; this directly risks losing
refreshed board updates during drag.

PR-#1686
PR-#1905

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hook stores a derived board in state and a ref without reconciling either when its project
argument changes; hover and placement prefer the ref. The new render path prefers that stored
preview over project, while the board route recomputes and passes a filtered or sorted project
when its inputs change.

apps/web/src/components/kanban-board/use-drag-preview.ts[6-30]
apps/web/src/components/kanban-board/index.tsx[69-70]
apps/web/src/components/kanban-board/index.tsx[334-334]
apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[217-229]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A cross-column preview masks project updates made during a drag and supplies stale data for drop placement.
## Fix Focus Areas
- apps/web/src/components/kanban-board/use-drag-preview.ts[6-30]
- apps/web/src/components/kanban-board/index.tsx[334-334]
## Recommended Fix
Invalidate or rebuild the preview when the underlying project changes, and ensure placement calculations use the current board. Cover a filter or task update between hover and drop.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 6 rules
✅ Cross-repo context — repo relationships
Review mode: Auto: 🚀 Fast: Localized collision-detection logic with focused tests and contained behavioral impact.

Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 62d172d 🚀 Fast

Results up to commit c3ffa5e 🧠 Deep


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Remediation recommended
1. Empty priority columns miss card drops ✓ Resolved
Description
The newly enabled priority drag path relies on ColumnDropzone for collisions, but its
content-sized droppable element has no height when a column contains no cards. On a priority-sorted
board with neighboring cards lower down, closestCorners can select a neighboring target rather
than the empty column, preventing the intended status change.
Code

apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[R333-335]

+                  (sort.field !== "position" &&
+                    sort.field !== "number" &&
+                    sort.field !== "priority")
Relevance

●●● Strong

Accepted history consistently addresses empty or filtered UI states; this concrete empty-droppable
collision bug affects the new drag path.

PR-#1686
PR-#1524

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The route newly permits priority-sorted dragging. The droppable ref is on an inner wrapper whose
task list supplies its content height; its parent is not a flex container, so the wrapper's flex-1
does not fill the column. The board uses closestCorners to choose targets.

apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[330-338]
apps/web/src/components/kanban-board/column/column-dropzone.tsx[21-47]
apps/web/src/components/kanban-board/column/index.tsx[69-83]
apps/web/src/components/kanban-board/index.tsx[319-325]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Priority-sorted boards now allow dragging, but an empty column's droppable area collapses and may lose collision detection to neighboring cards.
## Fix Focus Areas
- apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[330-338]
- apps/web/src/components/kanban-board/column/index.tsx[69-83]
- apps/web/src/components/kanban-board/column/column-dropzone.tsx[21-47]
## Recommended Fix
Make the droppable area fill the available column body even with no tasks, and test dragging into an empty priority-sorted column beside a populated one.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Board updates disappear during a drag ✓ Resolved
Description
useDragPreview retains its preview when the project prop changes, and KanbanBoard renders that
preview instead of the updated project. If a filter or task refresh changes the board after a
cross-column hover, cards and columns remain as they were before the update until the drag ends,
while drop placement is calculated from the stale snapshot.
Code

apps/web/src/components/kanban-board/index.tsx[334]

+            {(dragPreview.preview ?? project).columns.map((column) => (
Relevance

●●● Strong

Accepted history flags stale retained UI across project changes; this directly risks losing
refreshed board updates during drag.

PR-#1686
PR-#1905

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hook stores a derived board in state and a ref without reconciling either when its project
argument changes; hover and placement prefer the ref. The new render path prefers that stored
preview over project, while the board route recomputes and passes a filtered or sorted project
when its inputs change.

apps/web/src/components/kanban-board/use-drag-preview.ts[6-30]
apps/web/src/components/kanban-board/index.tsx[69-70]
apps/web/src/components/kanban-board/index.tsx[334-334]
apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[217-229]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A cross-column preview masks project updates made during a drag and supplies stale data for drop placement.
## Fix Focus Areas
- apps/web/src/components/kanban-board/use-drag-preview.ts[6-30]
- apps/web/src/components/kanban-board/index.tsx[334-334]
## Recommended Fix
Invalidate or rebuild the preview when the underlying project changes, and ensure placement calculations use the current board. Cover a filter or task update between hover and drop.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit 01e3065 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Remediation recommended
1. Cards jump to the bottom on release ✓ Resolved
Description
getPreviewDropPlacement moves the card to the end when the final target is its previewed
destination column, but useDragPreview.hover does not update the preview for a hover within that
same column. If a user previews a card before another card and then releases it over blank space
lower in the destination column, the saved position is at the bottom rather than the position shown.
Code

apps/web/src/components/kanban-board/drag-preview/get-preview-drop-placement.ts[R11-15]

+  const settled =
+    moveIntoHoveredColumn(board, activeId, overId) ??
+    (overId === activeId
+      ? board
+      : (moveBoardTask(board, activeId, overId)?.project ?? board));
Relevance

●●● Strong

Clear correctness bug: release over destination blank space saves at bottom despite the earlier
previewed position.

PR-#1816

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hover handler only changes the preview for a target in another column. For a final hover on the
destination column itself, the placement helper instead calls moveBoardTask; that function uses
the destination column ID to splice the card at the end. The column remains a droppable target below
its cards.

apps/web/src/components/kanban-board/drag-preview/use-drag-preview.ts[33-44]
apps/web/src/components/kanban-board/drag-preview/get-preview-drop-placement.ts[11-16]
apps/web/src/components/kanban-board/move-task.ts[37-48]
apps/web/src/components/kanban-board/column/column-dropzone.tsx[31-33]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Dropping over the destination column's blank space appends a card even though its preview still shows an earlier slot.

## Fix Focus Areas
- apps/web/src/components/kanban-board/drag-preview/get-preview-drop-placement.ts[11-16]
- apps/web/src/components/kanban-board/drag-preview/use-drag-preview.ts[33-44]

## Recommended Fix
When the final target is the column already containing the previewed card, derive placement from the shown board instead of appending it again. Add a test that previews a card before a destination task, then drops over that destination column.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit 1d40c64 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Remediation recommended
1. Header drops miss or misplace cards ✓ Resolved
Description
boardCollisionDetection returns no collision when pointerWithin misses, even when the pointer is
over a column header rather than its droppable content. A direct release over that header is
ignored; after a preview in another column, the same release commits the card to the previously
previewed column instead.
Code

apps/web/src/components/kanban-board/board-collision-detection.ts[R20-21]

+  if (data?.type !== "column" || !data.column?.tasks.length)
+    return hit ? [hit] : [];
Relevance

●●● Strong

Clear drag-and-drop correctness regression; recent board-move fixes show the team accepts missing
target behavior affecting status changes.

PR-#1816
PR-#1594

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The header is a sibling of the content containing ColumnDropzone, so it is outside the registered
droppable rectangle. The new collision detector returns an empty list for pointer positions outside
droppables; the drag-end handler then either has no target or substitutes the active card when an
earlier preview remains.

apps/web/src/components/kanban-board/column/index.tsx[78-96]
apps/web/src/components/kanban-board/board-collision-detection.ts[13-21]
apps/web/src/components/kanban-board/index.tsx[213-255]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Pointer drags over a column header produce no collision because only the content is droppable. Without a prior preview the drop is ignored; with one, the drop can commit to the previously hovered column.

## Fix Focus Areas
- apps/web/src/components/kanban-board/board-collision-detection.ts[13-21]
- apps/web/src/components/kanban-board/column/index.tsx[78-96]
- apps/web/src/components/kanban-board/index.tsx[213-255]

## Recommended Fix
Make the intended column target detectable over its header, while keeping pointers outside the board invalid. Ensure a null collision cannot commit a stale preview to a different column, and add tests for both direct and previously previewed header drops.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit c905658 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Hidden cards can accept off-board drops ✓ Resolved
Description
boardCollisionDetection accepts the first pointerWithin hit without checking whether a task is
visible inside its column's scroll viewport. After scrolling a column, a clipped task can retain a
rectangle in the padding above the column, so releasing a dragged card there produces a task target
instead of cancelling the off-board drop.
Code

apps/web/src/components/kanban-board/board-collision-detection.ts[R23-25]

+  const [hit] = pointerWithin(args);
+  const target = hit
+    ? droppableContainers.find((container) => container.id === hit.id)
Relevance

●●● Strong

Directly matches the PR’s stated off-board cancellation intent; hidden pointerWithin hits can bypass
cancellation.

PR-#1816

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Tasks sit inside an overflow-clipped scrolling region, while collision detection accepts their
measured rectangles before checking any column bounds. A documented dnd-kit collision case confirms
that pointerWithin can register droppables scrolled out of view; a non-null target then reaches
the drop handler rather than its cancellation guard.

apps/web/src/components/kanban-board/column/index.tsx[70-95]
apps/web/src/components/kanban-board/task-card/index.tsx[19-30]
apps/web/src/components/kanban-board/board-collision-detection.ts[23-40]
apps/web/src/components/kanban-board/index.tsx[228-254]
🌐 Reports collisions with droppables hidden by scrolling and shows a pointerWithin workaround that filters overflowed elements.

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A task scrolled out of view can still be selected by its measured rectangle when the pointer is outside every visible column.
## Fix Focus Areas
- apps/web/src/components/kanban-board/board-collision-detection.ts[23-40]
- apps/web/src/components/kanban-board/column/index.tsx[70-95]
- apps/web/src/components/kanban-board/board-collision-detection.test.ts[67-77]
## Recommended Fix
Filter task collisions against the visible bounds of their column and scroll viewport before accepting a pointer hit. Return no collision when the pointer is outside every column and its intended gap allowance, and test a scrolled-out task whose rectangle extends into the board padding.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
2. Gap drops favor the left column ✓ Resolved
Description
boardCollisionDetection expands each column by COLUMN_GAP and uses find() to select the first
match, even when the pointer is closer to the next column. Because the board's gap is also 16
pixels, both adjacent columns match throughout that gap; a release in its right half can still
target the left column.
Code

apps/web/src/components/kanban-board/board-collision-detection.ts[R26-27]

+    : droppableContainers.find((container) => {
+        const rect = droppableRects.get(container.id);
Relevance

●●● Strong

Expanded adjacent columns overlap across the gap, and first-match ordering contradicts
nearest-column drop behavior.

PR-#1816

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The fallback returns the first matching expanded column. Columns are rendered with gap-4 (16
pixels), equal to the expansion on each side, so both columns match every point in their shared gap;
the new test checks only its left half.

apps/web/src/components/kanban-board/board-collision-detection.ts[8-9]
apps/web/src/components/kanban-board/board-collision-detection.ts[23-37]
apps/web/src/components/kanban-board/index.tsx[329-340]
apps/web/src/components/kanban-board/board-collision-detection.test.ts[71-73]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Both columns match a pointer in the inter-column gap, so registration order selects the left one even when the right one is nearer.
## Fix Focus Areas
- apps/web/src/components/kanban-board/board-collision-detection.ts[24-37]
- apps/web/src/components/kanban-board/board-collision-detection.test.ts[71-73]
## Recommended Fix
Evaluate all columns whose expanded bounds contain the pointer and choose the one with the shortest horizontal distance to its actual rectangle. Add a test for a pointer in the right half of the gap.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit 62d172d 🚀 Fast


No changes from previous review

Grey Divider

Qodo Logo

Comment thread apps/web/src/components/kanban-board/index.tsx
andrejsshell and others added 2 commits October 5, 2026 20:27
Address review feedback on the cross-column preview:

- Rebuild the preview from the current board and the last cross-column
  hover, so filters and live updates during a drag stay visible and the
  drop is placed against fresh data
- Let each column's drop zone fill its body, so an empty column still
  wins collisions against cards in neighbouring columns
- Split the drag preview into single-purpose modules with tests beside
  them, and drop the inline comments
Comment on lines +333 to +338
(sort.field !== "position" &&
sort.field !== "number" &&
sort.field !== "priority")
}
sortedByNumber={sort.field === "number"}
sortedByPriority={sort.field === "priority"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

8. Cross-column drags fail whenever a board filter is active 🐞 Bug ≡ Correctness

disableDragDrop now allows dragging whenever sort.field is position, number, or priority,
while sortedProject passed to KanbanBoard is still derived from filteredProject, which drops
tasks that don't match the active filters. moveBoardTask builds its expectedTasks snapshot only
from the visible (filtered) source/destination columns, but the reorder endpoint compares that
snapshot against every task currently in those statuses on the server, so any hidden task causes the
request to fail with a 409 and the optimistic move to roll back for priority-sorted boards exactly
as it already did for number-sorted boards.
Agent Prompt
## Issue description
Enabling cross-column drag for priority-sorted boards (in addition to number-sorted) extends a pre-existing conflict: `sortedProject` fed to `KanbanBoard` is built from `filteredProject`, which omits tasks that don't match active filters. The reorder mutation's `expectedTasks` snapshot (built from the filtered, visible columns) will not match the server's unfiltered view of the same statuses whenever a filter is active, causing the reorder request to be rejected and the optimistic move rolled back.

## Fix Focus Areas
- apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/board.tsx[330-339]
- apps/web/src/components/kanban-board/move-task.ts[55-90]

## Recommended Fix
Extend `disableDragDrop` to also account for `hasActiveFilters` (or any other condition that hides tasks within a status), disabling drag-and-drop reordering while a filter is active, for both the number- and priority-sorted cases. Alternatively, pass the full unfiltered project's columns (not just the visible subset) into `moveBoardTask` so `expectedTasks` includes every task in the affected statuses.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +333 to +338
(sort.field !== "position" &&
sort.field !== "number" &&
sort.field !== "priority")
}
sortedByNumber={sort.field === "number"}
sortedByPriority={sort.field === "priority"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Informational

12. Priority-board reorders in one column snap back 🐞 Bug ≡ Correctness

board.tsx now enables dragging when the board is sorted by priority, but moveBoardTask returns
null when appendOnly is set and the source and destination columns are the same. handleDragOver
only sets the sort hint for a different column, so a drag inside one column shows the sortable shift
animation, then snaps back on drop with no hint explaining why.
Agent Prompt
## Issue description
On priority-sorted boards, dragging a card within its own column animates a reorder, but nothing is saved and the card snaps back. No hint is shown.

## Fix Focus Areas
- apps/web/src/components/kanban-board/index.tsx[207-221]
- apps/web/src/components/kanban-board/drag-preview.ts[13-21]

## Recommended Fix
When `isAutomaticallySorted` is true, also show the sort hint while the card hovers its own column. Another option is to disable sortable reordering within the source column on automatically sorted boards.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +207 to +211
const handleDragOver = ({ active, over }: DragOverEvent) => {
if (!isAutomaticallySorted) {
if (over) dragPreview.hover(active, over);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

9. Previewed moves can silently revert on drop 🐞 Bug ☼ Reliability

handleDragOver updates the cross-column preview on every hover without checking the guards in
handleDragEnd: disableDragDrop (keyboard drags are still allowed), isReordering, and an
in-flight ["tasks", id] fetch. Each successful reorder invalidates and refetches the board. A user
who drags again during that window sees the card settle in the new column, then jump back on drop
with no feedback.
Agent Prompt
## Issue description
The cross-column preview appears even when `handleDragEnd` will reject the drop (drag disabled, reorder pending, board fetching). The card visibly moves, then jumps back with no message.

## Fix Focus Areas
- apps/web/src/components/kanban-board/index.tsx[207-237]

## Recommended Fix
Pull the rejection checks (`disableDragDrop`, `isReordering`, fetchStatus === 'fetching') into a helper. Call it in `handleDragOver` and skip `dragPreview.hover` when it is true. Optionally show a toast when a drop is discarded.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit c3ffa5e

Dragging between columns re-rendered every card several times per
pointer move, and an empty column could send the preview into an
endless loop.

- Split TaskCard into a thin sortable wrapper and memoized content, so
  dnd-kit's per-move updates no longer re-render the card body
- Pass the dragged card's id to columns as a prop instead of reading
  the drag context, which changes on every pointer move
- Pick drop targets by what is under the pointer instead of corner
  distance, which made the preview flip between an empty column and a
  neighbouring card until React gave up
- Drop on the shown position when released between columns
Every board card and list row rendered its full context menu, with eight
mutation hooks and every submenu, plus a delete dialog, before anyone
opened them. With 200 tasks that dominated switching to the board or list.
Both now mount the first time they open and stay mounted afterwards.
Address Qodo review findings:

- Skip and clear the cross-column preview and sort hint while the board
  would refuse the drop (dragging disabled, a reorder in flight, or the
  board refetching), so a card no longer lands and then jumps back
- On number- and priority-sorted boards, stop shifting cards during a
  drag within a column and show the sort hint on the hovered column,
  including the card's own, instead of animating a move that snaps back
@andrejsshell

Copy link
Copy Markdown
Member

/review

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 01e3065

Releasing over empty space in a column targeted the column itself, which
the drop treated as "append" while the preview still showed the card in
its earlier slot. Blank space in a column with cards now targets the
nearest card, so the preview follows the pointer to the bottom and the
drop saves what was shown. Empty columns still target the column.
@andrejsshell

Copy link
Copy Markdown
Member

/review

Comment thread apps/web/src/components/kanban-board/board-collision-detection.ts Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 1d40c64

Comment thread apps/web/src/components/kanban-board/index.tsx Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 1d40c64

… drops

Address Qodo review findings:

- Make the whole column, header included, the drop target, so releasing
  over a header lands at the top of that column instead of being ignored
  or committed to a previously previewed column
- Snap the pointer to a column across the gap between columns
- Clear the preview when the pointer leaves every column and cancel a
  release there, instead of saving the last previewed position
@andrejsshell

Copy link
Copy Markdown
Member

/review

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

Comment thread apps/web/src/components/kanban-board/board-collision-detection.ts Outdated
Comment thread apps/web/src/components/kanban-board/board-collision-detection.ts Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit c905658

Address Qodo review findings:

- Choose the column whose visible area holds the pointer, or the nearer
  column across a gap, instead of the first match, so the right half of
  a gap no longer drops into the left column
- Only consider cards visible inside that column, so cards scrolled out
  of view no longer accept drops released above or below the board
@andrejsshell

Copy link
Copy Markdown
Member

/review

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 62d172d

disabled: disableDragDrop,
data: { isFinalColumn },
});
const { handleKeyDown } = useTaskCardClick(task, listeners);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

4. Board cards repeat workspace subscriptions 🐞 Bug ➹ Performance

TaskCard and TaskCardContent each call useTaskCardClick, which subscribes to workspace and
selection state, while TaskCardContent also calls useActiveWorkspace directly. Every displayed
card now runs the workspace hook three times instead of once, multiplying subscriptions and update
work on boards with many cards.
Agent Prompt
## Issue description
The card wrapper and content independently call `useTaskCardClick`, and the content separately calls `useActiveWorkspace`. This triples workspace-hook calls per displayed card.
## Fix Focus Areas
- apps/web/src/components/kanban-board/task-card/index.tsx[26-31]
- apps/web/src/components/kanban-board/task-card/task-card-content.tsx[81-81]
- apps/web/src/components/kanban-board/task-card/task-card-content.tsx[139-139]
- apps/web/src/components/kanban-board/task-card/use-task-card-click.ts[8-18]
## Recommended Fix
Create the click handlers once per card and pass them to the content, reusing the workspace value needed there rather than establishing duplicate hook subscriptions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 62d172d

@andrejsshell

Copy link
Copy Markdown
Member

Hey, I changed this a little and added some performance improvements.

The Linear approach was fine, however I believe we don't need to press Ctrl to re-order the tasks so I made it by default possible to re-order the tasks.

Thanks for the contribution!

@andrejsshell andrejsshell reopened this Oct 5, 2026
@andrejsshell
andrejsshell merged commit 95e9861 into usekaneo:main Oct 5, 2026
22 of 28 checks passed
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 62d172d

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 62d172d

adeyholar pushed a commit to adeyholar/kaneo that referenced this pull request Oct 5, 2026
### Features

- make cross-column dragged card sortable: usekaneo#1894
- **i18n:** add zh-TW locale: usekaneo#1893
- **integrations:** add label-based sync in advanced settings: usekaneo#1908

### Bug Fixes

- **web:** show the task label editor on narrow screens: usekaneo#1924
- convert ineligible contributions to draft pull requests: [291da4a](usekaneo@291da4a)
- **i18n:** translate the zh-CN strings added since the last sync: usekaneo#1916
- **ci:** exclude skipped events from eligibility concurrency: usekaneo#1917

### Credits

Huge thanks to @VictorOnwukwe, @kenny-ish, @ApplesBear-X, @tinsever, and @FunnyQ for helping!
@qodo-code-review qodo-code-review Bot mentioned this pull request Oct 6, 2026
12 of 21 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants