diff --git a/src/components/nodes/BaseNode.tsx b/src/components/nodes/BaseNode.tsx
index ba44b363..fb5cd96a 100644
--- a/src/components/nodes/BaseNode.tsx
+++ b/src/components/nodes/BaseNode.tsx
@@ -205,8 +205,14 @@ export function BaseNode({
${!fullBleed && selected && settingsExpanded ? "border-blue-500" : ""}
${className}
`}
- onMouseEnter={() => setHoveredNodeId(id)}
- onMouseLeave={() => setHoveredNodeId(null)}
+ onMouseEnter={() => {
+ if (document.querySelector('.react-flow__pane.dragging')) return;
+ setHoveredNodeId(id);
+ }}
+ onMouseLeave={() => {
+ if (document.querySelector('.react-flow__pane.dragging')) return;
+ setHoveredNodeId(null);
+ }}
>
{children}
diff --git a/src/store/workflowStore.ts b/src/store/workflowStore.ts
index 472206ab..c0a74cc9 100644
--- a/src/store/workflowStore.ts
+++ b/src/store/workflowStore.ts
@@ -382,6 +382,10 @@ let nodeIdCounter = 0;
let groupIdCounter = 0;
let autoSaveIntervalId: ReturnType | null = null;
+// RAF debounce for hover updates — coalesces rapid mouseenter/mouseleave events
+// into a single store update per animation frame
+let hoverRafId: number | null = null;
+
// Track pending save-generation syncs to ensure IDs are resolved before workflow save
const pendingImageSyncs = new Map>();
@@ -526,7 +530,11 @@ const workflowStoreImpl: StateCreator = (set, get) => ({
},
setHoveredNodeId: (id: string | null) => {
- set({ hoveredNodeId: id });
+ if (hoverRafId !== null) cancelAnimationFrame(hoverRafId);
+ hoverRafId = requestAnimationFrame(() => {
+ hoverRafId = null;
+ if (get().hoveredNodeId !== id) set({ hoveredNodeId: id });
+ });
},
addNode: (type: NodeType, position: XYPosition, initialData?: Partial) => {