diff --git a/src/renderer/core/layout/store/layoutStore.ts b/src/renderer/core/layout/store/layoutStore.ts index b6e139e6bc..786be06256 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 db1ce71e72..3e1341cd85 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