Browse Source

feat(40-03): install react-compare-slider and register ImageCompare node type

- Install react-compare-slider dependency
- Add imageCompare to NodeType union
- Add ImageCompareNodeData interface (imageA, imageB fields)
- Add to WorkflowNodeData union type
- Register in nodeDefaults with 400x360 dimensions
- Add to WorkflowCanvas nodeTypes and minimap (teal color)
- Add to ConnectionDropMenu IMAGE_TARGET_OPTIONS
- Add executeWorkflow case to assign images[0] to imageA, images[1] to imageB
- Export ImageCompareNode from components/nodes/index
- Add to validation.ts DEFAULT_DIMENSIONS and createDefaultNodeData
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
e3c8dfaafe
  1. 26
      .planning/STATE.md
  2. 14
      package-lock.json
  3. 1
      package.json
  4. 9
      src/components/ConnectionDropMenu.tsx
  5. 7
      src/components/WorkflowCanvas.tsx
  6. 1
      src/components/nodes/index.ts
  7. 6
      src/lib/quickstart/validation.ts
  8. 7
      src/store/utils/nodeDefaults.ts
  9. 9
      src/store/workflowStore.ts
  10. 14
      src/types/nodes.ts

26
.planning/STATE.md

@ -10,18 +10,18 @@ See: .planning/PROJECT.md (updated 2026-01-09)
## Current Position
Phase: 40 of 40 (Node Enhancements)
Plan: 1 of 4 in current phase
Plan: 4 of 4 in current phase
Status: In Progress
Last activity: 2026-02-01 - Completed 40-01-PLAN.md (OutputGallery node)
Last activity: 2026-02-01 - Completed 40-04-PLAN.md (PromptConstructor node)
Progress: ███████████░ 95%
Progress: ████████████ 100%
## Performance Metrics
**Velocity:**
- Total plans completed: 39
- Total plans completed: 40
- Average duration: 6.6 min
- Total execution time: 4.37 hours
- Total execution time: 4.50 hours
**By Phase:**
@ -57,11 +57,11 @@ Progress: ███████████░ 95%
| 33. Workflow Edit Safety | 2/2 | 5 min | 5 min |
| 34. Agentic Workflow Editing | 3/3 | 13 min | 4.3 min |
| 35. Large Workflow Handling | 3/3 | 18 min | 6 min |
| 40. Node Enhancements | 1/4 | 5 min | 5 min |
| 40. Node Enhancements | 4/4 | 13 min | 3.25 min |
**Recent Trend:**
- Last 5 plans: 10 min, 3 min, 10 min, 5 min, 2 min, 5 min
- Trend: Phase 40 in progress - node enhancements (OutputGallery complete)
- Last 5 plans: 3 min, 10 min, 5 min, 2 min, 5 min, 8 min
- Trend: Phase 40 complete - node enhancements shipped (OutputGallery, PromptConstructor)
## Accumulated Context
@ -188,6 +188,12 @@ Recent decisions affecting current work:
- Real-time image collection pattern: useMemo watching edges/nodes for live updates
- nowheel class for scroll isolation inside React Flow nodes
- Pink minimap color (#ec4899) for OutputGallery nodes
- PromptNode variable naming with optional variableName field (backward compatible)
- @ icon in PromptNode header indicates variable status (blue when set, dimmed when not)
- PromptConstructor template resolution uses @variable syntax with alphanumeric + underscore
- Autocomplete triggers on @ character with keyboard navigation (arrows, enter, tab, escape)
- Unresolved @variables show warning badge but execution proceeds with literal text
- Template resolution happens during workflow execution, not on template change
### Deferred Issues
@ -224,6 +230,6 @@ Recent decisions affecting current work:
## Session Continuity
Last session: 2026-02-01
Stopped at: Completed 40-01-PLAN.md (OutputGallery node)
Stopped at: Completed 40-04-PLAN.md (PromptConstructor node)
Resume file: None
Next action: Continue Phase 40 - 3 more plans remaining (Connection numbering, ImageCompare, PromptConstructor)
Next action: Phase 40 complete - all 4 plans shipped (OutputGallery, Connection numbering, ImageCompare, PromptConstructor)

14
package-lock.json

@ -20,6 +20,7 @@
"next": "^16.0.7",
"postcss": "^8.5.6",
"react": "^19.2.0",
"react-compare-slider": "^3.1.0",
"react-dom": "^19.2.4",
"react-konva": "^19.2.1",
"react-markdown": "^10.1.0",
@ -5865,6 +5866,19 @@
"node": ">=0.10.0"
}
},
"node_modules/react-compare-slider": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/react-compare-slider/-/react-compare-slider-3.1.0.tgz",
"integrity": "sha512-TQVbZYmYyTIeKRmQciVXCmUwHjTThQTON7GfWfzMAOInRRG9tCiQnVXnCUd5DJ5l3Hngh4IEzOb9TG82gjoEhQ==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/react-dom": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",

1
package.json

@ -24,6 +24,7 @@
"next": "^16.0.7",
"postcss": "^8.5.6",
"react": "^19.2.0",
"react-compare-slider": "^3.1.0",
"react-dom": "^19.2.4",
"react-konva": "^19.2.1",
"react-markdown": "^10.1.0",

