Compare commits

..

No commits in common. "7e2a69d4a55e8b42833a9319e6dbc930cb68302b" and "a6b3024a9975944c5de99cc469356365227dc4a6" have entirely different histories.

3 changed files with 103 additions and 169 deletions

View File

@ -1,10 +1,9 @@
--- ---
id: task-044 id: task-044
title: Test dev branch UI redesign and Map fixes title: Test dev branch UI redesign and Map fixes
status: Done status: To Do
assignee: [] assignee: []
created_date: '2025-12-07 23:26' created_date: '2025-12-07 23:26'
updated_date: '2025-12-08 01:19'
labels: [] labels: []
dependencies: [] dependencies: []
priority: high priority: high
@ -25,15 +24,3 @@ Test the changes pushed to dev branch in commit 8123f0f
- [ ] #5 Map scroll wheel zooms correctly - [ ] #5 Map scroll wheel zooms correctly
- [ ] #6 Old boards with Map shapes load without validation errors - [ ] #6 Old boards with Map shapes load without validation errors
<!-- AC:END --> <!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Session completed. All changes pushed to dev branch:
- UI redesign: unified top-right menu with grey oval container
- Social Network graph: dark theme with directional arrows
- MI bar: responsive layout (bottom on mobile)
- Map fixes: tool clicks work, scroll zoom works
- Automerge: Map shape schema validation fix
- Network graph: graceful fallback on API errors
<!-- SECTION:NOTES:END -->

View File

@ -1,19 +0,0 @@
---
id: task-045
title: Implement offline-first loading from IndexedDB
status: Done
assignee: []
created_date: '2025-12-08 08:47'
labels:
- bug-fix
- offline
- automerge
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Fixed a bug where the app would hang indefinitely when the server wasn't running because `await adapter.whenReady()` blocked IndexedDB loading. Now the app loads from IndexedDB first (offline-first), then syncs with server in the background with a 5-second timeout.
<!-- SECTION:DESCRIPTION:END -->

View File

