diff --git a/client/src/db/offlineDb.ts b/client/src/db/offlineDb.ts index 0726eb27..21da8a7c 100644 --- a/client/src/db/offlineDb.ts +++ b/client/src/db/offlineDb.ts @@ -68,6 +68,13 @@ class TrekOfflineDb extends Dexie { constructor() { super('trek-offline'); + // When the database is deleted externally (e.g. DevTools "Clear site data" + // while the tab is open), IDB fires versionchange on the open connection. + // Without an explicit close() here, Dexie keeps the stale connection alive + // and subsequent write transactions queue behind it indefinitely. Closing + // forces Dexie to auto-reopen on the next operation with a fresh connection. + this.on('versionchange', () => { this.close() }) + this.version(1).stores({ trips: 'id', days: 'id, trip_id', diff --git a/client/src/repo/tripRepo.ts b/client/src/repo/tripRepo.ts index 28d0918e..2726305e 100644 --- a/client/src/repo/tripRepo.ts +++ b/client/src/repo/tripRepo.ts @@ -8,10 +8,11 @@ type TripRefresh = Promise<{ trip: Trip } | null> export const tripRepo = { async list(): Promise<{ trips: Trip[]; archivedTrips: Trip[]; refresh: TripsRefresh }> { - // 2-second guard: if Dexie is in a bad state (e.g. externally deleted while tab - // was open), toArray() may hang. Fall back to the cold/network path. + // Guard: if Dexie is in a bad state (e.g. externally deleted while tab was + // open and the versionchange close() races with this read), fall back to the + // cold/network path rather than throwing or hanging. const all = await Promise.race([ - offlineDb.trips.toArray(), + offlineDb.trips.toArray().catch(() => [] as Trip[]), new Promise(resolve => setTimeout(() => resolve([]), 2000)), ])