Browse Source

fix: clamp stale arrayItemIndex via modulo in getSourceOutput

- Apply modulo clamping when reading arrayItemIndex from edge data
- Out-of-bounds indices wrap (e.g., index 3 on 2-item array becomes index 1)
- Empty arrays return null instead of crashing
- No edge mutation needed - clamping happens at read time
- Add tests for wrapping behavior and empty array handling
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
58edb52552
  1. 54
      src/store/utils/__tests__/connectedInputs.test.ts
  2. 5
      src/store/utils/connectedInputs.ts

54
src/store/utils/__tests__/connectedInputs.test.ts

@ -140,6 +140,60 @@ describe("getConnectedInputsPure", () => {
expect(result.text).toBe("three");
});
it("should wrap out-of-bounds arrayItemIndex via modulo", () => {
const nodes = [
makeNode("arr", "array", { outputText: "[\"one\",\"two\"]", outputItems: ["one", "two"] }),
makeNode("gen", "nanoBanana"),
];
const edges = [{
id: "arr-gen-stale",
source: "arr",
target: "gen",
sourceHandle: "text",
targetHandle: "text",
data: { arrayItemIndex: 3 }, // index 3 on 2-item array wraps to index 1
}] as WorkflowEdge[];
const result = getConnectedInputsPure("gen", nodes, edges);
expect(result.text).toBe("two");
});
it("should return null for out-of-bounds arrayItemIndex on empty array", () => {
const nodes = [
makeNode("arr", "array", { outputText: "[]", outputItems: [] }),
makeNode("gen", "nanoBanana"),
];
const edges = [{
id: "arr-gen-empty",
source: "arr",
target: "gen",
sourceHandle: "text",
targetHandle: "text",
data: { arrayItemIndex: 0 },
}] as WorkflowEdge[];
const result = getConnectedInputsPure("gen", nodes, edges);
expect(result.text).toBeNull();
});
it("should wrap large arrayItemIndex correctly", () => {
const nodes = [
makeNode("arr", "array", { outputText: "[\"a\",\"b\",\"c\"]", outputItems: ["a", "b", "c"] }),
makeNode("gen", "nanoBanana"),
];
const edges = [{
id: "arr-gen-large",
source: "arr",
target: "gen",
sourceHandle: "text",
targetHandle: "text",
data: { arrayItemIndex: 7 }, // 7 % 3 = 1
}] as WorkflowEdge[];
const result = getConnectedInputsPure("gen", nodes, edges);
expect(result.text).toBe("b");
});
it("should extract text from promptConstructor outputText", () => {
const nodes = [
makeNode("pc", "promptConstructor", { outputText: "constructed", template: "tmpl" }),

5
src/store/utils/connectedInputs.ts

@ -91,7 +91,10 @@ function getSourceOutput(
const arrayData = sourceNode.data as ArrayNodeData;
const dataIndex = edgeData?.arrayItemIndex;
if (typeof dataIndex === "number" && Number.isInteger(dataIndex) && dataIndex >= 0) {
return { type: "text", value: arrayData.outputItems[dataIndex] ?? null };
const items = arrayData.outputItems;
if (items.length === 0) return { type: "text", value: null };
const clampedIndex = dataIndex % items.length;
return { type: "text", value: items[clampedIndex] ?? null };
}
if (sourceHandle?.startsWith("text-")) {
const index = Number(sourceHandle.replace("text-", ""));

Loading…
Cancel
Save