When offline, hide Files and Admin from the bottom nav and display
"Offline Mode" next to the status dot. The SW offline fallback page
also only shows Capture and Queue nav items. Online-only pages
(browser, admin, reviewer) are never cached and go straight to the
offline fallback when the network is unavailable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The SW was served from /static/sw.js with default scope /static/,
so it only intercepted static asset requests. Page navigations to
/capture, /queue, /browser were not handled by the SW at all —
Safari showed its native offline error.
Fix: serve SW from /sw.js route with Service-Worker-Allowed: / header
and register with scope: /. Now the SW intercepts all navigations and
serves the offline fallback page when the network is unavailable.
Also remove auth-protected page routes from precache (they would cache
the login redirect). Pages are cached via network-first on visit instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add /capture, /queue, /browser to SW precache so these pages work
offline without needing a prior visit. Capture and queue are fully
functional offline since they use local IndexedDB.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Safari intercepts bare 503 responses and shows its native "not connected"
error. Return a styled HTML offline page with status 200 so the SW handles
it instead of Safari.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The service worker was using cache-first for ALL non-API routes,
including page routes like /queue, /capture, /browser. This meant
the browser kept serving old cached HTML with old inline JS even
after deploys, which is why the queue file list never updated
(old JS was running, silently crashing on API mismatches).
Now only /static/ assets use cache-first (they're versioned via
SW cache bumps). All pages and API calls use network-first with
cache as offline fallback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three fixes for queue list not updating:
1. Safe getBlob fallback - works even if old storage.js is cached
by the service worker (no Storage.getBlob method)
2. Include 'uploaded' status in active filter - photos briefly in
uploaded state (between upload and verify) were invisible
3. Wrap all interval refreshes in try/catch so one error doesn't
silently kill all future updates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous change extended the poll from 5s to 30s relying on
photo-updated events, but the service worker may still serve the old
storage.js without event dispatch, leaving the list stale.
Now polls every 3s while there are active uploads (pending/uploading),
15s fallback when idle, plus event-based refresh as a bonus.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Storage.updatePhoto() now fires a 'photo-updated' CustomEvent so the
queue page refreshes immediately (300ms debounce) when the sync engine
changes a photo's status, instead of waiting for the 5s poll.
Also reduces background poll to 30s (just a fallback now), and revokes
stale ObjectURLs on each rebuild to prevent memory leaks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
iOS Safari evicts Blob file-backed data from IndexedDB under memory
pressure, causing upload POSTs to throw 'Load failed' without ever
reaching the server. Two-pronged fix:
1. Store photos as ArrayBuffer (inline bytes) instead of Blob (file
reference) in IndexedDB — ArrayBuffers are not subject to eviction
2. Request navigator.storage.persist() to signal the browser not to
evict our storage under pressure
Also adds Storage.getBlob() helper for converting stored ArrayBuffer
back to Blob at upload/display time, with backward compat for any
existing Blob-stored photos.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
iOS Safari can evict blob data from IndexedDB while keeping the Blob
reference intact (instanceof/size checks pass but actual data read
fails). This caused upload POST to throw 'Load failed' client-side,
never reaching the server, burning retries endlessly.
Changes:
- Add validateBlobReadable() that reads first byte to verify data access
- If blob is unreadable, mark as 'failed' immediately with clear message
- Re-read photo from IndexedDB before upload for fresh blob reference
- Capture detailed error name/type in catch for better diagnostics
- Bump SW cache versions to v14/v10 to force new code delivery
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Offline-first photo capture app for Nextcloud with:
- Camera capture with continuous mode (auto-reopens after each photo)
- File browser with fullscreen image gallery, swipe navigation, and rename
- Upload queue with background sync engine
- Admin panel for Nextcloud user management
- Service worker for offline-first caching (v13)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>