From 8a07cf40356d70853c4a09e110ad7893548950de Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Thu, 23 Apr 2026 17:49:01 -0700 Subject: [PATCH] fix(canvas): skip already-nested workspaces as drop targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dragging one workspace onto another could pick a nested child as the "nearest" drop target instead of the visible parent card the user actually hovered. The effect: dropping a free-floating Hermes Agent onto a Claude Code Agent that already had a Hermes Agent nested inside showed "Move 'Hermes Agent' inside 'Hermes Agent'?" — the confirmation referenced the nested same-named child, not Claude Code. Why: getIntersectingNodes returns every overlapping node, including hidden=true children that render inside their parent's card. The parent and child share bounding boxes, so the child often "won" the nearest-distance check. Filter them out at the source: a node that's already got a parentId (or is hidden) is never a valid top-level drop target. Co-Authored-By: Claude Opus 4.7 (1M context) --- canvas/src/components/Canvas.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/canvas/src/components/Canvas.tsx b/canvas/src/components/Canvas.tsx index 0cb3c3de..621da392 100644 --- a/canvas/src/components/Canvas.tsx +++ b/canvas/src/components/Canvas.tsx @@ -96,6 +96,14 @@ function CanvasInner() { let nearest: { id: string; dist: number } | null = null; for (const candidate of getIntersectingNodes(node)) { if (candidate.id === node.id || isDescendant(node.id, candidate.id)) continue; + // Skip nodes already nested inside another workspace: they render + // as TEAM MEMBERS rows inside their parent card and share its + // bounding box, so getIntersectingNodes would otherwise pick the + // nested child (same "Hermes Agent" name) over the visible parent + // the user actually dropped onto. Hidden nodes + nodes with a + // parentId are never valid top-level drop targets. + const candData = candidate.data as WorkspaceNodeData | undefined; + if (candidate.hidden || candData?.parentId) continue; const dx = candidate.position.x - node.position.x; const dy = candidate.position.y - node.position.y; const dist2 = dx * dx + dy * dy;