Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
6 changes: 6 additions & 0 deletions src/lib/litegraph/src/LGraphCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5614,6 +5614,12 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
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) {
this.#visibleReroutes.clear()
return
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
const { graph, subgraph } = this
if (!graph) throw new NullGraphError()

Expand Down
14 changes: 14 additions & 0 deletions src/renderer/core/layout/store/layoutStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,26 @@ const raf = createRafBatch(() => {

function scheduleSlotLayoutSync(nodeId: string) {
pendingNodes.add(nodeId)
// Re-assert pending flag for late mounts (Vue components mounting after
// flushScheduledSlotLayoutSync was called synchronously in onConfigure)
layoutStore.setPendingSlotSync(true)
raf.schedule()
}

function flushScheduledSlotLayoutSync() {
if (pendingNodes.size === 0) return
export function flushScheduledSlotLayoutSync() {
if (pendingNodes.size === 0) {
// No pending nodes - don't clear the flag here as late mounts may still
// call scheduleSlotLayoutSync. The RAF callback will clear it after
// processing actual nodes.
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)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export function syncNodeSlotLayoutsFromDOM(
Expand Down
30 changes: 23 additions & 7 deletions src/scripts/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -730,17 +732,31 @@ export class ComfyApp {
private addAfterConfigureHandler(graph: LGraph) {
const { onConfigure } = graph
graph.onConfigure = function (...args) {
fixLinkInputSlots(this)
// Set pending sync flag to suppress link rendering until slots are synced

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential stuck state: if onConfigure (or callbacks it triggers) throws, pendingSlotSync is never cleared because the flush/clear logic is after the call. That can permanently suppress link rendering. Suggest wrapping the handler body in try/finally and clearing the flag in finally (or at least in a catch) so links don’t stay hidden on error paths.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit c2d548b - wrapped the onConfigure handler body in try/finally to ensure the flush runs even if an error is thrown.

if (LiteGraph.vueNodesMode) {
layoutStore.setPendingSlotSync(true)
}

try {
fixLinkInputSlots(this)

// Fire callbacks before the onConfigure, this is used by widget inputs to setup the config
triggerCallbackOnAllNodes(this, 'onGraphConfigured')
// Fire callbacks before the onConfigure, this is used by widget inputs to setup the config
triggerCallbackOnAllNodes(this, 'onGraphConfigured')

const r = onConfigure?.apply(this, args)
const r = onConfigure?.apply(this, args)

// Fire after onConfigure, used by primitives to generate widget using input nodes config
triggerCallbackOnAllNodes(this, 'onAfterGraphConfigured')
// Fire after onConfigure, used by primitives to generate widget using input nodes config
triggerCallbackOnAllNodes(this, 'onAfterGraphConfigured')

return r
return r
} finally {
// Flush pending slot layout syncs to fix link alignment after undo/redo
// Using finally ensures links aren't permanently suppressed if an error occurs
if (LiteGraph.vueNodesMode) {
flushScheduledSlotLayoutSync()
app.canvas?.setDirty(true, true)
}
}
}
}

Expand Down
Loading