fix: prevent IDB write-stall from blocking trip page and sync loop

clearAll() now clears all tables in a transaction instead of calling
offlineDb.delete(), which triggered our versionchange handler and put
Dexie into a broken write state for the rest of the session.

tripRepo.get() gets the same 2 s timeout guard as list() so a stalled
IDB read no longer freezes the trip splash screen.

_doSync wraps each syncTrip() in a 30 s per-trip timeout so a single
stalled write transaction cannot prevent the loop from advancing to
subsequent trips.
This commit is contained in:
jubnl
2026-05-05 22:47:37 +02:00
parent 935d91196b
commit c64101b12a
3 changed files with 52 additions and 5 deletions
+42 -3
View File
@@ -199,7 +199,46 @@ export async function clearBlobCache(): Promise<void> {
/** Wipe the entire offline database (called on logout). */
export async function clearAll(): Promise<void> {
await offlineDb.delete();
// Re-open so subsequent operations don't fail
await offlineDb.open();
// Use table.clear() instead of offlineDb.delete() to avoid triggering the
// versionchange handler (which calls close()), which would put Dexie into a
// broken write state for the remainder of the session.
await offlineDb.transaction(
'rw',
[
offlineDb.trips,
offlineDb.days,
offlineDb.places,
offlineDb.packingItems,
offlineDb.todoItems,
offlineDb.budgetItems,
offlineDb.reservations,
offlineDb.tripFiles,
offlineDb.accommodations,
offlineDb.tripMembers,
offlineDb.tags,
offlineDb.categories,
offlineDb.mutationQueue,
offlineDb.syncMeta,
offlineDb.blobCache,
],
async () => {
await Promise.all([
offlineDb.trips.clear(),
offlineDb.days.clear(),
offlineDb.places.clear(),
offlineDb.packingItems.clear(),
offlineDb.todoItems.clear(),
offlineDb.budgetItems.clear(),
offlineDb.reservations.clear(),
offlineDb.tripFiles.clear(),
offlineDb.accommodations.clear(),
offlineDb.tripMembers.clear(),
offlineDb.tags.clear(),
offlineDb.categories.clear(),
offlineDb.mutationQueue.clear(),
offlineDb.syncMeta.clear(),
offlineDb.blobCache.clear(),
])
},
)
}
+4 -1
View File
@@ -48,7 +48,10 @@ export const tripRepo = {
},
async get(tripId: number | string): Promise<{ trip: Trip; refresh: TripRefresh }> {
const cached = await offlineDb.trips.get(Number(tripId))
const cached = await Promise.race([
offlineDb.trips.get(Number(tripId)).catch(() => undefined),
new Promise<undefined>(resolve => setTimeout(() => resolve(undefined), 2000)),
])
const refresh: TripRefresh = (async () => {
try {
+6 -1
View File
@@ -195,7 +195,12 @@ export const tripSyncManager = {
onProgress?.({ phase: 'trip', tripId: trip.id, index: i, total: toSync.length })
let tripOk = false
try {
await syncTrip(trip.id)
await Promise.race([
syncTrip(trip.id),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('syncTrip timeout')), 30_000)
),
])
tripOk = true
} catch (err) {
if (isQuotaError(err)) {