diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index 37c0950a2..a56627064 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -5614,6 +5614,9 @@ export class LGraphCanvas implements CustomEventDispatcher this.renderedPaths.clear() if (this.links_render_mode === LinkRenderType.HIDDEN_LINK) return + // Skip link rendering while waiting for slot positions to sync after reconfigure + if (LiteGraph.vueNodesMode && layoutStore.pendingSlotSync) return + const { graph, subgraph } = this if (!graph) throw new NullGraphError() diff --git a/src/renderer/core/layout/store/layoutStore.ts b/src/renderer/core/layout/store/layoutStore.ts index 786be0625..ed928bb00 100644 --- a/src/renderer/core/layout/store/layoutStore.ts +++ b/src/renderer/core/layout/store/layoutStore.ts @@ -141,6 +141,20 @@ class LayoutStoreImpl implements LayoutStore { // Vue resizing state to prevent drag from activating during resize public isResizingVueNodes = ref(false) + /** + * Flag indicating slot positions are pending sync after graph reconfiguration. + * When true, link rendering should be skipped to avoid drawing with stale positions. + */ + private _pendingSlotSync = false + + get pendingSlotSync(): boolean { + return this._pendingSlotSync + } + + setPendingSlotSync(value: boolean): void { + this._pendingSlotSync = value + } + constructor() { // Initialize Yjs data structures this.ynodes = this.ydoc.getMap('nodes') diff --git a/src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts b/src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts index c92fc8c92..9f355f9fd 100644 --- a/src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts +++ b/src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts @@ -31,13 +31,19 @@ function scheduleSlotLayoutSync(nodeId: string) { raf.schedule() } -function flushScheduledSlotLayoutSync() { - if (pendingNodes.size === 0) return +export function flushScheduledSlotLayoutSync() { + if (pendingNodes.size === 0) { + // Even if no pending nodes, clear the flag (e.g., graph with no nodes) + layoutStore.setPendingSlotSync(false) + return + } const conv = useSharedCanvasPositionConversion() for (const nodeId of Array.from(pendingNodes)) { pendingNodes.delete(nodeId) syncNodeSlotLayoutsFromDOM(nodeId, conv) } + // Clear the pending sync flag - slots are now synced + layoutStore.setPendingSlotSync(false) } export function syncNodeSlotLayoutsFromDOM( diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 796965b27..b5ac60349 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -5,6 +5,8 @@ import { reactive, unref } from 'vue' import { shallowRef } from 'vue' import { useCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion' +import { layoutStore } from '@/renderer/core/layout/store/layoutStore' +import { flushScheduledSlotLayoutSync } from '@/renderer/extensions/vueNodes/composables/useSlotElementTracking' import { registerProxyWidgets } from '@/core/graph/subgraph/proxyWidget' import { st, t } from '@/i18n' import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces' @@ -730,6 +732,11 @@ export class ComfyApp { private addAfterConfigureHandler(graph: LGraph) { const { onConfigure } = graph graph.onConfigure = function (...args) { + // Set pending sync flag to suppress link rendering until slots are synced + if (LiteGraph.vueNodesMode) { + layoutStore.setPendingSlotSync(true) + } + fixLinkInputSlots(this) // Fire callbacks before the onConfigure, this is used by widget inputs to setup the config @@ -740,6 +747,12 @@ export class ComfyApp { // Fire after onConfigure, used by primitives to generate widget using input nodes config triggerCallbackOnAllNodes(this, 'onAfterGraphConfigured') + // Flush pending slot layout syncs to fix link alignment after undo/redo + if (LiteGraph.vueNodesMode) { + flushScheduledSlotLayoutSync() + app.canvas?.setDirty(true, true) + } + return r } }