[backport core/1.42] fix: prune orphaned SubgraphNode inputs after configure (#10149)

Backport of #10020 to `core/1.42`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10149-backport-core-1-42-fix-prune-orphaned-SubgraphNode-inputs-after-configure-3266d73d365081928c8ec29acc651e44)
by [Unito](https://www.unito.io)

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
This commit is contained in:
Comfy Org PR Bot
2026-03-17 17:24:10 +09:00
committed by GitHub
parent 608ca657c4
commit dc235380d4
2 changed files with 86 additions and 2 deletions

View File

@@ -9,9 +9,9 @@ import { describe, expect, it, vi } from 'vitest'
import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import type { SubgraphNode } from '@/lib/litegraph/src/litegraph'
import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph'
import { LGraph, Subgraph, SubgraphNode } from '@/lib/litegraph/src/litegraph'
import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput'
import type { ExportedSubgraphInstance } from '@/lib/litegraph/src/types/serialisation'
import { subgraphTest } from './__fixtures__/subgraphFixtures'
import {
@@ -618,6 +618,86 @@ describe.skip('SubgraphNode Cleanup', () => {
})
})
describe('SubgraphNode duplicate input pruning (#9977)', () => {
it('should prune inputs that have no matching subgraph slot after configure', () => {
setActivePinia(createTestingPinia({ stubActions: false }))
const subgraph = createTestSubgraph({
inputs: [
{ name: 'a', type: 'STRING' },
{ name: 'b', type: 'NUMBER' }
]
})
const parentGraph = new LGraph()
const instanceData = {
id: 1 as const,
type: subgraph.id,
pos: [0, 0] as [number, number],
size: [200, 100] as [number, number],
inputs: [
{ name: 'a', type: 'STRING', link: null },
{ name: 'b', type: 'NUMBER', link: null },
{ name: 'a', type: 'STRING', link: null },
{ name: 'b', type: 'NUMBER', link: null }
],
outputs: [],
properties: {},
flags: {},
mode: 0,
order: 0
}
const node = new SubgraphNode(
parentGraph,
subgraph,
instanceData as ExportedSubgraphInstance
)
expect(node.inputs).toHaveLength(2)
expect(node.inputs.every((i) => i._subgraphSlot)).toBe(true)
})
it('should not accumulate duplicate inputs on reconfigure', () => {
setActivePinia(createTestingPinia({ stubActions: false }))
const subgraph = createTestSubgraph({
inputs: [
{ name: 'a', type: 'STRING' },
{ name: 'b', type: 'NUMBER' }
]
})
const node = createTestSubgraphNode(subgraph)
expect(node.inputs).toHaveLength(2)
const serialized = node.serialize()
node.configure(serialized)
expect(node.inputs).toHaveLength(2)
const serialized2 = node.serialize()
node.configure(serialized2)
expect(node.inputs).toHaveLength(2)
})
it('should serialize with exactly the subgraph-defined inputs', () => {
setActivePinia(createTestingPinia({ stubActions: false }))
const subgraph = createTestSubgraph({
inputs: [
{ name: 'x', type: 'IMAGE' },
{ name: 'y', type: 'VAE' }
]
})
const node = createTestSubgraphNode(subgraph)
const serialized = node.serialize()
expect(serialized.inputs).toHaveLength(2)
expect(serialized.inputs?.map((i) => i.name)).toEqual(['x', 'y'])
})
})
describe('SubgraphNode promotion view keys', () => {
it('distinguishes tuples that differ only by colon placement', () => {
setActivePinia(createTestingPinia({ stubActions: false }))

View File

@@ -926,6 +926,10 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
override _internalConfigureAfterSlots() {
this._rebindInputSubgraphSlots()
// Prune inputs that don't map to any subgraph slot definition.
// This prevents stale/duplicate serialized inputs from persisting (#9977).
this.inputs = this.inputs.filter((input) => input._subgraphSlot)
// Ensure proxyWidgets is initialized so it serializes
this.properties.proxyWidgets ??= []