mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-08 16:17:58 +00:00
Badges go straight to plain BadgeData rows in a dedicated store, written by a reactive badge system; core badges materialize as rows too, killing the dual derivation and positional slice(1). Adds badge vocabulary to the domain glossary and repoints the node-data-store badges row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
140 lines
7.2 KiB
Markdown
140 lines
7.2 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.
|
|
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).
|