diff --git a/apps/web/src/service-worker.ts b/apps/web/src/service-worker.ts
new file mode 100644
index 0000000..565fecd
--- /dev/null
+++ b/apps/web/src/service-worker.ts
@@ -0,0 +1,77 @@
+///
+///
+
+import { build, files, version } from '$service-worker';
+
+// Give `self` the correct type
+const selfRef = globalThis.self as unknown as ServiceWorkerGlobalScope;
+
+// Unique cache key per deployment
+const CACHE = `gt-cache-${version}`;
+
+// Precache application shell (built assets) and static files
+const ASSETS = [
+ ...build,
+ ...files
+];
+
+selfRef.addEventListener('install', (event) => {
+ event.waitUntil(
+ (async () => {
+ const cache = await caches.open(CACHE);
+ await cache.addAll(ASSETS);
+ })()
+ );
+});
+
+selfRef.addEventListener('activate', (event) => {
+ event.waitUntil(
+ (async () => {
+ const keys = await caches.keys();
+ await Promise.all(keys.map((k) => (k === CACHE ? undefined : caches.delete(k))));
+ // Claim clients so updated SW takes control immediately on refresh
+ await selfRef.clients.claim();
+ })()
+ );
+});
+
+selfRef.addEventListener('fetch', (event: FetchEvent) => {
+ // Only handle GET requests
+ if (event.request.method !== 'GET') return;
+
+ const url = new URL(event.request.url);
+
+ // Serve precached ASSETS from cache directly
+ if (ASSETS.includes(url.pathname)) {
+ event.respondWith(
+ (async () => {
+ const cache = await caches.open(CACHE);
+ const cached = await cache.match(url.pathname);
+ if (cached) return cached;
+ // Fallback to network if somehow missing
+ const res = await fetch(event.request);
+ if (res.ok) cache.put(url.pathname, res.clone());
+ return res;
+ })()
+ );
+ return;
+ }
+
+ // Runtime: network-first with cache fallback for other GETs
+ event.respondWith(
+ (async () => {
+ const cache = await caches.open(CACHE);
+ try {
+ const res = await fetch(event.request);
+ if (res instanceof Response && res.status === 200) {
+ cache.put(event.request, res.clone());
+ }
+ return res;
+ } catch (err) {
+ const cached = await cache.match(event.request);
+ if (cached) return cached;
+ throw err; // propagate if nothing cached
+ }
+ })()
+ );
+});
diff --git a/apps/web/svelte.config.js b/apps/web/svelte.config.js
index 5c6b098..1d0ff18 100644
--- a/apps/web/svelte.config.js
+++ b/apps/web/svelte.config.js
@@ -8,7 +8,10 @@ const config = {
adapter: adapter({
fallback: '200.html'
}),
- // Service worker wiring comes in T008
+ serviceWorker: {
+ // keep default auto-registration explicit
+ register: true
+ },
paths: {
// supports GitHub Pages-like hosting later; keep default for now
}
diff --git a/specs/001-glowtrack-a-mood/tasks.md b/specs/001-glowtrack-a-mood/tasks.md
index e90df09..2c3aa41 100644
--- a/specs/001-glowtrack-a-mood/tasks.md
+++ b/specs/001-glowtrack-a-mood/tasks.md
@@ -60,7 +60,7 @@ Paths below are absolute to this repo.
- Root CI scripts in tools/ci (stub) and package scripts wiring
- Dependencies: T005
-- [ ] T008 PWA service worker wiring (SvelteKit)
+- [X] T008 PWA service worker wiring (SvelteKit)
- Enable service worker in SvelteKit config and add minimal SW handler
- Ensure static asset caching strategy is defined (runtime-minimal)
- Dependencies: T005