Skip to content

Node shrinks by title height on move after a ResizeObserver flush (LayoutSource.DOM leaks)Β #14840

Description

@DrJKL

πŸ€– Filed by an LLM agent using DrJKL's account, not by DrJKL.

Summary

layoutStore.batchUpdateNodeBounds conditionally subtracts NODE_TITLE_HEIGHT from the bounds it is given, gated on LayoutSource β€” which is ambient global state that leaks. After any Vue-node ResizeObserver flush, subsequent move operations silently shrink the node by one title height per call, and report a size change that fires LGraphNode.onResize.

Mechanism

  1. useVueNodeResizeTracking.ts:254 sets the source and never restores it:
if (updatesByType.size > 0) {
  layoutStore.setSource(LayoutSource.DOM)   // no restore
  ...
}
  1. batchUpdateNodeBounds restores currentSource to originalSource (layoutStore.ts:1328) β€” i.e. back to DOM. So the source stays DOM until some other writer sets it.

  2. While the source is DOM, batchUpdateNodeBounds rewrites the caller's height (layoutStore.ts:1294-1306):

const shouldNormalizeHeights = originalSource === LayoutSource.DOM
...
boundsRecord[nodeId] = shouldNormalizeHeights
  ? { ...bounds, height: removeNodeTitleHeight(bounds.height) }
  : bounds

This is correct for exactly one caller β€” the ResizeObserver handler, which passes raw border-box height. The other callers (layoutMutations.batchMoveNodes, useNodeDrag snap-on-release, LGraphCanvas paste) pass an already-normalized store height, so the subtraction is pure corruption.

  1. handleBatchUpdateBounds then sees rect[3] !== bounds.height and pushes to sizeChangedNodeIds (layoutStore.ts:1100-1106), so notifyLayoutChanges fires node.onResize(node.size) for what the user experienced as a drag.

The ResizeObserver is a single module-level instance observing every Vue node, so a resize on one node poisons a drag of another. useNodeDrag.ts:98 sets Vue at startDrag, which only protects the drag until the first mid-drag RO flush (preview image load, progress badge, widget hydration).

Impact

  • Node loses NODE_TITLE_HEIGHT per affected move.
  • Spurious onResize callbacks β€” visible to custom nodes, so extension-facing.

Suggested fix

Normalize at the call site. Have useVueNodeResizeTracking.ts:73 subtract the title height before calling, so batchUpdateNodeBounds takes its bounds argument literally and shouldNormalizeHeights / the originalSource read can be deleted. LayoutSource should describe provenance for listeners, not gate a data transform.

Related

Surfaced while reviewing #14133 (#14133 (comment)). Pre-existing β€” that PR does not touch batchUpdateNodeBounds, and the predecessor useLayoutSync fired onResize on the same corrupted write via its own size diff.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Potential BugUntriaged bugPublic APIAffects or interacts with the public API surface (affecting custom node or extension authors)area:nodes

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions