fix(subgraph): isolate duplicated host widget values via host-only hydration

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 <amp@ampcode.com>
This commit is contained in:
DrJKL
2026-05-09 14:07:30 -07:00
parent 3cfd4d0401
commit 263879a838
3 changed files with 25 additions and 3 deletions

View File

@@ -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(

View File

@@ -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).

View File

@@ -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
}
}