From 263879a838d88affd7f09e4fcfc8b60e3e4f29c2 Mon Sep 17 00:00:00 2001 From: DrJKL Date: Sat, 9 May 2026 14:07:30 -0700 Subject: [PATCH] fix(subgraph): isolate duplicated host widget values via host-only hydration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloning a SubgraphNode triggers serialize -> create -> configure on the new instance. The previous _applyPromotedWidgetValues went through PromotedWidgetView.set value, which cascades into getLinkedInputWidgets and resolveAtHost?.widget.value writes — stomping the shared interior widget state across every other SubgraphNode instance referencing the same shared interior. The DOM widget then races for ownership and the duplicated subgraph's textarea becomes unreachable. Add PromotedWidgetView.hydrateHostValue(value) that writes only to the host's widget value store entry (keyed on subgraphNode.id), and rewrite _applyPromotedWidgetValues to call it. Per-instance values stay isolated; interior widget state is no longer touched by the configure round-trip. Amp-Thread-ID: https://ampcode.com/threads/T-019e0e0d-1937-758e-8a9b-4f54716b0aa2 Co-authored-by: Amp --- src/core/graph/subgraph/promotedWidgetTypes.ts | 6 ++++++ src/core/graph/subgraph/promotedWidgetView.ts | 10 ++++++++++ src/lib/litegraph/src/subgraph/SubgraphNode.ts | 12 +++++++++--- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/core/graph/subgraph/promotedWidgetTypes.ts b/src/core/graph/subgraph/promotedWidgetTypes.ts index b68e177438..637e367695 100644 --- a/src/core/graph/subgraph/promotedWidgetTypes.ts +++ b/src/core/graph/subgraph/promotedWidgetTypes.ts @@ -31,6 +31,12 @@ export interface PromotedWidgetView extends IBaseWidget { */ readonly sourceNodeId: string readonly sourceWidgetName: string + + /** + * Per-instance value hydration that writes only to host widget state, never + * cascading into the shared interior widget. Used during configure/clone. + */ + hydrateHostValue(value: IBaseWidget['value']): void } export function isPromotedWidgetView( diff --git a/src/core/graph/subgraph/promotedWidgetView.ts b/src/core/graph/subgraph/promotedWidgetView.ts index 9ddec2df21..e30d6492d6 100644 --- a/src/core/graph/subgraph/promotedWidgetView.ts +++ b/src/core/graph/subgraph/promotedWidgetView.ts @@ -268,6 +268,16 @@ class PromotedWidgetView implements IPromotedWidgetView { if (state) state.label = value } + /** + * Write a value into this host's widget store entry without cascading into + * the shared interior widget — the only safe path for per-instance hydration + * during `configure()` and clone, where multiple SubgraphNode instances + * reference the same shared interior nodes. + */ + hydrateHostValue(value: IBaseWidget['value']): void { + this.setHostWidgetState(value) + } + /** * Returns the cached bound subgraph slot reference, refreshing only when * the subgraph node's input list has changed (length mismatch). diff --git a/src/lib/litegraph/src/subgraph/SubgraphNode.ts b/src/lib/litegraph/src/subgraph/SubgraphNode.ts index 482577cc28..aad7f9e22d 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphNode.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphNode.ts @@ -706,6 +706,12 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { this._applyPromotedWidgetValues(info.widgets_values) } + /** + * Hydrate per-instance promoted widget values into this host's widget value + * store entry. Routing through `PromotedWidgetView.set value` would cascade + * into the shared interior widget, stomping every other SubgraphNode + * instance that references the same shared interior. + */ private _applyPromotedWidgetValues( widgetValues: ExportedSubgraphInstance['widgets_values'] ): void { @@ -713,10 +719,10 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { let valueIndex = 0 for (const input of this.inputs) { - const widget = input._widget - if (!widget || !isPromotedWidgetView(widget)) continue + const view = input._widget + if (!view || !isPromotedWidgetView(view)) continue if (valueIndex >= widgetValues.length) return - widget.value = widgetValues[valueIndex] + view.hydrateHostValue(widgetValues[valueIndex]) valueIndex += 1 } }