From 6a70c5b5384f8c059459fedc0ed2384016aec244 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 11 Nov 2025 00:53:55 -0800 Subject: [PATCH] remove coordinate reset --- src/automerge/AutomergeToTLStore.ts | 24 ++++++++++++++++--- src/routes/Board.tsx | 37 +++++++++++++++++++---------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/automerge/AutomergeToTLStore.ts b/src/automerge/AutomergeToTLStore.ts index e667f68..b3267c0 100644 --- a/src/automerge/AutomergeToTLStore.ts +++ b/src/automerge/AutomergeToTLStore.ts @@ -27,6 +27,21 @@ export function applyAutomergePatchesToTLStore( const existingRecord = getRecordFromStore(store, id) + // CRITICAL: For shapes, get coordinates from store's current state BEFORE any patch processing + // This ensures we preserve coordinates even if patches don't include them + // This is especially important when patches come back after store.put operations + let storeCoordinates: { x?: number; y?: number } = {} + if (existingRecord && existingRecord.typeName === 'shape') { + const storeX = (existingRecord as any).x + const storeY = (existingRecord as any).y + if (typeof storeX === 'number' && !isNaN(storeX) && storeX !== null && storeX !== undefined) { + storeCoordinates.x = storeX + } + if (typeof storeY === 'number' && !isNaN(storeY) && storeY !== null && storeY !== undefined) { + storeCoordinates.y = storeY + } + } + // Infer typeName from ID pattern if record doesn't exist let defaultTypeName = 'shape' let defaultRecord: any = { @@ -135,9 +150,12 @@ export function applyAutomergePatchesToTLStore( } // CRITICAL: Store original x and y before patch application to preserve them - // We need to preserve coordinates from existing records to prevent them from being reset - const originalX = (record.typeName === 'shape' && typeof record.x === 'number' && !isNaN(record.x)) ? record.x : undefined - const originalY = (record.typeName === 'shape' && typeof record.y === 'number' && !isNaN(record.y)) ? record.y : undefined + // Priority: Use coordinates from store's current state (most reliable), then from record, then undefined + // This ensures we preserve coordinates even when patches come back after store.put operations + const recordX = (record.typeName === 'shape' && typeof record.x === 'number' && !isNaN(record.x)) ? record.x : undefined + const recordY = (record.typeName === 'shape' && typeof record.y === 'number' && !isNaN(record.y)) ? record.y : undefined + const originalX = storeCoordinates.x !== undefined ? storeCoordinates.x : recordX + const originalY = storeCoordinates.y !== undefined ? storeCoordinates.y : recordY const hadOriginalCoordinates = originalX !== undefined && originalY !== undefined switch (patch.action) { diff --git a/src/routes/Board.tsx b/src/routes/Board.tsx index fcf67e7..185caff 100644 --- a/src/routes/Board.tsx +++ b/src/routes/Board.tsx @@ -378,30 +378,41 @@ export function Board() { console.warn(`📊 Board: ${shapesWithInvalidParent.length} shapes have invalid or missing parentId. Fixing...`) // Fix shapes with invalid parentId by assigning them to current page // CRITICAL: Preserve x and y coordinates when fixing parentId + // This prevents coordinates from being reset when patches come back from Automerge const fixedShapes = shapesWithInvalidParent.map((s: any) => { // Get the shape from store to ensure we have all properties const shapeFromStore = store.store!.get(s.id) if (shapeFromStore && shapeFromStore.typeName === 'shape') { - // Preserve original x and y coordinates - const originalX = s.x !== undefined && typeof s.x === 'number' && !isNaN(s.x) ? s.x : (shapeFromStore as any).x - const originalY = s.y !== undefined && typeof s.y === 'number' && !isNaN(s.y) ? s.y : (shapeFromStore as any).y + // CRITICAL: Get coordinates from store's current state (most reliable) + // This ensures we preserve coordinates even if the shape object has been modified + const storeX = (shapeFromStore as any).x + const storeY = (shapeFromStore as any).y + const originalX = (typeof storeX === 'number' && !isNaN(storeX) && storeX !== null && storeX !== undefined) + ? storeX + : (s.x !== undefined && typeof s.x === 'number' && !isNaN(s.x) ? s.x : 0) + const originalY = (typeof storeY === 'number' && !isNaN(storeY) && storeY !== null && storeY !== undefined) + ? storeY + : (s.y !== undefined && typeof s.y === 'number' && !isNaN(s.y) ? s.y : 0) // Create fixed shape with preserved coordinates const fixed = { ...shapeFromStore, parentId: currentPageId } - if (typeof originalX === 'number' && !isNaN(originalX)) { - (fixed as any).x = originalX - } - if (typeof originalY === 'number' && !isNaN(originalY)) { - (fixed as any).y = originalY - } + // CRITICAL: Always preserve coordinates - never reset to 0,0 unless truly missing + (fixed as any).x = originalX + (fixed as any).y = originalY return fixed as TLRecord } - // Fallback if shape not in store - return { ...s, parentId: currentPageId } as TLRecord + // Fallback if shape not in store - preserve coordinates from s + const fallbackX = (s.x !== undefined && typeof s.x === 'number' && !isNaN(s.x)) ? s.x : 0 + const fallbackY = (s.y !== undefined && typeof s.y === 'number' && !isNaN(s.y)) ? s.y : 0 + return { ...s, parentId: currentPageId, x: fallbackX, y: fallbackY } as TLRecord }) try { - store.store.put(fixedShapes) - console.log(`📊 Board: Fixed ${fixedShapes.length} shapes by assigning them to current page ${currentPageId}`) + // CRITICAL: Use mergeRemoteChanges to prevent feedback loop + // This marks the changes as remote, preventing them from triggering another sync + store.store.mergeRemoteChanges(() => { + store.store.put(fixedShapes) + }) + console.log(`📊 Board: Fixed ${fixedShapes.length} shapes by assigning them to current page ${currentPageId} (coordinates preserved)`) } catch (error) { console.error(`📊 Board: Error fixing shapes with invalid parentId:`, error) }