fix(offline): start the tile prefetch once the app is idle

syncAll runs straight off the back of a successful login, while the app
is still mounting its first screen. Kicking off a bulk background
download at that exact moment is the worst possible timing even with the
prefetch throttled, so hold the tiles back until requestIdleCallback
says the browser has nothing better to do.

Files keep their old behaviour — they are a handful of requests, not
thousands.
This commit is contained in:
Maurice
2026-07-26 00:21:06 +02:00
committed by Maurice
parent 3c67afeade
commit 828c1db489
+27 -7
View File
@@ -56,6 +56,17 @@ function todayStr(): string {
return new Date().toISOString().slice(0, 10)
}
/**
* Run work once the browser has nothing better to do, with a ceiling so it
* still happens on browsers without requestIdleCallback (Safari) or in a tab
* that never goes idle.
*/
function whenIdle(fn: () => void | Promise<void>): void {
const run = () => { void Promise.resolve(fn()).catch(console.error) }
if (typeof requestIdleCallback === 'function') requestIdleCallback(run, { timeout: 10_000 })
else setTimeout(run, 2_000)
}
function shouldCache(trip: Trip): boolean {
if (!trip.end_date) return true // no end date → cache forever
return trip.end_date >= todayStr() // ongoing or future
@@ -195,11 +206,20 @@ export const tripSyncManager = {
for (const trip of toSync) {
const files = await offlineDb.tripFiles.where('trip_id').equals(trip.id).toArray()
cacheFilesForTrip(files).catch(console.error)
}
if (cacheTiles) {
const places = await offlineDb.places.where('trip_id').equals(trip.id).toArray()
prefetchTilesForTrip(trip.id, places, tileUrl).catch(console.error)
}
// Map tiles last, and only once the browser goes idle. syncAll runs right
// after login, where the app is still mounting the first screen — starting
// a bulk tile download into that leaves the UI waiting behind our own
// background traffic.
if (cacheTiles) {
whenIdle(async () => {
for (const trip of toSync) {
if (!isAuthed() || !navigator.onLine) return
const places = await offlineDb.places.where('trip_id').equals(trip.id).toArray()
await prefetchTilesForTrip(trip.id, places, tileUrl).catch(console.error)
}
})
}
} finally {
_syncing = false
@@ -209,9 +229,9 @@ export const tripSyncManager = {
/**
* "Prepare for offline" (#1135 ask 1): a fully-awaited sync the user runs while
* still online so everything they need is guaranteed on-device before they go
* offline. Unlike syncAll, this AWAITS file-blob and map-tile downloads and
* reports progress, so the UI can show a real completion state instead of
* resolving the moment the requests are merely dispatched.
* offline. Unlike syncAll it awaits the file-blob and map-tile downloads up
* front instead of deferring them to idle time, reports progress, and forces
* the tile prefetch to run even for a bbox we believe is already cached.
*
* Returns the number of trips prepared.
*/