fix(sidebar): stop bubbled dragleave events cancelling an in-progress drag - #6679
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Spring-open folders opened mid-drag (hover expand) are tracked and closed on drag end unless the drop landed in that folder or its ancestors; the hover timer is cleared synchronously on drag end to avoid a late expand racing the next drag. Reorder commits use Adds unit tests for both dragleave paths, spring-open revert edge cases, and existing stranded-drag behavior. Reviewed by Cursor Bugbot for commit 3250c57. Configure here. |
Greptile SummaryThe PR hardens sidebar drag-and-drop lifecycle handling and makes reorder failures visible.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-drag-drop.ts | Centralizes dragleave containment, manages spring-open folder cleanup, and reports total versus partial reorder failures. |
| apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-drag-drop.test.tsx | Adds regression coverage for internal and genuine dragleave events, drag cancellation, destination preservation, timer cleanup, and pre-expanded folders. |
Sequence Diagram
sequenceDiagram
participant User
participant Sidebar
participant FolderState
participant ReorderAPI
User->>Sidebar: Drag item across rows
Sidebar->>Sidebar: Ignore internal dragleave boundaries
User->>Sidebar: Hover collapsed folder
Sidebar->>FolderState: Spring-open after delay
User->>Sidebar: Drop or cancel
alt Drop
Sidebar->>Sidebar: Preserve destination and ancestors
Sidebar->>ReorderAPI: Reorder folders and workflows
ReorderAPI-->>Sidebar: Settled results
Sidebar->>FolderState: Collapse other spring-opened folders
else Cancel
Sidebar->>FolderState: Collapse all spring-opened folders
end
Reviews (2): Last reviewed commit: "fix(sidebar): disarm the spring-open tim..." | Re-trigger Greptile
bab5221 to
3250c57
Compare
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 3250c57. Configure here.
Summary
The drag bug
dragleavebubbles, so a container-level listener fires for every descendant the pointer leaves — and Chrome reportsrelatedTargetasnullondragleave(Firefox populates it). Two places read "no related node" as "the drag left the list" and cleared the drop indicator;handleDropbails on a null indicator, so releasing just after an internal boundary crossing did nothing at all.Rows nested inside an expanded folder sit behind three extra boundaries (content zone → spacing div → row wrapper), so an expanded folder made it dramatically more likely — which is why it read as "open folders break dragging".
Both sites (the container listener and
isLeavingElement, which backs the root drop zone'sonDragLeave) now go through one predicate that falls back to hit-testing the pointer against the element box whenrelatedTargetis null.Spring-open revert
Hovering a collapsed folder mid-drag opens it after 400ms, but nothing ever closed it again, so dragging past a folder left it permanently expanded. Folders opened this way are now tracked and closed on drag end, keeping the drop destination and its ancestors open. Runs on drop, Esc-cancel, and release-outside alike.
Reorder failures
Failures were
logger.erroronly, so a failed reorder looked like the sidebar spontaneously undoing the move. Now a toast.Promise.allalso becamePromise.allSettled: withall, one rejection abandoned the sibling request while it was still in flight and committing anyway, so the caller could not distinguish a total failure from a half-applied one.This does not make the two reorder writes atomic — folders and workflows share one sortOrder index space but commit through separate endpoints. The proper fix is a single application operation writing both in one transaction, which needs a new contract, a migration of two legacy raw routes, and a merged optimistic-update handler. Filed separately rather than bundled here.
Cleanup carried along
!isDraggingRef.current, so it was written only at drop time and cleared by the next dragstart before anything read it — a 100% miss rate on every pathcompareByOrderfrom the sidebar's own utilsmutateAsyncoff the reorder mutations: the mutation objects take a new identity on each state transition, so depending on them re-created every row's handler object mid-dropinitDragOvernow readsisDraggingRefrather than the state that lags it by a render, which also stabilises all six drop-zone factories for the whole dragType of Change
Testing
8 unit tests covering both dragleave paths, the spring-open revert (including "drop landed inside" and "user opened it themselves"), and the existing stranded-drag reset. Each fix was verified by reverting it individually and confirming exactly the matching test goes red.
Not verified in a browser — the diagnosis is from source plus tests reproducing the exact event shape.
Checklist