feat(designer-v2): multi-node selection with batch delete and wrap-in-scope#9231
Open
rllyy97 wants to merge 14 commits into
Open
feat(designer-v2): multi-node selection with batch delete and wrap-in-scope#9231rllyy97 wants to merge 14 commits into
rllyy97 wants to merge 14 commits into
Conversation
…p-in-scope Implement first-pass multi-selection in the DesignerV2 workflow editor: - Shift-click toggles nodes in a selection set (OperationCardNode, ScopeCardNode). - Box/marquee selection re-enabled in DesignerReactFlow (selectionOnDrag, SelectionMode.Partial, panOnDrag mouse buttons), filtered to operation/scope nodes. - panelSlice tracks canonical selectedNodeIds; reconciles to selectedNodeId (1) and alternateSelectedNode (2, side-by-side) for the existing single/dual panels. - New MultiSelectPanel (>2 selected) lists nodes with a batch Delete action. - Wrap-in-scope actions (Scope, Condition) shown when selected nodes are on the same graph level and contiguous/connected, via canWrapSelectedNodes contiguity analysis. - New deleteOperations batch thunk, wrapSelectedNodesInScope thunk, and wrapNodesInScope workflow reducer for graph restructuring + runAfter rewiring. Adds unit tests for the multiselect contiguity utils and panelSlice selection reducers. Switch/Agent wrap types are intentionally deferred for this first pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
🤖 AI PR Validation ReportPR Review ResultsThank you for your submission! Here's detailed feedback on your PR title and body compliance:✅ PR Title
✅ Commit Type
|
| Section | Status | Recommendation |
|---|---|---|
| Title | ✅ | |
| Commit Type | ✅ | |
| Risk Level | Set exactly one risk level and ensure it is High. |
|
| What & Why | ✅ | |
| Impact of Change | Add explicit user/developer/system impact. | |
| Test Plan | ✅ | |
| Contributors | ✅ | Add credits if relevant. |
| Screenshots/Videos | Add visuals for the UI workflow changes. |
The PR mostly passes review, but it should be updated to clearly mark the risk level as High and strengthen the Impact of Change section. The code change itself is substantial and should be treated as high-risk.
Last updated: Thu, 04 Jun 2026 19:34:35 GMT
Contributor
There was a problem hiding this comment.
Pull request overview
Adds first-pass multi-node selection support to DesignerV2, including a new multi-select details panel, batch delete, and “wrap in scope/condition” operations that restructure the workflow graph.
Changes:
- Introduces canonical multi-selection state in the panel slice (
selectedNodeIds) with reconciliation back to existing 1-node / 2-node panel behavior. - Re-enables ReactFlow box selection and wires selection events + shift-click toggling into Redux selection state.
- Adds multi-select utilities + tests, plus new wrap-in-scope thunk/reducer and batch delete thunk.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| Localize/lang/strings.json | Adds new localized strings for multi-select panel and wrap actions. |
| libs/designer-v2/src/lib/ui/panel/panelRoot.tsx | Routes Operation panel rendering to MultiSelectPanel when >2 nodes are selected. |
| libs/designer-v2/src/lib/ui/panel/multiSelectPanel/multiSelectPanel.tsx | New multi-select panel UI with selected list, wrap actions, and batch delete. |
| libs/designer-v2/src/lib/ui/panel/multiSelectPanel/multiSelectPanel.styles.ts | Fluent v9 makeStyles styling for MultiSelectPanel. |
| libs/designer-v2/src/lib/ui/DesignerReactFlow.tsx | Re-enables marquee selection and syncs selection changes into Redux. |
| libs/designer-v2/src/lib/ui/CustomNodes/ScopeCardNode.tsx | Adds shift-click toggling and multi-select highlighting for scope nodes. |
| libs/designer-v2/src/lib/ui/CustomNodes/OperationCardNode.tsx | Adds shift-click toggling and multi-select highlighting for operation nodes. |
| libs/designer-v2/src/lib/ui/CustomNodes/components/card/actionCard.tsx | Forwards MouseEvent through onClick to enable shift-click detection upstream. |
| libs/designer-v2/src/lib/core/utils/multiselect.ts | Adds utilities to validate same-graph contiguous chain selection for wrap eligibility. |
| libs/designer-v2/src/lib/core/utils/test/multiselect.spec.ts | Adds unit tests for multiselect chain/graph eligibility logic. |
| libs/designer-v2/src/lib/core/state/workflow/workflowSlice.ts | Adds workflow reducer to wrap selected nodes into a new scope/condition container. |
| libs/designer-v2/src/lib/core/state/panel/panelTypes.ts | Adds selectedNodeIds to operation panel state. |
| libs/designer-v2/src/lib/core/state/panel/panelSlice.ts | Adds set/toggle selection reducers and reconciliation to existing panel selection model. |
| libs/designer-v2/src/lib/core/state/panel/panelSelectors.ts | Adds selectors for selectedNodeIds, multi-select membership, and wrap eligibility. |
| libs/designer-v2/src/lib/core/state/panel/test/panelSlice.multiselect.spec.ts | Adds tests validating selection reconciliation behavior for 0/1/2/>2 selections. |
| libs/designer-v2/src/lib/core/actions/bjsworkflow/wrapInScope.ts | Adds thunk to create a new scope/condition and move selected nodes inside it. |
| libs/designer-v2/src/lib/core/actions/bjsworkflow/delete.ts | Adds batch delete thunk for deleting multiple selected nodes. |
Comment on lines
+49
to
+71
| title: intl.formatMessage({ | ||
| defaultMessage: 'Multiple actions selected', | ||
| id: 'AaVRhS', | ||
| description: 'Title for the panel shown when multiple workflow actions are selected', | ||
| }), | ||
| subtitle: intl.formatMessage( | ||
| { | ||
| defaultMessage: '{count} actions selected', | ||
| id: '2PaVqw', | ||
| description: 'Subtitle showing how many actions are currently selected', | ||
| }, | ||
| { count: selectedNodeIds.length } | ||
| ), | ||
| deleteLabel: intl.formatMessage({ | ||
| defaultMessage: 'Delete', | ||
| id: 'GxU+Zs', | ||
| description: 'Label for the button that deletes all selected actions', | ||
| }), | ||
| wrapHeading: intl.formatMessage({ | ||
| defaultMessage: 'Wrap in', | ||
| id: 'qDS8i+', | ||
| description: 'Heading for the group of actions that wrap selected nodes in a scope', | ||
| }), |
Comment on lines
+72
to
+86
| wrapScope: intl.formatMessage({ | ||
| defaultMessage: 'Scope', | ||
| id: 'cx1uqu', | ||
| description: 'Label for wrapping selected actions in a scope', | ||
| }), | ||
| wrapCondition: intl.formatMessage({ | ||
| defaultMessage: 'Condition', | ||
| id: 'qbJ5A3', | ||
| description: 'Label for wrapping selected actions in a condition', | ||
| }), | ||
| panelAriaLabel: intl.formatMessage({ | ||
| defaultMessage: 'Multiple selection panel', | ||
| id: 'sZLBvN', | ||
| description: 'Accessibility label for the multiple selection panel', | ||
| }), |
Comment on lines
+190
to
+193
| const state = getState() as RootState; | ||
| const workflowNode = getWorkflowNodeFromGraphState(state.workflow, nodeId); | ||
| const isTrigger = getRecordEntry(state.workflow.nodesMetadata, nodeId)?.isRoot ?? false; | ||
| if ( |
Comment on lines
+92
to
+95
| () => () => { | ||
| dispatch(storeStateToUndoRedoHistory({ type: deleteOperations.pending })); | ||
| dispatch(deleteOperations({ nodeIds: selectedNodeIds })); | ||
| }, |
| const definition = scopeDefinitions[scopeType]; | ||
| const scopeId = getNewNodeId(state.workflow, definition.idPrefix); | ||
|
|
||
| dispatch(storeStateToUndoRedoHistory({ type: wrapSelectedNodesInScope.pending } as any)); |
Contributor
|
📊 Coverage check completed. See workflow run for details. |
…+drag The multi-select feature swapped left-click drag from panning to marquee selection, which regressed the default canvas pan gesture. Restore panOnDrag to all buttons (left/middle/right) and gate the selection box behind the Shift key via react-flow's selectionKeyCode, matching react-flow defaults. Drop Shift from multiSelectionKeyCode so it no longer conflicts with the shift-click node toggle. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Commit box/marquee selection only on release instead of continuously during drag, so the panel no longer re-renders while dragging. - Gate the multi-select Delete action behind a confirmation dialog instead of deleting immediately. - Verified wrap-in-Condition nests the selected nodes in the true block. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When wrapping selected nodes in a Condition, the first moved node is now chained off the subgraph header (Condition-actions-#subgraph) so ELK lays it out inside the 'true' branch (below the header) instead of beside the true/false branch labels. Adds a reducer regression test covering the header->first-node edge for both Condition and Scope wraps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The delete confirmation dialog now lists each selected action (icon + display name) so users can see exactly what will be removed before confirming. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…i-copy, selection box styling - Add right-click bulk context menu (Delete/Copy N actions) when right-clicking a node that is part of a multi-selection - Extract a shared redux-driven MultiSelectDeleteModal (rendered at Designer level) so the delete confirmation with action list works from both the multi-select panel and the canvas context menu - Add copyOperations thunk that writes a multi-node clipboard payload; handle isMultiNode paste (reverse, sequential) in dropzone and EdgeContextualMenu - Style the selection box: 8px border-radius + 8px padding Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ine box styling Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ion race - Add Delete/Backspace, Ctrl+D (duplicate), and Ctrl+A (select all) hotkeys - Ctrl/Meta-click behaves like Shift-click for multi-select - Ctrl+A includes triggers via useAllSelectableNodeIds - Fix marquee selection randomly committing no nodes by committing from whichever of onSelectionEnd/onSelectionChange fires last, with empty-guard and dedupe to avoid echo loops - Guard multi-select panel against Escape clearing selection on delete dialog Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the bespoke header in MultiSelectPanelBody with the shared PanelHeader component so the multi-select drawer matches the chrome of every other panel. Adds a customIcon prop on PanelHeader and renders a primary-background count badge in its place. Overflow (...) button now hides automatically when headerItems is empty. PanelContainer customContent path lets the multi-select panel reuse the same Drawer infrastructure (mountNode, resize, position) instead of a one-off Drawer. Also folds in the shift-click-to-deselect fix (stopPropagation on modifier-key clicks in OperationCardNode and ScopeCardNode so React Flow's own shift-select logic doesn't clobber our toggle reducer), removes the FocusTrapZone wrapper that was swallowing canvas clicks while the panel was open, and keeps the existing delete-modal Escape guard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The msla-panel-layout div needs the msla-panel-border-selected class to render the panel chrome (background + border) — without it the multi-select content floats over the canvas with no visible drawer frame. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…vents Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add Ctrl+C/X canvas-level hotkeys for copy/cut operations - Convert paste option to split button (inline paste + parallel submenu) - Fix panel width: 480px single/multi-select, 680px dual-view - Fix NodeDetailsPanel overrideWidth initialization - Fix chevron click closing edge menu via stopPropagation
- Switch Drawer from type='inline' to type='overlay' so panel floats over canvas - Add PanelViewportShift component that detects when selected node is behind panel - Animate canvas translation (300ms) to keep selected node visible - Reverse the shift when panel closes - Re-apply Ctrl+C hotkey for copy, split button paste, panel width fixes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First pass at multi-node selection in the DesignerV2 workflow editor (
libs/designer-v2, not the legacydesignerlib).You can now select multiple actions at once and act on them as a group:
runAfter) correctly.Implementation notes
panelSlice.operationContent.selectedNodeIds(canonical source of truth).setNodeSelection/toggleNodeSelectionreconcile down toselectedNodeId(1) andalternateSelectedNode(2) so the existing single/side-by-side panels keep working with no changes.DesignerReactFlow.tsx:elementsSelectable,selectionOnDrag,SelectionMode.Partial,panOnDragon mouse buttons, and anonSelectionChangehandler that strips id tags and dispatchessetNodeSelection.OperationCardNode/ScopeCardNodeforward the clickMouseEvent; shift-click dispatchestoggleNodeSelection, plain click keeps existing behavior.ActionCardnow forwards the event throughonClick.core/utils/multiselect.ts):getCommonGraphId,getOrderedSelectedChain, andcanWrapSelectedNodesensure wrap is only offered for a single same-graph, convex (gap-free) chain that excludes triggers/root.deleteOperationsbatch thunk,wrapSelectedNodesInScopethunk +scopeDefinitions, and awrapNodesInScopeworkflow reducer that inserts the scope, moves the chain in order, and preservesrunAfter.Tests
multiselect.spec.ts— 12 tests covering contiguous / non-contiguous / parallel / cross-graph / trigger cases.panelSlice.multiselect.spec.ts— 8 tests for the selection reconcile logic.tscand Biome clean.Verification
Live-verified end-to-end in the standalone DesignerV2 (browser): 1 / 2 / >2 selection paths, batch delete, wrap-in-Scope, wrap-in-Condition (graph +
runAfterconfirmed correct), and the negative case where parallel siblings correctly hide the wrap buttons.Out of scope (deferred)