PWA Score Apps — Shared Blocks
Shared noweb blocks for tally_score_tracker.org and score_counter.org.
Consumer files use cross-file references like <<pwa_score_apps_shared_blocks.org:head-meta-ex()>>.
Head and PWA configuration
Meta tags
Standard PWA meta tags. The purge script at the top provides a ?purge query
parameter escape hatch: it nukes the service worker cache and registrations,
then reloads — useful when a bad SW gets stuck in production.
<meta name="theme-color" content="#1a1a2e">
<title>Tally</title>
Import map
All dependencies loaded via ESM from esm.sh. The deps=yjs@13.6.29 pins ensure
y-indexeddb, y-websocket and y-protocols all share the exact same Yjs instance —
without this, each would bundle its own copy, breaking cross-module instanceof
checks.
<script type="importmap">
{"imports":{
"yjs":"https://esm.sh/yjs@13.6.29",
"y-indexeddb":"https://esm.sh/y-indexeddb@9?deps=yjs@13.6.29",
"y-websocket":"https://esm.sh/y-websocket@2?bundle-deps&deps=yjs@13.6.29",
"lib0/broadcastchannel":"https://esm.sh/lib0@0.2.99/broadcastchannel",
"lib0/time":"https://esm.sh/lib0@0.2.99/time",
"lib0/encoding":"https://esm.sh/lib0@0.2.99/encoding",
"lib0/decoding":"https://esm.sh/lib0@0.2.99/decoding",
"lib0/observable":"https://esm.sh/lib0@0.2.99/observable",
"lib0/math":"https://esm.sh/lib0@0.2.99/math",
"lib0/url":"https://esm.sh/lib0@0.2.99/url",
"lib0/environment":"https://esm.sh/lib0@0.2.99/environment",
"y-protocols/sync":"https://esm.sh/y-protocols@1.0.6/sync?deps=yjs@13.6.29",
"y-protocols/auth":"https://esm.sh/y-protocols@1.0.6/auth?deps=yjs@13.6.29",
"y-protocols/awareness":"https://esm.sh/y-protocols@1.0.6/awareness?deps=yjs@13.6.29",
"alpinejs":"https://esm.sh/alpinejs@3"
}}
</script>
Service worker
Network-first strategy for navigation (always fetch the latest HTML), cache-first for static assets (CDN libraries that are versioned in the import map). On install, pre-caches the shell and Alpine. On activate, purges stale caches.
const CACHE = 'tally-v6';
const ASSETS = [
'./',
'./index.html',
'https://esm.sh/alpinejs@3',
'https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js'
];
self.addEventListener('install', e => {
e.waitUntil(caches.open(CACHE).then(c => c.addAll(ASSETS)));
self.skipWaiting();
});
self.addEventListener('activate', e => {
e.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
)
);
self.clients.claim();
});
self.addEventListener('fetch', e => {
if (e.request.mode === 'navigate') {
// Network-first for pages — always get the latest HTML
e.respondWith(
fetch(e.request)
.then(r => caches.open(CACHE).then(c => { c.put(e.request, r.clone()); return r; }))
.catch(() => caches.match(e.request))
);
} else {
// Cache-first for static assets (CDN libs)
e.respondWith(
caches.match(e.request).then(r => r || fetch(e.request))
);
}
});
Manifest
{
"name": "Tally",
"short_name": "Tally",
"description": "Score tracker for board games",
"start_url": ".",
"display": "standalone",
"background_color": "#1a1a2e",
"theme_color": "#1a1a2e",
"icons": [
{
"src": "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><rect width='512' height='512' rx='96' fill='%231a1a2e'/><text x='256' y='340' font-size='280' text-anchor='middle' fill='%23e94560'>T</text></svg>",
"sizes": "512x512",
"type": "image/svg+xml",
"purpose": "any maskable"
}
]
}