Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 @@ -12,6 +12,7 @@ import { useSharedCanvasPositionConversion } from '@/composables/element/useCanv
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { app } from '@/scripts/app'

@coderabbitai coderabbitai Bot Jan 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Resolve the @/scripts/app import or lint will fail.

ESLint flags import-x/no-unresolved for this path. Please confirm the alias
resolver includes src/scripts/app.ts, or adjust the import to a resolvable
path.

🧰 Tools
🪛 ESLint

[error] 15-15: Unable to resolve path to module '@/scripts/app'.

(import-x/no-unresolved)

🤖 Prompt for AI Agents
In `@src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts` at
line 15, The import "import { app } from '@/scripts/app'" in
useSlotElementTracking.ts is causing ESLint import-x/no-unresolved; either
update the project alias resolver to map "@" to the src directory including
src/scripts/app.ts, or change the import to a resolvable path (e.g., relative
import to the actual file) so the symbol app in useSlotElementTracking.ts
resolves; make the change where the import appears (useSlotElementTracking.ts)
or in the build/ESLint alias config so linting passes.

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.

This is a false positive - the @/scripts/app import resolves correctly and lint passes locally with no errors. The project's alias resolver includes this path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@christian-byrne, thank you for the clarification! I understand the import resolves correctly in your environment and the alias configuration is properly set up. I'll disregard this ESLint report.


🧠 Learnings used
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.{vue,ts} : Leverage VueUse functions for performance-enhancing styles

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-28T02:00:53.680Z
Learning: Applies to **/*.vue : Define slots via template usage, not `defineSlots`

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-28T02:00:53.680Z
Learning: Applies to **/*.{js,ts,tsx,vue} : Unused imports are disallowed and must be removed

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-01-27T20:51:47.936Z
Learning: Applies to src/**/*.{ts,tsx,vue} : Avoid `ts-expect-error` - fix the underlying type issue instead

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-28T02:00:53.680Z
Learning: Applies to **/*.{ts,tsx,vue} : Never use `as any` type assertions; fix the underlying type issue instead

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-28T02:00:53.680Z
Learning: Applies to eslint.config.ts : Configure ESLint with Vue + TS rules, no floating promises, and unused imports disallowed

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/lib/litegraph/AGENTS.md:0-0
Timestamp: 2026-01-28T02:01:01.525Z
Learning: Applies to src/lib/litegraph/**/*.{ts,tsx} : Always import from the barrel export for subgraph code to avoid circular dependencies (import from '@/lib/litegraph/src/litegraph' instead of individual modules like '@/lib/litegraph/src/LGraph')

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-01-27T20:51:47.936Z
Learning: Applies to src/**/*.{js,ts,vue} : Implement proper error propagation

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-01-27T20:51:47.936Z
Learning: Applies to src/**/*.{js,ts,vue} : Validate trusted sources before processing

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7416
File: src/stores/imagePreviewStore.ts:5-7
Timestamp: 2025-12-13T11:03:21.073Z
Learning: In the Comfy-Org/ComfyUI_frontend repository, linting rules enforce keeping `import type` statements separate from regular `import` statements, even when importing from the same module. Do not suggest consolidating them into a single import statement.

import type { SlotLayout } from '@/renderer/core/layout/types'
import {
isPointEqual,
Expand All @@ -28,16 +29,29 @@ 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 and no RAF scheduled - clear the flag to avoid
// permanently suppressing link rendering (e.g., workflows without Vue nodes)
layoutStore.setPendingSlotSync(false)
app.canvas?.setDirty(true, true)
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)
// Trigger canvas redraw now that links can render with correct positions
app.canvas?.setDirty(true, true)
}
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