Renders low-resolution JPEG thumbnails (256px, q=0.6) in nodes when
zoomed out (effective width < 300px), swapping to full resolution when
zoomed in. Thumbnails are generated eagerly and cached in memory.
- New utility: imageThumbnail.ts (offscreen canvas downscaling)
- New cache: thumbnailCache.ts (ephemeral Map-based cache)
- New hook: useAdaptiveImageSrc (zoom-aware src selector)
- Integrated into all image-bearing nodes (GenerateImage, ImageInput,
Output, Annotation, SplitGrid, VideoFrameGrab, OutputGallery, GLBViewer)
- Lightbox/modal views always use full resolution
https://claude.ai/code/session_01MvD1n4QeXutgwUpKJuDGHa
Two issues caused nodes to grow taller each time they scrolled back
into view with onlyRenderVisibleElements:
1. InlineParameterPanel initialized contentHeight to 0 and animated
from maxHeight:0 to the measured height on every remount. This CSS
transition caused the ResizeObserver in BaseNode to see the panel
growing and add its height to the node — even though the node
already included it. Fix: when mounted with expanded=true, render
with maxHeight:"none" (no transition) so the panel appears at full
size instantly.
2. The ResizeObserver skip in BaseNode only caught a single observation.
The panel settling could fire multiple times during the CSS
transition. Fix: use a time-based suppression (300ms) that syncs the
tracked height ref without modifying node dimensions for all
observations during the settle window.
https://claude.ai/code/session_01MvD1n4QeXutgwUpKJuDGHa
The previous fix skipped the expand animation on initial mount but
missed the ResizeObserver path. On remount, trackedSettingsHeightRef
resets to 0, and the ResizeObserver sees the full panel height as a
delta, adding it to the node height that already includes the panel.
Add skipFirstObserveRef: when a node mounts with settings already
expanded, the first ResizeObserver callback syncs the tracked height
without calling setNodes, preventing the cumulative height growth.
https://claude.ai/code/session_01MvD1n4QeXutgwUpKJuDGHa
When a node with inline parameters scrolls off-screen, React Flow
unmounts it, resetting trackedSettingsHeightRef to 0. On remount the
expand effect would add the panel height to the node again, even though
it was already included. Skip the expand animation on initial mount and
just sync the tracked ref from the DOM instead.
https://claude.ai/code/session_01MvD1n4QeXutgwUpKJuDGHa
backdrop-filter: blur() causes significant frame rate drops on Windows
due to DWM compositor contention and Chrome's GPU layer management.
Replaced with higher-opacity semi-transparent backgrounds that maintain
visual contrast without the GPU-intensive blur compositing.
https://claude.ai/code/session_01MvD1n4QeXutgwUpKJuDGHa
The PromptNode text-sync effect can overwrite the per-item text set by
addNode before edge arrayItemIndex data is fully settled. Add a deferred
fix-up pass (setTimeout 0) that re-applies the correct text after React
effects have run.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add pt-1 to content area so fields don't hug the top edge, and
max-w-[75%] to the Split/By field rows so they don't extend behind
the auto-route button icon.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace old input/select styling with bg-[#1a1a1a] rounded-md pattern,
switch grid layouts to flex, add rotating chevron for Advanced toggle,
tighten parsed items container and auto-resize formula, and update
default dimensions from 360x360 to 340x260 for a more compact node.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
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>
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>
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 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>
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>
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>
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>
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>
- Change outer wrapper bg from #2a2a2a to bg-neutral-800 to match node body,
eliminating visible color mismatch at rounded corners
- Remove overflow-hidden from InlineParameterPanel (was clipping fields)
- Use min(180px, 100%) in grid minmax so columns shrink to fit narrow nodes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add bg-[#2a2a2a] rounded-lg to outer wrapper when settings expanded to
fill gap behind rounded corners at node/panel junction
- Reduce grid min column width from 240px to 180px so fields fit in narrow nodes
- Add overflow-hidden to InlineParameterPanel content div as safety net
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert trackedSettingsHeight from useState to useRef to eliminate a
race condition between two useLayoutEffect hooks. On expand, both the
rAF-based measure (Effect 1) and the ResizeObserver (Effect 2) were
adding the panel height — a double-add. On collapse only one subtracted,
leaving residual height each cycle.
The fix removes the expand branch from Effect 1 (now collapse-only) and
lets the ResizeObserver solely handle initial and dynamic measurement.
Using a ref also prevents the ResizeObserver from being torn down and
recreated on every height change.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
calculateAspectFitSize was receiving the total node height (content +
settings panel), producing incorrect aspect-fit dimensions when settings
were expanded. Now subtracts trackedSettingsHeight before calculating
and re-adds it when applying the final height.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch BaseNode wrapper from `display: contents` to flex layout when
settings are expanded so NodeResizer handles span the full visual height.
Height management is now centralized in BaseNode via useLayoutEffect +
ResizeObserver instead of per-node useEffects that compounded height.
- BaseNode: track settings panel height, adjust node dimensions on
expand/collapse, observe dynamic content changes
- InlineParameterPanel: remove animation (instant show/hide), remove
selection ring logic (now handled by BaseNode outer container)
- GenerateImageNode/GenerateVideoNode: remove buggy height management
useEffects and parameterPanelRef
- All 5 node types: remove `selected` prop from InlineParameterPanel
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move InlineParameterPanel from BaseNode children to a settingsPanel prop
rendered as a sibling of the bordered div. This fixes:
- Notch between preview and settings (width mismatch from border inset)
- Selection ring overlap/gap at the junction (clip-path on separate overlay)
- Bottom border and rounding when settings expanded
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add settingsExpanded prop to BaseNode. When true + selected, the node
div uses rounded-t-lg instead of rounded-lg so its ring has no bottom
rounding. Combined with InlineParameterPanel's overlapping ring with
rounded-b-lg, this creates a seamless continuous selection outline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Revert min-h-full back to h-full (which broke preview layout). Instead,
InlineParameterPanel uses -mt-[3px] negative margin when expanded+selected
to overlap with BaseNode's ring bottom edge, creating a seamless continuous
blue outline around both preview and settings areas.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change BaseNode inner div from h-full to min-h-full so it grows to
encompass the settings panel. The ring now wraps the entire area as
one continuous outline instead of two separate rings with a gap.
Removed the now-unnecessary selected prop from InlineParameterPanel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>