From ee0789e153cef89ce9eba1305b0e097c601a1424 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Fri, 20 Feb 2026 19:14:57 -0800 Subject: [PATCH] fix: promoted widget labels show widgetName instead of "nodeId: widgetName" (#9013) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Promoted/proxied widgets on subgraph nodes showed generic "nodeId: widgetName" labels (e.g., "3: seed") in the LiteGraph renderer. Now they correctly show just the widget name (e.g., "seed"). ## Changes - **What**: Set proxy widget overlay `label` to `widgetName` instead of `name` (which contains the unique `"nodeId: widgetName"` format). The overlay `name` still retains the unique format for internal identification. - Added 2 tests verifying proxy widget label defaults and user rename behavior. ## Review Focus - The one-line fix in `proxyWidget.ts` line 157: `label: widgetName` instead of `label: name` - The overlay `name` property is unchanged — only `label` (used for display) is affected - No code parses the label string for identification; all lookups use `_overlay.nodeId` and `_overlay.widgetName` directly ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9013-fix-promoted-widget-labels-show-widgetName-instead-of-nodeId-widgetName-30d6d73d365081ca8d2feb68585fc187) by [Unito](https://www.unito.io) --- src/core/graph/subgraph/proxyWidget.test.ts | 92 ++++++++++++++++++++- src/core/graph/subgraph/proxyWidget.ts | 8 +- 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/src/core/graph/subgraph/proxyWidget.test.ts b/src/core/graph/subgraph/proxyWidget.test.ts index 188aba33a..cbcd2d336 100644 --- a/src/core/graph/subgraph/proxyWidget.test.ts +++ b/src/core/graph/subgraph/proxyWidget.test.ts @@ -5,8 +5,8 @@ import { beforeEach, describe, expect, test, vi } from 'vitest' import { registerProxyWidgets } from '@/core/graph/subgraph/proxyWidget' import { promoteWidget } from '@/core/graph/subgraph/proxyWidgetUtils' import { parseProxyWidgets } from '@/core/schemas/proxyWidget' -import { LGraphNode } from '@/lib/litegraph/src/litegraph' -import type { LGraphCanvas, SubgraphNode } from '@/lib/litegraph/src/litegraph' +import { LGraph, LGraphNode, SubgraphNode } from '@/lib/litegraph/src/litegraph' +import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph' import { createTestSubgraph, @@ -125,6 +125,94 @@ describe('Subgraph proxyWidgets', () => { subgraphNode.widgets[0].computedHeight = 10 expect(subgraphNode.widgets[0].value).toBe('value') }) + test('Proxy widget label shows widgetName, not "nodeId: widgetName"', () => { + const [subgraphNode, innerNodes] = setupSubgraph(1) + innerNodes[0].addWidget('text', 'seed', 'value', () => {}) + subgraphNode.properties.proxyWidgets = [['1', 'seed']] + + const proxyWidget = subgraphNode.widgets[0] + expect(proxyWidget.label).toBe('seed') + expect(proxyWidget.name).toBe('1: seed') + }) + + test('Proxy widget label reflects linked widget label', () => { + const [subgraphNode, innerNodes] = setupSubgraph(1) + innerNodes[0].addWidget('text', 'seed', 'value', () => {}) + subgraphNode.properties.proxyWidgets = [['1', 'seed']] + + const proxyWidget = subgraphNode.widgets[0] + expect(proxyWidget.label).toBe('seed') + + innerNodes[0].widgets![0].label = 'My Inner Label' + // Trigger re-resolve of linked widget + proxyWidget.computedHeight = 10 + expect(proxyWidget.label).toBe('My Inner Label') + }) + + test('Proxy widget user rename takes priority over linked widget label', () => { + const [subgraphNode, innerNodes] = setupSubgraph(1) + innerNodes[0].addWidget('text', 'seed', 'value', () => {}) + subgraphNode.properties.proxyWidgets = [['1', 'seed']] + + const proxyWidget = subgraphNode.widgets[0] + proxyWidget.label = 'My Custom Seed' + expect(proxyWidget.label).toBe('My Custom Seed') + + innerNodes[0].widgets![0].label = 'Inner Override' + proxyWidget.computedHeight = 10 + expect(proxyWidget.label).toBe('My Custom Seed') + }) + + test('Proxy widget label resets to linked widget on undefined', () => { + const [subgraphNode, innerNodes] = setupSubgraph(1) + innerNodes[0].addWidget('text', 'seed', 'value', () => {}) + subgraphNode.properties.proxyWidgets = [['1', 'seed']] + + const proxyWidget = subgraphNode.widgets[0] + proxyWidget.label = 'Custom' + expect(proxyWidget.label).toBe('Custom') + + proxyWidget.label = undefined + innerNodes[0].widgets![0].label = 'Inner Label' + proxyWidget.computedHeight = 10 + expect(proxyWidget.label).toBe('Inner Label') + }) + + test('Proxy widget labels are correct when loaded from serialized data', () => { + // Intentionally constructs SubgraphNode via constructor (not setupSubgraph) + // to exercise the deserialization/onConfigure path from blueprint JSON. + const subgraph = createTestSubgraph() + const innerNode = new LGraphNode('InnerNode') + subgraph.add(innerNode) + innerNode.addWidget('text', 'seed', 'value', () => {}) + innerNode.addWidget('text', 'steps', 'value', () => {}) + + const parentGraph = new LGraph() + const subgraphNode = new SubgraphNode(parentGraph, subgraph, { + id: 1, + type: subgraph.id, + pos: [100, 100], + size: [200, 100], + inputs: [], + outputs: [], + properties: { + proxyWidgets: [ + ['1', 'seed'], + ['1', 'steps'] + ] + }, + flags: {}, + mode: 0, + order: 0 + }) + + expect(subgraphNode.widgets).toHaveLength(2) + expect(subgraphNode.widgets[0].label).toBe('seed') + expect(subgraphNode.widgets[0].name).toBe('1: seed') + expect(subgraphNode.widgets[1].label).toBe('steps') + expect(subgraphNode.widgets[1].name).toBe('1: steps') + }) + test('Prevents duplicate promotion', () => { const [subgraphNode, innerNodes] = setupSubgraph(1) innerNodes[0].addWidget('text', 'stringWidget', 'value', () => {}) diff --git a/src/core/graph/subgraph/proxyWidget.ts b/src/core/graph/subgraph/proxyWidget.ts index 1370dfe49..4c27f81fe 100644 --- a/src/core/graph/subgraph/proxyWidget.ts +++ b/src/core/graph/subgraph/proxyWidget.ts @@ -154,7 +154,6 @@ function newProxyWidget( computedHeight: undefined, isProxyWidget: true, last_y: undefined, - label: name, name, node: subgraphNode, onRemove: undefined, @@ -202,12 +201,15 @@ function newProxyFromOverlay(subgraphNode: SubgraphNode, overlay: Overlay) { * and the value used as 'this' if property is a get/set method * @param {unknown} value - only used on set calls. The thing being assigned */ + let userLabel: string | undefined const handler = { get(_t: IBaseWidget, property: string, receiver: object) { let redirectedTarget: object = backingWidget let redirectedReceiver = receiver if (property == '_overlay') return overlay else if (property == 'value') redirectedReceiver = backingWidget + else if (property == 'label') + return userLabel ?? linkedWidget?.label ?? overlay.widgetName if (Object.prototype.hasOwnProperty.call(overlay, property)) { redirectedTarget = overlay redirectedReceiver = overlay @@ -215,6 +217,10 @@ function newProxyFromOverlay(subgraphNode: SubgraphNode, overlay: Overlay) { return Reflect.get(redirectedTarget, property, redirectedReceiver) }, set(_t: IBaseWidget, property: string, value: unknown) { + if (property == 'label') { + userLabel = value as string | undefined + return true + } let redirectedTarget: object = backingWidget if (property == 'computedHeight') { if (overlay.widgetName.startsWith('$$') && linkedNode) {