@@ -2772,10 +2772,37 @@ const WorkflowContent = React.memo(
27722772 ( changes : NodeChange [ ] ) => {
27732773 const hasSelectionChange = changes . some ( ( c ) => c . type === 'select' )
27742774 setDisplayNodes ( ( currentNodes ) => {
2775- const updated = applyNodeChanges ( changes , currentNodes )
2775+ // Filter out cross-context selection changes before applying so that
2776+ // nodes at a different nesting level never appear selected, even for
2777+ // a single frame.
2778+ let changesToApply = changes
2779+ if ( hasSelectionChange ) {
2780+ const currentlySelected = currentNodes . filter ( ( n ) => n . selected )
2781+ // Only filter on additive multi-select (shift-click), not replacement
2782+ // clicks. A replacement click includes deselections of currently selected
2783+ // nodes; a shift-click only adds selections.
2784+ const isReplacementClick = changes . some (
2785+ ( c ) =>
2786+ c . type === 'select' &&
2787+ 'selected' in c &&
2788+ ! c . selected &&
2789+ currentlySelected . some ( ( n ) => n . id === c . id )
2790+ )
2791+ if ( ! isReplacementClick && currentlySelected . length > 0 ) {
2792+ const selectionContext = getNodeSelectionContextId ( currentlySelected [ 0 ] , blocks )
2793+ changesToApply = changes . filter ( ( c ) => {
2794+ if ( c . type !== 'select' || ! ( 'selected' in c ) || ! c . selected ) return true
2795+ const node = currentNodes . find ( ( n ) => n . id === c . id )
2796+ if ( ! node ) return true
2797+ return getNodeSelectionContextId ( node , blocks ) === selectionContext
2798+ } )
2799+ }
2800+ }
2801+
2802+ const updated = applyNodeChanges ( changesToApply , currentNodes )
27762803 if ( ! hasSelectionChange ) return updated
27772804
2778- const preferredNodeId = [ ...changes ]
2805+ const preferredNodeId = [ ...changesToApply ]
27792806 . reverse ( )
27802807 . find (
27812808 ( change ) : change is NodeChange & { id : string ; selected : boolean } =>
@@ -3271,12 +3298,17 @@ const WorkflowContent = React.memo(
32713298 previousPositions : multiNodeDragStartRef . current ,
32723299 } )
32733300
3274- // Process parent updates using shared helper
3275- executeBatchParentUpdate (
3276- selectedNodes ,
3277- potentialParentId ,
3278- 'Batch moved nodes to new parent'
3279- )
3301+ // Only reparent when an actual drag changed the target container.
3302+ // onNodeDragStart sets both potentialParentId and dragStartParentId to the
3303+ // clicked node's current parent; they only diverge when onNodeDrag detects
3304+ // the selection being dragged over a different container.
3305+ if ( potentialParentId !== dragStartParentId ) {
3306+ executeBatchParentUpdate (
3307+ selectedNodes ,
3308+ potentialParentId ,
3309+ 'Batch moved nodes to new parent'
3310+ )
3311+ }
32803312
32813313 // Clear drag start state
32823314 setDragStartPosition ( null )
@@ -3687,6 +3719,20 @@ const WorkflowContent = React.memo(
36873719 const handleNodeClick = useCallback (
36883720 ( event : React . MouseEvent , node : Node ) => {
36893721 const isMultiSelect = event . shiftKey || event . metaKey || event . ctrlKey
3722+
3723+ // Ignore shift-clicks on nodes at a different nesting level
3724+ if ( isMultiSelect ) {
3725+ const clickedContext = getNodeSelectionContextId ( node , blocks )
3726+ const currentlySelected = getNodes ( ) . filter ( ( n ) => n . selected )
3727+ if ( currentlySelected . length > 0 ) {
3728+ const selectionContext = getNodeSelectionContextId ( currentlySelected [ 0 ] , blocks )
3729+ if ( clickedContext !== selectionContext ) {
3730+ usePanelEditorStore . getState ( ) . clearCurrentBlock ( )
3731+ return
3732+ }
3733+ }
3734+ }
3735+
36903736 setDisplayNodes ( ( currentNodes ) => {
36913737 const updated = currentNodes . map ( ( currentNode ) => ( {
36923738 ...currentNode ,
@@ -3699,7 +3745,7 @@ const WorkflowContent = React.memo(
36993745 return resolveSelectionConflicts ( updated , blocks , isMultiSelect ? node . id : undefined )
37003746 } )
37013747 } ,
3702- [ blocks ]
3748+ [ blocks , getNodes ]
37033749 )
37043750
37053751 /** Handles edge selection with container context tracking and Shift-click multi-selection. */
@@ -3808,9 +3854,17 @@ const WorkflowContent = React.memo(
38083854 ( targetNode ?. zIndex ?? 21 ) + 1
38093855 )
38103856
3857+ // Edges inside subflows need a z-index above the container's body area
3858+ // (which has pointer-events: auto) so they're directly clickable.
3859+ // Derive from the container's depth-based zIndex (+1) so the edge sits
3860+ // just above its parent container but below canvas blocks (z-21+) and
3861+ // child blocks (z-1000).
3862+ const containerNode = parentLoopId ? nodeMap . get ( parentLoopId ) : null
3863+ const baseZIndex = containerNode ? ( containerNode . zIndex ?? 0 ) + 1 : 0
3864+
38113865 return {
38123866 ...edge ,
3813- zIndex : connectedToElevated ? elevatedZIndex : 0 ,
3867+ zIndex : connectedToElevated ? elevatedZIndex : baseZIndex ,
38143868 data : {
38153869 ...edge . data ,
38163870 isSelected : selectedEdges . has ( edgeContextId ) ,
0 commit comments