Skip to content

Commit c1a5692

Browse files
fix: skip MatchType recalculation during graph configuration (#9004)
## Summary Fixes COM-14955: "Bug: Switch node in subgraph causes link disconnection on export" ## Problem When a MatchType node (like Switch) inside a subgraph is configured/restored, `LGraphNode.configure()` calls `onConnectionsChange` for each input sequentially. The `withComfyMatchType` callback was running before all links were restored, seeing incomplete state and incorrectly computing types, which could cause link disconnection. ## Solution Add early return when `app.configuringGraph` is true to defer type recalculation until after all links are restored. This pattern is already used throughout the codebase: - `widgetInputs.ts` - `rerouteNode.ts` - `customWidgets.ts` Post-configure recomputation is handled by the existing `requestAnimationFrame` callback in `applyMatchType`. ## Changes - `src/core/graph/widgets/dynamicWidgets.ts` - Added 1 line: `if (app.configuringGraph) return` - `src/core/graph/widgets/matchTypeConfiguring.test.ts` - New test file with 3 tests ## Testing - All existing tests pass - Added 3 new tests: - `skips type recalculation when configuringGraph is true` - `performs type recalculation during normal operation` - `connects both inputs with same type` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9004-fix-skip-MatchType-recalculation-during-graph-configuration-30d6d73d365081339088ffd8aebba107) by [Unito](https://www.unito.io)
1 parent 2b69d7b commit c1a5692

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

src/core/graph/widgets/dynamicWidgets.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ function withComfyMatchType(node: LGraphNode): asserts node is MatchTypeNode {
277277
) {
278278
const input = this.inputs[slot]
279279
if (contype !== LiteGraph.INPUT || !this.graph || !input) return
280+
if (app.configuringGraph) return
280281
const [matchKey, matchGroup] = Object.entries(
281282
this.comfyDynamic.matchType
282283
).find(([, group]) => input.name in group) ?? ['', undefined]
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { createTestingPinia } from '@pinia/testing'
2+
import { setActivePinia } from 'pinia'
3+
import { beforeEach, describe, expect, test, vi } from 'vitest'
4+
5+
import { LGraph, LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
6+
import { transformInputSpecV1ToV2 } from '@/schemas/nodeDef/migration'
7+
import { app } from '@/scripts/app'
8+
import { useLitegraphService } from '@/services/litegraphService'
9+
10+
setActivePinia(createTestingPinia())
11+
12+
const { addNodeInput } = useLitegraphService()
13+
14+
function createMatchTypeNode(graph: LGraph) {
15+
const node = new LGraphNode('switch')
16+
;(node.constructor as { nodeData: unknown }).nodeData = {
17+
name: 'ComfySwitchAny',
18+
output_matchtypes: ['a']
19+
}
20+
node.addOutput('out', '*')
21+
graph.add(node)
22+
23+
addNodeInput(
24+
node,
25+
transformInputSpecV1ToV2(
26+
[
27+
'COMFY_MATCHTYPE_V3',
28+
{ template: { allowed_types: '*', template_id: 'a' } }
29+
],
30+
{ name: 'on_true', isOptional: false }
31+
)
32+
)
33+
addNodeInput(
34+
node,
35+
transformInputSpecV1ToV2(
36+
[
37+
'COMFY_MATCHTYPE_V3',
38+
{ template: { allowed_types: '*', template_id: 'a' } }
39+
],
40+
{ name: 'on_false', isOptional: false }
41+
)
42+
)
43+
44+
return node
45+
}
46+
47+
function createSourceNode(graph: LGraph, type: string) {
48+
const node = new LGraphNode('source')
49+
node.addOutput('out', type)
50+
graph.add(node)
51+
return node
52+
}
53+
54+
describe('MatchType during configure', () => {
55+
beforeEach(() => {
56+
vi.clearAllMocks()
57+
})
58+
59+
test('skips type recalculation when configuringGraph is true', () => {
60+
const graph = new LGraph()
61+
const switchNode = createMatchTypeNode(graph)
62+
const source1 = createSourceNode(graph, 'IMAGE')
63+
const source2 = createSourceNode(graph, 'IMAGE')
64+
65+
source1.connect(0, switchNode, 0)
66+
source2.connect(0, switchNode, 1)
67+
68+
expect(switchNode.inputs[0].link).not.toBeNull()
69+
expect(switchNode.inputs[1].link).not.toBeNull()
70+
71+
const link1Id = switchNode.inputs[0].link!
72+
const link2Id = switchNode.inputs[1].link!
73+
74+
const outputTypeBefore = switchNode.outputs[0].type
75+
;(
76+
app as unknown as { configuringGraphLevel: number }
77+
).configuringGraphLevel = 1
78+
79+
try {
80+
const link1 = graph.links[link1Id]
81+
switchNode.onConnectionsChange?.(
82+
LiteGraph.INPUT,
83+
0,
84+
true,
85+
link1,
86+
switchNode.inputs[0]
87+
)
88+
89+
expect(switchNode.inputs[0].link).toBe(link1Id)
90+
expect(switchNode.inputs[1].link).toBe(link2Id)
91+
expect(graph.links[link1Id]).toBeDefined()
92+
expect(graph.links[link2Id]).toBeDefined()
93+
expect(switchNode.outputs[0].type).toBe(outputTypeBefore)
94+
} finally {
95+
;(
96+
app as unknown as { configuringGraphLevel: number }
97+
).configuringGraphLevel = 0
98+
}
99+
})
100+
101+
test('performs type recalculation during normal operation', () => {
102+
const graph = new LGraph()
103+
const switchNode = createMatchTypeNode(graph)
104+
const source1 = createSourceNode(graph, 'IMAGE')
105+
106+
expect(app.configuringGraph).toBe(false)
107+
108+
source1.connect(0, switchNode, 0)
109+
110+
expect(switchNode.inputs[0].link).not.toBeNull()
111+
expect(switchNode.outputs[0].type).toBe('IMAGE')
112+
})
113+
114+
test('connects both inputs with same type', () => {
115+
const graph = new LGraph()
116+
const switchNode = createMatchTypeNode(graph)
117+
const source1 = createSourceNode(graph, 'IMAGE')
118+
const source2 = createSourceNode(graph, 'IMAGE')
119+
120+
expect(app.configuringGraph).toBe(false)
121+
122+
source1.connect(0, switchNode, 0)
123+
source2.connect(0, switchNode, 1)
124+
125+
expect(switchNode.inputs[0].link).not.toBeNull()
126+
expect(switchNode.inputs[1].link).not.toBeNull()
127+
expect(switchNode.outputs[0].type).toBe('IMAGE')
128+
})
129+
})

0 commit comments

Comments
 (0)