Add drag-and-drop project reordering to the sidebar#185
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Sortable wrapper div creates invalid HTML in list
- Changed SortableProjectItem to render a SidebarMenuItem (
- ) instead of a , and removed the redundant inner SidebarMenuItem wrapper, producing valid
- …
Or push these changes by commenting:
@cursor push 410550ecbe
Preview (410550ecbe)
diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx
--- a/apps/web/src/components/Sidebar.tsx
+++ b/apps/web/src/components/Sidebar.tsx
@@ -273,15 +273,17 @@
function SortableProjectItem({
projectId,
+ className,
children,
}: {
projectId: ProjectId;
+ className?: string;
children: (handleProps: SortableProjectHandleProps) => React.ReactNode;
}) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging, isOver } =
useSortable({ id: projectId });
return (
- <div
+ <SidebarMenuItem
ref={setNodeRef}
style={{
transform: CSS.Translate.toString(transform),
@@ -289,10 +291,10 @@
}}
className={`rounded-md ${
isDragging ? "z-20 opacity-80" : ""
- } ${isOver && !isDragging ? "ring-1 ring-primary/40" : ""}`}
+ } ${isOver && !isDragging ? "ring-1 ring-primary/40" : ""} ${className ?? ""}`}
>
{children({ attributes, listeners })}
- </div>
+ </SidebarMenuItem>
);
}
@@ -1205,9 +1207,9 @@
: projectThreads;
return (
- <SortableProjectItem key={project.id} projectId={project.id}>
+ <SortableProjectItem key={project.id} projectId={project.id} className="group/collapsible">
{(dragHandleProps) => (
- <SidebarMenuItem className="group/collapsible">
+ <>
<div className="group/project-header relative">
<SidebarMenuButton
size="sm"
@@ -1448,7 +1450,7 @@
</SidebarMenuSub>
</div>
</div>
- </SidebarMenuItem>
+ </>
)}
</SortableProjectItem>
);|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
- wire project list to dnd-kit sortable with vertical drag constraints - prevent accidental expand/collapse clicks during drag gestures - update store logic/tests and add dnd-kit dependencies
- prevent accidental project toggles after drag operations - switch project rows to Collapsible for stable expand/collapse animation - add optional hidden scrollbar mode to ScrollArea and apply it in SidebarContent
- Reset post-drag click suppression when drag is canceled - Consume only the synthetic click after drag release, then clear suppression
28df73c to
f32db73
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Ambiguous operator precedence in fallback order calculation
- Added explicit outer parentheses around the fallback arithmetic expression to make the precedence between
??and+unambiguous.
- Added explicit outer parentheses around the fallback arithmetic expression to make the precedence between
Or push these changes by commenting:
@cursor push 148d455f06
Preview (148d455f06)
diff --git a/apps/web/src/store.ts b/apps/web/src/store.ts
--- a/apps/web/src/store.ts
+++ b/apps/web/src/store.ts
@@ -155,7 +155,7 @@
const orderIndex =
previousIndex ??
persistedIndex ??
- (usePersistedOrder ? persistedProjectOrderCwds.length : previous.length) + incomingIndex;
+ ((usePersistedOrder ? persistedProjectOrderCwds.length : previous.length) + incomingIndex);
return { project, incomingIndex, orderIndex };
})
.toSorted((a, b) => {| const orderIndex = | ||
| previousIndex ?? | ||
| persistedIndex ?? | ||
| (usePersistedOrder ? persistedProjectOrderCwds.length : previous.length) + incomingIndex; |
There was a problem hiding this comment.
Ambiguous operator precedence in fallback order calculation
Low Severity
The orderIndex fallback expression relies on + having higher precedence than ?? without explicit parentheses. While the current behavior is correct (the addition only applies when both previousIndex and persistedIndex are nullish), a reader could easily misinterpret this as adding incomingIndex to the result of the entire ?? chain. Wrapping the fallback arithmetic in parentheses would make the intent unambiguous and prevent accidental breakage if the expression is modified later.
- Prefer pointer-based hit testing during project reordering - Fall back to corner-based collision to make drop targeting more reliable
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Drag cancel doesn't suppress synthetic click after release
- Removed the line that reset suppressProjectClickAfterDragRef to false in handleProjectDragCancel, so the flag stays true and suppresses the synthetic click fired on pointer release after a cancelled drag, matching handleProjectDragEnd's behavior.
Or push these changes by commenting:
@cursor push 13c88d8bd1
Preview (13c88d8bd1)
diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx
--- a/apps/web/src/components/Sidebar.tsx
+++ b/apps/web/src/components/Sidebar.tsx
@@ -872,7 +872,6 @@
const handleProjectDragCancel = useCallback((_event: DragCancelEvent) => {
dragInProgressRef.current = false;
- suppressProjectClickAfterDragRef.current = false;
}, []);
const handleProjectTitlePointerDownCapture = useCallback(() => {…vent unintended toggle Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com> Applied via @cursor push command
| const orderIndex = | ||
| previousIndex ?? | ||
| persistedIndex ?? | ||
| (usePersistedOrder ? persistedProjectOrderCwds.length : previous.length) + incomingIndex; |
There was a problem hiding this comment.
Operator precedence bug in order index fallback calculation
Low Severity
The + operator has higher precedence than the ternary ?:, so the fallback orderIndex expression is parsed as usePersistedOrder ? persistedProjectOrderCwds.length : (previous.length + incomingIndex) instead of the likely intended (usePersistedOrder ? persistedProjectOrderCwds.length : previous.length) + incomingIndex. When usePersistedOrder is true, incomingIndex is not added, so all new projects receive the same orderIndex. The sort tiebreaker on incomingIndex masks this today, but the asymmetry is almost certainly unintentional and fragile.



CleanShot.2026-03-09.at.14.15.02.mp4
Summary
@dnd-kit(core,sortable,modifiers,utilities)Collapsibleto inline animated expand/collapse layoutTesting
apps/web/src/store.test.tsfor project reorder behaviorbun lintbun typecheckbun run testNote
Medium Risk
Moderate UI/state change that adds new drag interactions and persists project ordering in
localStorage, which could impact sidebar behavior and read-model sync ordering if edge cases are missed.Overview
Sidebar projects can now be reordered via drag-and-drop. The sidebar wraps each project in a
@dnd-kitsortable context (vertical-only, parent-bounded) and adds click/keyboard suppression logic to prevent accidental expand/collapse toggles during drag gestures.Project ordering is now stateful and persisted. The Zustand store adds
reorderProjects, persistsprojectOrderCwdsalongside expanded state, and updates read-model syncing to preserve existing (or persisted) project order when server updates arrive; tests were extended to cover both reorder and sync-order preservation.Small UI plumbing updates include a
hideScrollbarsoption onScrollArea(used bySidebarContent), aCollapsiblepanel animation tweak, and minor build tooling adjustments (Vite override / config cleanup).Written by Cursor Bugbot for commit 2c6bbc3. This will update automatically on new commits. Configure here.
Note
Add drag-and-drop project reordering to the sidebar
DndContextandSortableContextfrom@dnd-kit, with a newSortableProjectItemcomponent that applies transform/transition styles and drag handle props during drag interactions.reorderProjectsto the Zustand store as a pure function that moves a project to a target index; project order is persisted to and restored fromlocalStorage.CollapsibleTriggerwith aSidebarMenuButtonthat handles click, keyboard (Enter/Space), and context menu events directly, with click suppression immediately after a drag to prevent unintended toggles.localStorageunder a newprojectOrderCwdskey; users who clear storage will lose their custom ordering.Macroscope summarized 2c6bbc3.