From 66a9e59db67c80d841a2ed465e83efcb0bbc72ad Mon Sep 17 00:00:00 2001 From: goyban Date: Sat, 11 Jul 2026 23:07:16 +0000 Subject: [PATCH] Add tap-to-update button on version footer to force-refresh cached assets Phones stuck on stale service-worker caches (esp. iOS home-screen PWAs) could show the new version string while still running old app.js/css. Tapping the footer now unregisters the service worker, clears all caches, and reloads to pull the latest build. Co-Authored-By: Claude Opus 4.8 --- package.json | 2 +- public/app.js | 24 ++++++++++++++++++++++-- public/style.css | 5 +++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c449b27..711b598 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shelem", - "version": "1.1.3", + "version": "1.1.4", "description": "Shelem card game — multiplayer", "main": "server.js", "scripts": { diff --git a/public/app.js b/public/app.js index 664630f..d41db2f 100644 --- a/public/app.js +++ b/public/app.js @@ -1700,20 +1700,40 @@ async function validateAuth() { updateAuthBar(); } -// Show the running app version in the lobby footer (served from /api/config) +// Show the running app version in the lobby footer (served from /api/config). +// Tapping it force-updates: clears the service-worker caches and reloads, so a +// phone stuck on stale cached assets can pull the latest build. async function loadAppVersion() { try { const d = await fetch('/api/config').then(r => r.json()); - if (d.version) $('app-version').textContent = `v${d.version}`; + if (d.version) $('app-version').textContent = `v${d.version} · tap to update`; } catch { /* ignore */ } } +async function forceUpdate() { + const el = $('app-version'); + if (el) el.textContent = 'Updating…'; + try { + if ('serviceWorker' in navigator) { + const regs = await navigator.serviceWorker.getRegistrations(); + await Promise.all(regs.map(r => r.unregister())); + } + if (window.caches) { + const keys = await caches.keys(); + await Promise.all(keys.map(k => caches.delete(k))); + } + } catch { /* ignore */ } + // SW gone and caches cleared — a plain reload now refetches everything fresh + location.reload(); +} + // ─── Boot ───────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded', async () => { initLobby(); wireGameEvents(); applyBarBottom(); loadAppVersion(); + $('app-version').addEventListener('click', forceUpdate); // Drop any stale/corrupted auth token before we use it to connect await validateAuth(); diff --git a/public/style.css b/public/style.css index a798832..a8593d3 100644 --- a/public/style.css +++ b/public/style.css @@ -1040,7 +1040,12 @@ input:focus { border-color: var(--gold); } font-size: .72rem; color: rgba(255,255,255,.35); letter-spacing: .5px; + cursor: pointer; + user-select: none; + -webkit-tap-highlight-color: transparent; } +.app-version:hover { color: rgba(255,255,255,.6); text-decoration: underline; } +.app-version:active { color: var(--gold); } /* ── Round-by-round breakdown ───── */ .round-breakdown-wrap { margin: 6px 0 2px; }