fix: prevent node height shrinking on vueNodes/litegraph mode switch (#8697)
## Summary Three interacting bugs caused cumulative height loss (~66px per cycle): 1. useVueNodeLifecycle incorrectly subtracted NODE_TITLE_HEIGHT from LGraphNode.size[1] during layout init, but size[1] is already content-only (title excluded per LGraphNode.measure()). 2. ensureCorrectLayoutScale added/subtracted NODE_TITLE_HEIGHT in scale formulas, breaking the round-trip. Simplified to pure ratio scaling (size * scaleFactor). Also set LayoutSource.Canvas before batchUpdateNodeBounds to prevent stale DOM source from triggering incorrect height normalization. 3. initSizeStyles set --node-height (min-height of the full DOM element including title) to the content-only layout height. Added NODE_TITLE_HEIGHT so ResizeObserver captures the correct total height and normalization recovers the exact content height. ## Screenshots (if applicable) before https://github.com/user-attachments/assets/ae41124b-f9e3-4061-8127-eeacddc67a55 after https://github.com/user-attachments/assets/5ff288a6-73a3-481a-a750-150d9bdbc8fe ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8697-fix-prevent-node-height-shrinking-on-vueNodes-litegraph-mode-switch-2ff6d73d365081c7a2acdc7ec57e2e19) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com>
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 103 KiB |
@@ -31,7 +31,12 @@ test.describe('Vue Integer Widget', () => {
|
||||
await expect(seedWidget).toBeVisible()
|
||||
|
||||
// Delete the node that is linked to the slot (freeing up the widget)
|
||||
await comfyPage.vueNodes.getNodeByTitle('Int').click()
|
||||
// Click on the header to select the node (clicking center may land on
|
||||
// the widget area where pointerdown.stop prevents node selection)
|
||||
await comfyPage.vueNodes
|
||||
.getNodeByTitle('Int')
|
||||
.locator('.lg-node-header')
|
||||
.click()
|
||||
await comfyPage.vueNodes.deleteSelected()
|
||||
|
||||
// Test widget works when unlinked
|
||||
|
||||
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 79 KiB |
@@ -9,7 +9,6 @@ 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'
|
||||
|
||||
@@ -33,10 +32,7 @@ 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], removeNodeTitleHeight(node.size[1])] as [
|
||||
number,
|
||||
number
|
||||
]
|
||||
size: [node.size[0], node.size[1]] as [number, number]
|
||||
}))
|
||||
layoutStore.initializeFromLiteGraph(nodes)
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ describe('LGraphNode', () => {
|
||||
|
||||
expect(wrapper.element.style.getPropertyValue('--node-height')).toBe('')
|
||||
expect(wrapper.element.style.getPropertyValue('--node-height-x')).toBe(
|
||||
'100px'
|
||||
'130px'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -219,7 +219,7 @@ describe('LGraphNode', () => {
|
||||
})
|
||||
|
||||
expect(wrapper.element.style.getPropertyValue('--node-height')).toBe(
|
||||
'100px'
|
||||
'130px'
|
||||
)
|
||||
expect(wrapper.element.style.getPropertyValue('--node-height-x')).toBe('')
|
||||
})
|
||||
|
||||
@@ -342,7 +342,10 @@ function initSizeStyles() {
|
||||
const suffix = isCollapsed.value ? '-x' : ''
|
||||
|
||||
el.style.setProperty(`--node-width${suffix}`, `${width}px`)
|
||||
el.style.setProperty(`--node-height${suffix}`, `${height}px`)
|
||||
el.style.setProperty(
|
||||
`--node-height${suffix}`,
|
||||
`${height + LiteGraph.NODE_TITLE_HEIGHT}px`
|
||||
)
|
||||
}
|
||||
|
||||
const baseResizeHandleClasses =
|
||||
|
||||
@@ -4,11 +4,11 @@ import { createBounds } from '@/lib/litegraph/src/measure'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||
import { LayoutSource } from '@/renderer/core/layout/types'
|
||||
import type { NodeBoundsUpdate } from '@/renderer/core/layout/types'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
import type { SubgraphInputNode } from '@/lib/litegraph/src/subgraph/SubgraphInputNode'
|
||||
import type { SubgraphOutputNode } from '@/lib/litegraph/src/subgraph/SubgraphOutputNode'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
const SCALE_FACTOR = 1.2
|
||||
|
||||
@@ -67,9 +67,7 @@ export function ensureCorrectLayoutScale(
|
||||
|
||||
const scaledWidth = lgNode.width * scaleFactor
|
||||
|
||||
const scaledHeight = needsUpscale
|
||||
? lgNode.size[1] * scaleFactor + LiteGraph.NODE_TITLE_HEIGHT
|
||||
: (lgNode.size[1] - LiteGraph.NODE_TITLE_HEIGHT) * scaleFactor
|
||||
const scaledHeight = lgNode.size[1] * scaleFactor
|
||||
|
||||
// Directly update LiteGraph node to ensure immediate consistency
|
||||
// Dont need to reference vue directly because the pos and dims are already in yjs
|
||||
@@ -93,6 +91,7 @@ export function ensureCorrectLayoutScale(
|
||||
}
|
||||
|
||||
if (onActiveGraph && yjsMoveNodeUpdates.length > 0) {
|
||||
layoutStore.setSource(LayoutSource.Canvas)
|
||||
layoutStore.batchUpdateNodeBounds(yjsMoveNodeUpdates)
|
||||
}
|
||||
|
||||
|
||||