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
The manifest is what turns the page into something a phone will install rather
than merely bookmark, and the decisive part is the icons. A browser offers a real,
standalone install only when the manifest gives it a launcher icon at both 192 and
512 pixels as a raster it can resize, and only when it can fetch every icon listed:
minting the standalone package hands the icon URLs to a remote server that downloads
each one, so an icon with no fetchable URL silently downgrades the install to a
home-screen shortcut. So the manifest advertises icon-192.png and icon-512.png,
which each app renders from its own icon.svg, and lists that icon.svg alongside
as a resolution-independent variant — all three real files the app serves. The
full recipe and the failure modes behind that constraint are in how to make a PWA
install as a WebAPK.
{
"name": "Tally",
"short_name": "Tally",
"description": "Score tracker for board games",
"start_url": ".",
"scope": ".",
"display": "standalone",
"background_color": "#1a1a2e",
"theme_color": "#1a1a2e",
"icons": [
{ "src": "icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
{ "src": "icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" },
{ "src": "icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any maskable" }
]
}
Notes linking here
- Document skeleton
- How it all fits together
- how to make a PWA install as a WebAPK
- score counter tally
- tally score tracker
- testing render and sync stacks for local-first PWAs