diff --git a/app/static/sw.js b/app/static/sw.js index 6b0a85d..4c110ee 100644 --- a/app/static/sw.js +++ b/app/static/sw.js @@ -1,9 +1,9 @@ // NextSnap Service Worker // Provides offline-first caching for the app shell -const CACHE_VERSION = 'nextsnap-v17'; -const APP_SHELL_CACHE = 'nextsnap-shell-v13'; -const RUNTIME_CACHE = 'nextsnap-runtime-v13'; +const CACHE_VERSION = 'nextsnap-v18'; +const APP_SHELL_CACHE = 'nextsnap-shell-v14'; +const RUNTIME_CACHE = 'nextsnap-runtime-v14'; // Assets to cache on install const APP_SHELL_ASSETS = [ diff --git a/app/templates/queue.html b/app/templates/queue.html index 9d95fd4..744dc59 100644 --- a/app/templates/queue.html +++ b/app/templates/queue.html @@ -383,6 +383,15 @@ const currentUsername = '{{ username }}'; let deletePhotoId = null; +// Safe blob accessor — works even if old storage.js is cached without getBlob() +function safeGetBlob(photo) { + if (!photo || !photo.blob) return null; + if (typeof Storage.getBlob === 'function') return Storage.getBlob(photo); + if (photo.blob instanceof Blob) return photo.blob; + // ArrayBuffer stored by new storage.js but old storage.js loaded (no getBlob) + try { return new Blob([photo.blob], { type: 'image/jpeg' }); } catch (e) { return null; } +} + // Initialize storage and load queue Storage.init().then(() => { SyncEngine.init(currentUsername); @@ -398,9 +407,11 @@ async function loadQueue() { .where('username').equals(currentUsername) .sortBy('timestamp'); - // Split into active (pending/uploading) and completed (verified/failed) - const activePhotos = photos.filter(p => p.status === 'pending' || p.status === 'uploading'); - const completedPhotos = photos.filter(p => p.status === 'verified' || p.status === 'failed'); + // Split into active and completed + const activePhotos = photos.filter(p => + p.status === 'pending' || p.status === 'uploading' || p.status === 'uploaded'); + const completedPhotos = photos.filter(p => + p.status === 'verified' || p.status === 'failed'); if (photos.length === 0) { emptyState.style.display = 'block'; @@ -446,7 +457,7 @@ function createQueueItem(photo, isCompleted) { item.classList.add('completed'); } - if (photo.status === 'uploading') { + if (photo.status === 'uploading' || photo.status === 'uploaded') { item.classList.add('uploading'); } else if (photo.status === 'verified') { item.classList.add('verified'); @@ -457,7 +468,7 @@ function createQueueItem(photo, isCompleted) { // Create thumbnail (use placeholder if blob was stripped) const thumbnail = document.createElement('div'); thumbnail.className = 'queue-item-thumbnail'; - const photoBlob = Storage.getBlob(photo); + const photoBlob = safeGetBlob(photo); if (photoBlob && photoBlob.size > 0) { const img = document.createElement('img'); img.src = URL.createObjectURL(photoBlob); @@ -558,7 +569,7 @@ async function updateStats() { const pendingCount = photos.filter(p => p.status === 'pending').length; const uploadingCount = photos.filter(p => p.status === 'uploading').length; const totalSize = photos - .map(p => Storage.getBlob(p)) + .map(p => safeGetBlob(p)) .filter(b => b && b.size) .reduce((sum, b) => sum + b.size, 0) / 1024 / 1024; @@ -621,25 +632,24 @@ function scheduleRefresh() { if (refreshTimer) clearTimeout(refreshTimer); refreshTimer = setTimeout(() => { refreshTimer = null; - loadQueue(); - updateStats(); + refreshUI(); }, 300); } window.addEventListener('photo-updated', scheduleRefresh); +// Safe refresh wrapper — prevents silent failures from killing updates +async function refreshUI() { + try { await loadQueue(); } catch (e) { console.error('[QUEUE] loadQueue error:', e); } + try { await updateStats(); } catch (e) { console.error('[QUEUE] updateStats error:', e); } +} + // Poll every 3s while uploads are active, 15s when idle let hasActiveUploads = true; // assume active until first check setInterval(() => { - if (hasActiveUploads) { - loadQueue(); - updateStats(); - } + if (hasActiveUploads) refreshUI(); }, 3000); // Slower fallback poll for idle state -setInterval(() => { - loadQueue(); - updateStats(); -}, 15000); +setInterval(refreshUI, 15000); {% endblock %}