diff --git a/.planning/STATE.md b/.planning/STATE.md index fc7f8463..9e169fa6 100644 --- a/.planning/STATE.md +++ b/.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) diff --git a/package-lock.json b/package-lock.json index 9a9308e6..f29cb08c 100644 --- a/package-lock.json +++ b/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", diff --git a/package.json b/package.json index becfd1d9..1aeefda8 100644 --- a/package.json +++ b/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", diff --git a/src/components/ConnectionDropMenu.tsx b/src/components/ConnectionDropMenu.tsx index ee6084f1..cb3b5c4a 100644 --- a/src/components/ConnectionDropMenu.tsx +++ b/src/components/ConnectionDropMenu.tsx @@ -79,6 +79,15 @@ const IMAGE_TARGET_OPTIONS: MenuOption[] = [ ), }, + { + type: "imageCompare", + label: "Image Compare", + icon: ( + + + + ), + }, ]; const TEXT_TARGET_OPTIONS: MenuOption[] = [ diff --git a/src/components/WorkflowCanvas.tsx b/src/components/WorkflowCanvas.tsx index 73a2b9ee..4ee7fc51 100644 --- a/src/components/WorkflowCanvas.tsx +++ b/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"; } diff --git a/src/components/nodes/index.ts b/src/components/nodes/index.ts index cbd13572..0962be21 100644 --- a/src/components/nodes/index.ts +++ b/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"; diff --git a/src/lib/quickstart/validation.ts b/src/lib/quickstart/validation.ts index 236b16b0..f3bb97c5 100644 --- a/src/lib/quickstart/validation.ts +++ b/src/lib/quickstart/validation.ts @@ -36,6 +36,7 @@ const DEFAULT_DIMENSIONS: Record = 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, + }; } } diff --git a/src/store/utils/nodeDefaults.ts b/src/store/utils/nodeDefaults.ts index 63ce42f5..de962053 100644 --- a/src/store/utils/nodeDefaults.ts +++ b/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 { return { images: [], } as OutputGalleryNodeData; + case "imageCompare": + return { + imageA: null, + imageB: null, + } as ImageCompareNodeData; } }; diff --git a/src/store/workflowStore.ts b/src/store/workflowStore.ts index 9a3adba6..f52db15f 100644 --- a/src/store/workflowStore.ts +++ b/src/store/workflowStore.ts @@ -1832,6 +1832,15 @@ export const useWorkflowStore = create((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; + } } } diff --git a/src/types/nodes.ts b/src/types/nodes.ts index 8650f77e..d6226f4d 100644 --- a/src/types/nodes.ts +++ b/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)