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; }