mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-22 21:38:52 +00:00
Wraps `LGraphNode.configure()`'s widget-value restoration phase in a
hydration transaction (`beginHydration` / `commitHydration`), preventing
derived-state callbacks (e.g. CustomCombo's `updateCombo`) from firing
before all widget values are restored.
```mermaid
flowchart LR
subgraph core["Core Layer"]
LGN["LGraphNode.configure()"]
WVS["widgetValueStore"]
end
subgraph extensions["Extension Layer"]
CW["CustomCombo\n(customWidgets.ts)"]
PN["PrimitiveNode\n(widgetInputs.ts)"]
end
LGN -- "beginHydration /\ncommitHydration" --> WVS
CW -- "isHydrating() guard" --> WVS
CW -- "onHydrationComplete()" --> WVS
PN -- "serialize() override\npreserves widgets_values" --> LGN
```
```mermaid
sequenceDiagram
participant Caller as Paste / Load
participant Node as LGraphNode.configure()
participant Store as widgetValueStore
participant Widget as Widget Setters
participant Combo as CustomCombo.updateCombo
Caller->>Node: configure(serializedData)
Node->>Store: beginHydration(nodeId)
activate Store
Note over Store: hydratingNodes.add(nodeId)
loop For each widget
Node->>Widget: widget.value = restored_value
Widget->>Combo: updateCombo() triggered
Combo->>Store: isHydrating(nodeId)?
Store-->>Combo: true - early return
Note over Combo: Side effect suppressed
end
Node->>Node: onConfigure(info)
Note over Node: Extensions register onHydrationComplete callbacks
Node->>Store: commitHydration(nodeId)
Note over Store: hydratingNodes.delete(nodeId)
Store->>Combo: fire queued callbacks
Note over Combo: updateCombo() runs once with all values present
deactivate Store
```
```mermaid
flowchart TB
subgraph before["Before: Race Condition"]
direction TB
B1["configure starts"] --> B2["widget1.value = optionA"]
B2 --> B3["updateCombo fires immediately"]
B3 --> B4["values list incomplete,\ncomboWidget.value reset"]
B4 --> B5["widget2.value = optionB"]
B5 --> B6["updateCombo fires again"]
B6 --> B7["Combo has wrong value"]
end
subgraph after["After: Hydration Transaction"]
direction TB
A1["configure starts"] --> A2["beginHydration nodeId"]
A2 --> A3["widget1.value = optionA"]
A3 --> A4["updateCombo skipped"]
A4 --> A5["widget2.value = optionB"]
A5 --> A6["updateCombo skipped"]
A6 --> A7["commitHydration nodeId"]
A7 --> A8["updateCombo runs once,\nall values present"]
end
```
- **`LGraphNode.configure()`**: Wraps widget restoration + `onConfigure`
in `beginHydration`/`commitHydration` with `try/finally` for exception
safety
- **`PrimitiveNode.serialize()`**: Adds override to preserve
`widgets_values` when widgets are dynamically disconnected
- **`customWidgets.ts`**: Simplifies CustomCombo's `onConfigure` to use
`onHydrationComplete()` instead of manually managing hydration state
- **3 new tests**: Hydration transaction wrapping, `onHydrationComplete`
callback firing, and exception safety
Stacked on #10010. Implements Design A from the widget state
architecture RFC.