[backport cloud/1.33] fix: Vue Node <-> Litegraph node height offset normalization (#6978)

## Summary
Backport of #6966 onto cloud/1.33.

- cherry-picked 29dbfa3f
- accepted upstream snapshot updates (only zoomed-in ctrl+shift PNG
conflicted)

## Testing
- pnpm typecheck

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6978-backport-cloud-1-33-fix-Vue-Node-Litegraph-node-height-offset-normalization-2b86d73d365081a19a81f4fac0fd2e91)
by [Unito](https://www.unito.io)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Christian Byrne
2025-11-26 21:38:13 -08:00
committed by GitHub
parent 334404aa3b
commit 0cd0218946
24 changed files with 159 additions and 23 deletions

View File

@@ -1,11 +1,9 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import {
type LayoutChange,
LayoutSource,
type NodeLayout
} from '@/renderer/core/layout/types'
import { LayoutSource } from '@/renderer/core/layout/types'
import type { LayoutChange, NodeLayout } from '@/renderer/core/layout/types'
describe('layoutStore CRDT operations', () => {
beforeEach(() => {
@@ -304,4 +302,108 @@ describe('layoutStore CRDT operations', () => {
expect(recentOps.length).toBeGreaterThanOrEqual(1)
expect(recentOps[0].type).toBe('moveNode')
})
it('normalizes DOM-sourced heights before storing', () => {
const nodeId = 'dom-node'
const layout = createTestNode(nodeId)
layoutStore.applyOperation({
type: 'createNode',
entity: 'node',
nodeId,
layout,
timestamp: Date.now(),
source: LayoutSource.External,
actor: 'test'
})
layoutStore.setSource(LayoutSource.DOM)
layoutStore.batchUpdateNodeBounds([
{
nodeId,
bounds: {
x: layout.bounds.x,
y: layout.bounds.y,
width: layout.size.width,
height: layout.size.height + LiteGraph.NODE_TITLE_HEIGHT
}
}
])
const nodeRef = layoutStore.getNodeLayoutRef(nodeId)
expect(nodeRef.value?.size.height).toBe(layout.size.height)
expect(nodeRef.value?.size.width).toBe(layout.size.width)
expect(nodeRef.value?.position).toEqual(layout.position)
})
it('normalizes very small DOM-sourced heights safely', () => {
const nodeId = 'small-dom-node'
const layout = createTestNode(nodeId)
layout.size.height = 10
layoutStore.applyOperation({
type: 'createNode',
entity: 'node',
nodeId,
layout,
timestamp: Date.now(),
source: LayoutSource.External,
actor: 'test'
})
layoutStore.setSource(LayoutSource.DOM)
layoutStore.batchUpdateNodeBounds([
{
nodeId,
bounds: {
x: layout.bounds.x,
y: layout.bounds.y,
width: layout.size.width,
height: layout.size.height + LiteGraph.NODE_TITLE_HEIGHT
}
}
])
const nodeRef = layoutStore.getNodeLayoutRef(nodeId)
expect(nodeRef.value?.size.height).toBeGreaterThanOrEqual(0)
})
it('handles undefined NODE_TITLE_HEIGHT without NaN results', () => {
const nodeId = 'undefined-title-height'
const layout = createTestNode(nodeId)
layoutStore.applyOperation({
type: 'createNode',
entity: 'node',
nodeId,
layout,
timestamp: Date.now(),
source: LayoutSource.External,
actor: 'test'
})
const originalTitleHeight = LiteGraph.NODE_TITLE_HEIGHT
// @ts-expect-error intentionally simulate undefined runtime value
LiteGraph.NODE_TITLE_HEIGHT = undefined
try {
layoutStore.setSource(LayoutSource.DOM)
layoutStore.batchUpdateNodeBounds([
{
nodeId,
bounds: {
x: layout.bounds.x,
y: layout.bounds.y,
width: layout.size.width,
height: layout.size.height
}
}
])
const nodeRef = layoutStore.getNodeLayoutRef(nodeId)
expect(nodeRef.value?.size.height).toBe(layout.size.height)
} finally {
LiteGraph.NODE_TITLE_HEIGHT = originalTitleHeight
}
})
})

View File

@@ -5,7 +5,9 @@ import { LayoutSource } from '@/renderer/core/layout/types'
import { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex'
// Mock the layout mutations module
vi.mock('@/renderer/core/layout/operations/layoutMutations')
vi.mock('@/renderer/core/layout/operations/layoutMutations', () => ({
useLayoutMutations: vi.fn()
}))
const mockedUseLayoutMutations = vi.mocked(useLayoutMutations)