Wire up a lightweight callback registry so the floating header can trigger
each generate node's model browse dialog. Moves Browse into the controls
section for consistent vertical alignment and caps title width at 60% to
truncate earlier on narrow nodes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The easeCurve settings handle propagated bezierHandles and easingPreset
but omitted outputDuration, so children always used their local duration
instead of the parent's.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace broken `.react-flow__pane.dragging` DOM query guard with a shared
`isPanningRef` set by ReactFlow's onMoveStart/onMoveEnd callbacks. Clear
hoveredNodeId when panning starts. Use a derived boolean selector in
useVideoAutoplay so hovering node A doesn't re-render video nodes B–E.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Improve settings panel expand/collapse with animation-aware resize
suppression. Refactor Gemini controls to responsive grid layout with
column-first reordering. Add max-width to LLM controls. Move prompt
variable button to a bottom bar with backdrop blur. Use object-contain
for SplitGrid source image.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Manages video play/pause based on hover and selection state
- 300ms hover delay before playing, immediate play on selection
- Pauses on mouse leave or deselection (resumes from position)
- Catches AbortError from interrupted play() promises
Add fullBleed prop so calculateAspectFitSize() uses chromeHeight=0
instead of incorrectly subtracting 100px for non-existent node chrome.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ControlPanel: only send the active provider's API key header instead of all three
- LLMControls: use nullish coalescing (??) for temperature so 0 is not treated as falsy
- PromptConstructorNode: single-pass regex for @variable resolution to prevent prefix collisions (@foo matching inside @foobar)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Animate InlineParameterPanel expand/collapse with max-height transition
instead of conditional rendering. Lock BaseNode content height during
resize operations to prevent intermediate layout states causing image
flicker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace warning banner with inline highlights: blue for resolved @vars, red for unresolved
- Add "N vars missing" notice in footer bar
- Change PromptNode empty variable label from "@var" to "Add variable"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add presetsPopupRef to EaseCurve click-outside handler so clicking
preset buttons no longer dismisses the popup
- ProjectSetupModal overlay only closes when clicking the overlay itself
(e.target === e.currentTarget), preventing portal child clicks from
triggering close
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add video, audio, text, 3d, easeCurve entries to EDGE_COLORS in SharedEdgeGradients
- Normalize handle type suffixes (e.g. image-0 -> image) in EditableEdge color lookup
- Gate Kie provider filter behind API key check (return 400 if missing)
- Fix getMediaDimensions to check URL pathname for image extensions before defaulting to video
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a Run button matching the pattern used by other node control
panels, allowing users to execute the conditional switch node
directly from its settings panel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The easing presets popup could only be closed by clicking the
toggle button again. Now it also closes on Escape key or clicking
outside the popup area.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fal was unconditionally listed as available, which could surface
fal models to users without a fal API key configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Guard against undefined childNodeIds with optional chaining to
prevent a runtime crash when the array hasn't been initialized yet.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The `selected` variable inside the Zustand selector shadowed the
`selected` prop from React Flow's EdgeProps, which could cause
confusion during maintenance.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The useEffect registered an event listener whose handler body was
empty (only a comment stub), so it added overhead with no behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`||` treats temperature=0 as falsy and falls back to 0.7, which
incorrectly clamps a valid zero temperature. `??` only falls back
when the value is null/undefined.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The `|| true` was forcing external providers to always appear
available, bypassing the actual API key check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace broad state.nodes subscriptions in EditableEdge and ReferenceEdge
with narrow Zustand selectors that return primitives, eliminating ~50
unnecessary edge re-renders per node interaction. Share gradient defs
across all edges via SharedEdgeGradients (10 gradients vs 50+ per-edge
defs). Replace SVG blur(6px) filter with layered semi-transparent strokes
for loading glow, avoiding expensive filter evaluation on Windows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On 144Hz+ displays, requestAnimationFrame fires ~144 times/sec. This
adds a throttledRAF utility that limits callback execution to ~60fps
regardless of display refresh rate, normalizing performance across
60Hz, 120Hz, and 144Hz monitors.
Applied to:
- workflowStore hover debounce (event-driven throttle)
- FloatingNodeHeader position tracking (continuous loop throttle)
- ModelParameters ResizeObserver (event-driven throttle)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WorkflowCanvas, Header, and FloatingActionBar all used
useWorkflowStore() without selectors, subscribing to the entire store.
Every hoveredNodeId change triggered re-renders in all three components
plus a full cascade through edges and nodes from WorkflowCanvas.
- WorkflowCanvas: split into individual selectors for clipboard state
- Header: wrap in useShallow with explicit field selection
- FloatingActionBar: wrap in useShallow with explicit field selection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The displaySchema useMemo was placed after conditional early returns,
violating React's Rules of Hooks and causing hook order mismatch errors
when the component toggled between rendering null and its content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rapid mouse movement across the canvas fires multiple mouseout events
per frame, each triggering ~150 Zustand selector evaluations. This
coalesces hover updates into one per animation frame and skips them
entirely during canvas panning/dragging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Wrap InlineParameterPanel in React.memo
- Simplify handleToggleParams in all 5 generate nodes
- Use local isParamsExpanded instead of re-querying store
- Eliminates O(n) nodes.find() on every toggle click
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Wrap ModelParameters and ParameterInput in React.memo
- Add useMemo on sortedSchema and displaySchema computations
- RAF-debounce ResizeObserver with redundancy guard
- Stabilize onChange callback (pass name as prop, not closure)
- Eliminates re-renders during canvas pan/zoom/drag
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The capabilities prop doesn't exist on ModelSearchDialogProps. Use the
correct initialCapabilityFilter="image" prop instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap the ResizeObserver callback in requestAnimationFrame with
cancellation to coalesce rapid resize events into one setNodes call
per frame. Cleanup cancels any pending RAF on unmount.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
useViewport() re-renders on every pan AND zoom. The narrow selector
useStore((s) => s.transform[2]) only fires when zoom changes, eliminating
unnecessary group overlay re-renders during panning.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When dimmedNodeIds is empty and no node has a stale switch-dimmed class,
return the nodes array directly (same reference, zero iteration). The
.map() + regex only runs when dimming is actually active, which is the
uncommon case for most workflows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Skip Zustand set() when hoveredNodeId already matches the incoming value.
Prevents redundant store updates when the mouse stays on the same node
or when onMouseLeave fires with null when already null.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The `.react-flow__node:not(.switch-dimmed)` rule applied a transition to
every node DOM element unnecessarily. The `.switch-dimmed` rule already
handles the transition for dimming. Removing this makes undimming instant
(snappier) and eliminates a transition declaration from every node.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace underline tab bar with pill-style segmented tabs. Replace radio
button lists in Canvas tab with compact segmented button groups. Add
backdrop blur, click-outside-to-close, rounded-lg inputs, and improved
text contrast (neutral-400 instead of neutral-500 for descriptions).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restructure floating group label to use bottom-anchored positioning so
inverse-zoom scaling grows upward instead of overlapping group content.
Switch from useReactFlow().getViewport() to useViewport() hook for
proper reactive zoom updates. Change group name to double-click to edit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add beforeunload handler that prompts the user when they try to close
the tab or navigate away while there are unsaved changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Increase resize handle hit area from 8px to 16px (edge handles) and
from 3px to 5px (corner handles) so they're easier to grab.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace subscribing to the full nodes array with a Zustand selector that
returns only the single selected node. The selector runs the filter
internally but only causes a re-render when the selected node reference
changes, rather than on every store mutation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split the monolithic useWorkflowStore() destructure into useShallow for
data fields (nodes, edges, groups, dimmedNodeIds, etc.) and individual
selectors for action references. Actions are stable in Zustand and don't
trigger re-renders, while useShallow ensures the component only re-renders
when data values actually change rather than on every store mutation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace inline closure callbacks (onCustomTitleChange, onCommentChange,
onRun, onExpand) with stable handler patterns that pass nodeId. Replace
titlePrefix ReactNode prop with primitive provider string. Replace data
object prop with individual primitive fields (isInLockedGroup, isExecuting,
focusedCommentNodeId). This prevents N unnecessary re-renders per frame
during canvas panning.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove solid color header bar from groups, replace with floating rounded-rectangle label at top-left
- Add floating controls toolbar (color picker, lock, delete) at top-right with bg-neutral-800/80 backdrop
- Both label and controls use transform: scale(1/zoom) for constant screen size regardless of canvas zoom
- Remove headerHeight offset from createGroup in workflowStore (no longer reserves vertical space for header)
- Keep all existing group interactions: rename, recolor, lock, delete, drag, resize
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>