WIP PWA for Grain

feat: add automatic PWA updates with build-time cache versioning

Ensures PWA users get updates without re-adding to home screen by:
- Injecting build timestamp into service worker cache name
- Auto-reloading when new service worker takes control

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+21 -1
+1 -1
public/sw.js
··· 1 - const CACHE_NAME = 'grain-shell-v1'; 1 + const CACHE_NAME = 'grain-shell-__BUILD_TIME__'; 2 2 const SHELL_ASSETS = [ 3 3 '/', 4 4 '/index.html'
+5
src/main.js
··· 6 6 navigator.serviceWorker.register('/sw.js') 7 7 .then((reg) => console.log('SW registered:', reg.scope)) 8 8 .catch((err) => console.error('SW registration failed:', err)); 9 + 10 + // Reload when new service worker takes control 11 + navigator.serviceWorker.addEventListener('controllerchange', () => { 12 + window.location.reload(); 13 + }); 9 14 } 10 15 11 16 import { auth } from './services/auth.js';
+15
vite.config.js
··· 1 1 import { defineConfig } from 'vite'; 2 + import { readFileSync, writeFileSync } from 'fs'; 3 + import { resolve } from 'path'; 4 + 5 + function swVersionPlugin() { 6 + return { 7 + name: 'sw-version', 8 + writeBundle() { 9 + const swPath = resolve('dist/sw.js'); 10 + const content = readFileSync(swPath, 'utf-8'); 11 + const versioned = content.replace('__BUILD_TIME__', Date.now().toString()); 12 + writeFileSync(swPath, versioned); 13 + } 14 + }; 15 + } 2 16 3 17 export default defineConfig({ 4 18 root: '.', ··· 7 21 outDir: 'dist', 8 22 target: 'esnext' 9 23 }, 24 + plugins: [swVersionPlugin()], 10 25 server: { 11 26 port: 3000 12 27 },