diff --git a/app/static/js/storage.js b/app/static/js/storage.js index 0fbf03c..76b54da 100644 --- a/app/static/js/storage.js +++ b/app/static/js/storage.js @@ -93,7 +93,14 @@ const Storage = { }, async updatePhoto(id, updates) { - return await this.db.photos.update(id, updates); + const result = await this.db.photos.update(id, updates); + // Notify any listening UI (e.g. queue page) of the change + try { + window.dispatchEvent(new CustomEvent('photo-updated', { + detail: { id: id, updates: updates } + })); + } catch (e) { /* ignore if no window context */ } + return result; }, async deletePhoto(id) { diff --git a/app/static/sw.js b/app/static/sw.js index 968ae06..e6b5724 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-v15'; -const APP_SHELL_CACHE = 'nextsnap-shell-v11'; -const RUNTIME_CACHE = 'nextsnap-runtime-v11'; +const CACHE_VERSION = 'nextsnap-v16'; +const APP_SHELL_CACHE = 'nextsnap-shell-v12'; +const RUNTIME_CACHE = 'nextsnap-runtime-v12'; // Assets to cache on install const APP_SHELL_ASSETS = [ diff --git a/app/templates/queue.html b/app/templates/queue.html index fb5d37e..7d33d26 100644 --- a/app/templates/queue.html +++ b/app/templates/queue.html @@ -411,6 +411,10 @@ async function loadQueue() { } emptyState.style.display = 'none'; + // Revoke old ObjectURLs before rebuilding to prevent memory leaks + queueList.querySelectorAll('img[src^="blob:"]').forEach(img => { + URL.revokeObjectURL(img.src); + }); queueList.innerHTML = ''; // Show active uploads first (newest first) @@ -610,10 +614,23 @@ window.addEventListener('offline', () => { document.getElementById('sync-now-btn').disabled = true; }); -// Refresh queue periodically +// Live updates: listen for photo status changes from the sync engine +let refreshTimer = null; +window.addEventListener('photo-updated', () => { + // Debounce: rapid status transitions (pending→uploading→verified) + // would otherwise trigger multiple full rebuilds + if (refreshTimer) clearTimeout(refreshTimer); + refreshTimer = setTimeout(() => { + refreshTimer = null; + loadQueue(); + updateStats(); + }, 300); +}); + +// Fallback: full refresh every 30s in case events are missed setInterval(() => { loadQueue(); updateStats(); -}, 5000); +}, 30000); {% endblock %}