fix: subgraph output slot labels not updating in v2 renderer (#9266)

## Summary

Custom names set on subgraph output nodes are ignored in the v2 renderer
— it always shows the data type name (e.g. "texts") instead of the
user-defined label. Works correctly in v1.

## Changes

- **What**: Made `outputs` in `extractVueNodeData` reactive via
`shallowReactive` + `defineProperty` (matching the existing `inputs`
pattern). Added a `node:slot-label:changed` graph trigger that
`SubgraphNode` fires when input/output labels are renamed, so the Vue
layer picks up the change.

## Review Focus

- The `outputs` reactivity mirrors `inputs` exactly — same
`shallowReactive` + setter pattern. The new trigger event forces
`shallowReactive` to detect the deep property change by re-assigning the
array.
- Also handles input label renames for consistency, even though the
current bug report is output-specific.

## Screenshots

**v1 — output correctly shows custom label "output_text":**
<img width="1076" height="628" alt="Screenshot 2026-02-26 at 4 43 00 PM"
src="https://github.com/user-attachments/assets/b4d6ae4c-9970-4d99-a872-4ce1b28522f2"
/>

**v2 before fix — output shows type name "texts" instead of custom
label:**
<img width="808" height="298" alt="Screenshot 2026-02-26 at 4 43 30 PM"
src="https://github.com/user-attachments/assets/cf06aa6c-6d4d-4be9-9bcd-dcc072ed1907"
/>

**v2 after fix — output correctly shows "output_text":**
<img width="1013" height="292" alt="Screenshot 2026-02-26 at 5 14 44 PM"
src="https://github.com/user-attachments/assets/3c43fa9b-0615-4758-bee6-be3481168675"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9266-fix-subgraph-output-slot-labels-not-updating-in-v2-renderer-3146d73d365081979327fd775a6ef62b)
by [Unito](https://www.unito.io)
This commit is contained in:
Arthur R Longbottom
2026-03-13 09:58:13 -07:00
committed by GitHub
parent 9447a1f5d6
commit 5640eb7d92
5 changed files with 125 additions and 4 deletions

View File

@@ -243,6 +243,78 @@ describe('Widget slotMetadata reactivity on link disconnect', () => {
})
})
describe('Subgraph output slot label reactivity', () => {
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))
})
it('updates output slot labels when node:slot-label:changed is triggered', async () => {
const graph = new LGraph()
const node = new LGraphNode('test')
node.addOutput('original_name', 'STRING')
node.addOutput('other_name', 'STRING')
graph.add(node)
const { vueNodeData } = useGraphNodeManager(graph)
const nodeId = String(node.id)
const nodeData = vueNodeData.get(nodeId)
if (!nodeData?.outputs) throw new Error('Expected output data to exist')
expect(nodeData.outputs[0].label).toBeUndefined()
expect(nodeData.outputs[1].label).toBeUndefined()
// Simulate what SubgraphNode does: set the label, then fire the trigger
node.outputs[0].label = 'custom_label'
graph.trigger('node:slot-label:changed', {
nodeId: node.id,
slotType: NodeSlotType.OUTPUT
})
await nextTick()
const updatedData = vueNodeData.get(nodeId)
expect(updatedData?.outputs?.[0]?.label).toBe('custom_label')
expect(updatedData?.outputs?.[1]?.label).toBeUndefined()
})
it('updates input slot labels when node:slot-label:changed is triggered', async () => {
const graph = new LGraph()
const node = new LGraphNode('test')
node.addInput('original_name', 'STRING')
graph.add(node)
const { vueNodeData } = useGraphNodeManager(graph)
const nodeId = String(node.id)
const nodeData = vueNodeData.get(nodeId)
if (!nodeData?.inputs) throw new Error('Expected input data to exist')
expect(nodeData.inputs[0].label).toBeUndefined()
node.inputs[0].label = 'custom_label'
graph.trigger('node:slot-label:changed', {
nodeId: node.id,
slotType: NodeSlotType.INPUT
})
await nextTick()
const updatedData = vueNodeData.get(nodeId)
expect(updatedData?.inputs?.[0]?.label).toBe('custom_label')
})
it('ignores node:slot-label:changed for unknown node ids', () => {
const graph = new LGraph()
useGraphNodeManager(graph)
expect(() =>
graph.trigger('node:slot-label:changed', {
nodeId: 'missing-node',
slotType: NodeSlotType.OUTPUT
})
).not.toThrow()
})
})
describe('Subgraph Promoted Pseudo Widgets', () => {
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))