From 7daf945757302bfd41d8af08a8ff15ac1d35365f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:23:46 +0000 Subject: [PATCH] Transform StepView into a PWA with offline support and update notifications - Add manifest.json with app metadata and SVG icons - Implement sw.js with cache-first strategy and versioned asset pre-caching - Update app.js to register service worker and manage update flow with UI banner - Update index.html to link manifest and add update notification banner - Add styles for update banner in styles.css - Bump BUILD version to 11 Closes #4 Co-authored-by: axe-fb <33528067+axe-fb@users.noreply.github.com> --- app.js | 33 ++++++++++++++++++++++++++++++- index.html | 8 ++++++-- manifest.json | 16 +++++++++++++++ styles.css | 21 ++++++++++++++++++++ sw.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 manifest.json create mode 100644 sw.js diff --git a/app.js b/app.js index a5e0634..114a9b3 100644 --- a/app.js +++ b/app.js @@ -2,7 +2,7 @@ 'use strict'; // ==================== CONFIG ==================== - var BUILD = '10'; // bump every deploy — shown on Debug screen to confirm version + var BUILD = '11'; // bump every deploy — shown on Debug screen to confirm version var STORAGE_KEY = 'stepview_v1'; var RING_CIRC = 603; // 2*pi*r, r=96 var KCAL_PER_KG_PER_KM = 0.9; // gross walking estimate @@ -313,6 +313,7 @@ case 'weight-dec': state.settings.weightKg = clampWeight(state.settings.weightKg - 1); afterSetting(); break; case 'height-inc': state.settings.heightCm = clampHeight(state.settings.heightCm + 1); afterSetting(); break; case 'height-dec': state.settings.heightCm = clampHeight(state.settings.heightCm - 1); afterSetting(); break; + case 'update-refresh': if (waitingWorker) waitingWorker.postMessage('skipWaiting'); break; } } function afterSetting() { renderSettings(); render(); save(); } @@ -437,10 +438,40 @@ }; } + // ==================== SERVICE WORKER ==================== + var waitingWorker = null; + function setupSW() { + if (!('serviceWorker' in navigator)) return; + navigator.serviceWorker.register('sw.js').then(function (reg) { + if (reg.waiting) { + waitingWorker = reg.waiting; + showUpdateBanner(); + } + reg.addEventListener('updatefound', function () { + var newWorker = reg.installing; + newWorker.addEventListener('statechange', function () { + if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { + waitingWorker = newWorker; + showUpdateBanner(); + } + }); + }); + }); + navigator.serviceWorker.addEventListener('controllerchange', function () { + window.location.reload(); + }); + } + + function showUpdateBanner() { + var banner = $('update-banner'); + if (banner) banner.classList.remove('hidden'); + } + // ==================== INIT ==================== function init() { document.querySelectorAll('.screen').forEach(function (s) { if (s.id) screens[s.id] = s; }); load(); + setupSW(); setupEvents(); setupMotion(); // always-on accelerometer capture (independent of the UI) render(); diff --git a/index.html b/index.html index 84c693c..e64602d 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,8 @@