- 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>
- 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>
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>
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>
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>
- 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>
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>
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>
- 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>
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>
- 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)
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>
The loopCount (1x/2x/3x) feature was only applied in executeWorkflow()
but not in regenerateNode(), which is the code path triggered by
clicking the "Stitch" button. This adds the blob duplication logic to
both code paths and includes the UI controls and type definitions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Delete unused generateWithFal function (replaced by generateWithFalQueue)
- Add 20MB size guard in uploadImageToFal to prevent memory spikes
- Add refresh button to ModelSearchDialog that clears model and schema
localStorage caches plus in-memory fetch cache
- Wire "Try Again" error button to also clear stale caches
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace blocking fal.run endpoint with async queue.fal.run + 1s polling
to match Replicate/WaveSpeed behavior. Upload base64 images to fal CDN
before queue submission to avoid large inline payloads. Cache schema
mappings in-memory (30min TTL) to eliminate redundant API calls per
generation. Also fix localStorage cache leaking between tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds 48-hour client-side caching to persist model data across dev server
restarts and page refreshes, eliminating repeated API calls during hot
reloading in development mode.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Stabilize thumbnail extraction deps with a string key derived from clip
edge IDs, preventing excessive re-runs on unrelated node changes
- Use ref-based cache for thumbnails and add cleanup abort for pending
extractions
- Fix clipOrder sync triggering extra render by tracking last written order
- Add WaveSpeed case to ProviderBadge in GenerateImageNode and
GenerateVideoNode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- AudioInputNode: use named event handlers with removeEventListener in cleanup
- deduplicatedFetch: track setTimeout IDs and clear them in clearFetchCache()
- waitForPendingImageSyncs: clear timeout in finally block after Promise.race
- decodeWithMediaBunny: wrap in try/finally to close sink, input, blobSource
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update stale test mocks from currentNodeId to currentNodeIds in
AudioInputNode, EaseCurveNode, and VideoStitchNode tests. Replace
hard 401 return for missing FAL API key with console.warn, allowing
requests to proceed without auth (rate-limited) since
generateWithFal() already handles null keys gracefully.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The presets overlay was being clipped by BaseNode's overflow-hidden container.
Render it through createPortal to document.body with position tracking via
requestAnimationFrame, matching the existing pattern used for comment tooltips.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add nano-banana-pro model to Kie provider with schema and image input key
- Reorder Kie generation to process dynamicInputs before fallback image
uploads, preventing double-uploads when schema-mapped connections exist
- Update test mocks for deduplicatedFetch, useProviderApiKeys, and static
handle changes across GenerateImageNode, GenerateVideoNode,
ModelParameters, and ModelSearchDialog tests
- Add test commands to CLAUDE.md
- Fix SplitGridSettingsModal grid preview count (5 → 6 options)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handleNodeDragStop used a hardcoded 300x280 fallback for node dimensions,
causing miscalculated centers for non-standard nodes (easeCurve 340x480,
videoStitch 400x280, audioInput 300x200). Now uses the same 3-level
fallback (measured → style → defaultNodeDimensions) as createGroup.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use fixed-width editor, absolute-positioned video to fill node, and
uniform gap-based spacing for controls below the curve editor.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Split AudioInputNode waveform effect to avoid ResizeObserver churn on every animation tick
- Add missing kieApiKey/wavespeedApiKey deps to ModelParameters useEffect
- Clean up VideoSampleSource/Output on error in useApplySpeedCurve
- Fix off-by-one in useStitchVideos duplicate-frame skip (<= to <) and add try/finally for resource cleanup
- Replace leftover currentNodeId: null with currentNodeIds: [] in workflowStore
- Store full response metadata (status, headers, body) in deduplicatedFetch cache
- Add in-flight guard to imageStorage to prevent duplicate concurrent saves
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add ~38 new image/video models (Imagen 4, FLUX.2, Flux Kontext,
Grok Imagine, Ideogram, Qwen, Runway, Hailuo, Seedance variants,
Kling older, Wan older) with schemas and endpoint routing for
dedicated APIs (Flux Kontext, Runway). Add pageUrl to all Kie
models and update ModelSearchDialog to use it.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cover 11 new source files with 280 specs across pure function libs,
React components, and async hook logic. Key areas: easing functions,
bezier presets, speed curve warping, video encoding config,
AudioInputNode, EaseCurveNode, CubicBezierEditor, audio mixing,
audio visualization, video stitching, and speed curve application.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added validation to ensure fal.ai API key is provided in requests, returning a 401 status with an appropriate error message if not configured.
- Updated related tests to reflect the new API key requirement and adjusted assertions for expected responses.
- Modified documentation to clarify that fal.ai API key is now mandatory for model access.
- Node height adjusts between 480px (editor) and 320px (video) on tab switch
- Default to video tab when settings are inherited from parent
- Rename "Ease In"/"Ease Out" handle labels to "Settings"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
HTML5 drag-and-drop doesn't work inside React Flow nodes because
React Flow captures pointer events at a higher level. Switch to
pointer-event-based reorder with visual feedback (dragged item dims,
hover target highlights with blue ring).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add nodrag class to thumbnail items so React Flow doesn't capture
drag events, allowing the HTML drag-and-drop reorder to work.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract thumbnails from easeCurve and videoStitch source nodes (not
just generateVideo)
- Add content-start to grid container so rows pack to top instead of
stretching to fill available space
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace 2 video handles with 4 labeled handles (Video In/Out at 35%, Ease In/Out at 75%)
- Add lime-300 color for easeCurve handles matching node accent
- Compute inheritance state from edges (useMemo)
- Add inheritance overlay on Editor tab: semi-transparent dark bg with informational text
- Add "Control manually" button that removes easeCurve edge and clears inheritedFrom
- Disable CubicBezierEditor, Presets button, and Apply button when inherited
- Video tab unaffected by inheritance state
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace horizontal scroll filmstrip with 4-column CSS grid layout
- Use aspect-video for thumbnails that scale with grid cells
- Add blue "Video N" labels to each video input handle
- Add blue "Output" label to video output handle
- Audio handle label unchanged (purple)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add videoStitch handle resolution in handleConnect batch loop (both original source and other selected nodes)
- Add batchUsed parameter to findCompatibleHandle for batch-aware handle lookup
- Add videoStitch resolution in ConnectionDropMenu batch handler
- Prevents all bulk connections from routing to the same video-0 handle
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add inheritedFrom field to EaseCurveNodeData interface
- Add easeCurve to HandleType union
- Update getHandleType, getNodeHandles, isValidConnection for easeCurve
- Add easeCurve extraction in getConnectedInputs
- Propagate parent bezierHandles/easingPreset in both execution paths
- Fix validation.ts default data for easeCurve node
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
All Easing Functions presets were not updating the editor because
handleSelectEasing fell back to the same DEFAULT_CUSTOM_BEZIER for every
non-preset easing. Added EASING_BEZIER_MAP with correct cubic-bezier
values from easings.net and a getEasingBezier() lookup. Also corrected
three PRESET_BEZIERS entries (easeInOutExpo, easeInOutCubic, easeInOutSine)
that diverged from the easings.net source.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Import and register EaseCurveNode in nodeTypes
- Add easeCurve case to getNodeHandles (video in, video out)
- Add easeCurve to isValidConnection video target list
- Add easeCurve video handle mapping in handleMenuSelect
- Add amber minimap color (#f59e0b) for easeCurve nodes
- Add Ease Curve to VIDEO_TARGET_OPTIONS and VIDEO_SOURCE_OPTIONS
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add easeCurve (340x480) to WorkflowCanvas keyboard shortcut dimensions
- Add easeCurve (340x480) to quickstart validation DEFAULT_DIMENSIONS
- Add easeCurve case to createDefaultNodeData switch (fixes exhaustive return)
- Required for NodeType union exhaustiveness after adding easeCurve type
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Two tabs: Editor (curve editor) and Video (processed result)
- CubicBezierEditor integration with nodrag isolation
- Preset popover with visual SVG thumbnails of all 24 easing functions
- 5 Bezier presets with exact handles, plus 19 sampled polyline thumbnails
- Duration control (0.1-30 sec) with nodrag input
- Video preview with auto-play, loop, click-to-clear
- Auto-switch to Video tab when processing completes
- Encoder detection on mount with unsupported browser fallback
- Processing overlay with spinner and progress percentage
- Error display below tabs
- Run button via BaseNode onRun
- Exported from src/components/nodes/index.ts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Multiple node instances (GenerateImageNode, GenerateVideoNode, etc.) were
each making their own /api/models requests when mounted, causing N duplicate
network requests when N nodes existed on the canvas.
This fix:
- Adds deduplicatedFetch utility that shares in-flight requests to the same URL
- Adds useProviderApiKeys() hook with shallow equality for stable API key refs
- Updates all components to use primitive dependencies instead of object refs
- Uses deduplicatedFetch for all model-fetching operations
Expected result: 1 request instead of N when multiple nodes need the same data.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add "video" to Output node's getNodeHandles inputs list
- Consistent with isValidConnection and findCompatibleHandle video->output handling
- Output node now properly accepts VideoStitch connections via all code paths
- Add video handle mapping for videoStitch, generateVideo, and output
- Add audio handle mapping for audioInput and videoStitch
- Connection drop now auto-connects for all handle types
- Detect audio file types in handleDragOver (audio/*)
- Handle audio files in handleDrop creating AudioInput nodes
- Show "Drop audio to create node" overlay for audio drag
- Add "audio" to dropType state union