From 532231d2eff8f7be62098b960820c5c9c5da0114 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Thu, 12 Mar 2026 20:47:26 +1300 Subject: [PATCH 1/2] fix: cache router/switch passthrough results to fix multi-type data loss When a router node had multiple output edges to the same downstream node (e.g., passing both text and image), only the first edge type received data. The shared _visited set prevented the router from being traversed a second time, causing subsequent edge types to get empty results. Co-Authored-By: Claude Opus 4.6 --- src/store/utils/connectedInputs.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/store/utils/connectedInputs.ts b/src/store/utils/connectedInputs.ts index 3e28a254..a848a380 100644 --- a/src/store/utils/connectedInputs.ts +++ b/src/store/utils/connectedInputs.ts @@ -166,6 +166,11 @@ export function getConnectedInputsPure( }); } + // Cache passthrough node results so multiple edges from the same router/switch + // all receive correct data (the _visited set prevents re-traversal, so we cache + // the result from the first traversal and reuse it for subsequent edges). + const passthroughCache = new Map(); + edges .filter((edge) => edge.target === nodeId) .forEach((edge) => { @@ -177,7 +182,9 @@ export function getConnectedInputsPure( // Router passthrough — traverse upstream to find actual data source if (sourceNode.type === "router") { - const routerInputs = getConnectedInputsPure(sourceNode.id, nodes, edges, _visited, dimmedNodeIds); + const routerInputs = passthroughCache.get(sourceNode.id) + ?? getConnectedInputsPure(sourceNode.id, nodes, edges, _visited, dimmedNodeIds); + passthroughCache.set(sourceNode.id, routerInputs); // Determine which type this edge carries based on the source handle const edgeType = edge.sourceHandle; // Will be "image", "text", "video", "audio", "3d", or "easeCurve" @@ -210,7 +217,9 @@ export function getConnectedInputsPure( } // Enabled switch: recursively get upstream data (same pattern as router) - const switchInputs = getConnectedInputsPure(sourceNode.id, nodes, edges, _visited, dimmedNodeIds); + const switchInputs = passthroughCache.get(sourceNode.id) + ?? getConnectedInputsPure(sourceNode.id, nodes, edges, _visited, dimmedNodeIds); + passthroughCache.set(sourceNode.id, switchInputs); const edgeType = switchData.inputType; if (edgeType === "image") { From e5ae80aad1139b94a4f5f8ce3994526a4674374f Mon Sep 17 00:00:00 2001 From: shrimbly Date: Thu, 12 Mar 2026 21:12:54 +1300 Subject: [PATCH 2/2] fix: reactively update SplitGrid sourceImage when connection is made The Split button was permanently disabled because sourceImage was only set during execution, not when an edge was connected. Added a reactive effect that watches for connected images and updates sourceImage. Co-Authored-By: Claude Opus 4.6 --- .../__tests__/SplitGridNode.test.tsx | 4 ++++ src/components/nodes/SplitGridNode.tsx | 23 ++++++++++++++++++- src/store/execution/splitGridExecutor.ts | 4 ++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/components/__tests__/SplitGridNode.test.tsx b/src/components/__tests__/SplitGridNode.test.tsx index d6f83dd4..7d02f29a 100644 --- a/src/components/__tests__/SplitGridNode.test.tsx +++ b/src/components/__tests__/SplitGridNode.test.tsx @@ -51,6 +51,8 @@ describe("SplitGridNode", () => { currentNodeIds: [], groups: {}, nodes: [], + edges: [], + getConnectedInputs: vi.fn(() => ({ images: [], videos: [], audio: [], model3d: null, text: null, dynamicInputs: {}, easeCurve: null })), getNodesWithComments: vi.fn(() => []), markCommentViewed: vi.fn(), setNavigationTarget: vi.fn(), @@ -273,6 +275,8 @@ describe("SplitGridNode", () => { currentNodeIds: [], groups: {}, nodes: [], + edges: [], + getConnectedInputs: vi.fn(() => ({ images: [], videos: [], audio: [], model3d: null, text: null, dynamicInputs: {}, easeCurve: null })), getNodesWithComments: vi.fn(() => []), markCommentViewed: vi.fn(), setNavigationTarget: vi.fn(), diff --git a/src/components/nodes/SplitGridNode.tsx b/src/components/nodes/SplitGridNode.tsx index 46c8b5b1..a0924fbc 100644 --- a/src/components/nodes/SplitGridNode.tsx +++ b/src/components/nodes/SplitGridNode.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useState, useEffect } from "react"; +import { useCallback, useState, useEffect, useMemo } from "react"; import { Handle, Position, NodeProps, Node } from "@xyflow/react"; import { BaseNode } from "./BaseNode"; import { useWorkflowStore } from "@/store/workflowStore"; @@ -14,8 +14,29 @@ export function SplitGridNode({ id, data, selected }: NodeProps state.updateNodeData); const regenerateNode = useWorkflowStore((state) => state.regenerateNode); const isRunning = useWorkflowStore((state) => state.isRunning); + const getConnectedInputs = useWorkflowStore((state) => state.getConnectedInputs); + const edges = useWorkflowStore((state) => state.edges); + const nodes = useWorkflowStore((state) => state.nodes); const [showSettings, setShowSettings] = useState(false); + // Reactively track the connected source image + const hasIncomingImageConnection = useMemo(() => { + return edges.some((edge) => edge.target === id && edge.targetHandle === "image"); + }, [edges, id]); + + const connectedSourceImage = useMemo(() => { + if (!hasIncomingImageConnection) return null; + const { images } = getConnectedInputs(id); + return images[0] || null; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hasIncomingImageConnection, id, getConnectedInputs, nodes]); + + useEffect(() => { + if (connectedSourceImage !== nodeData.sourceImage) { + updateNodeData(id, { sourceImage: connectedSourceImage }); + } + }, [connectedSourceImage, id, updateNodeData, nodeData.sourceImage]); + // Show settings modal on first creation (when not configured) useEffect(() => { if (!nodeData.isConfigured && (!nodeData.childNodeIds || nodeData.childNodeIds.length === 0)) { diff --git a/src/store/execution/splitGridExecutor.ts b/src/store/execution/splitGridExecutor.ts index 66b8acde..bf9e3ba5 100644 --- a/src/store/execution/splitGridExecutor.ts +++ b/src/store/execution/splitGridExecutor.ts @@ -11,8 +11,8 @@ import type { NodeExecutionContext } from "./types"; export async function executeSplitGrid(ctx: NodeExecutionContext): Promise { const { node, getConnectedInputs, updateNodeData } = ctx; - const { images } = getConnectedInputs(node.id); - const sourceImage = images[0] || null; + const connectedInputs = getConnectedInputs(node.id); + const sourceImage = connectedInputs.images[0] || null; if (!sourceImage) { updateNodeData(node.id, {