Skip to content

Commit 0f33444

Browse files
authored
fix: undo breaking Vue node image preview reactivity (#8839)
## Summary restoreOutputs was assigning the same object reference to both app.nodeOutputs and the Pinia reactive ref. This caused subsequent writes via setOutputsByLocatorId to mutate the reactive proxy's target through the raw reference before the proxy write, making Vue detect no change and skip reactivity updates permanently. Shallow-copy the outputs when assigning to the reactive ref so the proxy target remains a separate object from app.nodeOutputs. ## Screenshots before https://github.com/user-attachments/assets/98f2b17c-87b9-41e7-9caa-238e36c3c032 after https://github.com/user-attachments/assets/cb6e1d25-bd2e-41ed-a536-7b8250f858ec ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8839-fix-undo-breaking-Vue-node-image-preview-reactivity-3056d73d365081d2a1c7d4d9553f30e0) by [Unito](https://www.unito.io)
1 parent 44ce937 commit 0f33444

File tree

2 files changed

+42
-1
lines changed

2 files changed

+42
-1
lines changed

src/stores/imagePreviewStore.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,47 @@ describe('imagePreviewStore setNodeOutputsByExecutionId with merge', () => {
8787
})
8888
})
8989

90+
describe('imagePreviewStore restoreOutputs', () => {
91+
beforeEach(() => {
92+
setActivePinia(createTestingPinia({ stubActions: false }))
93+
vi.clearAllMocks()
94+
app.nodeOutputs = {}
95+
app.nodePreviewImages = {}
96+
})
97+
98+
it('should keep reactivity after restoreOutputs followed by setNodeOutputsByExecutionId', () => {
99+
const store = useNodeOutputStore()
100+
101+
// Simulate execution: set outputs for node "4" (e.g., PreviewImage)
102+
const executionOutput = createMockOutputs([
103+
{ filename: 'ComfyUI_00001.png', subfolder: '', type: 'temp' }
104+
])
105+
const savedOutputs: Record<string, ExecutedWsMessage['output']> = {
106+
'4': executionOutput
107+
}
108+
109+
// Simulate undo: restoreOutputs makes app.nodeOutputs and the ref
110+
// share the same underlying object if not handled correctly.
111+
store.restoreOutputs(savedOutputs)
112+
113+
expect(store.nodeOutputs['4']).toStrictEqual(executionOutput)
114+
expect(store.nodeOutputs['3']).toBeUndefined()
115+
116+
// Simulate widget callback setting outputs for node "3" (e.g., LoadImage)
117+
const widgetOutput = createMockOutputs([
118+
{ filename: 'example.png', subfolder: '', type: 'input' }
119+
])
120+
store.setNodeOutputsByExecutionId('3', widgetOutput)
121+
122+
// The reactive store must reflect the new output.
123+
// Before the fix, the raw write to app.nodeOutputs would mutate the
124+
// proxy's target before the proxy write, causing Vue to skip the
125+
// reactivity update.
126+
expect(store.nodeOutputs['3']).toStrictEqual(widgetOutput)
127+
expect(app.nodeOutputs['3']).toStrictEqual(widgetOutput)
128+
})
129+
})
130+
90131
describe('imagePreviewStore getPreviewParam', () => {
91132
beforeEach(() => {
92133
setActivePinia(createTestingPinia({ stubActions: false }))

src/stores/imagePreviewStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
365365
outputs: Record<string, ExecutedWsMessage['output']>
366366
) {
367367
app.nodeOutputs = outputs
368-
nodeOutputs.value = outputs
368+
nodeOutputs.value = { ...outputs }
369369
}
370370

371371
function updateNodeImages(node: LGraphNode) {

0 commit comments

Comments
 (0)