regenerateNode was not creating an AbortController or passing a signal
to _buildExecutionContext, so stopWorkflow() couldn't cancel in-progress
batch iterations. Now creates and stores _abortController on entry and
clears it on all exit paths (early returns, success, and error).
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
copySelectedNodes, pasteNodes, and AI captureSnapshot were still using
JSON.parse(JSON.stringify(...)) which duplicates multi-MB base64 blobs.
Switched to clonePreservingStrings which shares immutable string refs.
Also clarified docstring re: toJSON not being called.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Edge metadata (arrayBatchAll) was stamped at connection creation time and
went stale if the source node's batchMode was toggled later. Now
connectedInputs.ts reads batchMode directly from the source node, making
batch behavior always consistent with the current toggle state.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The batch textItems loop was only in executeWorkflow. regenerateNode and
executeSelectedNodes bypassed batch mode entirely, causing inconsistent
behavior when array nodes feed into generate nodes.
Extracted runBatchIfApplicable() into src/store/execution/batchExecution.ts
and wired it into all three execution entry points.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When dragging an audio connection and dropping to create a generateVideo
node, the audio input handle was not wired because the case was missing
from handleMenuSelect.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Removes the absolute-positioned header div and places the buttons
in the same flex row as the Split select, preventing overlap at
narrow widths and ensuring vertical alignment.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
executeOutputGallery was reading gallery images from the stale node copy
captured during topological sort. When upstream batch execution pushed
images via appendOutputGalleryImage, the stale copy didn't reflect them,
so the gallery was overwritten with only the last generated image.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When batch mode is enabled on an array node, all parsed items are sent
through a single connection to a downstream generate node, which loops
through them sequentially. This eliminates the need to create separate
downstream nodes for each array item.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace JSON.parse(JSON.stringify(...)) in captureUndoSnapshot with
clonePreservingStrings — a custom deep-clone that creates new
object/array containers but returns string primitives by reference.
Since JS strings are immutable, snapshots safely share base64 blobs
instead of duplicating them. Reduces undo history memory from ~500MB
to ~12.5MB for workflows with 10MB of media data (97.5% reduction).
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The handle dot color is set via data-handletype attribute selector in CSS.
Audio handles had no rule, so they fell back to the default appearance.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
isImageInput() has permissive description heuristics (e.g. "data uri")
that false-positive on audio properties like audio_url whose description
mentions "base64 data URI". Reorder the checks so isAudioInput() — which
uses specific name patterns — runs first.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add missing "audio-to-video" case to getCapabilityBadges() so the
capability badge renders in the model explorer. Add generateVideo to
AUDIO_TARGET_OPTIONS in ConnectionDropMenu so dropping an audio
connection offers Generate Video as a target node.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add "audio" to generateVideo's input handles in getNodeHandles() for
connection validation. Update ModelSearchDialog capability filter,
isVideoModel check, and recent models filter to include audio-to-video.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add audio-to-video to VIDEO_CAPABILITIES, render audio input handles
from model schema with purple color (--handle-color-audio: #a855f7),
and include backward-compatibility hidden audio handle. Audio handles
are positioned between image and text handle groups.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend handleToSchemaName in getConnectedInputsPure() to map audio-0
handles to schema names, enabling audio data flow via dynamicInputs.
Update generateVideoExecutor to destructure audio from connected inputs
and allow execution when audio is the only connected input.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add AUDIO_INPUT_PATTERNS, isAudioInput() function modeled after
isImageInput(), and integrate into extractParametersFromSchema() to
detect audio inputs from model schemas. Audio inputs are sorted
between image and text inputs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add "audio-to-video" to ModelCapability union, extend ModelInput.type
and ModelInputDef.type with "audio", and include "audio-to-video" in
fal.ai RELEVANT_CATEGORIES for model discovery.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The selected-node runner was treating audioInput and videoInput as no-ops,
so select-executing these nodes with upstream connections wouldn't pull
connected data. Now matches executeWorkflow's hydration logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The render guard at line 222 blocked all Gemini-provider models from
rendering parameters, including Veo models. Hoisted the isVeoModel
check to component scope and added the same exception used in the
fetch guard so Veo parameters (aspect ratio, duration, resolution)
display correctly.
Fixes#106
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix redo shortcut by normalizing event.key to lowercase (Shift makes it "Z")
- Flush pendingDataSnapshot in undo/redo instead of discarding it, preserving
the pre-edit checkpoint so Cmd+Z doesn't skip states
- Recompute dimmedNodeIds after undo/redo to keep dimming in sync
- Set deleteCheckpointActive in removeEdge before clearStaleInputImages to
prevent split undo entries
- Wire videoInput in handleMenuSelect and isValidConnection for video connections
- Validate video MIME type against ACCEPTED_FORMATS whitelist instead of broad regex
- Use URL.createObjectURL(file) for metadata extraction instead of base64→Blob
- Add Shift+Y (Video Input) to keyboard shortcuts dialog
- Update executeNode JSDoc to include videoInput as data-source no-op
- Clear all *Ref/*Refs fields dynamically in clearNodeImageRefs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ModelParameters had an early return for all Gemini provider models,
skipping the API schema fetch. This was correct for Gemini image
models (which have built-in parameters) but prevented Veo video
models from loading their aspect ratio, duration, and resolution
parameters from the /api/models/[modelId] endpoint.
Closes#106
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Veo models were excluded from ModelParameters rendering, hiding their
configurable properties. Remove the isVeoModel() guard so Veo models
display parameters like all other providers.
Closes#106
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a text link with banana icon at the bottom of the links list in
the welcome modal, linking to https://nodebananapro.com.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
If a debounced data-edit snapshot was pending when loading or clearing
a workflow, it could fire after undoManager.clear() and push stale
state into the new workflow's undo history. Now both paths cancel the
pending timer and snapshot before clearing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
If a debounced data-edit snapshot was pending when loading or clearing
a workflow, it could fire after undoManager.clear() and push stale
state into the new workflow's undo history. Now both paths cancel the
pending timer and snapshot before clearing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a text link with banana icon below the description in the welcome
modal left column, linking to https://nodebananapro.com.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
React Flow v12's deleteElements fires onEdgesChange(remove) BEFORE
onNodesChange(remove), not after. The previous fix assumed the opposite
order, so the microtask-based nodeRemoveCheckpointActive flag was never
true when onEdgesChange ran.
Replace with deleteCheckpointActive — a bidirectional flag set by
whichever handler (edges or nodes) fires first. The second handler
skips its checkpoint. Use setTimeout(0) instead of Promise.resolve()
so the flag survives all microtasks in the current event-loop turn.
Also suppresses debounced undo snapshots from clearStaleInputImages
side effects during the same deletion cycle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When deleting a connected node, clearStaleInputImages calls updateNodeData
on target nodes to clear stale input images. Each updateNodeData call was
creating a debounced undo snapshot, resulting in 3-4 undos needed instead
of 1. Now updateNodeData skips undo tracking when nodeRemoveCheckpointActive
is true, since the node removal checkpoint already captured full pre-deletion state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When deleting a node via the canvas (Delete key), React Flow fires
onNodesChange(remove) then onEdgesChange(remove) synchronously.
Previously this created two undo entries, requiring two undos and
leaving an intermediate state with orphaned edges.
Now onNodesChange sets a microtask-scoped flag that tells onEdgesChange
to skip its checkpoint, collapsing both into a single undo entry.
Added integration test verifying single-undo restores node + edges.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a top-level "Video" quick-access button to the floating action bar
and adds videoInput to the Input category in the All Nodes menu. Fixes
test selectors to disambiguate the new top-level Video button from the
Generate dropdown's Video option.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Captures snapshots before all undoable mutations (add/remove nodes,
connect/remove edges, paste, group operations, edge style, drag start).
Data edits are debounced (500ms). Execution outputs are not undoable.
History depth: 50 entries. Cleared on load/clear workflow.
Keyboard shortcuts added to WorkflowCanvas and shortcuts dialog.
15 integration tests covering all undo/redo scenarios through the store.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Standalone undo manager with push/pop/clear/max-depth (50) logic,
independent of Zustand. Includes 11 unit tests covering all operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a new videoInput node type that allows users to drag-and-drop or
browse for video files (MP4, WebM, QuickTime), preview them inline with
native video controls, and feed them into downstream video processing
nodes. Includes full media externalization/hydration support, Shift+Y
keyboard shortcut, and ConnectionDropMenu integration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove obsolete scripts/cleanup-workflow.js
- Revert locked guards from group drag/resize/rename (lock is
execution-only, not interaction)
- Add lock icon in group title pill for visual feedback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GroupsOverlay: reset color picker when menu closes, guard locked groups
from drag/resize/rename
- AudioInputNode: add keyboard accessibility to dropzone (role, tabIndex,
onKeyDown)
- list-workflows: fix name regex to handle escaped quotes
- GroupsOverlay.test: add missing NBP Input menu item assertion
- WorkflowBrowserView: make relativePath required to match API contract
- WorkflowBrowserModal: add dialog role, aria-modal, aria-labelledby
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- cleanup-workflow.js: Filter only embedded data: images from outputGallery
instead of wiping entire array; preserve imageRef/video on output nodes
- workflow/route.ts: Validate parsed.version is number, nodes/edges are
arrays; sort candidates by mtime desc for deterministic file selection
- mediaStorage.ts: Guard against undefined mediaData in loadMediaById
before caching/returning
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The group controls (lock, delete, color) moved from individual toolbar
buttons into a dropdown menu behind "Group options". Update tests to
open the menu first, then interact with items by text label.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add recursive directory scanning (up to 3 levels deep) to the
list-workflows API so projects organized into subdirectories are
visible in the workflow browser. Skips hidden dirs and node_modules.
Returns relativePath field displayed as the subtitle in the UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>