Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions src/lib/litegraph/src/LGraphCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5614,6 +5614,9 @@ 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) 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 @@ -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)

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.

Possible race condition: flushScheduledSlotLayoutSync() is called synchronously right after onConfigure. If Vue slot elements haven’t mounted yet, pendingNodes.size === 0 here, so this block clears pendingSlotSync and returns. That re‑enables link rendering before any DOM-based slot positions exist, which can reintroduce the misaligned-links bug under load. pendingNodes only becomes non‑empty when useSlotElementTracking runs in onMounted and calls scheduleSlotLayoutSync(). Consider only clearing the flag after a real flush (non-empty), or setting pendingSlotSync to true in scheduleSlotLayoutSync() so late mounts re‑assert it.

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 2e1845b - scheduleSlotLayoutSync() now re-asserts the pendingSlotSync flag for late-mounting Vue components, and we only clear the flag when there are actual nodes to process.

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)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export function syncNodeSlotLayoutsFromDOM(
Expand Down
13 changes: 13 additions & 0 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,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

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)
}

fixLinkInputSlots(this)

// Fire callbacks before the onConfigure, this is used by widget inputs to setup the config
Expand All @@ -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
}
}
Expand Down