Compare commits

...

4 Commits

Author SHA1 Message Date
bymyself
42500d452e refactor: wrap console.warn spy in try/finally for cleanup safety
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 15:23:23 -07:00
github-actions
98b7934aa5 [automated] Update test expectations 2026-05-14 21:44:04 +00:00
Lidang-Jiang
99b9e64e4c fix: update deprecation warning and add console.warn test
Address CodeRabbit review:
- Update defaultInput deprecation message to not recommend forceInput
- Add test verifying deprecation warning emission
2026-05-14 14:24:57 -07:00
Lidang-Jiang
e6f9570bf7 fix: stop converting defaultInput to forceInput on migration
The _migrateDefaultInput method set forceInput = true for every
optional input with defaultInput, preventing the widget from being
created. When users toggled a defaultInput socket back to a widget and
saved, the next reload re-applied forceInput, reverting it to a
socket-only input.

Since frontend 1.16+, widgets and sockets co-exist on every input, so
defaultInput no longer needs conversion to forceInput. Remove the
assignment while keeping the deprecation warning.

Fixes Comfy-Org/ComfyUI#1500
2026-05-14 14:24:57 -07:00
4 changed files with 162 additions and 7 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -1,4 +1,4 @@
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import { transformNodeDefV1ToV2 } from '@/schemas/nodeDef/migration'
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
@@ -537,4 +537,152 @@ describe('ComfyNodeDefImpl', () => {
expect(result.api_node).toBe(expected)
}
)
describe('defaultInput migration', () => {
it('should not set forceInput on optional input with defaultInput', () => {
const nodeDef = new ComfyNodeDefImpl({
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
optional: {
seed_override: ['INT', { defaultInput: true, default: 0 }]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1)
expect(nodeDef.inputs['seed_override']).toBeDefined()
expect(nodeDef.inputs['seed_override'].forceInput).toBeUndefined()
})
it('should preserve widget type on optional input with defaultInput', () => {
const nodeDef = new ComfyNodeDefImpl({
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
optional: {
my_param: [
'FLOAT',
{ defaultInput: true, default: 0.5, min: 0, max: 1 }
]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1)
const inputSpec = nodeDef.inputs['my_param']
expect(inputSpec.type).toBe('FLOAT')
expect(inputSpec.isOptional).toBe(true)
expect(inputSpec.default).toBe(0.5)
expect(inputSpec.forceInput).toBeUndefined()
})
it('should not affect required inputs with defaultInput', () => {
const nodeDef = new ComfyNodeDefImpl({
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
required: {
value: ['INT', { defaultInput: true, default: 42 }]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1)
expect(nodeDef.inputs['value']).toBeDefined()
expect(nodeDef.inputs['value'].forceInput).toBeUndefined()
})
it('should preserve explicit forceInput when set alongside defaultInput', () => {
const nodeDef = new ComfyNodeDefImpl({
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
optional: {
forced: ['INT', { forceInput: true, defaultInput: true }]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1)
expect(nodeDef.inputs['forced'].forceInput).toBe(true)
})
it('should emit deprecation warning for optional input with defaultInput', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
try {
new ComfyNodeDefImpl({
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
optional: {
seed: ['INT', { defaultInput: true }]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1)
expect(warnSpy).toHaveBeenCalledWith(
'Use of defaultInput on optional input test_module:TestNode:seed is deprecated and ignored. Remove defaultInput. Use forceInput only if you intentionally want a socket-only input.'
)
} finally {
warnSpy.mockRestore()
}
})
it('should not mutate the original node definition', () => {
const originalDef = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
optional: {
param: ['INT', { defaultInput: true }]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1
new ComfyNodeDefImpl(originalDef)
expect(
originalDef.input!.optional!['param'][1]!.forceInput
).toBeUndefined()
})
})
})

View File

@@ -103,7 +103,14 @@ export class ComfyNodeDefImpl
/**
* @internal
* Migrate default input options to forceInput.
* Migrate default input options. Since frontend version 1.16+, widget and
* input socket co-exist on every input, so `defaultInput` no longer needs
* special handling. We only emit deprecation warnings.
*
* Previously `defaultInput` on optional inputs was migrated to `forceInput`,
* which prevented the widget from being created at all and made it impossible
* for users to toggle back to widget mode after reload.
* See: https://github.com/Comfy-Org/ComfyUI_frontend/issues/1500
*/
private static _migrateDefaultInput(nodeDef: ComfyNodeDefV1): ComfyNodeDefV1 {
const def = _.cloneDeep(nodeDef)
@@ -118,16 +125,16 @@ export class ComfyNodeDefImpl
)
}
}
// For optional inputs, defaultInput is used to distinguish the null state.
// We migrate it to forceInput. One example is the "seed_override" input usage.
// User can connect the socket to override the seed.
// For optional inputs, defaultInput previously converted the widget into
// a socket-only input. Since 1.16+ widgets and sockets co-exist, we no
// longer need to set forceInput. The widget will be created alongside the
// socket, and users can choose how to provide the value.
for (const [name, spec] of Object.entries(def.input.optional ?? {})) {
const inputOptions = spec[1]
if (inputOptions && inputOptions.defaultInput) {
console.warn(
`Use of defaultInput on optional input ${nodeDef.python_module}:${nodeDef.name}:${name} is deprecated. Please use forceInput instead.`
`Use of defaultInput on optional input ${nodeDef.python_module}:${nodeDef.name}:${name} is deprecated and ignored. Remove defaultInput. Use forceInput only if you intentionally want a socket-only input.`
)
inputOptions.forceInput = true
}
}
return def