From 2ff14fadc21477175776410a793be403838ae261 Mon Sep 17 00:00:00 2001 From: Alexander Brown Date: Tue, 24 Feb 2026 13:12:16 -0800 Subject: [PATCH] fix: prevent infinite node resize loop in Vue mode (#9177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fix infinite node resize loop in Vue mode where textarea widgets caused nodes to grow ~33px per frame indefinitely. ## Changes - **What**: Two feedback loops broken in the LiteGraph↔Vue layout sync: 1. `_arrangeWidgets()` in LiteGraph's draw loop was calling `setSize()` every frame with its own computed widget height, which disagreed with Vue's DOM-measured height. Guarded with `!LiteGraph.vueNodesMode`. 2. `useLayoutSync` was calling `setSize()` which triggers the size setter → writes back to layoutStore with `source=Canvas` → `handleLayoutChange` updates CSS vars → ResizeObserver fires → loop. Changed to direct array assignment (matching the existing position sync pattern). ## Review Focus - The `_arrangeWidgets` guard: in Vue mode, the DOM/ResizeObserver is the source of truth for node sizing, so LiteGraph should not grow nodes via `setSize()`. Verify no Vue-mode features depend on this growth path. - The `useLayoutSync` change: `liteNode.size[0] = ...` modifies `_size` via the getter without triggering the setter, avoiding the Canvas-source bounce. `onResize` is still called. Verify no downstream code relies on the setter side effects when syncing from layout store. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9177-fix-prevent-infinite-node-resize-loop-in-Vue-mode-3116d73d365081e4ad88f1cfad51df18) by [Unito](https://www.unito.io) Co-authored-by: Amp --- src/lib/litegraph/src/LGraphNode.ts | 7 ++++++- src/renderer/core/layout/sync/useLayoutSync.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index bd032c7e06..99eb1c14d5 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -4187,7 +4187,12 @@ export class LGraphNode // Ref: https://github.com/Comfy-Org/ComfyUI_frontend/issues/2652 // TODO: Move the layout logic before drawing of the node shape, so we don't // need to trigger extra round of rendering. - if (y > bodyHeight) { + // In Vue mode, the DOM is the source of truth for node sizing — the + // ResizeObserver feeds measurements back to the layout store. Allowing + // LiteGraph to also call setSize() here creates an infinite feedback loop + // (LG grows node → CSS min-height increases → textarea fills extra space → + // ResizeObserver reports larger size → LG grows node again). + if (!LiteGraph.vueNodesMode && y > bodyHeight) { this.setSize([this.size[0], y]) this.graph.setDirtyCanvas(false, true) } diff --git a/src/renderer/core/layout/sync/useLayoutSync.ts b/src/renderer/core/layout/sync/useLayoutSync.ts index 221fe64eb9..06449a1cc1 100644 --- a/src/renderer/core/layout/sync/useLayoutSync.ts +++ b/src/renderer/core/layout/sync/useLayoutSync.ts @@ -50,8 +50,12 @@ export function useLayoutSync() { liteNode.size[0] !== layout.size.width || liteNode.size[1] !== layout.size.height ) { - // Use setSize() to trigger onResize callback - liteNode.setSize([layout.size.width, layout.size.height]) + // Update internal size directly (like position above) to avoid + // the size setter writing back to layoutStore with Canvas source, + // which would create a feedback loop through handleLayoutChange. + liteNode.size[0] = layout.size.width + liteNode.size[1] = layout.size.height + liteNode.onResize?.(liteNode.size) } }