Variables from upstream Prompt nodes weren't discovered when a Router sat
between the Prompt and PromptConstructor. Added resolveTextSourcesThroughRouters()
helper that recursively traverses router nodes to find actual text sources,
used in both the component and executor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The default content div used overflow-hidden which clipped handles positioned
at left: -7px / right: -7px. Nodes that need clipping (ImageInput, Annotation,
VideoTrim) already set their own contentClassName.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
contain: paint clips rendering at the element boundary like overflow:
hidden, cutting off connection handles. Keep layout and style
containment for performance without the clipping side effect.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move removePending to .finally() so it runs on both resolve and reject,
preventing stale rejected promises from accumulating in the pending map.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Split button was permanently disabled because sourceImage was only
set during execution, not when an edge was connected. Added a reactive
effect that watches for connected images and updates sourceImage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a router node had multiple output edges to the same downstream node
(e.g., passing both text and image), only the first edge type received
data. The shared _visited set prevented the router from being traversed
a second time, causing subsequent edge types to get empty results.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Browser hit-testing against large base64 <img> elements is expensive
even when no React state updates occur. Two CSS-level fixes:
1. pointer-events: none on all .react-flow__node img — images never
need direct mouse events (parent containers handle clicks)
2. .canvas-interacting class on <html> during pan/drag disables
pointer-events on all node content via CSS descendant selector,
toggled via direct DOM access (zero React overhead)
https://claude.ai/code/session_01MvD1n4QeXutgwUpKJuDGHa
The isDraggingNodeRef check had a race condition: fast mouse movement
could trigger onMouseEnter before React Flow's onNodeDragStart fired.
Adding e.buttons !== 0 catches all cases where a button is held,
which is always true during drag, with no timing dependency.
https://claude.ai/code/session_01MvD1n4QeXutgwUpKJuDGHa
When dragging a node over another, onMouseEnter/onMouseLeave were
firing on underlying nodes, updating hoveredNodeId in Zustand and
triggering re-renders of nodes with large base64 images.
- Add isDraggingNodeRef (set on onNodeDragStart, cleared on
onNodeDragStop) to suppress hover events during drag
- Add CSS contain: layout style paint on BaseNode content area to
isolate repaints within each node
https://claude.ai/code/session_01MvD1n4QeXutgwUpKJuDGHa
The cache key was using only src.slice(0, 200) which is not unique —
different base64 images share the same MIME prefix and similar headers.
Now samples from multiple positions (start, middle, end) plus length
to virtually eliminate collisions.
https://claude.ai/code/session_01MvD1n4QeXutgwUpKJuDGHa
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>
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>