From a144d2b19f3f8d4929a29f735fa1e7bdc91e1f85 Mon Sep 17 00:00:00 2001 From: DrJKL Date: Thu, 14 May 2026 15:12:16 -0700 Subject: [PATCH] refactor(subgraph): address small review nits batch - promotedWidgetView.ts: seed ensureHostWidgetState from this.value (full effective resolution chain) instead of resolveAtHost(), so a restored promoted value isn't shadowed on first render - LGraphNode.ts: add JSDoc to clone() documenting the borrowed source-id window between clone() and graph.add() (replaces former -1 sentinel) - appModeStore.test.ts: add suite-level afterEach(restoreAllMocks) so a failing assertion can't leak per-test console.warn spies - AppBuilder.vue: prefer entry.displayName over opaque entry.entityId for resolved-input title fallback; mirrors symmetric branch on :240 Amp-Thread-ID: https://ampcode.com/threads/T-019e2812-d683-710e-946f-9ddb9018ff5a Co-authored-by: Amp --- src/components/builder/AppBuilder.vue | 2 +- src/core/graph/subgraph/promotedWidgetView.ts | 5 ++++- src/lib/litegraph/src/LGraphNode.ts | 11 ++++++++++- src/stores/appModeStore.test.ts | 8 +++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/components/builder/AppBuilder.vue b/src/components/builder/AppBuilder.vue index d70b077bac..5c9b3367da 100644 --- a/src/components/builder/AppBuilder.vue +++ b/src/components/builder/AppBuilder.vue @@ -226,7 +226,7 @@ const renderedInputs = computed<[string, MaybeRef | undefined][]>( :class=" cn(dragClass, 'my-2 rounded-lg bg-primary-background/30 p-2') " - :title="entry.widget.label ?? entry.entityId" + :title="entry.widget.label ?? entry.displayName" :sub-title="entry.node.title" can-rename :remove="() => appModeStore.removeSelectedInput(entry.widget)" diff --git a/src/core/graph/subgraph/promotedWidgetView.ts b/src/core/graph/subgraph/promotedWidgetView.ts index d3f493074c..02fad6fb84 100644 --- a/src/core/graph/subgraph/promotedWidgetView.ts +++ b/src/core/graph/subgraph/promotedWidgetView.ts @@ -241,7 +241,10 @@ class PromotedWidgetView implements IPromotedWidgetView { */ ensureHostWidgetState(): void { if (this.getHostWidgetState()) return - this.registerHostWidgetState(this.resolveAtHost()?.widget.value) + // Seed from the effective promoted value (host → linked → deepest → + // interior fallback) instead of the raw interior default, so a restored + // promoted value isn't shadowed on first render. + this.registerHostWidgetState(this.value) } private registerHostWidgetState(value: IBaseWidget['value']): void { diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index 6b82ebaa3e..9247a5efb0 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -997,7 +997,16 @@ export class LGraphNode return o } - /* Creates a clone of this node */ + /** + * Creates a clone of this node. + * + * Note: between this call returning and `graph.add(clone)` running, the + * returned node briefly carries the **source node's id** rather than the + * historical `-1` sentinel. This is required so that subclass `configure()` + * implementations (e.g. {@link SubgraphNode}) which key per-instance state + * by id hydrate into the correct slot. `graph.add` will reassign a fresh id + * (or detect the collision and reassign) on insert. + */ clone(): LGraphNode | null { if (this.type == null) return null const node = LiteGraph.createNode(this.type) diff --git a/src/stores/appModeStore.test.ts b/src/stores/appModeStore.test.ts index 111114c840..7a4d2047b0 100644 --- a/src/stores/appModeStore.test.ts +++ b/src/stores/appModeStore.test.ts @@ -2,7 +2,7 @@ import { createTestingPinia } from '@pinia/testing' import { fromAny, fromPartial } from '@total-typescript/shoehorn' import { setActivePinia } from 'pinia' import { nextTick } from 'vue' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode' import { SubgraphNode } from '@/lib/litegraph/src/litegraph' @@ -118,6 +118,12 @@ describe('appModeStore', () => { vi.clearAllMocks() }) + afterEach(() => { + // Restore any per-test spies (e.g. console.warn) so an assertion failure + // can't leak the spy into the next test. + vi.restoreAllMocks() + }) + describe('enterBuilder', () => { it('navigates to builder:arrange when in app mode with outputs', () => { workflowStore.activeWorkflow = createBuilderWorkflowWithOutputs('app')