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>
The sync engine was adding per-photo backoff delays (2-60s) inside
the processing loop, during which the user would navigate to another
page (browser -> queue -> browser), killing the sync mid-wait. The
upload POST never fired because the delay ran out the clock.
Changes:
- Remove per-photo backoff delay from processQueue loop — uploads
start immediately on page load without blocking
- Move retry scheduling to after the full queue pass: if any uploads
failed, schedule a new triggerSync() after 15s instead of blocking
inline
- Keep the duplicate check on retries (fast, catches interrupted
uploads that actually succeeded server-side)
- uploadPhoto now returns true/false so processQueue can track
failures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
iOS Safari kills fetch requests when the app goes to background or
the connection drops during large uploads, producing 'load failed'.
The sync engine was burning through all 5 retries instantly with no
delay, so a transient failure became permanent.
Changes:
- Add AbortController timeout (120s) on upload fetch
- Add exponential backoff between retries (2s, 5s, 10s...60s)
- Increase max retries from 5 to 15 for flaky mobile networks
- Remove 10MB resize step that was re-compressing photos already
sized at capture time, avoiding extra memory pressure on iOS
- Log photo size with each upload attempt for easier debugging
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After renaming a file, loadDirectory() was called which rebuilt the
galleryImages array in new alphabetical order. The gallery index
didn't change, so it now pointed to a different file — subsequent
renames hit the wrong file.
Fix: only update local state during the gallery session, defer the
file list refresh to when the gallery closes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Filenames now include milliseconds (e.g. kamaji_20260207_143022347.jpg)
so rapid consecutive captures don't produce identical names and
overwrite each other on Nextcloud.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds an "Upload from Library" button below the camera button
that opens the device photo picker (no capture attribute) with
multi-select support. Selected photos are converted to JPEG and
queued for upload, with a progress counter during processing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous approach used setTimeout to re-trigger the file input
after each capture, which iOS Safari blocks because it loses the
user gesture context.
Now uses getUserMedia for a fullscreen camera viewfinder that stays
open between shots. Shutter button captures frames from the video
stream, saves to IndexedDB, and the camera remains open until the
user taps close. Includes flash feedback, session counter, and
camera flip. Falls back to single-shot file input if getUserMedia
is unavailable.
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>