fix: emit layout change for batch node bounds (#5939)

## Summary

Fixes issue where node size changes are not serialized by routing
DOM-driven node bounds updates through a single CRDT operation so Vue
node geometry stays synchronized with LiteGraph.

## Changes

- **What**: Added `BatchUpdateBoundsOperation` to the layout store,
applied it via the existing Yjs pipeline, notified link sync to
recompute touched nodes, and covered the path with a regression test

## Review Focus

Correctness of the new batch operation when multiple nodes update
simultaneously, especially remote replay/undo scenarios and link
geometry recomputation.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5939-fix-emit-layout-change-for-batch-node-bounds-2846d73d365081db8f8cca5bf7b85308)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Christian Byrne
2025-10-10 20:47:12 -07:00
committed by GitHub
parent 7e3c04399a
commit e6534f17e6
29 changed files with 331 additions and 55 deletions

View File

@@ -154,6 +154,7 @@ import {
import { cn } from '@/utils/tailwindUtil'
import { useNodeResize } from '../composables/useNodeResize'
import { calculateIntrinsicSize } from '../utils/calculateIntrinsicSize'
import NodeContent from './NodeContent.vue'
import NodeHeader from './NodeHeader.vue'
import NodeSlots from './NodeSlots.vue'
@@ -247,7 +248,7 @@ onErrorCaptured((error) => {
})
// Use layout system for node position and dragging
const { position, size, zIndex, resize } = useNodeLayout(() => nodeData.id)
const { position, size, zIndex } = useNodeLayout(() => nodeData.id)
const { pointerHandlers, isDragging, dragStyle } = useNodePointerInteractions(
() => nodeData,
handleNodeSelect
@@ -269,13 +270,19 @@ const handleContextMenu = (event: MouseEvent) => {
}
onMounted(() => {
if (size.value && transformState?.camera) {
const scale = transformState.camera.z
const screenSize = {
width: size.value.width * scale,
height: size.value.height * scale
}
resize(screenSize)
// Set initial DOM size from layout store, but respect intrinsic content minimum
if (size.value && nodeContainerRef.value && transformState) {
const intrinsicMin = calculateIntrinsicSize(
nodeContainerRef.value,
transformState.camera.z
)
// Use the larger of stored size or intrinsic minimum
const finalWidth = Math.max(size.value.width, intrinsicMin.width)
const finalHeight = Math.max(size.value.height, intrinsicMin.height)
nodeContainerRef.value.style.width = `${finalWidth}px`
nodeContainerRef.value.style.height = `${finalHeight}px`
}
})