[backport core/1.33] fix: preserve Vue node reactivity during undo/redo operations (#7257)

Backport of #7222 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7257-backport-core-1-33-fix-preserve-Vue-node-reactivity-during-undo-redo-operations-2c46d73d36508184aff1c7c0e8e82408)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
This commit is contained in:
Comfy Org PR Bot
2025-12-09 09:55:29 +09:00
committed by GitHub
parent 195d779035
commit b17ab15e90
2 changed files with 26 additions and 5 deletions

View File

@@ -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)

View File

@@ -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<string>) {
// 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