From 6820633feaf99b6628061cec05e126ad58a9eb3c Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Mon, 8 Dec 2025 13:38:27 -0500 Subject: [PATCH] fix: preserve Vue node reactivity during undo/redo operations (#7222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary preserve Vue node reactivity during undo/redo operations Root Cause: The Vue reactivity chain was broken during undo/redo operations: 1. handleDeleteNode was deleting nodeRefs and nodeTriggers 2. Vue components still held references to the old refs 3. When nodes were recreated, finalizeOperation tried to call triggers but they were already deleted 4. Vue didn't know the data had changed, so nodes didn't visually update fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/7040 ## Screenshots https://github.com/user-attachments/assets/2feb294a-36e8-4bbe-b3f7-b7015066abc5 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7222-fix-preserve-Vue-node-reactivity-during-undo-redo-operations-2c36d73d3650819ab72afb10cbdaf39a) by [Unito](https://www.unito.io) --- src/renderer/core/layout/store/layoutStore.ts | 24 +++++++++++++++---- .../vueNodes/layout/useNodeLayout.ts | 7 +++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/renderer/core/layout/store/layoutStore.ts b/src/renderer/core/layout/store/layoutStore.ts index b6e139e6b..786be0625 100644 --- a/src/renderer/core/layout/store/layoutStore.ts +++ b/src/renderer/core/layout/store/layoutStore.ts @@ -956,6 +956,15 @@ class LayoutStoreImpl implements LayoutStore { return this.currentActor } + /** + * Clean up refs and triggers for a node when its Vue component unmounts. + * This should be called from the component's onUnmounted hook. + */ + cleanupNodeRef(nodeId: NodeId): void { + this.nodeRefs.delete(nodeId) + this.nodeTriggers.delete(nodeId) + } + /** * Initialize store with existing nodes */ @@ -964,8 +973,10 @@ class LayoutStoreImpl implements LayoutStore { ): void { this.ydoc.transact(() => { this.ynodes.clear() - this.nodeRefs.clear() - this.nodeTriggers.clear() + // Note: We intentionally do NOT clear nodeRefs and nodeTriggers here. + // Vue components may already hold references to these refs, and clearing + // them would break the reactivity chain. The refs will be reused when + // nodes are recreated, and stale refs will be cleaned up over time. this.spatialIndex.clear() this.linkSegmentSpatialIndex.clear() this.slotSpatialIndex.clear() @@ -995,6 +1006,9 @@ class LayoutStoreImpl implements LayoutStore { // Add to spatial index this.spatialIndex.insert(layout.id, layout.bounds) }) + + // Trigger all existing refs to notify Vue of the new data + this.nodeTriggers.forEach((trigger) => trigger()) }, 'initialization') } @@ -1085,8 +1099,10 @@ class LayoutStoreImpl implements LayoutStore { if (!this.ynodes.has(operation.nodeId)) return this.ynodes.delete(operation.nodeId) - this.nodeRefs.delete(operation.nodeId) - this.nodeTriggers.delete(operation.nodeId) + // Note: We intentionally do NOT delete nodeRefs and nodeTriggers here. + // During undo/redo, Vue components may still hold references to the old ref. + // If we delete the trigger, Vue won't be notified when the node is re-created. + // The trigger will be called in finalizeOperation to notify Vue of the change. // Remove from spatial index this.spatialIndex.remove(operation.nodeId) diff --git a/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts b/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts index db1ce71e7..3e1341cd8 100644 --- a/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts +++ b/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts @@ -1,4 +1,4 @@ -import { computed, toValue } from 'vue' +import { computed, onUnmounted, toValue } from 'vue' import type { MaybeRefOrGetter } from 'vue' import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' @@ -17,6 +17,11 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter) { // Get the customRef for this node (shared write access) const layoutRef = layoutStore.getNodeLayoutRef(nodeId) + // Clean up refs and triggers when Vue component unmounts + onUnmounted(() => { + layoutStore.cleanupNodeRef(nodeId) + }) + // Computed properties for easy access const position = computed(() => { const layout = layoutRef.value