fix: skip non-serializable source widgets in promoted view serialization

Add sourceSerialize getter to PromotedWidgetView that checks the
resolved source widget's serialize flag. SubgraphNode.serialize() and
configure now filter out views whose source widget has serialize=false,
preventing preview/audio/video widget values from being persisted.
This commit is contained in:
dante01yoon
2026-04-09 13:19:50 +09:00
parent c191a88d05
commit 3a83180770
4 changed files with 50 additions and 6 deletions

View File

@@ -24,6 +24,8 @@ export interface PromotedWidgetView extends IBaseWidget {
* origin.
*/
readonly disambiguatingSourceNodeId?: string
/** Whether the resolved source widget is workflow-persistent. */
readonly sourceSerialize: boolean
}
export function isPromotedWidgetView(

View File

@@ -77,6 +77,15 @@ class PromotedWidgetView implements IPromotedWidgetView {
readonly serialize = false
/**
* Whether the resolved source widget is workflow-persistent.
* Used by SubgraphNode.serialize to skip preview/audio/video widgets
* whose source sets serialize = false.
*/
get sourceSerialize(): boolean {
return this.resolveDeepest()?.widget.serialize !== false
}
last_y?: number
computedHeight?: number

View File

@@ -132,4 +132,35 @@ describe('SubgraphNode multi-instance widget isolation', () => {
expect(restoredWidget?.value).toBe(33)
expect(restoredWidget?.serializeValue?.(restoredInstance, 0)).toBe(33)
})
it('skips non-serializable source widgets during serialize', () => {
const subgraph = createTestSubgraph({
inputs: [{ name: 'value', type: 'number' }]
})
const { node, widget } = createNodeWithWidget('TestNode', 10)
subgraph.add(node)
subgraph.inputNode.slots[0].connect(node.inputs[0], node)
// Mark the source widget as non-persistent (e.g. preview widget)
widget.serialize = false
const instance = createTestSubgraphNode(subgraph, { id: 501 })
instance.configure({
id: 501,
type: subgraph.id,
pos: [100, 100],
size: [200, 100],
inputs: [],
outputs: [],
mode: 0,
order: 0,
flags: {},
properties: { proxyWidgets: [['-1', 'widget']] },
widgets_values: []
})
const serialized = instance.serialize()
expect(serialized.widgets_values).toBeUndefined()
})
})

View File

@@ -1138,15 +1138,16 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
store.promote(this.rootGraph.id, this.id, source)
}
// Restore per-instance promoted widget values from serialized widgets_values.
// LGraphNode.configure skips promoted widgets (serialize === false), so they
// must be applied here after the promoted views are created.
// Hydrate per-instance promoted widget values from serialized data.
// LGraphNode.configure skips promoted widgets (serialize === false on
// the view), so they must be applied here after promoted views exist.
// Only iterate serializable views to match what serialize() wrote.
if (this._pendingWidgetsValues) {
const views = this._getPromotedViews()
let i = 0
for (const view of views) {
if (!view.sourceSerialize) continue
if (i >= this._pendingWidgetsValues.length) break
// Use the setter which stores in instance Map AND syncs to inner node
view.value = this._pendingWidgetsValues[i++] as typeof view.value
}
this._pendingWidgetsValues = undefined
@@ -1611,8 +1612,9 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
const serialized = super.serialize()
const views = this._getPromotedViews()
if (views.length > 0) {
serialized.widgets_values = views.map((view) => {
const serializableViews = views.filter((view) => view.sourceSerialize)
if (serializableViews.length > 0) {
serialized.widgets_values = serializableViews.map((view) => {
const value = view.serializeValue
? view.serializeValue(this, -1)
: view.value