From 8283438ee67172953696e8c41984bbf4244af488 Mon Sep 17 00:00:00 2001 From: AustinMroz Date: Thu, 5 Feb 2026 21:27:11 -0800 Subject: [PATCH] Fix incorrect widgetValue migration (#8625) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under a combination of many edge cases, the `widget_values` migration code added in #3326 would cause the progress text on a "Recraft Text to Image" node to incorrectly deserialize into the `control_after_generate` - widgets_values is of length 1 greater than it should be because progress text serializes - It should not, there is no code to deserialize it - negative_prompt has force_input set and skips serialization - Migration only applies when `widgets_values` is equal to actual inputs length. The two above edge cases cancel to make this true - Seed is accounted for when calculating the length of widgets, but not when applying the migration - Migration occurs even though we track workflow version now and have an accurate way of determining that it can not be needed The two primary edge cases which cause the bug are both addressed - `options.serialize` does nothing and has never done anything. I've been guilty of making the same mistake in the ancient past, and want to clean up the misconception where I can. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8625-Fix-incorrect-widgetValue-migration-2fe6d73d365081a683b4c675eaeebb6c) by [Unito](https://www.unito.io) --- .../composables/useProgressTextWidget.ts | 2 +- src/utils/litegraphUtil.test.ts | 24 +++++++++++++++++++ src/utils/litegraphUtil.ts | 24 +++++++------------ 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useProgressTextWidget.ts b/src/renderer/extensions/vueNodes/widgets/composables/useProgressTextWidget.ts index d3e412b85c..c73d9bbd32 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useProgressTextWidget.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useProgressTextWidget.ts @@ -42,11 +42,11 @@ export function useTextPreviewWidget( widgetValue.value = typeof value === 'string' ? value : String(value) }, getMinHeight: () => options.minHeight ?? 42 + PADDING, - serialize: false, read_only: true }, type: inputSpec.type }) + widget.serialize = false addWidget(node, widget) return widget } diff --git a/src/utils/litegraphUtil.test.ts b/src/utils/litegraphUtil.test.ts index 50a6ef8217..4640bd4b5c 100644 --- a/src/utils/litegraphUtil.test.ts +++ b/src/utils/litegraphUtil.test.ts @@ -89,6 +89,30 @@ describe('migrateWidgetsValues', () => { const result = migrateWidgetsValues(inputDefs, widgets, widgetValues) expect(result).toEqual(['first value', 'last value']) }) + it('should correctly handle seed with unexpected value', () => { + const inputDefs: Record = { + normalInput: { + type: 'INT', + name: 'normalInput', + control_after_generate: true + }, + forceInputField: { + type: 'STRING', + name: 'forceInputField', + forceInput: true + } + } + + const widgets = [ + { name: 'normalInput', type: 'number' }, + { name: 'control_after_generate', type: 'string' } + ] as Partial[] as IWidget[] + + const widgetValues = [42, 'fixed', 'unexpected widget value'] + + const result = migrateWidgetsValues(inputDefs, widgets, widgetValues) + expect(result).toEqual([42, 'fixed']) + }) }) describe('compressWidgetInputSlots', () => { diff --git a/src/utils/litegraphUtil.ts b/src/utils/litegraphUtil.ts index 898b3ef237..c5f9d49fd9 100644 --- a/src/utils/litegraphUtil.ts +++ b/src/utils/litegraphUtil.ts @@ -112,23 +112,17 @@ export function migrateWidgetsValues( const originalWidgetsInputs = Object.values(inputDefs).filter( (input) => widgetNames.has(input.name) || input.forceInput ) - // Count the number of original widgets inputs. - const numOriginalWidgets = _.sum( - originalWidgetsInputs.map((input) => - // If the input has control, it will have 2 widgets. - input.control_after_generate || - ['seed', 'noise_seed'].includes(input.name) - ? 2 - : 1 - ) + + const widgetIndexHasForceInput = originalWidgetsInputs.flatMap((input) => + input.control_after_generate + ? [!!input.forceInput, false] + : [!!input.forceInput] ) - if (numOriginalWidgets === widgetsValues?.length) { - return _.zip(originalWidgetsInputs, widgetsValues) - .filter(([input]) => !input?.forceInput) - .map(([_, value]) => value as TWidgetValue) - } - return widgetsValues + if (widgetIndexHasForceInput.length !== widgetsValues?.length) + return widgetsValues + + return widgetsValues.filter((_, index) => !widgetIndexHasForceInput[index]) } /**