// Recap Studio — Service Worker (PWA) const CACHE = 'recap-v1'; const STATIC = [ '/', '/manifest.json', ]; // Install: cache static shell self.addEventListener('install', e => { e.waitUntil( caches.open(CACHE).then(c => c.addAll(STATIC)) ); self.skipWaiting(); }); // Activate: clean old caches 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(); }); // Fetch: network-first for API, cache-first for shell self.addEventListener('fetch', e => { const url = new URL(e.request.url); // API calls — always network, no cache if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/outputs/') || url.pathname.startsWith('/auth/')) { return; } // Navigation (HTML pages) — network first, fallback to cache if (e.request.mode === 'navigate') { e.respondWith( fetch(e.request) .then(r => { const clone = r.clone(); caches.open(CACHE).then(c => c.put(e.request, clone)); return r; }) .catch(() => caches.match('/')) ); return; } // Static assets — cache first e.respondWith( caches.match(e.request).then(cached => { if (cached) return cached; return fetch(e.request).then(r => { if (r && r.status === 200 && r.type !== 'opaque') { const clone = r.clone(); caches.open(CACHE).then(c => c.put(e.request, clone)); } return r; }); }) ); });