- TripPlannerPage: change splash effect dep from `trip` (object ref) to
`trip?.id` (primitive) — background refreshes no longer reset the 1500 ms
timer on every new object reference, fixing the forever-splash on SPA nav
- tripRepo.list: await upserts on the cold-IDB path so the next mount reads
from Dexie instead of hitting the network again, fixing the remount skeleton
- tripSyncManager: add stale-flag detection (>2 min resets _syncing), 90 s
hard timeout via Promise.race, parallel post-sync prefetch via
Promise.allSettled, and updated header comment to reflect manual-only policy
- OfflineTab: guard handleResync with a 120 s client-side timeout that
interrupts and clears the spinner if syncAll stalls
QuotaExceededError from a full IndexedDB was being caught by the IIFE's
try/catch (after the earlier await-upsert change), causing repos to return
null/empty even when the network fetch succeeded. Fire-and-forget upserts
with .catch(()=>{}) ensure write failures never suppress fetched data.
navigator.onLine returns false transiently during service worker activation
(skipWaiting + clientsClaim), causing all repo refresh IIFEs to return null
immediately on first page load — leaving the UI with empty data until F5.
Fixes applied across all list repos (trip, day, place, packing, todo, budget,
reservation, accommodation, file):
- Drop navigator.onLine guard; let fetch fail naturally when truly offline
- Await all upsert calls (some were fire-and-forget, risking race conditions
against subsequent reads and silent swallowed failures)
- Return Promise.resolve(null) instead of Promise.resolve(fresh) in the
IDB-empty network path, so loadTrip's background refresh Promise.all
resolves null and skips set({trip}), preventing a spurious reference change
that was resetting the 1500ms splash timer
Tests updated: placeRepo and packingRepo "empty cache" tests now simulate
genuine network failure (HttpResponse.error) instead of relying on the
navigator.onLine guard that no longer exists; DashboardPage tests clear IDB
before each test and use a query-safe assertion after background refresh.
All create/update/delete repo methods now write to IndexedDB optimistically
and fire mutationQueue.flush() as fire-and-forget, returning immediately
without waiting for the network. This eliminates the 8-second UX freeze
previously seen when the API was unreachable but navigator.onLine was true.
- Repos rewritten: trip, day, place, packing, todo, budget, accommodation,
reservation, file — write methods never throw, always return optimistic data
- mutationQueue.flush() changed to iterative (one item per loop iteration)
so mutations enqueued mid-flush (e.g. bulk check-all) are picked up
- fileRepo.toggleStar skips the IDB put when the file is not cached locally
- DayDetailPanel passes place_name into accommodationRepo.create so the
optimistic accommodation renders the correct hotel label immediately
- Test suite updated throughout to reflect optimistic-first semantics:
no more rollback assertions, IDB cleared in component test beforeEach hooks,
FileManager tests switched from filesApi spy to MSW endpoint assertions
Add navigator.onLine guard to SWR refresh IIFEs so background
network calls don't fire in offline mode (prevents fake-IDB leakage
in tests via MSW default handlers).
Fix IDB isolation in affected test files by flushing pending macro
tasks then clearing IDB tables in beforeEach, so stale IDB writes
from previous tests' background IIFEs don't bleed into the next test.
Restore loadBudgetItems and refreshPlaces to apply background refresh
results to store state.
Move tags/categories API calls before the main Promise.all in
loadTrip so MSW handlers resolve during the await window.
navigator.onLine is unreliable on Android — returns true whenever any
network interface is up, regardless of actual reachability. This caused
all repo reads to take the API branch and either wait 5 s for the SW
NetworkFirst timeout (cache hit) or hang indefinitely (cache miss).
- All read repos (list/get) now return cached IndexedDB data instantly
and carry a background refresh promise that resolves to fresh data or
null on failure. Callers that opted in (loadTrip, loadTrips) apply
fresh data silently when it arrives.
- tripStore.loadTrip: Promise.all now reads all 7 resources from
IndexedDB (instant), fires network refreshes in background, sets
isLoading: false immediately, then applies fresh data via a second
Promise.all when ready. Tags/categories use upsertTags/upsertCategories.
- DashboardPage.loadTrips: same pattern — renders from cache instantly,
silently updates trip list on refresh.
- axios timeout set to 8 s so requests can never hang indefinitely.
- SW networkTimeoutSeconds lowered from 5 to 2 as defence in depth.
Add type-selector UI in the file import modal letting users choose which
GPX elements (waypoints, routes, tracks) or KML/KMZ elements (points,
paths) to import. KML LineString placemarks are now imported as path
places with route_geometry.
Performance improvements:
- Extract MemoPlaceRow with React.memo and contentVisibility:auto to cut
unnecessary re-renders in PlacesSidebar
- Add weatherQueue to cap concurrent weather fetches at 3
- Replace sequential per-place deletes with a single bulkDelete API call
(new DELETE /places/bulk endpoint + deletePlacesMany service)
- Memoize atlas/photo/weather service calls to avoid redundant requests
- Add multi-select mode to PlacesSidebar for bulk operations
Add large GPX/KML/KMZ fixtures for integration/perf testing and two
profiler analysis scripts under scripts/.