fix: prevent nested SubgraphNode input slots from doubling on reload (#10187)

## Summary

- Fix nested SubgraphNode input slots doubling on each page reload
- Root cause: during configure, `_configureSubgraph` recreates
`SubgraphInput` objects with new references, and the `input-added` event
handler used `===` identity check which failed for these new objects,
causing `addInput()` to duplicate inputs
- Add `id`-based fallback matching in the `input-added` handler and
rebind `_subgraphSlot` with re-registered listeners

## Changes

**`SubgraphNode.ts:614-622`**: Add UUID `id` fallback to the `===`
reference check in the `input-added` event handler. When a stale
reference is matched by id, call `_addSubgraphInputListeners()` to
update `_subgraphSlot` and re-register listeners on the new
`SubgraphInput` object.

**`SubgraphNode.test.ts`**: 2 regression tests for nested subgraph
reconfigure scenarios.

## Test plan

- [x] Existing SubgraphNode tests pass (6 passed, 34 skipped)
- [x] New tests verify inputs don't duplicate after single and repeated
reconfigure cycles
- [x] Manual: create a subgraph containing another subgraph node, save,
reload — input slots should remain unchanged

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10187-fix-prevent-nested-SubgraphNode-input-slots-from-doubling-on-reload-3266d73d3650817286abea52365a626e)
by [Unito](https://www.unito.io)
This commit is contained in:
jaeone94
2026-03-18 03:23:05 +09:00
committed by GitHub
parent 6a8e6ad254
commit 15442e7ff8
2 changed files with 55 additions and 1 deletions

View File

@@ -698,6 +698,55 @@ describe('SubgraphNode duplicate input pruning (#9977)', () => {
})
})
describe('Nested SubgraphNode duplicate input prevention', () => {
it('should not duplicate inputs when the referenced subgraph is reconfigured', () => {
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)
// Simulate what happens during nested subgraph configure:
// B.configure() calls _configureSubgraph(), which recreates SubgraphInput
// objects and dispatches 'input-added' events with new references.
const serialized = subgraph.asSerialisable()
subgraph.configure(serialized)
// The SubgraphNode's event listener should recognize existing inputs
// by ID and NOT add duplicates.
expect(node.inputs).toHaveLength(2)
expect(node.inputs.every((i) => i._subgraphSlot)).toBe(true)
})
it('should not accumulate inputs across multiple reconfigure cycles', () => {
setActivePinia(createTestingPinia({ stubActions: false }))
const subgraph = createTestSubgraph({
inputs: [
{ name: 'x', type: 'IMAGE' },
{ name: 'y', type: 'VAE' }
]
})
const node = createTestSubgraphNode(subgraph)
expect(node.inputs).toHaveLength(2)
for (let i = 0; i < 5; i++) {
const serialized = subgraph.asSerialisable()
subgraph.configure(serialized)
}
expect(node.inputs).toHaveLength(2)
expect(node.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

@@ -612,9 +612,14 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
const subgraphInput = e.detail.input
const { name, type } = subgraphInput
const existingInput = this.inputs.find(
(input) => input._subgraphSlot === subgraphInput
(input) =>
input._subgraphSlot === subgraphInput ||
(input._subgraphSlot && input._subgraphSlot.id === subgraphInput.id)
)
if (existingInput) {
// Rebind to the new SubgraphInput object and re-register listeners
// (configure recreates SubgraphInput objects with the same id)
this._addSubgraphInputListeners(subgraphInput, existingInput)
const linkId = subgraphInput.linkIds[0]
if (linkId === undefined) return