From ae9ab596bbc5affe2cafc04b37e962e4f1a46776 Mon Sep 17 00:00:00 2001 From: Keith Smith Date: Sat, 5 Sep 2026 00:05:27 -0600 Subject: [PATCH] Refresh the page automatically after a pending offline sync completes The real dashboard is server-rendered before any client JS runs, so the first page load after reconnecting always reflects the pre-sync database state -- the background sync then completes silently, but nothing told that already-rendered page to update. A task created offline would appear to vanish until the next navigation happened to load fresh data. runBackgroundSync() now reports whether it pushed pending changes, and both places that trigger it automatically reload the page when it did. Co-Authored-By: Claude Sonnet 5 --- static/js/app.js | 7 +++++-- static/js/offline-sync.js | 18 ++++++++++++++---- templates/base.html | 4 ++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/static/js/app.js b/static/js/app.js index 5d87095..2c00011 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -11,8 +11,11 @@ document.addEventListener('DOMContentLoaded', function() { maybeBackgroundSync(); }); -window.addEventListener('online', function() { - runBackgroundSync(); +window.addEventListener('online', async function() { + const pushedChanges = await runBackgroundSync(); + if (pushedChanges) { + window.location.reload(); + } }); /* ============================================ diff --git a/static/js/offline-sync.js b/static/js/offline-sync.js index 05cb34e..e529131 100644 --- a/static/js/offline-sync.js +++ b/static/js/offline-sync.js @@ -92,10 +92,14 @@ async function mergeAndResolveEntity({ entityType, dirtyLocal, serverRows, confl } } +// Returns true if locally-queued offline changes were successfully pushed up +// this call - meaning the current (server-rendered) page was rendered before +// those changes existed and is now stale, so the caller should refresh it. +// Returns false if there was nothing to push, or the sync didn't complete. async function runBackgroundSync() { const csrfToken = getCsrfToken(); if (!csrfToken) { - return; // Not on an authenticated page (e.g. login/register). + return false; // Not on an authenticated page (e.g. login/register). } const deviceId = await ensureDeviceId(); @@ -104,6 +108,7 @@ async function runBackgroundSync() { const dirtyTasks = await getDirtyTasks(); const dirtyTags = await getDirtyTags(); const dirtyTimeEntries = await getDirtyTimeEntries(); + const hadPendingChanges = dirtyTasks.length + dirtyTags.length + dirtyTimeEntries.length > 0; let response; try { @@ -124,11 +129,11 @@ async function runBackgroundSync() { }), }); } catch (err) { - return; // Offline or network error - retry next time, nothing to clean up. + return false; // Offline or network error - retry next time, nothing to clean up. } if (!response.ok) { - return; // Includes 429 throttled - retry next time. + return false; // Includes 429 throttled - retry next time. } const data = await response.json(); @@ -149,6 +154,8 @@ async function runBackgroundSync() { await setMeta('last_sync_token', data.sync_token); await setMeta('last_sync_at', new Date().toISOString()); + + return hadPendingChanges; } async function maybeBackgroundSync() { @@ -156,5 +163,8 @@ async function maybeBackgroundSync() { if (lastSyncAt && Date.now() - new Date(lastSyncAt).getTime() < BACKGROUND_SYNC_MIN_INTERVAL_MS) { return; } - runBackgroundSync(); + const pushedChanges = await runBackgroundSync(); + if (pushedChanges) { + window.location.reload(); + } } diff --git a/templates/base.html b/templates/base.html index cc9b779..51117ba 100644 --- a/templates/base.html +++ b/templates/base.html @@ -148,8 +148,8 @@ {% endblock %} - - + + {% block extra_js %}{% endblock %}