The same 20-line block for evaluating rules and updating node data before
executing a ConditionalSwitch appeared in both executeWorkflow and
executeSelectedNodes. Extract to evaluateAndExecuteConditionalSwitch helper.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Identical rule evaluation logic existed in ConditionalSwitchNode.tsx and
workflowStore.ts. Extract to src/store/utils/ruleEvaluation.ts and
import from both locations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add geminiApiKey to useProviderApiKeys hook. Update GenerateVideoNode to
include Gemini as a provider when API key is configured, and pass the
X-Gemini-API-Key header when fetching models.
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>
- 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
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>
- 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>
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>
- 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
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>
- 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
- 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>
- 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>
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>
- 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
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>
- 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
- 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>
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>
* refactor: extract schema utilities from generate route
Extract INPUT_PATTERNS, InputMapping, ParameterTypeInfo, getParameterTypesFromSchema,
coerceParameterTypes, and getInputMappingFromSchema into schemaUtils.ts with 23 new tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract Gemini provider from generate route
Move MODEL_MAP and generateWithGemini() into providers/gemini.ts.
Remove unused imports (GoogleGenAI, uploadImageForUrl, shouldUseImageUrl,
deleteImages, ProviderModel) from route.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract Replicate provider from generate route
Move generateWithReplicate() into providers/replicate.ts.
It imports schema utilities from schemaUtils.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract fal.ai, Kie.ai, WaveSpeed providers from generate route
Move generateWithFalQueue, generateWithKie, generateWithWaveSpeed and all
their helpers into providers/fal.ts, providers/kie.ts, providers/wavespeed.ts.
Add barrel export at providers/index.ts.
route.ts reduced from 2,838 to 518 lines (POST handler + routing only).
Re-export clearFalInputMappingCache from route.ts for test backward compat.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract API header builder from workflow store
Replace 6 duplicated header-building if/else chains (across executeWorkflow
and regenerateNode) with buildGenerateHeaders() and buildLlmHeaders() utility
functions. Add 13 new tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract execution utilities from workflow store
Move groupNodesByLevel, chunk, revokeBlobUrl, clearNodeImageRefs,
and concurrency settings into executionUtils.ts with 21 new tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract getConnectedInputs and validateWorkflow as pure functions
Move getConnectedInputs (136 lines) and validateWorkflow (59 lines) into
connectedInputs.ts as pure functions taking (nodeId, nodes, edges).
Store methods become thin wrappers. Add 22 new tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: define node executor interface and context types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract simple node executors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify nanoBanana execution into shared executor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify generateVideo execution into shared executor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify llmGenerate execution into shared executor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify splitGrid execution into shared executor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify videoStitch and easeCurve executors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: replace switch/if-else with node executor registry
Wire extracted executors into workflowStore, replacing the ~1000-line
switch statement in executeWorkflow and ~750-line if-else chain in
regenerateNode with executor registry calls. Store reduced from 3,402
to 1,782 lines.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: final cleanup of workflowStore module structure
Remove dead trackSaveGeneration function (no longer called after executor
extraction) and unused type imports. Store: 3,838 -> 1,721 lines (55% reduction).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: video regeneration spinner stuck + cancel error message
Fix video element not reloading on regeneration by adding key prop tied
to video history ID. Fix cancel showing timeout message by passing
"user-cancelled" reason through AbortController and checking it in all
three executor catch blocks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR #59 review — 18 bug, security, and robustness fixes
Providers:
- fal: don't cache failed schema fetches, remove duplicate param spread, SSRF-validate mediaUrl
- gemini: remove redundant header overrides, fix nano-banana model ID
- kie: module-level MAX_MEDIA_SIZE, post-download size check, upload size guard
- replicate: remove duplicate param spread, validate modelId format
- wavespeed: post-download size check, NaN-safe parseInt, prevent prompt overwrite
Executors:
- generateVideo: validate stored fallback inputs same as regular path
- nanoBanana: cap imageHistory at 50 entries
- splitGrid: handle img.onerror with warning + null data
- videoProcessing: add FileReader onerror/onabort handlers
Utilities:
- connectedInputs: use ?? instead of || for outputText/template
- executionUtils: guard orphan children in topological sort, validate chunk() size
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: additional PR review fixes — SSRF, data semantics, sync tracking, error propagation
Providers:
- replicate: add SSRF validation on mediaUrl before fetch
- fal/kie/replicate/wavespeed: large-video returns data="" with url set
instead of putting raw URL in data field (violates GenerationOutput.data semantic)
- kie: remove stale WaveSpeed JSDoc block from kie provider file
- route.ts: update all output handling to support data="" + url for large videos
Executors:
- generateVideoExecutor/nanoBananaExecutor: track save-generation fetch in
pendingImageSyncs via new trackSaveGeneration context method so auto-save
waits for server ID resolution
- videoProcessingExecutors: throw errors instead of returning in validation
branches (encoderSupported, insufficient videos, no video connected) so
Promise.allSettled sees rejections
- types.ts: add trackSaveGeneration to NodeExecutionContext interface
- workflowStore: wire trackSaveGeneration into both execution context sites
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: content-type fallback, replicate polling/validation, atomic gallery append
- fal.ts: Use model capabilities to determine fallback content-type
(video/mp4 vs image/png) instead of defaulting to video for all models
- replicate.ts: Increase polling timeout to 10 min for video models;
filter output array to valid strings before URL validation
- nanoBananaExecutor.ts: Replace read-modify-write on outputGallery with
atomic appendOutputGalleryImage using Zustand set() callback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract _buildExecutionContext to deduplicate NodeExecutionContext construction
Replace identical inline context creation in executeWorkflow and regenerateNode
with a single shared helper, ensuring consistent behavior across both code paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add keyboard shortcuts help dialog
- New KeyboardShortcutsDialog component showing all available shortcuts
- Accessible via ? button in header and ? key on canvas
- Groups shortcuts by category: General, Add Nodes, Layout, Canvas
- Auto-detects Mac vs Windows/Linux for modifier key display
- Follows existing modal patterns (Escape to close, backdrop click)
* feat: add keyboard shortcuts dialog and 3D GLB viewer node
- Keyboard shortcuts: press ? or click keyboard icon to view all shortcuts
- 3D GLB viewer: upload .GLB files, orbit/rotate, capture as image output
- Fix: restore trailing newline in gemini.ts provider
* fix: make Prompt node editable when connected to LLM node
When a Prompt node receives text from an LLM Generate node, the text
should be editable by the user. Previously, the textarea was marked as
readOnly when there was an incoming text connection, preventing edits
in both the main view and the expanded modal.
Changes:
- Track last received LLM output to detect when it changes
- Only update prompt when LLM node runs again (output changes)
- Allow user edits in between LLM runs
- Remove readOnly restriction on textarea
- Update placeholder text to indicate editability
This allows users to edit the LLM-generated text, which will be
replaced only when the LLM node runs again, not on every render.
Fixes#1470061054246518927
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: PR review fixes — lazy-load three.js, blob URL cleanup, missing integrations
- Remove unused `useLoader` import from GLBViewerNode
- Add useEffect cleanup to revoke blob URL on node unmount (memory leak)
- Replace alert() with useToast for file validation errors
- Lazy-load GLBViewerNode via next/dynamic to avoid bundling three.js for all users
- Add glbViewer to ConnectionDropMenu IMAGE_SOURCE_OPTIONS
- Add glbViewer case to quickstart validation createDefaultNodeData (build fix)
- Restore trailing newline in gemini.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR #64 review — blob leak, error handling, tests, docs
Move @types/three to devDependencies, fix blob URL cleanup to track
changes (not just unmount), add try/catch to capture and scene disposal,
remove GLBViewerNode from barrel export to preserve lazy-loading, fix
JSDoc, update CLAUDE.md with glbViewer node and ? shortcut, add
connectedInputs and nodeDefaults tests for glbViewer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: improve GLB viewer — fix viewport sizing, add spot light, handle labels, scroll isolation
- Fix 3D viewport not filling node by adding resize={{ offsetSize: true }} for correct measurement in CSS-transformed React Flow nodes
- Replace grid/axis indicators with spot light for cleaner scene
- Add labeled input (3D) and output (Image) handles matching generate node style
- Add native wheel event listener to prevent graph zoom/pan while scrolling to zoom 3D model
- Remove redundant CSS workarounds (absolute positioning style, extra relative wrapper)
- Add edge-to-edge viewport via contentClassName (removes default padding when GLB loaded)
- Overlay controls bar on viewport with gradient background
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add customizable canvas navigation settings (#65)
* feat: add customizable canvas navigation settings
Add a new settings modal that allows users to configure canvas navigation preferences:
- Pan Mode: Space + Drag (default), Middle Mouse, or Always On (ComfyUI-style)
- Zoom Mode: Alt + Scroll (default), Ctrl + Scroll, or Scroll (no modifier)
- Selection Mode: Click (default) or Alt + Drag
Features:
- Settings persist in localStorage
- New canvas settings button in header (monitor icon)
- Modal UI with radio button groups for each setting
- React Flow props dynamically configured based on user preferences
- Updated wheel event handler to respect zoom mode settings
This addresses user requests for ComfyUI/TouchDesigner-style navigation where
panning and zooming work without holding modifier keys.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: PR #65 review — altDrag selection bug, icon, dedup, tests
- Fix altDrag selection mode: use selectionKeyCode="Alt" instead of
selectionOnDrag=true which made drag-select work without any key
- Replace misleading video camera icon with arrows-pointing-out for
canvas navigation settings button
- Extract duplicated settings buttons JSX into settingsButtons variable
shared by both configured/unconfigured project branches
- Add defensive useEffect to sync CanvasSettingsModal state on open
- Add missing semicolon in types/index.ts canvas re-export
- Add localStorage tests for getCanvasNavigationSettings and
saveCanvasNavigationSettings
- Add integration tests for updateCanvasNavigationSettings
- Fix WorkflowCanvas test mocks to include canvasNavigationSettings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move canvas navigation settings into Project Settings modal
Consolidate canvas settings from standalone CanvasSettingsModal into a
new "Canvas" tab in ProjectSetupModal. Remove "like ComfyUI" text, add
Shift+Drag selection mode, and fix save button to bottom of dialog.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove double semicolon in types/index.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add executeSelectedNodes with topo sort, abort, and downstream propagation
Implements the "Run selected nodes" feature (PR #61) with fixes for all 8
review issues: topological ordering via groupNodesByLevel, AbortController
cancellation, glbViewer case coverage, AbortError filtering, downstream
consumer propagation with dedup guard, and dynamic button label. Also
extracts saveLogSession helper to replace 4 inline duplicates across
executeWorkflow and regenerateNode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: 3D model saving, error handling, and node improvements
- Save 3D models (GLB) to generations folder via save-generation API
- Add try-catch to WaveSpeed polling loop to handle network errors
- Skip downloading 3D model bodies in WaveSpeed (return URL directly)
- Fix schemaUtils false-positive matching for short property names
- Stabilize GenerateImageNode output handle ID (always "image")
- Fix GLBViewerNode: block-body forEach lint, store-based resize
- Use useRef instead of useState for PromptNode lastReceivedText
- Add executeGlbViewer tests and abort signal support
- Fix annotation stale pass-through when upstream image changes
- Skip error status on AbortError in all executor catch blocks
- Revoke glbUrl blob URLs in clearNodeImageRefs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: require per-task commits in multi-task plans
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Generate3DNodeData type definitions
Add "generate3d" to NodeType union, Generate3DNodeData interface,
Generate3DNodeDefaults, and update NodeDefaultsConfig.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add generate3d node defaults and dimensions
Add default dimensions (300x300) and createDefaultNodeData case for
the generate3d node type.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: create generate3d executor
Extracted from nanoBananaExecutor's 3D handling code. Handles 3D model
generation via /api/generate with mediaType: "3d", cost tracking, and
auto-save to generations folder.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: create Generate3DNode component
Dedicated node component for 3D model generation, modeled after
GenerateVideoNode. Features: dynamic input handles from schema,
3D output handle, model browsing, parameter controls, status overlays.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: register generate3d node type across codebase
Register in execution index, workflowStore (executeWorkflow,
regenerateNode, executeSelectedNodes), nodes index, WorkflowCanvas
(nodeTypes, getNodeHandles, minimap color, findCompatibleHandle).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add generate3d source output in connectedInputs
Add generate3d case in getSourceOutput() to return 3D URL data,
enabling downstream nodes to receive 3D model output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: route 3D models to generate3d in ModelSearchDialog
Add "3d" capability filter, update handleSelectModel to detect 3D
models and create generate3d nodes with capabilities passed through,
update recent models filter for 3D.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: update ConnectionDropMenu 3D source to use generate3d
Change THREE_D_SOURCE_OPTIONS from nanoBanana to generate3d with
cube icon and "Generate 3D" label.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove 3D code from GenerateImageNode and nanoBananaExecutor
- Remove text-to-3d/image-to-3d from IMAGE_CAPABILITIES
- Remove is3D memo and conditional handle/label styling
- Remove 3D output preview block
- Remove output3dUrl from model change handlers
- Remove is3DModel detection and mediaType from nanoBanana executor
- Remove 3D response handling from nanoBanana executor
- Remove output3dUrl from NanoBananaNodeData and its defaults
- Remove 3D fallback from nanoBanana in connectedInputs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: update tests and remaining registrations for generate3d
- Add Generate3DNode.test.tsx with render, handle, and output tests
- Add 3D handle tests to ConnectionDropMenu.test.tsx
- Add 3D model routing test to ModelSearchDialog.test.tsx
- Update ModelSearchDialog test expectations for capabilities passthrough
- Register generate3d in executeNode.ts dispatcher
- Add generate3d to WorkflowCanvas keyboard shortcut dimensions
- Add generate3d to quickstart validation (VALID_NODE_TYPES, dimensions, defaults)
- Add generate3d to chat tools (VALID_NODE_TYPES, description)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: unwrap array inputs for non-array API parameters
When multiple images are connected to a generation node, dynamicInputs
aggregates them into arrays. Providers wrapped single values into arrays
for array-typed params, but never unwrapped arrays for string-typed params,
causing API failures (e.g. fal's image_url receiving an array instead of
a string). Add symmetric unwrap logic across all four providers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: move 3D from floating action bar to Generate submenu
Replace top-level glbViewer button with generate3d option in the
Generate combo dropdown, between Video and Text (LLM).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: hide CostIndicator when non-Gemini providers are in workflow
The pricing calculator only has reliable data for Gemini models. When
non-Gemini providers (fal, replicate, kie, wavespeed) are used, the
displayed cost is incomplete/misleading. Add hasNonGeminiProviders()
utility and use it to hide the CostIndicator entirely in that case.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add 3D capability badges to ModelSearchDialog
Add text-to-3d and image-to-3d capability badge styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add play/run icon to LLM Generate node title bar
Wire onRun and isExecuting props to BaseNode, matching the pattern
used in GenerateImageNode and GenerateVideoNode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: clear imageRef when user provides new image to prevent stale saves
When a user pastes/uploads a new image into a node that already has a
saved imageRef, the externalization logic incorrectly assumes the base64
is just hydrated data and discards it. Clear *Ref fields whenever new
image data is set so the save logic correctly persists the new image.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: guard against undefined from empty array unwrap in fal provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add 3x2 grid layout option to split grid node
Change layout selector from count-based to layout-based, allowing both
2x3 and 3x2 (portrait) layouts that produce 6 images. Now shows 7
layout options (2x2, 1x5, 2x3, 3x2, 2x4, 3x3, 2x5) with RxC labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: charlieshoelace <charlieshoelace@gmail.com>
Implements the "Run selected nodes" feature (PR #61) with fixes for all 8
review issues: topological ordering via groupNodesByLevel, AbortController
cancellation, glbViewer case coverage, AbortError filtering, downstream
consumer propagation with dedup guard, and dynamic button label. Also
extracts saveLogSession helper to replace 4 inline duplicates across
executeWorkflow and regenerateNode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add customizable canvas navigation settings
Add a new settings modal that allows users to configure canvas navigation preferences:
- Pan Mode: Space + Drag (default), Middle Mouse, or Always On (ComfyUI-style)
- Zoom Mode: Alt + Scroll (default), Ctrl + Scroll, or Scroll (no modifier)
- Selection Mode: Click (default) or Alt + Drag
Features:
- Settings persist in localStorage
- New canvas settings button in header (monitor icon)
- Modal UI with radio button groups for each setting
- React Flow props dynamically configured based on user preferences
- Updated wheel event handler to respect zoom mode settings
This addresses user requests for ComfyUI/TouchDesigner-style navigation where
panning and zooming work without holding modifier keys.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: PR #65 review — altDrag selection bug, icon, dedup, tests
- Fix altDrag selection mode: use selectionKeyCode="Alt" instead of
selectionOnDrag=true which made drag-select work without any key
- Replace misleading video camera icon with arrows-pointing-out for
canvas navigation settings button
- Extract duplicated settings buttons JSX into settingsButtons variable
shared by both configured/unconfigured project branches
- Add defensive useEffect to sync CanvasSettingsModal state on open
- Add missing semicolon in types/index.ts canvas re-export
- Add localStorage tests for getCanvasNavigationSettings and
saveCanvasNavigationSettings
- Add integration tests for updateCanvasNavigationSettings
- Fix WorkflowCanvas test mocks to include canvasNavigationSettings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move canvas navigation settings into Project Settings modal
Consolidate canvas settings from standalone CanvasSettingsModal into a
new "Canvas" tab in ProjectSetupModal. Remove "like ComfyUI" text, add
Shift+Drag selection mode, and fix save button to bottom of dialog.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove double semicolon in types/index.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- Fix 3D viewport not filling node by adding resize={{ offsetSize: true }} for correct measurement in CSS-transformed React Flow nodes
- Replace grid/axis indicators with spot light for cleaner scene
- Add labeled input (3D) and output (Image) handles matching generate node style
- Add native wheel event listener to prevent graph zoom/pan while scrolling to zoom 3D model
- Remove redundant CSS workarounds (absolute positioning style, extra relative wrapper)
- Add edge-to-edge viewport via contentClassName (removes default padding when GLB loaded)
- Overlay controls bar on viewport with gradient background
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New KeyboardShortcutsDialog component showing all available shortcuts
- Accessible via ? button in header and ? key on canvas
- Groups shortcuts by category: General, Add Nodes, Layout, Canvas
- Auto-detects Mac vs Windows/Linux for modifier key display
- Follows existing modal patterns (Escape to close, backdrop click)
Replace identical inline context creation in executeWorkflow and regenerateNode
with a single shared helper, ensuring consistent behavior across both code paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- fal.ts: Use model capabilities to determine fallback content-type
(video/mp4 vs image/png) instead of defaulting to video for all models
- replicate.ts: Increase polling timeout to 10 min for video models;
filter output array to valid strings before URL validation
- nanoBananaExecutor.ts: Replace read-modify-write on outputGallery with
atomic appendOutputGalleryImage using Zustand set() callback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Providers:
- replicate: add SSRF validation on mediaUrl before fetch
- fal/kie/replicate/wavespeed: large-video returns data="" with url set
instead of putting raw URL in data field (violates GenerationOutput.data semantic)
- kie: remove stale WaveSpeed JSDoc block from kie provider file
- route.ts: update all output handling to support data="" + url for large videos
Executors:
- generateVideoExecutor/nanoBananaExecutor: track save-generation fetch in
pendingImageSyncs via new trackSaveGeneration context method so auto-save
waits for server ID resolution
- videoProcessingExecutors: throw errors instead of returning in validation
branches (encoderSupported, insufficient videos, no video connected) so
Promise.allSettled sees rejections
- types.ts: add trackSaveGeneration to NodeExecutionContext interface
- workflowStore: wire trackSaveGeneration into both execution context sites
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix video element not reloading on regeneration by adding key prop tied
to video history ID. Fix cancel showing timeout message by passing
"user-cancelled" reason through AbortController and checking it in all
three executor catch blocks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove dead trackSaveGeneration function (no longer called after executor
extraction) and unused type imports. Store: 3,838 -> 1,721 lines (55% reduction).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire extracted executors into workflowStore, replacing the ~1000-line
switch statement in executeWorkflow and ~750-line if-else chain in
regenerateNode with executor registry calls. Store reduced from 3,402
to 1,782 lines.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move getConnectedInputs (136 lines) and validateWorkflow (59 lines) into
connectedInputs.ts as pure functions taking (nodeId, nodes, edges).
Store methods become thin wrappers. Add 22 new tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>