From c1515374b88bdf29002e1f0250a07a1aa68b919b Mon Sep 17 00:00:00 2001 From: DrJKL Date: Wed, 13 May 2026 11:57:29 -0700 Subject: [PATCH] refactor(subgraph): key promoted widget state by entityId, drop encoded host-state name Replace the encoded `[name, sourceNodeId, sourceWidgetName].join(':')` host-state-name with the canonical `WidgetEntityId` per ADR 0009. Producers and consumers flip in the same diff: - Delete `getPromotedWidgetHostStateName` (was internal disambiguator) - `PromotedWidgetView` reads/writes via `widgetValueIO` keyed by `entityId` - `SubgraphNode.serialize` reads via `readWidgetValue(widget.entityId)` - `getExplicitHostWidgetValue` drops its now-unused `subgraphNode` param - `safeWidgetMapper` no longer overrides `storeNodeId/storeName`; those fields are removed from `SafeWidgetData` - `useProcessedWidgets` looks up state via `getWidgetState(entityId)` - `getWidgetIdentity` collapses to `${entityId}:${type}` (type suffix preserves the PR #9896 invariant for node-def schema drift) Net -66 LOC. No fallback / accept-either branches: entityId is the single source of truth for widget identity across the renderer. Amp-Thread-ID: https://ampcode.com/threads/T-019e2260-c2ba-70b4-9962-1638be4bf645 Co-authored-by: Amp --- .../graph/useGraphNodeManager.test.ts | 22 ++++---- src/composables/graph/useGraphNodeManager.ts | 22 ++------ .../graph/subgraph/promotedWidgetTypes.ts | 6 --- src/core/graph/subgraph/promotedWidgetView.ts | 31 +++-------- src/core/graph/subgraph/promotionUtils.ts | 21 ++------ .../litegraph/src/subgraph/SubgraphNode.ts | 12 ++--- .../vueNodes/components/NodeWidgets.test.ts | 54 +++++++++++-------- .../composables/useProcessedWidgets.test.ts | 48 ++++++----------- .../composables/useProcessedWidgets.ts | 42 ++++++--------- 9 files changed, 95 insertions(+), 163 deletions(-) diff --git a/src/composables/graph/useGraphNodeManager.test.ts b/src/composables/graph/useGraphNodeManager.test.ts index d73f0e4ee5..6283ede675 100644 --- a/src/composables/graph/useGraphNodeManager.test.ts +++ b/src/composables/graph/useGraphNodeManager.test.ts @@ -7,6 +7,7 @@ import { computed, nextTick, watch } from 'vue' import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager' import { createPromotedWidgetView } from '@/core/graph/subgraph/promotedWidgetView' import { BaseWidget, LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph' +import { widgetEntityId } from '@/world/entityIds' import { createTestSubgraph, createTestSubgraphNode @@ -440,8 +441,9 @@ describe('Nested promoted widget mapping', () => { expect(mappedWidget?.type).toBe('combo') // Promoted widgets read from a host-scoped store entry so per-host values // stay independent across SubgraphNode instances sharing the same subgraph. - expect(mappedWidget?.storeNodeId).toBe(String(subgraphNodeB.id)) - expect(mappedWidget?.storeName).toBe('b_input:11:a_input') + expect(mappedWidget?.entityId).toBe( + widgetEntityId(graph.id, subgraphNodeB.id, 'b_input') + ) }) it('preserves distinct store identity for duplicate-named promoted widgets', () => { @@ -475,13 +477,15 @@ describe('Nested promoted widget mapping', () => { const widgets = nodeData?.widgets expect(widgets).toHaveLength(2) - // Both promoted widgets live on the same host node, so storeNodeId is - // shared; storeName encodes the source identity to keep them distinct. - expect(widgets?.[0]?.storeNodeId).toBe(String(subgraphNode.id)) - expect(widgets?.[1]?.storeNodeId).toBe(String(subgraphNode.id)) - expect(widgets?.[0]?.storeName).toBe(`first_seed:${firstNode.id}:seed`) - expect(widgets?.[1]?.storeName).toBe(`second_seed:${secondNode.id}:seed`) - expect(widgets?.[0]?.storeName).not.toBe(widgets?.[1]?.storeName) + // Both promoted widgets live on the same host node; their entityIds use + // the distinct subgraph input names to keep them independent. + expect(widgets?.[0]?.entityId).toBe( + widgetEntityId(graph.id, subgraphNode.id, 'first_seed') + ) + expect(widgets?.[1]?.entityId).toBe( + widgetEntityId(graph.id, subgraphNode.id, 'second_seed') + ) + expect(widgets?.[0]?.entityId).not.toBe(widgets?.[1]?.entityId) }) }) diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index fd446221f1..6a04c6797b 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -7,10 +7,7 @@ import { reactive, shallowReactive } from 'vue' import { useChainCallback } from '@/composables/functional/useChainCallback' import type { PromotedWidgetSource } from '@/core/graph/subgraph/promotedWidgetTypes' -import { - getPromotedWidgetHostStateName, - isPromotedWidgetView -} from '@/core/graph/subgraph/promotedWidgetTypes' +import { isPromotedWidgetView } from '@/core/graph/subgraph/promotedWidgetTypes' import { matchPromotedInput } from '@/core/graph/subgraph/matchPromotedInput' import { resolveConcretePromotedWidget, @@ -69,9 +66,7 @@ export interface SafeWidgetData { */ entityId?: WidgetEntityId nodeId?: NodeId - storeNodeId?: NodeId name: string - storeName?: string type: string /** Callback to invoke when widget value changes (wraps LiteGraph callback + triggerDraw) */ callback?: ((value: unknown) => void) | undefined @@ -339,23 +334,14 @@ function safeWidgetMapper( : undefined const name = sourceWidgetName ?? displayName - // Promoted widgets carry per-host values that the migration writes to a - // host-scoped store entry. Vue rendering must read from that same entry - // (not the shared interior key) for per-instance independence. - let storeNodeId: NodeId | undefined = nodeId - let storeName = sourceWidgetName - if (isPromotedWidgetView(widget)) { - widget.ensureHostWidgetState() - storeNodeId = String(node.id) - storeName = getPromotedWidgetHostStateName(widget) - } + // Promoted widgets carry per-host values keyed by `widget.entityId`. + // Seed the store entry so Vue rendering can read it on first render. + if (isPromotedWidgetView(widget)) widget.ensureHostWidgetState() return { entityId: widget.entityId, nodeId, - storeNodeId, name, - storeName, type: effectiveWidget.type, ...sharedEnhancements, callback, diff --git a/src/core/graph/subgraph/promotedWidgetTypes.ts b/src/core/graph/subgraph/promotedWidgetTypes.ts index 292ae7b968..02991bd013 100644 --- a/src/core/graph/subgraph/promotedWidgetTypes.ts +++ b/src/core/graph/subgraph/promotedWidgetTypes.ts @@ -52,12 +52,6 @@ export interface PromotedWidgetView extends IBaseWidget { ensureHostWidgetState(): void } -export function getPromotedWidgetHostStateName( - widget: PromotedWidgetView -): string { - return [widget.name, widget.sourceNodeId, widget.sourceWidgetName].join(':') -} - export function isPromotedWidgetView( widget: IBaseWidget ): widget is PromotedWidgetView { diff --git a/src/core/graph/subgraph/promotedWidgetView.ts b/src/core/graph/subgraph/promotedWidgetView.ts index 6f8653ba95..2a634604e4 100644 --- a/src/core/graph/subgraph/promotedWidgetView.ts +++ b/src/core/graph/subgraph/promotedWidgetView.ts @@ -23,18 +23,13 @@ import { matchPromotedInput } from '@/core/graph/subgraph/matchPromotedInput' import { hasWidgetNode } from '@/core/graph/subgraph/widgetNodeTypeGuard' import type { WidgetEntityId } from '@/world/entityIds' import { widgetEntityId } from '@/world/entityIds' +import { ensureWidgetState, getWidgetState } from '@/world/widgetValueIO' -import { - getPromotedWidgetHostStateName, - isPromotedWidgetView -} from './promotedWidgetTypes' +import { isPromotedWidgetView } from './promotedWidgetTypes' import type { PromotedWidgetView as IPromotedWidgetView } from './promotedWidgetTypes' export type { PromotedWidgetView } from './promotedWidgetTypes' -export { - getPromotedWidgetHostStateName, - isPromotedWidgetView -} from './promotedWidgetTypes' +export { isPromotedWidgetView } from './promotedWidgetTypes' interface SubgraphSlotRef { name: string @@ -230,11 +225,7 @@ class PromotedWidgetView implements IPromotedWidgetView { } private getHostWidgetState(): WidgetState | undefined { - return useWidgetValueStore().getWidget( - this.graphId, - this.subgraphNode.id, - this.hostWidgetStateName - ) + return getWidgetState(this.entityId) } private setHostWidgetState(value: IBaseWidget['value']): void { @@ -251,9 +242,9 @@ class PromotedWidgetView implements IPromotedWidgetView { /** * Idempotently register a host-scoped widget state seeded with the current - * effective value. Vue rendering reads from this entry (via `safeWidgetMapper` - * exposing the host-scoped `storeNodeId`/`storeName` for promoted widgets), - * so the entry must exist before first render even if migration has not run. + * effective value. Vue rendering reads from this entry keyed by + * {@link entityId}, so it must exist before first render even if migration + * has not run. */ ensureHostWidgetState(): void { if (this.getHostWidgetState()) return @@ -262,9 +253,7 @@ class PromotedWidgetView implements IPromotedWidgetView { private registerHostWidgetState(value: IBaseWidget['value']): void { const resolved = this.resolveDeepest() - useWidgetValueStore().registerWidget(this.graphId, { - nodeId: this.subgraphNode.id, - name: this.hostWidgetStateName, + ensureWidgetState(this.entityId, { type: resolved?.widget.type ?? 'button', value, // Clone — never share the interior widget's options reference, or @@ -277,10 +266,6 @@ class PromotedWidgetView implements IPromotedWidgetView { }) } - private get hostWidgetStateName(): string { - return getPromotedWidgetHostStateName(this) - } - get label(): string | undefined { const slot = this.getBoundSubgraphSlot() if (slot) return slot.label ?? slot.displayName ?? slot.name diff --git a/src/core/graph/subgraph/promotionUtils.ts b/src/core/graph/subgraph/promotionUtils.ts index d9120a612f..c0193f4a02 100644 --- a/src/core/graph/subgraph/promotionUtils.ts +++ b/src/core/graph/subgraph/promotionUtils.ts @@ -1,9 +1,6 @@ import * as Sentry from '@sentry/vue' import type { PromotedWidgetSource } from '@/core/graph/subgraph/promotedWidgetTypes' -import { - getPromotedWidgetHostStateName, - isPromotedWidgetView -} from '@/core/graph/subgraph/promotedWidgetTypes' +import { isPromotedWidgetView } from '@/core/graph/subgraph/promotedWidgetTypes' import { t } from '@/i18n' import type { IContextMenuValue, @@ -21,7 +18,7 @@ import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useLitegraphService } from '@/services/litegraphService' import { usePreviewExposureStore } from '@/stores/previewExposureStore' import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore' -import { useWidgetValueStore } from '@/stores/widgetValueStore' +import { readWidgetValue } from '@/world/widgetValueIO' type PartialNode = Pick @@ -143,10 +140,7 @@ function applySubgraphInputOrder( const rows = subgraphNode.subgraph.inputs.map((input, index) => ({ subgraphInput: input, hostInput: subgraphNode.inputs[index], - value: getExplicitHostWidgetValue( - subgraphNode, - subgraphNode.inputs[index]?._widget - ) + value: getExplicitHostWidgetValue(subgraphNode.inputs[index]?._widget) })) const orderedRows = orderedIndices.flatMap((index) => rows[index] ?? []) @@ -177,18 +171,13 @@ function applySubgraphInputOrder( } function getExplicitHostWidgetValue( - subgraphNode: SubgraphNode, widget: IBaseWidget | undefined ): IBaseWidget['value'] | undefined { if (!widget) return undefined if (!isPromotedWidgetView(widget)) return widget.value - const state = useWidgetValueStore().getWidget( - subgraphNode.rootGraph.id, - subgraphNode.id, - getPromotedWidgetHostStateName(widget) - ) - return state && isWidgetValue(state.value) ? state.value : undefined + const value = readWidgetValue(widget.entityId) + return isWidgetValue(value) ? value : undefined } function isSamePromotedWidget(left: IBaseWidget, right: IBaseWidget): boolean { diff --git a/src/lib/litegraph/src/subgraph/SubgraphNode.ts b/src/lib/litegraph/src/subgraph/SubgraphNode.ts index dfd38e078d..fbd479ff7f 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphNode.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphNode.ts @@ -34,7 +34,6 @@ import type { } from '@/lib/litegraph/src/types/widgets' import { createPromotedWidgetView, - getPromotedWidgetHostStateName, isPromotedWidgetView } from '@/core/graph/subgraph/promotedWidgetView' import type { PromotedWidgetView } from '@/core/graph/subgraph/promotedWidgetView' @@ -48,7 +47,7 @@ import { parsePreviewExposures } from '@/core/schemas/previewExposureSchema' import { parseProxyWidgetErrorQuarantine } from '@/core/schemas/proxyWidgetQuarantineSchema' import { useDomWidgetStore } from '@/stores/domWidgetStore' import { usePreviewExposureStore } from '@/stores/previewExposureStore' -import { useWidgetValueStore } from '@/stores/widgetValueStore' +import { readWidgetValue } from '@/world/widgetValueIO' import { ExecutableNodeDTO } from './ExecutableNodeDTO' import type { ExecutableLGraphNode, ExecutionId } from './ExecutableNodeDTO' @@ -1168,16 +1167,11 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { serialized.properties = serializedProperties - const widgetStore = useWidgetValueStore() const widgetValues = this.inputs.flatMap((input) => { const widget = input._widget if (!widget || !isPromotedWidgetView(widget)) return [] - const state = widgetStore.getWidget( - rootGraphId, - this.id, - getPromotedWidgetHostStateName(widget) - ) - return [state && isWidgetValue(state.value) ? state.value : undefined] + const value = readWidgetValue(widget.entityId) + return [isWidgetValue(value) ? value : undefined] }) if (widgetValues.some((value) => value !== undefined)) { diff --git a/src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts b/src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts index 83e912cbc9..7a041a0025 100644 --- a/src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts +++ b/src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts @@ -12,6 +12,9 @@ import type { } from '@/composables/graph/useGraphNodeManager' import NodeWidgets from '@/renderer/extensions/vueNodes/components/NodeWidgets.vue' import { useWidgetValueStore } from '@/stores/widgetValueStore' +import { widgetEntityId } from '@/world/entityIds' + +const GRAPH_ID = 'graph-test' vi.mock('@/renderer/core/canvas/canvasStore', () => ({ useCanvasStore: () => ({ @@ -126,28 +129,35 @@ describe('NodeWidgets', () => { }) it('deduplicates widgets with identical render identity while keeping distinct promoted sources', () => { + const duplicateEntityId = widgetEntityId( + GRAPH_ID, + '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', + 'string_a' + ) + const distinctEntityId = widgetEntityId( + GRAPH_ID, + '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:20', + 'string_a' + ) const duplicateA = createMockWidget({ name: 'string_a', type: 'text', nodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeNodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeName: 'string_a', + entityId: duplicateEntityId, slotName: 'string_a' }) const duplicateB = createMockWidget({ name: 'string_a', type: 'text', nodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeNodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeName: 'string_a', + entityId: duplicateEntityId, slotName: 'string_a' }) const distinct = createMockWidget({ name: 'string_a', type: 'text', nodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:20', - storeNodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:20', - storeName: 'string_a', + entityId: distinctEntityId, slotName: 'string_a' }) const nodeData = createMockNodeData('SubgraphNode', [ @@ -162,12 +172,16 @@ describe('NodeWidgets', () => { }) it('prefers a visible duplicate over a hidden duplicate when identities collide', () => { + const sharedEntityId = widgetEntityId( + GRAPH_ID, + '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', + 'string_a' + ) const hiddenDuplicate = createMockWidget({ name: 'string_a', type: 'text', nodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeNodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeName: 'string_a', + entityId: sharedEntityId, slotName: 'string_a', options: { hidden: true } }) @@ -175,8 +189,7 @@ describe('NodeWidgets', () => { name: 'string_a', type: 'text', nodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeNodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeName: 'string_a', + entityId: sharedEntityId, slotName: 'string_a', options: { hidden: false } }) @@ -191,20 +204,23 @@ describe('NodeWidgets', () => { }) it('does not deduplicate entries that share names but have different widget types', () => { + const sharedEntityId = widgetEntityId( + GRAPH_ID, + '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', + 'string_a' + ) const textWidget = createMockWidget({ name: 'string_a', type: 'text', nodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeNodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeName: 'string_a', + entityId: sharedEntityId, slotName: 'string_a' }) const comboWidget = createMockWidget({ name: 'string_a', type: 'combo', nodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeNodeId: '5e0670b8-ea2c-4fb6-8b73-a1100a2d4f8f:19', - storeName: 'string_a', + entityId: sharedEntityId, slotName: 'string_a' }) const nodeData = createMockNodeData('SubgraphNode', [ @@ -220,18 +236,14 @@ describe('NodeWidgets', () => { it('keeps unresolved same-name promoted entries distinct by source execution identity', () => { const firstTransientEntry = createMockWidget({ nodeId: undefined, - storeNodeId: undefined, name: 'string_a', - storeName: 'string_a', slotName: 'string_a', type: 'text', sourceExecutionId: '65:18' }) const secondTransientEntry = createMockWidget({ nodeId: undefined, - storeNodeId: undefined, name: 'string_a', - storeName: 'string_a', slotName: 'string_a', type: 'text', sourceExecutionId: '65:19' @@ -251,16 +263,14 @@ describe('NodeWidgets', () => { name: 'text', type: 'text', nodeId: 'outer-subgraph:1', - storeNodeId: 'outer-subgraph:1', - storeName: 'text', + entityId: widgetEntityId(GRAPH_ID, 'outer-subgraph:1', 'text'), slotName: 'text' }) const secondPromoted = createMockWidget({ name: 'text', type: 'text', nodeId: 'outer-subgraph:2', - storeNodeId: 'outer-subgraph:2', - storeName: 'text', + entityId: widgetEntityId(GRAPH_ID, 'outer-subgraph:2', 'text'), slotName: 'text' }) diff --git a/src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts b/src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts index de5ed3f160..bf92a97b10 100644 --- a/src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts +++ b/src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts @@ -13,6 +13,9 @@ import { import { useExecutionErrorStore } from '@/stores/executionErrorStore' import { useMissingModelStore } from '@/platform/missingModel/missingModelStore' import { useWidgetValueStore } from '@/stores/widgetValueStore' +import { widgetEntityId } from '@/world/entityIds' + +const GRAPH_ID = 'graph-test' vi.mock('@/renderer/core/canvas/canvasStore', () => ({ useCanvasStore: () => ({ @@ -41,37 +44,19 @@ const createMockWidget = ( }) describe('getWidgetIdentity', () => { - it('returns stable dedupeIdentity for widgets with storeNodeId', () => { - const widget = createMockWidget({ - storeNodeId: 'subgraph:19', - storeName: 'text', - slotName: 'text', - type: 'text' - }) + it('keys dedupeIdentity by entityId and widget type', () => { + const entityId = widgetEntityId(GRAPH_ID, 'subgraph:19', 'text') + const widget = createMockWidget({ entityId, name: 'text', type: 'text' }) const { dedupeIdentity, renderKey } = getWidgetIdentity(widget, '1', 0) - expect(dedupeIdentity).toBe('node:19:text:text:text') + expect(dedupeIdentity).toBe(`${entityId}:text`) expect(renderKey).toBe(dedupeIdentity) }) - it('returns transient renderKey for widgets without stable identity', () => { - const widget = createMockWidget({ - nodeId: undefined, - storeNodeId: undefined, - sourceExecutionId: undefined - }) + it('falls back to a transient renderKey when no entityId is set', () => { + const widget = createMockWidget({ entityId: undefined }) const { dedupeIdentity, renderKey } = getWidgetIdentity(widget, '5', 3) expect(dedupeIdentity).toBeUndefined() - expect(renderKey).toBe('transient:5:test_widget:test_widget:combo:3') - }) - - it('uses sourceExecutionId for identity when no nodeId', () => { - const widget = createMockWidget({ - nodeId: undefined, - storeNodeId: undefined, - sourceExecutionId: '65:18' - }) - const { dedupeIdentity } = getWidgetIdentity(widget, '1', 0) - expect(dedupeIdentity).toBe('exec:65:18:test_widget:test_widget:combo') + expect(renderKey).toBe('transient:5:test_widget:combo:3') }) }) @@ -211,8 +196,7 @@ describe('computeProcessedWidgets borderStyle', () => { name: 'text', type: 'combo', nodeId: 'inner-subgraph:1', - storeNodeId: 'inner-subgraph:1', - storeName: 'text', + entityId: widgetEntityId(GRAPH_ID, 'inner-subgraph:1', 'text'), slotName: 'text', promotedLabel: 'Text' }) @@ -245,8 +229,7 @@ describe('computeProcessedWidgets borderStyle', () => { name: 'text', type: 'combo', nodeId: 'inner-subgraph:1', - storeNodeId: 'inner-subgraph:1', - storeName: 'text', + entityId: widgetEntityId(GRAPH_ID, 'inner-subgraph:1', 'text'), slotName: 'text' }) @@ -306,12 +289,12 @@ describe('computeProcessedWidgets borderStyle', () => { }) it('deduplication keeps visible widget over hidden duplicate', () => { + const sharedEntityId = widgetEntityId(GRAPH_ID, '1', 'text') const hiddenWidget = createMockWidget({ name: 'text', type: 'combo', nodeId: '1', - storeNodeId: '1', - storeName: 'text', + entityId: sharedEntityId, slotName: 'text', options: { hidden: true } }) @@ -320,8 +303,7 @@ describe('computeProcessedWidgets borderStyle', () => { name: 'text', type: 'combo', nodeId: '1', - storeNodeId: '1', - storeName: 'text', + entityId: sharedEntityId, slotName: 'text' }) diff --git a/src/renderer/extensions/vueNodes/composables/useProcessedWidgets.ts b/src/renderer/extensions/vueNodes/composables/useProcessedWidgets.ts index 8858c76711..a98d2b7fd2 100644 --- a/src/renderer/extensions/vueNodes/composables/useProcessedWidgets.ts +++ b/src/renderer/extensions/vueNodes/composables/useProcessedWidgets.ts @@ -31,6 +31,7 @@ import { } from '@/stores/widgetValueStore' import { useMissingModelStore } from '@/platform/missingModel/missingModelStore' import { useExecutionErrorStore } from '@/stores/executionErrorStore' +import { getWidgetState } from '@/world/widgetValueIO' import type { LGraph } from '@/lib/litegraph/src/litegraph' import type { LinkedUpstreamInfo, @@ -125,26 +126,13 @@ export function getWidgetIdentity( dedupeIdentity?: string renderKey: string } { - const rawWidgetId = widget.storeNodeId ?? widget.nodeId - const storeWidgetName = widget.storeName ?? widget.name - const slotNameForIdentity = widget.slotName ?? widget.name - const stableIdentityRoot = rawWidgetId - ? `node:${String(stripGraphPrefix(rawWidgetId))}` - : widget.sourceExecutionId - ? `exec:${widget.sourceExecutionId}` - : undefined - - const dedupeIdentity = stableIdentityRoot - ? `${stableIdentityRoot}:${storeWidgetName}:${slotNameForIdentity}:${widget.type}` + const dedupeIdentity = widget.entityId + ? `${widget.entityId}:${widget.type}` : undefined const renderKey = dedupeIdentity ?? - `transient:${String(nodeId ?? '')}:${storeWidgetName}:${slotNameForIdentity}:${widget.type}:${index}` - - return { - dedupeIdentity, - renderKey - } + `transient:${String(nodeId ?? '')}:${widget.name}:${widget.type}:${index}` + return { dedupeIdentity, renderKey } } export function isWidgetVisible( @@ -193,13 +181,15 @@ export function computeProcessedWidgets({ if (!shouldRenderAsVue(widget)) continue const identity = getWidgetIdentity(widget, nodeId, index) - const storeWidgetName = widget.storeName ?? widget.name - const bareWidgetId = String( - stripGraphPrefix(widget.storeNodeId ?? widget.nodeId ?? nodeId ?? '') - ) - const widgetState = graphId - ? widgetValueStore.getWidget(graphId, bareWidgetId, storeWidgetName) - : undefined + const widgetState = widget.entityId + ? getWidgetState(widget.entityId) + : graphId + ? widgetValueStore.getWidget( + graphId, + String(stripGraphPrefix(widget.nodeId ?? nodeId ?? '')), + widget.name + ) + : undefined const mergedOptions: IWidgetOptions = { ...(widget.options ?? {}), ...(widgetState?.options ?? {}) @@ -247,9 +237,7 @@ export function computeProcessedWidgets({ widgetState, identity: { renderKey } } of uniqueWidgets) { - const bareWidgetId = String( - stripGraphPrefix(widget.storeNodeId ?? widget.nodeId ?? nodeId ?? '') - ) + const bareWidgetId = String(stripGraphPrefix(widget.nodeId ?? nodeId ?? '')) const vueComponent = getComponent(widget.type) ||