- 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
- 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>
- 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>
- 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>
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>
- 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>
- 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
- 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
Zundo-based undo/redo was causing image flickering, failing to restore
deleted nodes, and creating memory issues from large state snapshots.
Removes the entire workflow undo/redo system while keeping the
independent annotation canvas undo/redo intact.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace BINARY_DATA_KEYS (strips all binary) with BINARY_TO_REF mapping
that only strips binary data when a corresponding ref exists (recoverable
from disk). Fields without refs (imageA, imageB, capturedImage, etc.) are
kept in snapshots since they're unrecoverable. After undo/redo, hydrate
missing binary data from refs using existing hydrateWorkflowImages().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Wrap store creation with temporal() middleware
- partialize: Only track nodes (binary-stripped), edges, edgeStyle, groups
- limit: 20 undo steps max
- equality: Fast referential check using undoStateEquality()
- Add keyboard shortcuts:
- Ctrl/Cmd+Z: Undo last change
- Ctrl/Cmd+Shift+Z: Redo
- Ctrl/Cmd+Y: Redo (Windows alt)
- Add drag debouncing to prevent per-pixel snapshots:
- onNodeDragStart: Pause temporal tracking
- onNodeDragStop: Resume tracking and capture final position
- Add _nudgeForSnapshot() helper to force snapshot capture
- Mark workflow as unsaved after undo/redo
- Install zundo temporal middleware for Zustand
- Create src/store/undoUtils.ts with:
- BINARY_DATA_KEYS: Set of 20+ binary field names to exclude
- stripBinaryData(): Deep-clone nodes without binary data
- partializeForUndo(): Extract trackable state slice
- undoStateEquality(): Fast referential equality check
- UndoState type for type safety
- Output Node now automatically triggers execution when a new connection is made
- Added Run button to Output Node header for manual triggering
- Updated regenerateNode to support output node type
- Prevents re-triggering on initial mount or disconnections using edge count tracking
Resolves issue where Output Node would not display data from already-generated upstream nodes unless connected before generation.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix nanoBananaExecutor narrowing issue where promptText was narrowed
to `never` after type guard, and fix addEdge type mismatch where
Edge<T> data can be undefined but addEdge expects it defined.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Save the previous workflowId and workflowName before mutating state.
If saveToFile() fails, restore the previous values so the workflow
identity is unchanged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of mutating selectedOutputIndex N times in a loop (which races
with React renders), pass { arrayItemIndex: index } as an edge data
override through onConnect. Adds optional edgeDataOverrides parameter
to onConnect and addEdgeWithType.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When importing a workflow via file picker, loadWorkflow had no way to
find the image files on disk because directoryPath was only stored in
localStorage (which doesn't survive browser switches or clears).
Now auto-save embeds directoryPath in the workflow JSON, and
loadWorkflow uses it as a fallback after the workflowPath parameter
and localStorage config.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Saved workflows can contain multiple edges with the same ID (from
repeated connections or migration normalization). React warns about
duplicate keys and may render edges incorrectly. Deduplicate by
keeping the last occurrence of each edge ID on load.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Saved workflows could contain edges targeting nanoBanana nodes with
indexed handle IDs (image-0, text-0) from a previous schema-based
handle format. GenerateImageNode now always renders non-indexed
handles (image, text), causing React Flow error #008 on every
interaction. Normalize these edge handles during loadWorkflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Audio from generateAudio was being silently dropped during video
stitching. Add [VideoStitch] checkpoint logs at each stage where
audio can be discarded: getConnectedInputs, prepareAudioAsync,
and codec detection. Surface codec failure as a visible progress
warning instead of only console.warn.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add path traversal guard to workflow-images GET handler (matching POST handler)
- Preserve stored prompt in generateAudioExecutor when connected text is null
- Reset conflicting isVideo/isAudio flags in kie.ts content-type detection
- Fix Infinity aspect ratio causing zero-height canvas in VideoStitchNode
- Check response content-type before trusting isAudioModel in fal.ts
- Move audio feeding inside inner try block in useStitchVideos for proper cleanup
- Add 30s seek timeout to frame extraction in videoProcessingExecutors
- Surface file-open errors via toast in Generate3DNode instead of failing silently
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract shared video-probing module (constants, helpers, probeVideoMetadata)
from useTrimVideo and useStitchVideos into src/lib/video-probing.ts
- Fix audio interleaving in useTrimVideo to feed audio before video frames,
matching useStitchVideos fix for Discord playback compatibility
- Fix blob URL leaks in VideoTrimNode and videoProcessingExecutors
- Fix nanoBananaExecutor: null coercion for non-string prompts, redact PII
in console.warn, remove duplicate dynamicInputs.prompt
- Fix generateAudioExecutor double updateNodeData on error paths
- Fix fal.ts media fetch ordering for 3D model detection
- Fix kie.ts and replicate.ts content-type detection for audio models
- Fix WorkflowCanvas connection drop menu wiring for generateAudio nodes
- Fix open-file route Windows path quoting
- Add path traversal protection to workflow-images route
- Add AudioInputNode onerror handler for metadata loading failures
- Fix useAudioPlayback uncaught play() promise rejection
- Add generateAudio case to regenerateNode in workflowStore
- Update placeholder Discord links to real invite URL
- Extract ProviderBadge into shared component in Generate3DNode
- Fix VideoTrimNode duration detection with proper cleanup and cancellation
- Fix GenerateAudioNode test fetch mock cleanup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restore fal-only cost tracking guard in video/audio executors — other
providers don't have reliable pricing. Also add generateAudio to
hasNonGeminiProviders so the CostIndicator hides when audio nodes
are present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Without this, videoTrim nodes were silently skipped during full
workflow execution (Cmd+Enter). The regenerateNode and
executeSelectedNodes paths already had it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add generateAudio and videoFrameGrab cases to executeNode dispatcher
- Add selectedModel null guards in route.ts for all non-Gemini providers
- Fix ConnectionDropMenu wrap-around test (5->6 items after generateAudio)
- Fix audio duration edge cases (NaN guard in formatTime, truthiness check)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Register VideoFrameGrabNode in node exports, nodeTypes map, and minimap colors
- Add to ConnectionDropMenu as video target (Frame Grab) and image source
- Add executeVideoFrameGrab executor with canvas-based frame extraction
- Wire into all three execution paths: executeWorkflow, regenerateNode, executeSelectedNodes
- Add video connection validation for videoFrameGrab target type
- Add handle mapping for video->videoFrameGrab and videoFrameGrab->image connections
- Add videoFrameGrab case to quickstart validation defaults
- Add 'videoFrameGrab' to NodeType union and VideoFrameGrabNodeData interface
- Register default dimensions (320x320) and default data in nodeDefaults
- Wire getSourceOutput to return image type from videoFrameGrab.outputImage
- Add videoFrameGrab dimensions to local Record types in WorkflowCanvas and validation
Two fixes in pasteNodes:
1. Deep copy node data with JSON.parse/stringify (was shallow spread)
2. requestAnimationFrame selection correction to override React Flow's
stale selection reconciliation that could re-select original nodes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The VideoTrim executor was missing from three places:
- execution/index.ts barrel export
- workflowStore.ts regenerateNode if-else chain
- workflowStore.ts executeSelectedNodes switch
This caused the Trim button to complete instantly with no processing
and no video output downstream.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add 'videoTrim' to NodeType union and VideoTrimNodeData interface in nodes.ts
- Add VideoTrimNodeData to WorkflowNodeData union
- Add default dimensions (360x360) and default data to nodeDefaults.ts
- Create useTrimVideo.ts hook with MediaBunny frame-by-frame trim + audio preservation
- Add getSourceOutput case for videoTrim in connectedInputs.ts
- Add executeVideoTrim executor to videoProcessingExecutors.ts
- Wire 'videoTrim' case into executeNode.ts dispatcher
- Fix duplicate Record<NodeType> dimensions in WorkflowCanvas.tsx and validation.ts
- Resolve prompt from dynamicInputs.prompt in Gemini code path as fallback
- Return clear 400 error when prompt is provided but is not a string (corrupted data)
- Improve parallel batch error logging to include nodeId and nodeType for diagnosis
- Replace find() with for-loop to preserve index mapping between results and batch nodes
- Add defensive type coercion guard for promptText (handles non-string at runtime)
- Sanitize dynamicInputs.prompt to string before including in request payload
- Add final assertion guard before fetch to provide clear error if prompt is not a string
- Ensure text values in connectedInputs are always strings via String() coercion
The audio and video executors only tracked costs when the provider was
"fal". Other providers with pricing info (replicate, kie, etc.) were
silently skipped. Match the pattern used in generate3dExecutor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename node title from "Audio Input" to "Audio"
- Rename label in ConnectionDropMenu from "Audio Input" to "Audio"
- Add target (input) handle to Audio node so it can receive audio from
GenerateAudio and other upstream nodes
- Add Audio node to AUDIO_TARGET_OPTIONS so it appears in the context
menu when dragging from GenerateAudio's output connector
- Add execution logic: connected audio populates audioFile (connection
wins over uploaded file), enabling pass-through to downstream nodes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add audio support to the Output node so GenerateAudio can pipe audio
through for playback and download. Adds audio input handle, <audio>
player, audio→output connection validation, and executeOutput audio
handling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create generateAudioExecutor.ts following the video executor pattern,
handling text-to-audio generation with history tracking and file saving.
Register in workflowStore for both executeWorkflow and regenerateNode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add comprehensive text-to-speech (TTS) support with a new generateAudio node
that integrates with multiple AI providers (Kie.ai, Replicate, fal.ai, WaveSpeed).
Changes:
- Add 'text-to-audio' capability to ModelCapability type
- Add GenerateAudioNodeData type and CarouselAudioItem for audio history
- Create GenerateAudioNode component with audio playback and waveform visualization
- Add TTS models from Kie.ai (ElevenLabs Turbo v2.5, Multilingual v2, Eleven V3)
- Update fal.ai category mapping to support 'text-to-speech' -> 'text-to-audio'
- Add audio filter option to ModelSearchDialog
- Register generateAudio node in WorkflowCanvas with fuchsia minimap color
- Add generateAudio to ConnectionDropMenu for both audio source and text target
- Update connectedInputs to handle audio output from generateAudio nodes
- Add default node data and dimensions for generateAudio type
- Update quickstart validation to include generateAudio node type
The node supports:
- Text-to-audio generation with multiple TTS models
- Real-time audio playback with waveform visualization
- Audio history carousel for navigating generated audio
- Dynamic model parameters based on selected provider
- Integration with existing audio handle system
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Allow upstream nodes to define variables using <var="name">value</var>
syntax in their text content. These are parsed and made available as
@name variables in the template, coexisting with the existing named
variable system (which takes precedence on name conflicts).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>