T5: Offline queue + background sync (commit 37ba54b)

2026-05-21 12:04:11 -04:00
parent 626160beb6
commit 6d501c69ff
+11
@@ -24,6 +24,17 @@ Mobile-first web app for tracking physical assets via barcode scanning, OCR stic
- Server: `ConnectLabelRequest` Pydantic model, `connect_label()` async endpoint
- Commit: 6229966
### T5 Offline Queue + Background Sync — May 2026
- **Service Worker** (`static/sw.js`): Caches app shell (index.html) + CDN deps (Leaflet, ZXing) on install. Network-first with cache fallback for API GET calls — offline reads still work from cache. POST/PUT/DELETE pass through to the main thread. Cache version: `canteen-v2`.
- **IndexedDB offline queue** (`CanteenOfflineDB`): `queueOfflineCreate(assetPayload, photoBlob)` stores asset creation payloads with GPS data and photo blobs. Queue items tracked with status: pending/syncing/done/failed. Retry limit of 5.
- **Sync processing**: `processOfflineQueue()` replays pending/failed items when back online — uploads photo, creates asset via API, creates check-in. Auto-refreshes asset list on success.
- **Online/offline detection**: `window.addEventListener("online"/"offline")` with amber banner showing "📡 Offline mode — changes will sync when connected". Banner auto-hides when connectivity restored and triggers sync.
- **Queue status indicator**: Header badge showing pending item count. Clickable to manually trigger sync. Syncing animation (accent color).
- **Conflict handling**: Duplicate/uniqueness errors from the server mark queue items as 'done' (not 'failed') to prevent retry loops.
- **API wrapper**: `window.api` function intercepts POST to `/api/assets` when offline, queues the payload instead of failing.
- **Fix**: Missing `#offlineBanner` HTML element was added (CSS + JS referenced it but the DOM element didn't exist). `queueCountBadge` span inside the banner shows pending count.
- Commit: 37ba54b
### T3 EXIF GPS Extraction from Photo — May 2026
- **exifr.js CDN**: Loaded `exifr@7/dist/lite.umd.js` in `<head>` for JPEG EXIF GPS reading
- **`extractGpsFromPhoto(file)`**: Async function using `exifr.gps()`, returns `{lat, lng, accuracy}` or null, catches errors gracefully