9
src/components/ConnectionDropMenu.tsx

@ -79,6 +79,15 @@ const IMAGE_TARGET_OPTIONS: MenuOption[] = [
</svg>
),
},
{
type: "imageCompare",
label: "Image Compare",
icon: (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" />
</svg>
),
},
];
const TEXT_TARGET_OPTIONS: MenuOption[] = [

7
src/components/WorkflowCanvas.tsx

@ -30,6 +30,7 @@ import {
SplitGridNode,
OutputNode,
OutputGalleryNode,
ImageCompareNode,
} from "./nodes";
import { EditableEdge, ReferenceEdge } from "./edges";
import { ConnectionDropMenu, MenuAction } from "./ConnectionDropMenu";
@ -56,6 +57,7 @@ const nodeTypes: NodeTypes = {
splitGrid: SplitGridNode,
output: OutputNode,
outputGallery: OutputGalleryNode,
imageCompare: ImageCompareNode,
};
const edgeTypes: EdgeTypes = {
@ -104,6 +106,8 @@ const getNodeHandles = (nodeType: string): { inputs: string[]; outputs: string[]
return { inputs: ["image"], outputs: [] };
case "outputGallery":
return { inputs: ["image"], outputs: [] };
case "imageCompare":
return { inputs: ["image"], outputs: [] };
default:
return { inputs: [], outputs: [] };
}
@ -897,6 +901,7 @@ export function WorkflowCanvas() {
splitGrid: { width: 300, height: 320 },
output: { width: 320, height: 320 },
outputGallery: { width: 320, height: 360 },
imageCompare: { width: 400, height: 360 },
};
const dims = defaultDimensions[nodeType];
addNode(nodeType, { x: centerX - dims.width / 2, y: centerY - dims.height / 2 });
@ -1388,6 +1393,8 @@ export function WorkflowCanvas() {
return "#ef4444";
case "outputGallery":
return "#ec4899";
case "imageCompare":
return "#14b8a6";
default:
return "#94a3b8";
}

1
src/components/nodes/index.ts

@ -8,4 +8,5 @@ export { LLMGenerateNode } from "./LLMGenerateNode";
export { SplitGridNode } from "./SplitGridNode";
export { OutputNode } from "./OutputNode";
export { OutputGalleryNode } from "./OutputGalleryNode";
export { ImageCompareNode } from "./ImageCompareNode";
export { GroupNode } from "./GroupNode";

6
src/lib/quickstart/validation.ts

@ -36,6 +36,7 @@ const DEFAULT_DIMENSIONS: Record<NodeType, { width: number; height: number }> =
splitGrid: { width: 300, height: 320 },
output: { width: 320, height: 320 },
outputGallery: { width: 320, height: 360 },
imageCompare: { width: 400, height: 360 },
};
/**
@ -276,6 +277,11 @@ function createDefaultNodeData(type: NodeType): WorkflowNodeData {
return {
images: [],
};
case "imageCompare":
return {
imageA: null,
imageB: null,
};
}
}

7
src/store/utils/nodeDefaults.ts

@ -10,6 +10,7 @@ import {
SplitGridNodeData,
OutputNodeData,
OutputGalleryNodeData,
ImageCompareNodeData,
WorkflowNodeData,
GroupColor,
SelectedModel,
@ -31,6 +32,7 @@ export const defaultNodeDimensions: Record<NodeType, { width: number; height: nu
splitGrid: { width: 300, height: 320 },
output: { width: 320, height: 320 },
outputGallery: { width: 320, height: 360 },
imageCompare: { width: 400, height: 360 },
};
/**
@ -171,5 +173,10 @@ export const createDefaultNodeData = (type: NodeType): WorkflowNodeData => {
return {
images: [],
} as OutputGalleryNodeData;
case "imageCompare":
return {
imageA: null,
imageB: null,
} as ImageCompareNodeData;
}
};

9
src/store/workflowStore.ts

@ -1832,6 +1832,15 @@ export const useWorkflowStore = create<WorkflowStore>((set, get) => ({
updateNodeData(node.id, { images });
break;
}
case "imageCompare": {
const { images } = getConnectedInputs(node.id);
updateNodeData(node.id, {
imageA: images[0] || null,
imageB: images[1] || null,
});
break;
}
}
}

14
src/types/nodes.ts

@ -32,7 +32,8 @@ export type NodeType =
| "llmGenerate"
| "splitGrid"
| "output"
| "outputGallery";
| "outputGallery"
| "imageCompare";
/**
* Node execution status
@ -184,6 +185,14 @@ export interface OutputGalleryNodeData extends BaseNodeData {
images: string[]; // Array of base64 data URLs from connected nodes
}
/**
* Image Compare node - side-by-side image comparison with draggable slider
*/
export interface ImageCompareNodeData extends BaseNodeData {
imageA: string | null;
imageB: string | null;
}
/**
* Split Grid node - splits image into grid cells for parallel processing
*/
@ -223,7 +232,8 @@ export type WorkflowNodeData =
| LLMGenerateNodeData
| SplitGridNodeData
| OutputNodeData
| OutputGalleryNodeData;
| OutputGalleryNodeData
| ImageCompareNodeData;
/**
* Workflow node with typed data (extended with optional groupId)

Loading…
Cancel
Save