diff --git a/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx b/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx index b3c5a15a..eaf17853 100644 --- a/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx @@ -857,9 +857,14 @@ function FlowRenderer({ doLayout(tempNodes, edges); } - // Only track changes after initial load is complete + // Only track changes after initial load is complete and not during internal updates + // (e.g., switching conditional branches which is UI state, not workflow data) + // Use getState() to get real-time value (not stale closure from render time) + const isInternalUpdate = + useWorkflowHasChangesStore.getState().isInternalUpdate; if ( !isInitialLoadRef.current && + !isInternalUpdate && changes.some((change) => { return ( change.type === "add" || diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/ConditionalNode/ConditionalNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/ConditionalNode/ConditionalNode.tsx index 985de702..f0130599 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/ConditionalNode/ConditionalNode.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/ConditionalNode/ConditionalNode.tsx @@ -23,6 +23,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { cn } from "@/util/utils"; +import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore"; import { useUpdate } from "../../useUpdate"; import { NodeHeader } from "../components/NodeHeader"; import { AppNode, isWorkflowBlockNode } from ".."; @@ -44,6 +45,7 @@ function ConditionalNodeComponent({ id, data }: NodeProps) { const nodes = useNodes(); const { setNodes, setEdges } = useReactFlow(); const node = nodes.find((n) => n.id === id); + const { setIsInternalUpdate } = useWorkflowHasChangesStore(); const update = useUpdate({ id, @@ -109,11 +111,17 @@ function ConditionalNodeComponent({ id, data }: NodeProps) { useEffect(() => { if (!data.activeBranchId && orderedBranches.length > 0) { + // Mark as internal update to prevent triggering "unsaved changes" dialog + setIsInternalUpdate(true); update({ activeBranchId: orderedBranches[0]?.id ?? null, }); + // Clear the flag after layout completes + setTimeout(() => { + setIsInternalUpdate(false); + }, 50); } - }, [data.activeBranchId, orderedBranches, update]); + }, [data.activeBranchId, orderedBranches, update, setIsInternalUpdate]); // Toggle visibility of branch nodes/edges when activeBranchId changes useEffect(() => { @@ -271,7 +279,14 @@ function ConditionalNodeComponent({ id, data }: NodeProps) { if (!data.editable) { return; } + // Mark as internal update to prevent triggering "unsaved changes" dialog + // Switching branches is UI state, not actual workflow data changes + setIsInternalUpdate(true); update({ activeBranchId: branchId }); + // Clear the flag after layout completes (layout uses setTimeout(10)) + setTimeout(() => { + setIsInternalUpdate(false); + }, 50); }; const handleRemoveBranch = (branchId: string) => { diff --git a/skyvern-frontend/src/store/WorkflowHasChangesStore.ts b/skyvern-frontend/src/store/WorkflowHasChangesStore.ts index f78fb1ce..643eb511 100644 --- a/skyvern-frontend/src/store/WorkflowHasChangesStore.ts +++ b/skyvern-frontend/src/store/WorkflowHasChangesStore.ts @@ -31,11 +31,13 @@ type WorkflowHasChangesStore = { saveIsPending: boolean; saidOkToCodeCacheDeletion: boolean; showConfirmCodeCacheDeletion: boolean; + isInternalUpdate: boolean; setGetSaveData: (getSaveData: () => SaveData) => void; setHasChanges: (hasChanges: boolean) => void; setSaveIsPending: (isPending: boolean) => void; setSaidOkToCodeCacheDeletion: (saidOkToCodeCacheDeletion: boolean) => void; setShowConfirmCodeCacheDeletion: (show: boolean) => void; + setIsInternalUpdate: (isInternalUpdate: boolean) => void; }; interface WorkflowSaveOpts { @@ -48,6 +50,7 @@ const useWorkflowHasChangesStore = create((set) => { saveIsPending: false, saidOkToCodeCacheDeletion: false, showConfirmCodeCacheDeletion: false, + isInternalUpdate: false, getSaveData: () => null, setGetSaveData: (getSaveData: () => SaveData) => { set({ getSaveData }); @@ -64,6 +67,9 @@ const useWorkflowHasChangesStore = create((set) => { setShowConfirmCodeCacheDeletion: (show: boolean) => { set({ showConfirmCodeCacheDeletion: show }); }, + setIsInternalUpdate: (isInternalUpdate: boolean) => { + set({ isInternalUpdate }); + }, }; });