From dbe31d469bd626882d11dea6be352388edd9485d Mon Sep 17 00:00:00 2001 From: shrimbly Date: Sat, 28 Feb 2026 22:57:09 +1300 Subject: [PATCH] fix: clear stale inputImages when last image-source edge is removed After disconnecting an image edge, the target node's inputImages data was not cleared. On regeneration, useStoredFallback would pick up the stale images and send them to the API, causing results to still resemble the old reference. Now removeEdge and onEdgesChange call clearStaleInputImages to reset inputImages when no image-source edges remain on the target node. Co-Authored-By: Claude Opus 4.6 --- src/store/workflowStore.ts | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/store/workflowStore.ts b/src/store/workflowStore.ts index 2b956728..a8528e26 100644 --- a/src/store/workflowStore.ts +++ b/src/store/workflowStore.ts @@ -410,6 +410,37 @@ async function waitForPendingImageSyncs(timeout: number = 60000): Promise export { generateWorkflowId, saveGenerateImageDefaults, saveNanoBananaDefaults } from "./utils/localStorage"; export { GROUP_COLORS } from "./utils/nodeDefaults"; +/** Node types whose output carries image data */ +const IMAGE_SOURCE_NODE_TYPES = new Set([ + "imageInput", "annotation", "nanoBanana", "glbViewer", "videoFrameGrab", +]); + +/** + * After edges are removed, clear inputImages on any target node that no longer + * has an image-source edge. Prevents stale images from being sent to the API + * when useStoredFallback picks up old node data. + */ +function clearStaleInputImages( + removedEdges: WorkflowEdge[], + get: () => WorkflowStore +): void { + if (removedEdges.length === 0) return; + const { edges, nodes, updateNodeData } = get(); + const targetIds = new Set(removedEdges.map((e) => e.target)); + for (const targetId of targetIds) { + const node = nodes.find((n) => n.id === targetId); + if (!node || !("inputImages" in (node.data as Record))) continue; + const hasRemainingImageSource = edges.some((e) => { + if (e.target !== targetId) return false; + const src = nodes.find((n) => n.id === e.source); + return src ? IMAGE_SOURCE_NODE_TYPES.has(src.type ?? "") : false; + }); + if (!hasRemainingImageSource) { + updateNodeData(targetId, { inputImages: [] }); + } + } +} + const workflowStoreImpl: StateCreator = (set, get) => ({ nodes: [], edges: [], @@ -575,12 +606,22 @@ const workflowStoreImpl: StateCreator = (set, get) => ({ const hasRemoveChange = changes.some((c) => c.type === "remove"); const hasAddOrRemove = changes.some((c) => c.type === "add" || c.type === "remove"); + // Capture removed edges before applyEdgeChanges removes them + let removedEdges: WorkflowEdge[] = []; + if (hasRemoveChange) { + const removeIds = new Set( + changes.filter((c) => c.type === "remove").map((c) => c.id) + ); + removedEdges = get().edges.filter((e) => removeIds.has(e.id)); + } + set((state) => ({ edges: applyEdgeChanges(changes, state.edges), ...(hasMeaningfulChange ? { hasUnsavedChanges: true } : {}), })); if (hasRemoveChange) { + clearStaleInputImages(removedEdges, get); get().incrementManualChangeCount(); } @@ -625,10 +666,12 @@ const workflowStoreImpl: StateCreator = (set, get) => ({ }, removeEdge: (edgeId: string) => { + const removedEdge = get().edges.find((e) => e.id === edgeId); set((state) => ({ edges: state.edges.filter((edge) => edge.id !== edgeId), hasUnsavedChanges: true, })); + if (removedEdge) clearStaleInputImages([removedEdge], get); get().incrementManualChangeCount(); },