- Remove router/switch/conditionalSwitch entries from WorkflowCanvas
defaultDimensions and validation.ts (these types only exist on develop)
- Add ModelType import and cast in nodeDefaults.ts for type safety
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pre-existing type error — conditionalSwitch was missing from the
createDefaultNodeData switch in validation.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pre-existing type error — conditionalSwitch was added to NodeType but the
local defaultDimensions map in keyboard shortcut handler was not updated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Nano Banana 2 entry to GEMINI_IMAGE_MODELS in model registry
- Add model to GenerateImageNode dropdown with 512px resolution option
- Extend resolution/Google Search gates to support nano-banana-2
- Replace hardcoded model name ternaries with MODEL_DISPLAY_NAMES lookup
in workflowStore, nodeDefaults, and GenerateImageNode migration code
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add per-resolution pricing for nano-banana-2 (512/1K/2K/4K).
Add 512px tier to all model pricing tables.
Use MODEL_DISPLAY_NAMES lookup instead of ternaries.
Add getPricing fallback for nano-banana-2 / gemini-3.1-flash-image-preview.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Map nano-banana-2 to gemini-3.1-flash-image-preview API model.
Enable resolution control and Google Search grounding for nano-banana-2.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add "nano-banana-2" to ModelType union for Gemini 3.1 Flash Image Preview.
Add "512" resolution tier. Add MODEL_DISPLAY_NAMES lookup to replace ternaries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Conditional Switch was missing from the text target connection drop menu
and incorrectly present in the image source menu.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Import ConditionalSwitchNodeData type in dimmingUtils.ts
- Add conditionalSwitch block to collect potentially dimmed nodes
- Non-matching rules (isMatched=false) trigger downstream dimming
- Default output dims when ANY rule matches (inactive when no match)
- Extend smart cascade blockedTypes collection
- Check conditionalSwitch rules for match status
- Add inactive outputs to blockedTypes
- Extend hasReplacementInput check to skip non-matching outputs
- Guard conditionalSwitch nodes from dimming in WorkflowCanvas
- Updated guard: if (node.type === "switch" || node.type === "conditionalSwitch")
Phase 44's dimming system now handles both Switch and Conditional Switch.
- Import executeConditionalSwitch in workflowStore
- Add evaluateRuleMatch helper function for rule evaluation
- Add conditionalSwitch case to executeWorkflow with fresh match evaluation
- Add conditionalSwitch case to regenerateNode with same pattern
- Import ConditionalSwitchNodeData and MatchMode in connectedInputs
- Add conditionalSwitch passthrough in getConnectedInputsPure
- Block non-active outputs (isMatched=false) from data flow
- Default output active when no rules match
- Active outputs recursively get upstream text
- Import and add ConditionalSwitchNode to nodeTypes map
- Add getNodeHandles case with text input and dynamic outputs
- Add cyan-500 (#06b6d4) minimap color distinct from Router/Switch
- Add isValidConnection text-only validation
- Add handleMenuSelect with text handle and first rule/default source
- Add findCompatibleHandle for text input and rule outputs
- Add ConditionalSwitch to TEXT_TARGET_OPTIONS and TEXT_SOURCE_OPTIONS with filter icon
- Positioned after Switch entry in both menus
- Text input handle on left (blue, text type)
- Text preview section showing truncated incoming text (50 chars)
- Rule rows with:
- Match status indicator (green checkmark when matched, gray dot when not)
- Up/down arrow buttons for reordering rules
- Editable label (double-click to edit, inline editing pattern from SwitchNode)
- Mode dropdown (exact, contains, starts-with, ends-with)
- Value text field (comma-separated match values)
- Delete button (hidden if only one rule to prevent empty state)
- Add rule button (creates new rule with auto-generated label)
- Default output row (always at bottom, not editable/deletable)
- Active (green) when NO rules match, inactive (gray) otherwise
- Dynamic output handles per rule + default handle (all text type)
- Non-matching rule handles at 30% opacity (same as Switch disabled pattern)
- evaluateRule function: case-insensitive, multi-value OR logic, all 4 modes
- Match status updates reactively via useEffect watching incoming text
- Dynamic handle sync via useUpdateNodeInternals on rule count change
- Auto-resize height based on rule count
- Teal/cyan theme (bg-teal-950/50 border-teal-600) to differentiate from Router/Switch
- Add MatchMode type (exact, contains, starts-with, ends-with)
- Add ConditionalSwitchRule interface with id, value, mode, label, isMatched
- Add ConditionalSwitchNodeData interface with incomingText and rules array
- Add conditionalSwitch to NodeType union (after switch)
- Add ConditionalSwitchNodeData to WorkflowNodeData union
- Add default data with single rule (mode: contains, empty value)
- Add default dimensions (260x180, wider than Switch for mode dropdown)
- Add executeConditionalSwitch passthrough executor
- Add conditionalSwitch to VALID_NODE_TYPES in validation
- Export ConditionalSwitchNode from components index (component created in next task)
When paths reconverge downstream of a Switch, dimmed nodes (from disabled
outputs) still held stale data from previous runs. getConnectedInputs now
accepts dimmedNodeIds and skips any source node in the dimmed set, preventing
disabled Switch paths from leaking data into downstream nodes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A Prompt (text) connecting to a Generate node shouldn't prevent dimming
when the image input from a disabled Switch is blocked. The cascade now
only un-dims a node if an active input provides the same data type as
the blocked path (e.g., another image source replaces a disabled image).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The menu path calls onConnect() directly, bypassing handleConnect's
resolveSwitchHandle. This left edges with targetHandle "generic-input"
instead of the actual type, causing inputType to be invalid, handles
to render without color, and downstream connections to fail validation.
- Set targetHandleId to actual type in handleMenuSelect Switch case
- Update Switch inputType immediately when created from menu
- Filter "generic-input" in derivedInputType as safety net
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch output handles use dynamic IDs (random strings) that getHandleType()
can't resolve. Now falls back to the Switch node's inputType field so dragging
from a Switch output shows the correct type-specific connection menu.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Subscribe to dimmedNodeIds in WorkflowCanvas
- Apply switch-dimmed className in allNodes useMemo
- Exclude Switch nodes from dimming (they stay fully visible)
- Added CSS: 40% opacity with 250ms transition
- Transition applies in both directions (dimming and un-dimming)
- Dimming updates reactively when toggles or edges change
- Created dimmingUtils.ts with computeDimmedNodes function
- DFS traversal finds all downstream nodes of disabled Switch outputs
- Smart cascade: nodes with active inputs stay active
- Added dimmedNodeIds state to workflowStore
- Added recomputeDimmedNodes action with set equality check
- Recomputes on: switch toggle change, edge add/remove, workflow load
- Resets dimmedNodeIds on clearWorkflow
- Added execution skip logic in executeSingleNode
- Dimmed nodes skip execution but preserve previous output
- Export executeSwitch from execution/index.ts
- Import executeSwitch in workflowStore.ts
- Add switch case to executeWorkflow (after router)
- Add switch case to regenerateNode (after router)
- Import SwitchNodeData in connectedInputs.ts
- Add Switch passthrough logic with toggle filtering in connectedInputs.ts
- Disabled outputs block data flow (no downstream input)
- Enabled outputs recursively traverse upstream (same pattern as Router)
- Edge type determined by switch inputType field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Import and register SwitchNode in nodeTypes
- Add switch case to getNodeHandles (generic-input, dynamic outputs)
- Add resolveSwitchHandle function for input type resolution
- Add Switch-specific validation in isValidConnection
- Add switch case to handleMenuSelect (generic-input target)
- Add switch support to findCompatibleHandle
- Add switch minimap color (#8b5cf6 violet-500)
- Add Switch to all 8 ConnectionDropMenu arrays (IMAGE/TEXT/VIDEO/AUDIO SOURCE/TARGET)
- Import HandleType for type resolution
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create SwitchNode.tsx with violet theme (bg-violet-950/50, border-violet-600)
- Implement toggle controls per switch (sliding toggle with peer-checked styling)
- Add/remove switch management with + button and delete X button on hover
- Double-click to rename switch labels with inline editing
- Dynamic input handle: generic gray dashed when no connection, typed colored when connected
- Dynamic output handles: only shown when inputType is set (input connected)
- Output handles use 30% opacity when switch is disabled
- Auto-resize height based on switch count using useEffect and setNodes
- Handle color mapping matches RouterNode's HANDLE_COLORS
- useUpdateNodeInternals called when switch count or inputType changes
- Remove edges when switch is deleted via setEdges filter
- Component is 277 lines with full toggle, add, delete, rename functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "switch" to NodeType union in types/nodes.ts
- Create SwitchNodeData interface with inputType and switches array
- Add SwitchNodeData to WorkflowNodeData union
- Register switch dimensions (220x120) in nodeDefaults.ts and validation.ts
- Add createDefaultNodeData case with single default switch
- Implement executeSwitch passthrough executor in simpleNodeExecutors.ts
- Add SwitchNode export to components/nodes/index.ts
- Add switch dimensions to WorkflowCanvas.tsx default dimensions map
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Raise baseOffset from 20 to 38 so handles clear the header bar
- Calculate minHeight from total handle slots (active + placeholder)
- Programmatically grow node height via setNodes when handles are added
- Respects user-resized heights (only grows, never shrinks)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make input placeholder grey with solid border matching other handles,
and remove the unnecessary output-side placeholder connector.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Image and text colors were swapped, 3d was cyan instead of orange,
video/audio/easeCurve used incorrect colors. Now matches globals.css
and inline styles used by other node components.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ConnectionDropMenu.tsx:
- Add Router entry to all 8 connection drop menu arrays
- Router appears as last item (utility node) in each menu
- Uses git-branch SVG icon for routing/branching concept
workflowStore.ts:
- Import executeRouter from execution module
- Add "router" case to executeWorkflow switch (after easeCurve)
- Add "router" case to regenerateNode switch (after easeCurve)
execution/index.ts:
- Export executeRouter from simpleNodeExecutors
connectedInputs.ts:
- Add optional visited parameter to getConnectedInputsPure for circular protection
- Add Router passthrough logic before getSourceOutput call
- Router nodes recursively fetch upstream data and route by handle type
- Prevents infinite loops with visited Set tracking
- Handle all 6 data types: image, text, video, audio, 3d, easeCurve
RouterNode.tsx (bugfix):
- Add missing id prop to BaseNode (from 43-01)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Import RouterNode and register in nodeTypes map
- Add router case to getNodeHandles (all 6 types + generic handles)
- Update getHandleType to return null for generic-input/generic-output
- Update isValidConnection to allow router for video/audio/3d/easeCurve
- Add resolveRouterHandle and resolveRouterSourceHandle functions
- Apply router handle resolution in handleConnect (single and batch)
- Update handleMenuSelect with router-specific handle mapping
- Update findCompatibleHandle to support router nodes
- Change Shift+R keyboard shortcut from array to router
- Add router to defaultDimensions (200x80)
- Add router minimap color (#6b7280 neutral-500 gray/slate)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create RouterNode.tsx with dynamic handle arrays derived from edges
- Implement color-coded typed handles (blue, emerald, purple, amber, cyan, red)
- Add gray dashed generic handles when not all 6 types are connected
- Use useUpdateNodeInternals to sync handle positions on type changes
- Implement compact strip BaseNode layout with dynamic height adjustment
- Add activeInputTypes useMemo watching edges for handle derivation
- Display type count in node body (or 'Drop connections here' hint)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add 'router' to NodeType union in types/nodes.ts
- Add RouterNodeData interface (extends BaseNodeData, no internal state)
- Add RouterNodeData to WorkflowNodeData union
- Add router dimensions (200x80) to nodeDefaults.ts and validation.ts
- Add router default data (empty object) to both files
- Add executeRouter pure passthrough function to simpleNodeExecutors.ts
- Add RouterNode export to components/nodes/index.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add resolvePropertyType() helper to extract effective type/format from
OpenAPI wrapper patterns (anyOf, oneOf, allOf, $ref) used by Pydantic
and other code generators. Update isImageInput() and convertSchemaProperty()
to use resolved types instead of reading prop.type directly.
This ensures nullable fields like `{anyOf: [{type: "string"}, {type: "null"}]}`
are correctly identified as image inputs when their name matches image patterns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Propagate hadQuantifier to parent depth when closing groups
- Fixes detection of deeply nested patterns like ((a+))+ and (((x+))+)+
- Inner groups with quantifiers now properly marked at parent level
- Add resolved flag and safeResolve guard pattern from getVideoDimensions
- Add cleanup() function to null event handlers and clear src
- Add 10-second timeout that resolves with null if neither onload nor onerror fires
- Prevents promise from hanging indefinitely on corrupt/malformed images
- Add Shift+T for Audio node to shortcuts list
- Add Shift+R for Array node to shortcuts list
- Both shortcuts already wired in WorkflowCanvas, now visible in dialog
- Add early return in setNodes when height unchanged to prevent unnecessary updates
- Rename shadowed 'selected' variable to 'currentSelection' in validation useEffect
- Reduces redundant React Flow re-renders when array items don't change height
- Capture prevUnsaved from hasUnsavedChanges before set() call
- Add hasUnsavedChanges to rollback set() call on save failure
- All three fields now properly restored: workflowId, workflowName, hasUnsavedChanges
- Prevents dirty flag from remaining stuck at true after failed save
- Track created blob URL in async chain with createdUrl variable
- Cleanup function revokes orphaned URLs created after cancel flag set
- Remove unnecessary prevInputRef deduplication (React handles this)
- Fixes race where rapid videoUrl changes orphan blob URLs in memory
- Remove directoryPath field from WorkflowFile literal in saveToFile
- Change loadWorkflow fallback chain to exclude workflow.directoryPath
- directoryPath already stored in localStorage via saveSaveConfig()
- Keep optional directoryPath field in WorkflowFile type for backward compatibility
- Old files with directoryPath will still load, new files won't contain it
- 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
- Create validateWorkflowPath() utility to prevent path traversal attacks
- Reject non-absolute paths, traversal sequences, and dangerous system directories
- Apply validation to workflow and workflow-images POST/GET handlers
- Fix workflow-images ENOENT-specific catch block to match workflow route pattern
- Fix workflow mkdir failure status code from 400 to 500 (server error)
- Add comprehensive path traversal tests for both routes
- All malicious path attempts now return 400 Bad Request
- 3 tests per provider (Replicate, fal.ai): prompt included with dynamicInputs,
no duplication when dynamicInputs already has prompt, existing non-dynamicInputs path works
- Mock fetch intercepts provider API calls and captures request bodies
- Verifies the fix from the previous commit prevents "prompt is required" errors
- Include input.prompt in API request when dynamicInputs are present but don't contain a prompt key
- Use schema paramMap to resolve model-specific prompt parameter name
- Skip if dynamicInputs already provides a prompt value (no duplication)