Skip to content

feat(designer-v2): multi-node selection with batch delete and wrap-in-scope#9231

Open
rllyy97 wants to merge 14 commits into
mainfrom
rllyy97/designer-v2-multi-selection
Open

feat(designer-v2): multi-node selection with batch delete and wrap-in-scope#9231
rllyy97 wants to merge 14 commits into
mainfrom
rllyy97/designer-v2-multi-selection

Conversation

@rllyy97
Copy link
Copy Markdown
Contributor

@rllyy97 rllyy97 commented Jun 1, 2026

Summary

First pass at multi-node selection in the DesignerV2 workflow editor (libs/designer-v2, not the legacy designer lib).

You can now select multiple actions at once and act on them as a group:

  • Shift-click toggles a node in/out of the selection set.
  • Box / marquee selection is re-enabled on the canvas (it was previously turned off), filtered to operation/scope nodes (notes excluded).
  • 1 node selected → existing single node panel.
  • 2 nodes selected → existing side-by-side panels (selected + alternate), unchanged.
  • >2 nodes selected → a new MultiSelectPanel listing the selected actions with a batch Delete action.
  • Wrap in scope: when the selected nodes are all on the same graph level and contiguous/connected (no unselected node between two selected nodes), the panel offers Scope and Condition wrap actions that move the selection into a new scope node and rewire the graph (edges + runAfter) correctly.

Implementation notes

  • Selection state lives in panelSlice.operationContent.selectedNodeIds (canonical source of truth). setNodeSelection / toggleNodeSelection reconcile down to selectedNodeId (1) and alternateSelectedNode (2) so the existing single/side-by-side panels keep working with no changes.
  • Canvas wiring in DesignerReactFlow.tsx: elementsSelectable, selectionOnDrag, SelectionMode.Partial, panOnDrag on mouse buttons, and an onSelectionChange handler that strips id tags and dispatches setNodeSelection.
  • Cards: OperationCardNode / ScopeCardNode forward the click MouseEvent; shift-click dispatches toggleNodeSelection, plain click keeps existing behavior. ActionCard now forwards the event through onClick.
  • Contiguity analysis (core/utils/multiselect.ts): getCommonGraphId, getOrderedSelectedChain, and canWrapSelectedNodes ensure wrap is only offered for a single same-graph, convex (gap-free) chain that excludes triggers/root.
  • Graph restructuring: new deleteOperations batch thunk, wrapSelectedNodesInScope thunk + scopeDefinitions, and a wrapNodesInScope workflow reducer that inserts the scope, moves the chain in order, and preserves runAfter.

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.
  • Full panel + workflow state suites pass (51 tests) with no regressions. tsc and 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 + runAfter confirmed correct), and the negative case where parallel siblings correctly hide the wrap buttons.

Out of scope (deferred)

  • Additional wrap types (Switch, Agent + tool, Foreach/Until) — only Scope and Condition ship in this first pass; the wiring is structured so more can be added easily.

…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>
Copilot AI review requested due to automatic review settings June 1, 2026 19:42
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🤖 AI PR Validation Report

PR Review Results

Thank you for your submission! Here's detailed feedback on your PR title and body compliance:

PR Title

  • Current: feat(designer-v2): multi-node selection with batch delete and wrap-in-scope
  • Issue: None — the title is clear, specific, and accurately describes the change.
  • Recommendation: No change needed.

Commit Type

  • Properly selected (feature).
  • Only one commit type is selected, which is correct.

⚠️ Risk Level

  • The submitted risk level is not visible in the provided body/labels in a way that matches this change, and the code diff suggests this should be treated as High risk due to broad interaction changes across selection, copy/cut/paste, delete, panel behavior, and workflow graph restructuring.
  • Recommendation: Ensure the PR body has exactly one risk level checked and that it matches the risk:high label/overall impact.

What & Why

  • Current: A detailed summary is provided and it clearly explains the feature and motivation.
  • Issue: None.
  • Recommendation: No change needed.

⚠️ Impact of Change

  • The section is present, but it is still mostly template text and does not clearly spell out the impact for each audience based on this diff.
  • Recommendation:
    • Users: Note that users can now multi-select, batch delete, copy/cut/paste, and wrap actions into Scope/Condition/etc.
    • Developers: Mention new selection state, multi-select panel, and workflow restructuring thunks/selectors.
    • System: Call out the broader risk from selection state, clipboard serialization, and graph mutation logic.

Test Plan

  • Automated tests are present in the diff (multiselect.spec.ts, panelSlice.multiselect.spec.ts, wrapNodesInScope.spec.ts).
  • This satisfies the test-plan requirement.

Contributors

  • No contributors listed, but this is optional and does not fail the PR.
  • Recommendation only: add collaborators/design reviewers if applicable.

⚠️ Screenshots/Videos

  • The change is clearly UI-facing (multi-select panel, marquee selection, contextual menus), so screenshots or a short video would be helpful.
  • Recommendation: Add screenshots or a short screen recording of:
    • marquee selection,
    • multi-select panel,
    • batch delete confirmation,
    • wrap-in-scope / wrap-in-condition.

Summary Table

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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));
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

📊 Coverage check completed. See workflow run for details.

rllyy97 and others added 3 commits June 1, 2026 14:54
…+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>
rllyy97 and others added 3 commits June 1, 2026 16:06
…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>
rllyy97 and others added 6 commits June 2, 2026 11:44
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants