// Offline app-shell cache. API calls are never cached. The shell (HTML/JS/CSS) // is NETWORK-FIRST so code updates land immediately for online clients (a stale // cache-first app.js is why "?job=" showed no model on an old install); heavy, // stable assets (three.js vendor, demo.glb, icons) stay cache-first. const CACHE = 'genrecon-v4'; // bump on shell change so clients refresh const SHELL = [ './', './index.html', './styles.css', './app.js', './viewer.js', './manifest.webmanifest', './demo.glb', './icons/icon-192.png', './icons/icon-512.png', './vendor/three.module.js', './vendor/jsm/loaders/GLTFLoader.js', './vendor/jsm/utils/BufferGeometryUtils.js', './vendor/jsm/math/Octree.js', './vendor/jsm/math/Capsule.js', ]; self.addEventListener('install', (e) => { e.waitUntil(caches.open(CACHE).then((c) => c.addAll(SHELL)).then(() => self.skipWaiting())); }); self.addEventListener('activate', (e) => { e.waitUntil( caches.keys() .then((ks) => Promise.all(ks.filter((k) => k !== CACHE).map((k) => caches.delete(k)))) .then(() => self.clients.claim()), ); }); self.addEventListener('fetch', (e) => { const req = e.request; const url = new URL(req.url); if (url.pathname.startsWith('/api/')) return; // dynamic — straight to network, never cached const isVendor = url.pathname.includes('/vendor/') || url.pathname.endsWith('.glb') || url.pathname.includes('/icons/'); const isShell = !isVendor && (req.mode === 'navigate' || /\.(?:js|css|webmanifest)$/.test(url.pathname)); if (isShell) { // Network-first: always try the latest code, cache it, fall back to cache offline. e.respondWith( fetch(req) .then((r) => { const copy = r.clone(); caches.open(CACHE).then((c) => c.put(req, copy)); return r; }) .catch(() => caches.match(req).then((r) => r || caches.match('./index.html'))), ); } else { // Cache-first for heavy, stable assets (three.js, demo.glb, icons). e.respondWith(caches.match(req).then((r) => r || fetch(req))); } });