From 0bea258d391d757923eaafe73be0d9292fa4d0a8 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 10 Nov 2025 23:17:16 -0800 Subject: [PATCH] preserve coordinates --- src/automerge/AutomergeToTLStore.ts | 12 ++- src/automerge/useAutomergeStoreV2.ts | 125 ++------------------------- src/routes/Board.tsx | 70 ++------------- 3 files changed, 25 insertions(+), 182 deletions(-) diff --git a/src/automerge/AutomergeToTLStore.ts b/src/automerge/AutomergeToTLStore.ts index 9653099..ad6a3f4 100644 --- a/src/automerge/AutomergeToTLStore.ts +++ b/src/automerge/AutomergeToTLStore.ts @@ -360,9 +360,15 @@ export function sanitizeRecord(record: any): TLRecord { // For shapes, only ensure basic required fields exist if (sanitized.typeName === 'shape') { // Ensure required shape fields exist - // CRITICAL: Check for non-number, null, undefined, or NaN values - if (typeof sanitized.x !== 'number' || sanitized.x === null || isNaN(sanitized.x)) sanitized.x = 0 - if (typeof sanitized.y !== 'number' || sanitized.y === null || isNaN(sanitized.y)) sanitized.y = 0 + // CRITICAL: Only set defaults if coordinates are truly missing or invalid + // DO NOT overwrite valid coordinates (including 0, which is a valid position) + // Only set to 0 if the value is undefined, null, or NaN + if (sanitized.x === undefined || sanitized.x === null || (typeof sanitized.x === 'number' && isNaN(sanitized.x))) { + sanitized.x = 0 + } + if (sanitized.y === undefined || sanitized.y === null || (typeof sanitized.y === 'number' && isNaN(sanitized.y))) { + sanitized.y = 0 + } if (typeof sanitized.rotation !== 'number') sanitized.rotation = 0 if (typeof sanitized.isLocked !== 'boolean') sanitized.isLocked = false if (typeof sanitized.opacity !== 'number') sanitized.opacity = 1 diff --git a/src/automerge/useAutomergeStoreV2.ts b/src/automerge/useAutomergeStoreV2.ts index 6f6cf10..8b4d144 100644 --- a/src/automerge/useAutomergeStoreV2.ts +++ b/src/automerge/useAutomergeStoreV2.ts @@ -368,45 +368,9 @@ export function useAutomergeStoreV2({ if (existingStoreShapes.length > 0) { console.log(`✅ Store already populated from patches (${existingStoreShapes.length} shapes) - using patch-based loading like dev`) - // CRITICAL: Force editor to see shapes by refreshing them multiple times - // Sometimes the editor needs multiple updates to detect shapes - const refreshShapes = (attempt: number) => { - // CRITICAL: Preserve x and y coordinates when refreshing shapes - const shapesToRefresh = existingStoreShapes.map(s => { - const shapeFromStore = store.get(s.id) - if (shapeFromStore && shapeFromStore.typeName === 'shape') { - // Preserve original x and y from the store shape data - // Type assertion needed because s is TLRecord but we know it's a shape - const sAny = s as any - const shapeFromStoreAny = shapeFromStore as any - const originalX = sAny.x !== undefined && typeof sAny.x === 'number' && !isNaN(sAny.x) ? sAny.x : shapeFromStoreAny.x - const originalY = sAny.y !== undefined && typeof sAny.y === 'number' && !isNaN(sAny.y) ? sAny.y : shapeFromStoreAny.y - - // Ensure x and y are preserved - if (typeof originalX === 'number' && !isNaN(originalX) && typeof originalY === 'number' && !isNaN(originalY)) { - return { ...shapeFromStore, x: originalX, y: originalY } as TLRecord - } - return shapeFromStore - } - return null - }).filter((s): s is TLRecord => s !== null && s.typeName === 'shape') - - if (shapesToRefresh.length > 0) { - store.mergeRemoteChanges(() => { - // Re-put shapes to trigger editor update - store.put(shapesToRefresh) - }) - console.log(`📊 Refreshed ${shapesToRefresh.length} existing shapes (attempt ${attempt}) to ensure editor visibility`) - - // Try multiple times to ensure editor picks them up - if (attempt < 3) { - setTimeout(() => refreshShapes(attempt + 1), 200) - } - } - } - - // Start refreshing after a short delay - setTimeout(() => refreshShapes(1), 100) + // REMOVED: Aggressive shape refresh that was causing coordinate loss + // Shapes should be visible through normal patch application + // If shapes aren't visible, it's likely a different issue that refresh won't fix setStoreWithStatus({ store, @@ -435,45 +399,8 @@ export function useAutomergeStoreV2({ if (currentShapes.length > 0) { console.log(`✅ Patches applied successfully: ${currentShapes.length} shapes loaded via patches`) - // CRITICAL: Force editor to see shapes by refreshing them multiple times - // Sometimes the editor needs multiple updates to detect shapes - const refreshShapes = (attempt: number) => { - // CRITICAL: Preserve x and y coordinates when refreshing shapes - const shapesToRefresh = currentShapes.map(s => { - const shapeFromStore = store.get(s.id) - if (shapeFromStore && shapeFromStore.typeName === 'shape') { - // Preserve original x and y from the store shape data - // Type assertion needed because s is TLRecord but we know it's a shape - const sAny = s as any - const shapeFromStoreAny = shapeFromStore as any - const originalX = sAny.x !== undefined && typeof sAny.x === 'number' && !isNaN(sAny.x) ? sAny.x : shapeFromStoreAny.x - const originalY = sAny.y !== undefined && typeof sAny.y === 'number' && !isNaN(sAny.y) ? sAny.y : shapeFromStoreAny.y - - // Ensure x and y are preserved - if (typeof originalX === 'number' && !isNaN(originalX) && typeof originalY === 'number' && !isNaN(originalY)) { - return { ...shapeFromStore, x: originalX, y: originalY } as TLRecord - } - return shapeFromStore - } - return null - }).filter((s): s is TLRecord => s !== null && s.typeName === 'shape') - - if (shapesToRefresh.length > 0) { - store.mergeRemoteChanges(() => { - // Re-put shapes to trigger editor update - store.put(shapesToRefresh) - }) - console.log(`📊 Refreshed ${shapesToRefresh.length} shapes (attempt ${attempt}) to ensure editor visibility`) - - // Try multiple times to ensure editor picks them up - if (attempt < 3) { - setTimeout(() => refreshShapes(attempt + 1), 200) - } - } - } - - // Start refreshing after a short delay - setTimeout(() => refreshShapes(1), 100) + // REMOVED: Aggressive shape refresh that was causing coordinate loss + // Shapes loaded via patches should be visible without forced refresh setStoreWithStatus({ store, @@ -534,46 +461,8 @@ export function useAutomergeStoreV2({ }) console.log(`✅ Applied ${allRecords.length} records directly to store (fallback for missed patches - works in dev and production)`) - // CRITICAL: Force editor to see shapes by refreshing them multiple times - // Sometimes the editor needs multiple updates to detect shapes - const refreshShapes = (attempt: number) => { - const shapes = store.allRecords().filter((r: any) => r.typeName === 'shape') - // CRITICAL: Preserve x and y coordinates when refreshing shapes - const shapesToRefresh = shapes.map(s => { - const shapeFromStore = store.get(s.id) - if (shapeFromStore && shapeFromStore.typeName === 'shape') { - // Preserve original x and y from the store shape data - // Type assertion needed because s is TLRecord but we know it's a shape - const sAny = s as any - const shapeFromStoreAny = shapeFromStore as any - const originalX = sAny.x !== undefined && typeof sAny.x === 'number' && !isNaN(sAny.x) ? sAny.x : shapeFromStoreAny.x - const originalY = sAny.y !== undefined && typeof sAny.y === 'number' && !isNaN(sAny.y) ? sAny.y : shapeFromStoreAny.y - - // Ensure x and y are preserved - if (typeof originalX === 'number' && !isNaN(originalX) && typeof originalY === 'number' && !isNaN(originalY)) { - return { ...shapeFromStore, x: originalX, y: originalY } as TLRecord - } - return shapeFromStore - } - return null - }).filter((s): s is TLRecord => s !== null && s.typeName === 'shape') - - if (shapesToRefresh.length > 0) { - store.mergeRemoteChanges(() => { - // Re-put shapes to trigger editor update - store.put(shapesToRefresh) - }) - console.log(`📊 Refreshed ${shapesToRefresh.length} shapes (attempt ${attempt}) to ensure editor visibility`) - - // Try multiple times to ensure editor picks them up - if (attempt < 3) { - setTimeout(() => refreshShapes(attempt + 1), 200) - } - } - } - - // Start refreshing after a short delay - setTimeout(() => refreshShapes(1), 100) + // REMOVED: Aggressive shape refresh that was causing coordinate loss + // Shapes loaded directly should be visible without forced refresh } } catch (error) { console.error(`❌ Error applying records directly:`, error) diff --git a/src/routes/Board.tsx b/src/routes/Board.tsx index e139e98..a5b46fd 100644 --- a/src/routes/Board.tsx +++ b/src/routes/Board.tsx @@ -282,34 +282,12 @@ export function Board() { console.log(`📊 Board: Shape parent ID distribution:`, Array.from(parentIdCounts.entries())) } - // CRITICAL: If shapes are in store but editor sees 0, force refresh - // This handles the case where old bulk loading code doesn't refresh shapes - if (storeShapes.length > 0 && editorShapes.length === 0 && storeShapesOnCurrentPage.length > 0 && store.store) { - console.warn(`⚠️ Board: ${storeShapes.length} shapes in store (${storeShapesOnCurrentPage.length} on current page) but editor sees 0. Forcing refresh...`) - - // Force refresh by re-putting shapes with mergeRemoteChanges - // CRITICAL: Preserve x and y coordinates when refreshing shapes - const shapesToRefresh = storeShapesOnCurrentPage.map((s: any) => { - const shapeFromStore = store.store!.get(s.id) - if (shapeFromStore && shapeFromStore.typeName === 'shape') { - // Preserve original x and y from the store shape data - 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 - - // Ensure x and y are preserved - if (typeof originalX === 'number' && !isNaN(originalX) && typeof originalY === 'number' && !isNaN(originalY)) { - return { ...shapeFromStore, x: originalX, y: originalY } as TLRecord - } - return shapeFromStore - } - return null - }).filter((s): s is TLRecord => s !== null && s.typeName === 'shape') - if (shapesToRefresh.length > 0) { - store.store.mergeRemoteChanges(() => { - store.store!.put(shapesToRefresh) - }) - console.log(`🔄 Board: Forced refresh of ${shapesToRefresh.length} shapes to make editor see them`) - } + // REMOVED: Aggressive force refresh that was causing coordinate loss + // If shapes are in store but editor doesn't see them, it's likely a different issue + // Forcing refresh by re-putting was resetting coordinates to 0,0 + if (storeShapes.length > 0 && editorShapes.length === 0 && storeShapesOnCurrentPage.length > 0) { + console.warn(`⚠️ Board: ${storeShapes.length} shapes in store (${storeShapesOnCurrentPage.length} on current page) but editor sees 0. This may indicate a sync issue.`) + // Don't force refresh - it was causing coordinate loss } // Check if there are shapes in store on current page that editor can't see @@ -343,39 +321,9 @@ export function Board() { // Shapes don't exist in editor - might be a sync issue console.error(`📊 Board: ${missingShapes.length} shapes are in store but don't exist in editor - possible sync issue`) - // Try to force a refresh by updating the store - // This might help if shapes are stuck in a validation error state - console.log(`📊 Board: Attempting to refresh store to make shapes visible`) - try { - // Force a store update by reading and re-putting the shapes - if (!store.store) return - - const currentStore = store.store - const shapesToRefresh = missingShapes.slice(0, 10) // Limit to first 10 to avoid performance issues - // CRITICAL: Preserve x and y coordinates when refreshing shapes - const refreshedShapes = shapesToRefresh.map((s: any) => { - const shapeFromStore = currentStore.get(s.id) - if (shapeFromStore && shapeFromStore.typeName === 'shape') { - // Preserve original x and y from the store shape data - 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 - - // Ensure x and y are preserved - if (typeof originalX === 'number' && !isNaN(originalX) && typeof originalY === 'number' && !isNaN(originalY)) { - return { ...shapeFromStore, x: originalX, y: originalY } as TLRecord - } - return shapeFromStore - } - return null - }).filter((s): s is TLRecord => s !== null && s.typeName === 'shape') - - if (refreshedShapes.length > 0) { - console.log(`📊 Board: Refreshing ${refreshedShapes.length} shapes in store`) - currentStore.put(refreshedShapes) - } - } catch (error) { - console.error(`📊 Board: Error refreshing shapes:`, error) - } + // REMOVED: Force refresh that was causing coordinate loss + // Re-putting shapes was resetting coordinates to 0,0 + console.log(`📊 Board: ${missingShapes.length} shapes are in store but not visible in editor - this may indicate a sync issue`) } // Check if shapes are outside viewport