@ -312,8 +312,11 @@ export function useAutomergeSync(config: AutomergeSyncConfig): TLStoreWithStatus
const initializeHandle = async () => { const initializeHandle = async () => {
try { try {
// OFFLINE-FIRST: Load from IndexedDB immediately, don't wait for network // CRITICAL: Wait for the network adapter to be ready before creating document
// Network sync happens in the background after local data is loaded // This ensures the WebSocket connection is established for sync
await adapter.whenReady()
if (!mounted) return
let handle: DocHandle<TLStoreSnapshot> let handle: DocHandle<TLStoreSnapshot>
let loadedFromLocal = false let loadedFromLocal = false
@ -351,7 +354,7 @@ export function useAutomergeSync(config: AutomergeSyncConfig): TLStoreWithStatus
const localShapeCount = localDoc?.store ? Object.values(localDoc.store).filter((r: any) => r?.typeName === 'shape').length : 0 const localShapeCount = localDoc?.store ? Object.values(localDoc.store).filter((r: any) => r?.typeName === 'shape').length : 0
if (localRecordCount > 0) { if (localRecordCount > 0) {
console.log(`📦 Loaded document from IndexedDB: ${localRecordCount} records, ${localShapeCount} shapes`) console.log(`Loaded document from IndexedDB: ${localRecordCount} records, ${localShapeCount} shapes`)
// CRITICAL: Migrate local IndexedDB data to fix any invalid indices // CRITICAL: Migrate local IndexedDB data to fix any invalid indices
// This ensures shapes with old-format indices like "b1" are fixed // This ensures shapes with old-format indices like "b1" are fixed
@ -391,46 +394,9 @@ export function useAutomergeSync(config: AutomergeSyncConfig): TLStoreWithStatus
if (!mounted) return if (!mounted) return
// OFFLINE-FIRST: Set the handle and mark as ready BEFORE network sync // Sync with server to get latest data (or upload local changes if offline was edited)
// This allows the UI to render immediately with local data // This ensures we're in sync even if we loaded from IndexedDB
if (handle.url) {
adapter.setDocumentId(handle.url)
console.log(`📋 Set documentId on adapter: ${handle.url}`)
}
// If we loaded from local, set handle immediately so UI can render
if (loadedFromLocal) {
const localDoc = handle.doc() as any
const localShapeCount = localDoc?.store ? Object.values(localDoc.store).filter((r: any) => r?.typeName === 'shape').length : 0
console.log(`📴 Offline-ready: ${localShapeCount} shapes available from IndexedDB`)
setHandle(handle)
setIsLoading(false)
}
// Sync with server in the background (non-blocking for offline-first)
// This runs in parallel - if it fails, we still have local data
const syncWithServer = async () => {
try { try {
// Wait for network adapter with a timeout
const networkReadyPromise = adapter.whenReady()
const timeoutPromise = new Promise<'timeout'>((resolve) =>
setTimeout(() => resolve('timeout'), 5000)
)
const result = await Promise.race([networkReadyPromise, timeoutPromise])
if (result === 'timeout') {
console.log(`⏱️ Network adapter timeout - continuing in offline mode`)
// If we haven't set the handle yet (no local data), set it now
if (!loadedFromLocal && mounted) {
setHandle(handle)
setIsLoading(false)
}
return
}
if (!mounted) return
const response = await fetch(`${workerUrl}/room/${roomId}`) const response = await fetch(`${workerUrl}/room/${roomId}`)
if (response.ok) { if (response.ok) {
let serverDoc = await response.json() as TLStoreSnapshot let serverDoc = await response.json() as TLStoreSnapshot
@ -489,7 +455,7 @@ export function useAutomergeSync(config: AutomergeSyncConfig): TLStoreWithStatus
const finalDoc = handle.doc() const finalDoc = handle.doc()
const finalRecordCount = finalDoc?.store ? Object.keys(finalDoc.store).length : 0 const finalRecordCount = finalDoc?.store ? Object.keys(finalDoc.store).length : 0
console.log(`🔄 Merged server data: server had ${serverRecordCount}, local had ${localRecordCount}, final has ${finalRecordCount} records`) console.log(`Merged server data: server had ${serverRecordCount}, local had ${localRecordCount}, final has ${finalRecordCount} records`)
} else if (!loadedFromLocal) { } else if (!loadedFromLocal) {
// Server is empty and we didn't load from local - fresh start // Server is empty and we didn't load from local - fresh start
console.log(`Starting fresh - no data on server or locally`) console.log(`Starting fresh - no data on server or locally`)
@ -507,7 +473,7 @@ export function useAutomergeSync(config: AutomergeSyncConfig): TLStoreWithStatus
} catch (error) { } catch (error) {
// Network error - continue with local data if available // Network error - continue with local data if available
if (loadedFromLocal) { if (loadedFromLocal) {
console.log(`📴 Offline mode: using local data from IndexedDB`) console.log(`Offline mode: using local data from IndexedDB`)
} else { } else {
console.error("Error loading from server (offline?):", error) console.error("Error loading from server (offline?):", error)
} }
@ -517,18 +483,18 @@ export function useAutomergeSync(config: AutomergeSyncConfig): TLStoreWithStatus
const finalDoc = handle.doc() as any const finalDoc = handle.doc() as any
const finalStoreKeys = finalDoc?.store ? Object.keys(finalDoc.store).length : 0 const finalStoreKeys = finalDoc?.store ? Object.keys(finalDoc.store).length : 0
const finalShapeCount = finalDoc?.store ? Object.values(finalDoc.store).filter((r: any) => r?.typeName === 'shape').length : 0 const finalShapeCount = finalDoc?.store ? Object.values(finalDoc.store).filter((r: any) => r?.typeName === 'shape').length : 0
console.log(`✅ Automerge handle ready: ${finalStoreKeys} records, ${finalShapeCount} shapes (loaded from ${loadedFromLocal ? 'IndexedDB' : 'server/new'})`) console.log(`Automerge handle ready: ${finalStoreKeys} records, ${finalShapeCount} shapes (loaded from ${loadedFromLocal ? 'IndexedDB' : 'server/new'})`)
// CRITICAL: Set the documentId on the adapter BEFORE setHandle
// This ensures the adapter can properly route incoming binary sync messages
// The server may send sync messages immediately after connection, before we send anything
if (handle.url) {
adapter.setDocumentId(handle.url)
console.log(`📋 Set documentId on adapter: ${handle.url}`)
}
// If we haven't set the handle yet (no local data), set it now after server sync
if (!loadedFromLocal && mounted) {
setHandle(handle) setHandle(handle)
setIsLoading(false) setIsLoading(false)
}
}
// Start server sync in background (don't await - non-blocking)
syncWithServer()
} catch (error) { } catch (error) {
console.error("Error initializing Automerge handle:", error) console.error("Error initializing Automerge handle:", error)
if (mounted) { if (mounted) {