Compare commits

...

3 Commits

Author SHA1 Message Date
goyban 66a9e59db6 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 <noreply@anthropic.com>
2026-07-11 23:07:16 +00:00
goyban ab069f2ec3 Show all 4 bids on mobile during bidding (remove inner scroll cap)
The mobile bid-history was capped at 110px, hiding the 4th player's bid behind
a scroll even though the panel had room. There are always exactly 4 players, so
show all four rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:54:50 +00:00
goyban cb664e4505 Show app version in lobby footer; expose version via /api/config
- Server reads version from package.json and returns it from /api/config
- Client fetches it at boot and renders it in a lobby footer
- Bump service-worker cache to shelem-v3 so precache refreshes

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:46:13 +00:00
6 changed files with 51 additions and 5 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "shelem", "name": "shelem",
"version": "1.0.0", "version": "1.1.4",
"description": "Shelem card game — multiplayer", "description": "Shelem card game — multiplayer",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
+29
View File
@@ -1700,11 +1700,40 @@ async function validateAuth() {
updateAuthBar(); updateAuthBar();
} }
// 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} · 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 ───────────────────────────────────────────────────── // ─── Boot ─────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
initLobby(); initLobby();
wireGameEvents(); wireGameEvents();
applyBarBottom(); applyBarBottom();
loadAppVersion();
$('app-version').addEventListener('click', forceUpdate);
// Drop any stale/corrupted auth token before we use it to connect // Drop any stale/corrupted auth token before we use it to connect
await validateAuth(); await validateAuth();
+1
View File
@@ -91,6 +91,7 @@
<p id="lobby-error" class="error-msg"></p> <p id="lobby-error" class="error-msg"></p>
</div> </div>
<div id="app-version" class="app-version"></div>
</div> </div>
<!-- ══════════════ WAITING ROOM ══════════════ --> <!-- ══════════════ WAITING ROOM ══════════════ -->
+16 -2
View File
@@ -1033,6 +1033,20 @@ input:focus { border-color: var(--gold); }
.lb-table td { border-bottom: 1px solid rgba(255,255,255,.05); } .lb-table td { border-bottom: 1px solid rgba(255,255,255,.05); }
.lb-table tr:hover td { background: rgba(255,255,255,.04); } .lb-table tr:hover td { background: rgba(255,255,255,.04); }
/* ── App version footer ───── */
.app-version {
text-align: center;
margin-top: 14px;
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-by-round breakdown ───── */
.round-breakdown-wrap { margin: 6px 0 2px; } .round-breakdown-wrap { margin: 6px 0 2px; }
.rb-scroll { max-height: 42vh; overflow-y: auto; -webkit-overflow-scrolling: touch; } .rb-scroll { max-height: 42vh; overflow-y: auto; -webkit-overflow-scrolling: touch; }
@@ -1269,8 +1283,8 @@ input:focus { border-color: var(--gold); }
box-shadow: 0 6px 28px rgba(0,0,0,.8); box-shadow: 0 6px 28px rgba(0,0,0,.8);
max-height: 55vh; max-height: 55vh;
} }
/* Shrink bid history on mobile so it doesn't swallow the screen */ /* Always 4 players, so show all four bids without an inner scroll */
.bid-history { max-height: 110px; } .bid-history { max-height: none; }
.bid-box h3 { font-size: .95rem; margin-bottom: 0; } .bid-box h3 { font-size: .95rem; margin-bottom: 0; }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
'use strict'; 'use strict';
const CACHE_NAME = 'shelem-v2'; const CACHE_NAME = 'shelem-v3';
// ── Generate all 55 card paths ────────────────────────────────── // ── Generate all 55 card paths ──────────────────────────────────
const SUITS = ['CLUB', 'DIAMOND', 'HEART', 'SPADE']; const SUITS = ['CLUB', 'DIAMOND', 'HEART', 'SPADE'];
+3 -1
View File
@@ -132,8 +132,10 @@ app.use(express.static(path.join(__dirname, 'public'), {
})); }));
// ─── Auth API ───────────────────────────────────────────────── // ─── Auth API ─────────────────────────────────────────────────
const APP_VERSION = require('./package.json').version;
app.get('/api/config', (_req, res) => { app.get('/api/config', (_req, res) => {
res.json({ signupsOpen: config.signupsOpen }); res.json({ signupsOpen: config.signupsOpen, version: APP_VERSION });
}); });
// Validate the stored auth token; the client uses this at startup to detect a // Validate the stored auth token; the client uses this at startup to detect a