From d7ba5b65b5f535c9ba9fbf3083cbf79f19580057 Mon Sep 17 00:00:00 2001 From: Isaac Wasserman Date: Mon, 14 Sep 2026 16:57:17 -0400 Subject: [PATCH] fix(ui): prevent node/edge deletion when viewer is not editing React Flow's built-in delete-key handler removed selected nodes and edges even when isEditing was false. Disable deleteKeyCode in view mode and drop remove changes from onNodesChange/onEdgesChange unless editing. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/prevent-view-mode-node-delete.md | 5 +++++ packages/ui/src/workflow-viewer.tsx | 24 +++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 .changeset/prevent-view-mode-node-delete.md diff --git a/.changeset/prevent-view-mode-node-delete.md b/.changeset/prevent-view-mode-node-delete.md new file mode 100644 index 0000000..7874d07 --- /dev/null +++ b/.changeset/prevent-view-mode-node-delete.md @@ -0,0 +1,5 @@ +--- +"@remoraflow/ui": patch +--- + +Prevent node deletion in the workflow viewer when editing is disabled. React Flow's built-in delete-key handler removed selected nodes even in view mode; the delete key is now disabled and `remove` node changes are ignored unless `isEditing` is true. diff --git a/packages/ui/src/workflow-viewer.tsx b/packages/ui/src/workflow-viewer.tsx index fa569b7..6a96a36 100644 --- a/packages/ui/src/workflow-viewer.tsx +++ b/packages/ui/src/workflow-viewer.tsx @@ -19,6 +19,7 @@ import { type EdgeTypes, MiniMap, type NodeTypes, + type OnEdgesChange, type OnNodesChange, ReactFlow, useEdgesState, @@ -356,12 +357,26 @@ export function WorkflowViewer({ ]); const [nodes, setNodes, onNodesChangeBase] = useNodesState(layout.nodes); - const [edges, setEdges, onEdgesChange] = useEdgesState(layout.edges); + const [edges, setEdges, onEdgesChangeBase] = useEdgesState(layout.edges); + + const onEdgesChange: OnEdgesChange = useCallback( + (changes) => { + onEdgesChangeBase( + isEditing + ? changes + : changes.filter((change) => change.type !== "remove"), + ); + }, + [onEdgesChangeBase, isEditing], + ); const onNodesChange: OnNodesChange = useCallback( (changes) => { - onNodesChangeBase(changes); - for (const change of changes) { + const applied = isEditing + ? changes + : changes.filter((change) => change.type !== "remove"); + onNodesChangeBase(applied); + for (const change of applied) { if ( change.type === "dimensions" && change.dimensions?.width && @@ -374,7 +389,7 @@ export function WorkflowViewer({ } } }, - [onNodesChangeBase], + [onNodesChangeBase, isEditing], ); useEffect(() => { @@ -973,6 +988,7 @@ export function WorkflowViewer({ minZoom={0.1} nodesDraggable={isEditing} nodesConnectable={isEditing} + deleteKeyCode={isEditing ? undefined : null} defaultEdgeOptions={{ type: "workflow", }}