mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-17 01:07:56 +00:00
## Summary Migrates the `inputs[].link` mirror-based reactivity tracking to `linkStore` queries and deletes the slot-link reprojection machinery in `useGraphNodeManager`. ## Changes - **What**: `NodeSlots.linkedWidgetedInputs`, `usePartitionedBadges`' pricing pokes, and `trackNodePrice` now query `linkStore.isInputSlotConnected` (root graph id + unfiltered slot index). Deletes the `node:slot-links:changed` handler and its load-bearing comment, the emitter-less `node:slot-errors:changed` handler, `refreshNodeInputs`, and the node-removal refresh-all loop. Adds unified-mode NodeSlots tests (none existed) and a `trackNodePrice` reactivity test. Drafts `docs/architecture/node-data-store.md` for the next phase. - **Not removed**: the `node:slot-links:changed` emitters and trigger type entries, the `input.link` mirror itself, the computation-path `inp.link` reads in `useNodePricing.buildJsonataContext` (migrated in #13498 alongside the mirror deletion), and the `node:slot-label:changed` handler — those belong to the Slot extraction / nodeDataStore phases. ## Review Focus - The node-removal refresh-all loop deletion (own commit) — originally shipped as a regression fix; gated locally on the vue-nodes legacy widget e2e spec plus the full vueNodes suite (126 passed). - Pricing badges previously only re-evaluated for widgeted inputs (the trigger's gate); linkStore queries now also cover non-widgeted pricing inputs — a correctness improvement, not parity. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
146 lines
7.6 KiB
Markdown
146 lines
7.6 KiB
Markdown
# Node Data Store
|
|
|
|
Date: 2026-07-05
|
|
Status: Draft (design interview in progress; follow-up to the
|
|
[link topology store](link-topology-store.md) and
|
|
[reroute chain store](reroute-chain-store.md))
|
|
|
|
Design record for extracting the remaining Node-owned components into a
|
|
dedicated store per [ADR 0008](../adr/0008-entity-component-system.md),
|
|
eliminating the `VueNodeData` mirror and most of
|
|
`src/composables/graph/useGraphNodeManager.ts`.
|
|
|
|
## Decision 1: One store, one plain state object per node
|
|
|
|
`nodeDataStore` holds a single plain `NodeState` object per node,
|
|
registered by reference with proxy-returning registration (the
|
|
`BaseWidget` pattern, [reroute store Decision 4](reroute-chain-store.md)).
|
|
ADR 0008's Node component rows (`NodeVisual`, `Execution`, ...) become
|
|
field groupings inside `NodeState`, not separate records or stores.
|
|
|
|
Buckets are root-graph-scoped (`rootGraph.id`), keyed by `NodeId`.
|
|
Node-id uniqueness across sibling subgraph definitions is already
|
|
guaranteed by the load-time dedup pass
|
|
(`src/lib/litegraph/src/subgraph/subgraphDeduplication.ts`).
|
|
|
|
## Decision 2: Field set — what is NodeState, what is elsewhere
|
|
|
|
```
|
|
NodeState {
|
|
id: NodeId
|
|
graphId: UUID // owning (sub)graph — partitioning + locator ids
|
|
type: string // identity, with apiNode?: boolean
|
|
title: string
|
|
titleMode?: TitleMode
|
|
mode: number
|
|
flags: { collapsed?, pinned?, ghost? }
|
|
color?: string
|
|
bgcolor?: string
|
|
shape?: number
|
|
resizable?: boolean
|
|
showAdvanced?: boolean
|
|
}
|
|
```
|
|
|
|
Excluded — owned or derived elsewhere; referencing them here would be a
|
|
mirror (the hard constraint of this phase):
|
|
|
|
| Field | Owner |
|
|
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| `selected` | `canvasStore.selectedNodeIds` (already what `LGraphNode.vue` reads) |
|
|
| `executing` | `executionStore` via `useNodeExecutionState` (already what Vue reads) |
|
|
| `hasErrors` | derived from `executionErrorStore` / missing-model/media stores; `node.has_errors` stays a legacy-canvas class field written by `useNodeErrorFlagSync` |
|
|
| position / size / z | `layoutStore` |
|
|
| widget values / order | `widgetValueStore` |
|
|
| input link connectivity | `linkStore` (`getInputSlotLink` / `isInputSlotConnected`) |
|
|
| `badges` | `nodeBadgeStore` — plain `BadgeData` rows written by a badge system; see [Node Badge Store](node-badge-store.md) |
|
|
| `inputs` / `outputs` | deferred — see Decision 3 |
|
|
|
|
`VueNodeData.selected` and `.executing` are dead fields today (no
|
|
production consumer reads them); they are deleted, not migrated.
|
|
|
|
## Decision 3: Slot arrays deferred; `inputs[].link` readers migrate now
|
|
|
|
`NodeInputSlot` / `NodeOutputSlot` are class instances with methods —
|
|
Slot entity extraction (ADR 0008 `SlotIdentity` etc.) is its own future
|
|
phase. The slot arrays stay class-side, keeping the `shallowReactive`
|
|
graft for renderer reactivity.
|
|
|
|
What this phase does remove is the last `inputs[].link` dependency: the
|
|
three remaining readers (`nodeDataUtils.linkedWidgetedInputs` used by
|
|
`NodeSlots`, and `usePartitionedBadges`' badge computed plus its
|
|
exported `trackNodePrice`) move to `linkStore.isInputSlotConnected`
|
|
queries — presence is all any of them needed — which deletes the
|
|
`node:slot-links:changed` → `refreshNodeInputs` reprojection in
|
|
`useGraphNodeManager`, the dead `node:slot-errors:changed` handler
|
|
(zero emitters repo-wide), and the node-removal refresh-all loop.
|
|
With their last listeners gone, both trigger actions are deleted
|
|
outright — emitters, event-map entries, and types (the badge system
|
|
sources connectivity from `linkStore`, not events; resurrect from git
|
|
if a consumer ever materialises). Readers get the root graph id from
|
|
`canvasStore.rootGraphId`, the shared tracked accessor, rather than
|
|
per-site `canvas?.graph?.rootGraph.id` chains.
|
|
Shipped ahead of the store itself (2026-07-05).
|
|
|
|
## Decision 4: Renderer consumes the NodeState proxy, `VueNodeData` dies
|
|
|
|
`GraphCanvas` iterates the store's bucket for the active graph (filtered
|
|
by `NodeState.graphId`) and passes the reactive `NodeState` proxy down
|
|
the existing prop-drilling path (`LGraphNode` → `NodeHeader` /
|
|
`NodeSlots` / `NodeContent` / `NodeWidgets`). Children read proxy fields
|
|
directly; Vue tracks the store state, so the per-property
|
|
`node:property:changed` → snapshot-rewrite handlers in
|
|
`useGraphNodeManager` are deleted wholesale.
|
|
|
|
Slot arrays reach `NodeSlots` via the existing live-node access
|
|
(`getNodeByLocatorId`), not through `NodeState`.
|
|
|
|
`LGraphNodePreview` constructs a synthetic `NodeState` (as it does a
|
|
synthetic `VueNodeData` today). `AppModeWidgetList` stops calling
|
|
`extractVueNodeData` and reads the registered `NodeState` + live node.
|
|
|
|
## Decision 5: Registration lifecycle and class adoption
|
|
|
|
Follows the shipped trio convention (`LLink` / `Reroute`):
|
|
|
|
- `LGraphNode` constructs its `_state: NodeState` at instantiation;
|
|
`registerNodeState(graph, node)` inserts it by reference and the class
|
|
adopts the returned reactive proxy; `node._graphId` (root id) is the
|
|
registration-ownership marker.
|
|
- Chokepoints: `LGraph.add` / `LGraph.remove` (the canonical sites),
|
|
`unregisterAllNodeStates(graph)` on graph `clear()`, identity-checked
|
|
delete (`toRaw` compare) so only the registered state vacates its key.
|
|
- Class fields become accessors reading through `_state`.
|
|
`LGraphNodeProperties`' instrumented descriptors keep their
|
|
get/set + `node:property:changed` emission but store the value in
|
|
`_state` instead of a closure — trigger consumers (minimap,
|
|
`useErrorClearingHooks`) keep working unchanged.
|
|
- Serialization is unaffected: `serialize()` reads the same properties
|
|
through the accessors.
|
|
|
|
## Decision 6: What remains of useGraphNodeManager
|
|
|
|
Deleted: `extractVueNodeData`, the `vueNodeData` map, all
|
|
`node:property:changed` handlers, `syncWithGraph`, `getNode()`
|
|
(consumers use `graph.getNodeById`), the `node:slot-links:changed`
|
|
handler (Decision 3).
|
|
|
|
Remaining renderer-side lifecycle, slimmed into `useVueNodeLifecycle`
|
|
(or a small successor):
|
|
|
|
- layoutStore seeding on node add/remove (`createNode`/`deleteNode`
|
|
layout mutations, including the `onAfterGraphConfigured` deferral) —
|
|
layout is renderer policy, not entity data.
|
|
- `node:slot-label:changed` slot-array reprojection — dies with the
|
|
Slot extraction phase.
|
|
|
|
## Scope
|
|
|
|
Covers node shell state, the `VueNodeData` deletion, and the
|
|
`inputs[].link` reader migration. Out of scope: Slot entity extraction,
|
|
`Properties` (`properties` / `properties_info`) and `NodeType` metadata
|
|
beyond `type`/`apiNode` (`category`, `nodeData`, `description` remain on
|
|
the class/constructor), badges, `WidgetContainer` (already owned by
|
|
`widgetValueStore`), and command-pattern mutators (future work per
|
|
ADR 0003/0008).
|