Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3dd3e26
[backport cloud/1.33] feat: open template via URL in linear mode (#6968)
comfy-pr-bot Nov 27, 2025
31d8422
[backport cloud/1.33] fix: don't use registry when only checking for …
christian-byrne Nov 27, 2025
334404a
[backport cloud/1.33] fix: remove LOD from vue nodes (#6983)
christian-byrne Nov 27, 2025
0cd0218
[backport cloud/1.33] fix: Vue Node <-> Litegraph node height offset …
christian-byrne Nov 27, 2025
896867b
[backport cloud/1.33] fix: add filter for combo widgets (#7003)
comfy-pr-bot Nov 27, 2025
550ca0c
[backport cloud/1.33] Remove app.graph usage from widgetInput code (#…
comfy-pr-bot Nov 28, 2025
637c199
[backport cloud/1.33] [fix] Re-encode cloud-subscription video to VP9…
comfy-pr-bot Nov 28, 2025
22aea29
[backport cloud/1.33] [feat] Show "Finished in" duration for complete…
comfy-pr-bot Nov 28, 2025
72a2581
[backport cloud/1.33] feat(api-nodes-pricing): add prices for ByteDan…
comfy-pr-bot Nov 29, 2025
7b589b5
[backport cloud/1.33] mark vue nodes menu toggle with beta tag (#7052)
comfy-pr-bot Nov 30, 2025
3856e0d
[backport cloud/1.33] fix: loader node widget value shows placeholder…
comfy-pr-bot Nov 30, 2025
ffa55cb
[backport cloud/1.33] Simplify Vue node resize to bottom-right corner…
christian-byrne Dec 1, 2025
3920413
[backport cloud/1.33] [fix] Prevent drag activation during Vue node r…
comfy-pr-bot Dec 1, 2025
c8a1df3
[backport cloud/1.33] feat(api-nodes-pricing): add prices for Kling O…
comfy-pr-bot Dec 1, 2025
b663244
[backport cloud/1.33] fix: normalize path separators in comfyAPIPlugi…
comfy-pr-bot Dec 3, 2025
6c34085
[backport cloud/1.33] cloud: increase feature flag polling interval t…
comfy-pr-bot Dec 3, 2025
d314172
[feat] Add remote config support for model upload and asset update fe…
luke-mino-altherr Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[backport cloud/1.33] fix: Vue Node <-> Litegraph node height offset …
…normalization (#6978)

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

- cherry-picked 29dbfa3
- 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 <[email protected]>
  • Loading branch information
christian-byrne and github-actions authored Nov 27, 2025
commit 0cd0218946b535b8fa08bc62d0cb40bd465d20be
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion src/composables/graph/useVueNodeLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync'
import { removeNodeTitleHeight } from '@/renderer/core/layout/utils/nodeSizeUtil'
import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'
import { app as comfyApp } from '@/scripts/app'
import { useToastStore } from '@/platform/updates/common/toastStore'
Expand Down Expand Up @@ -38,7 +39,10 @@ function useVueNodeLifecycleIndividual() {
const nodes = activeGraph._nodes.map((node: LGraphNode) => ({
id: node.id.toString(),
pos: [node.pos[0], node.pos[1]] as [number, number],
size: [node.size[0], node.size[1]] as [number, number]
size: [node.size[0], removeNodeTitleHeight(node.size[1])] as [
number,
number
]
}))
layoutStore.initializeFromLiteGraph(nodes)

Expand Down
28 changes: 19 additions & 9 deletions src/lib/litegraph/src/LGraphCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getSlotPosition } from '@/renderer/core/canvas/litegraph/slotCalculatio
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { LayoutSource } from '@/renderer/core/layout/types'
import { removeNodeTitleHeight } from '@/renderer/core/layout/utils/nodeSizeUtil'

import { CanvasPointer } from './CanvasPointer'
import type { ContextMenu } from './ContextMenu'
Expand Down Expand Up @@ -4043,16 +4044,25 @@ export class LGraphCanvas

// TODO: Report failures, i.e. `failedNodes`

const newPositions = created.map((node) => ({
nodeId: String(node.id),
bounds: {
x: node.pos[0],
y: node.pos[1],
width: node.size?.[0] ?? 100,
height: node.size?.[1] ?? 200
}
}))
const newPositions = created
.filter((item): item is LGraphNode => item instanceof LGraphNode)
.map((node) => {
const fullHeight = node.size?.[1] ?? 200
const layoutHeight = LiteGraph.vueNodesMode
? removeNodeTitleHeight(fullHeight)
: fullHeight
return {
nodeId: String(node.id),
bounds: {
x: node.pos[0],
y: node.pos[1],
width: node.size?.[0] ?? 100,
height: layoutHeight
}
}
})

if (newPositions.length) layoutStore.setSource(LayoutSource.Canvas)
layoutStore.batchUpdateNodeBounds(newPositions)

this.selectItems(created)
Expand Down
13 changes: 11 additions & 2 deletions src/renderer/core/layout/store/layoutStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { computed, customRef, ref } from 'vue'
import type { ComputedRef, Ref } from 'vue'
import * as Y from 'yjs'

import { removeNodeTitleHeight } from '@/renderer/core/layout/utils/nodeSizeUtil'

import { ACTOR_CONFIG } from '@/renderer/core/layout/constants'
import { LayoutSource } from '@/renderer/core/layout/types'
import type {
Expand Down Expand Up @@ -1414,8 +1416,8 @@ class LayoutStoreImpl implements LayoutStore {
batchUpdateNodeBounds(updates: NodeBoundsUpdate[]): void {
if (updates.length === 0) return

// Set source to Vue for these DOM-driven updates
const originalSource = this.currentSource
const shouldNormalizeHeights = originalSource === LayoutSource.DOM
this.currentSource = LayoutSource.Vue

const nodeIds: NodeId[] = []
Expand All @@ -1426,8 +1428,15 @@ class LayoutStoreImpl implements LayoutStore {
if (!ynode) continue
const currentLayout = yNodeToLayout(ynode)

const normalizedBounds = shouldNormalizeHeights
? {
...bounds,
height: removeNodeTitleHeight(bounds.height)
}
: bounds

boundsRecord[nodeId] = {
bounds,
bounds: normalizedBounds,
previousBounds: currentLayout.bounds
}
nodeIds.push(nodeId)
Expand Down
6 changes: 4 additions & 2 deletions src/renderer/core/layout/sync/useLayoutSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { onUnmounted, ref } from 'vue'

import type { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { addNodeTitleHeight } from '@/renderer/core/layout/utils/nodeSizeUtil'

/**
* Composable for syncing LiteGraph with the Layout system
Expand Down Expand Up @@ -43,12 +44,13 @@ export function useLayoutSync() {
liteNode.pos[1] = layout.position.y
}

const targetHeight = addNodeTitleHeight(layout.size.height)
if (
liteNode.size[0] !== layout.size.width ||
liteNode.size[1] !== layout.size.height
liteNode.size[1] !== targetHeight
) {
// Use setSize() to trigger onResize callback
liteNode.setSize([layout.size.width, layout.size.height])
liteNode.setSize([layout.size.width, targetHeight])
}
}

Expand Down
1 change: 1 addition & 0 deletions src/renderer/core/layout/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { ComputedRef, Ref } from 'vue'
export enum LayoutSource {
Canvas = 'canvas',
Vue = 'vue',
DOM = 'dom',
External = 'external'
}

Expand Down
7 changes: 7 additions & 0 deletions src/renderer/core/layout/utils/nodeSizeUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { LiteGraph } from '@/lib/litegraph/src/litegraph'

export const removeNodeTitleHeight = (height: number) =>
Math.max(0, height - (LiteGraph.NODE_TITLE_HEIGHT || 0))

export const addNodeTitleHeight = (height: number) =>
height + LiteGraph.NODE_TITLE_HEIGHT
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const resizeObserver = new ResizeObserver((entries) => {
x: topLeftCanvas.x,
y: topLeftCanvas.y + LiteGraph.NODE_TITLE_HEIGHT,
width: Math.max(0, width),
height: Math.max(0, height - LiteGraph.NODE_TITLE_HEIGHT)
height: Math.max(0, height)
}

let updates = updatesByType.get(elementType)
Expand All @@ -123,8 +123,7 @@ const resizeObserver = new ResizeObserver((entries) => {
}
}

// Set source to Vue before processing DOM-driven updates
layoutStore.setSource(LayoutSource.Vue)
layoutStore.setSource(LayoutSource.DOM)

// Flush per-type
for (const [type, updates] of updatesByType) {
Expand Down
112 changes: 107 additions & 5 deletions tests-ui/tests/renderer/core/layout/layoutStore.test.ts
Original file line number Diff line number Diff line change
@@ -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(() => {
Expand Down Expand Up @@ -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
}
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down