diff --git a/raggr-frontend/public/apple-touch-icon.png b/raggr-frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..d3e884a Binary files /dev/null and b/raggr-frontend/public/apple-touch-icon.png differ diff --git a/raggr-frontend/public/manifest.json b/raggr-frontend/public/manifest.json new file mode 100644 index 0000000..f5e9768 --- /dev/null +++ b/raggr-frontend/public/manifest.json @@ -0,0 +1,14 @@ +{ + "name": "Ask Simba", + "short_name": "Simba", + "description": "Chat with Simba - your AI cat companion", + "start_url": "/", + "display": "standalone", + "background_color": "#FAF8F2", + "theme_color": "#2A4D38", + "icons": [ + { "src": "/pwa-icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/pwa-icon-512.png", "sizes": "512x512", "type": "image/png" }, + { "src": "/pwa-icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } + ] +} diff --git a/raggr-frontend/public/pwa-icon-192.png b/raggr-frontend/public/pwa-icon-192.png new file mode 100644 index 0000000..336bb91 Binary files /dev/null and b/raggr-frontend/public/pwa-icon-192.png differ diff --git a/raggr-frontend/public/pwa-icon-512.png b/raggr-frontend/public/pwa-icon-512.png new file mode 100644 index 0000000..0d7254c Binary files /dev/null and b/raggr-frontend/public/pwa-icon-512.png differ diff --git a/raggr-frontend/public/sw.js b/raggr-frontend/public/sw.js new file mode 100644 index 0000000..3422a03 --- /dev/null +++ b/raggr-frontend/public/sw.js @@ -0,0 +1,46 @@ +const CACHE = 'simba-v1'; + +self.addEventListener('install', (e) => { + 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) => { + const { request } = e; + const url = new URL(request.url); + + // Network-only for API calls + if (url.pathname.startsWith('/api/')) return; + + // Cache-first for fingerprinted static assets + if (url.pathname.startsWith('/static/')) { + e.respondWith( + caches.match(request).then( + (cached) => + cached || + fetch(request).then((res) => { + const clone = res.clone(); + caches.open(CACHE).then((c) => c.put(request, clone)); + return res; + }) + ) + ); + return; + } + + // Network-first for navigation (offline fallback to cache) + if (request.mode === 'navigate') { + e.respondWith( + fetch(request).catch(() => caches.match(request)) + ); + return; + } +}); diff --git a/raggr-frontend/rsbuild.config.ts b/raggr-frontend/rsbuild.config.ts index 16771fc..47888b0 100644 --- a/raggr-frontend/rsbuild.config.ts +++ b/raggr-frontend/rsbuild.config.ts @@ -4,7 +4,16 @@ import { pluginReact } from '@rsbuild/plugin-react'; export default defineConfig({ plugins: [pluginReact()], html: { - title: 'Raggr', + title: 'Ask Simba', favicon: './src/assets/favicon.svg', + tags: [ + { tag: 'link', attrs: { rel: 'manifest', href: '/manifest.json' } }, + { tag: 'meta', attrs: { name: 'theme-color', content: '#2A4D38' } }, + { tag: 'link', attrs: { rel: 'apple-touch-icon', href: '/apple-touch-icon.png' } }, + { tag: 'meta', attrs: { name: 'apple-mobile-web-app-capable', content: 'yes' } }, + ], + }, + output: { + copy: [{ from: './public', to: '.' }], }, }); diff --git a/raggr-frontend/src/index.tsx b/raggr-frontend/src/index.tsx index 55f29bf..f36a44d 100644 --- a/raggr-frontend/src/index.tsx +++ b/raggr-frontend/src/index.tsx @@ -11,3 +11,9 @@ if (rootEl) { , ); } + +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js').catch(console.warn); + }); +}