feat(pwa): add Progressive Web App support — installable to homescreen
Some checks are pending
CI / Backend (Python) (push) Waiting to run
CI / Frontend (Vue) (push) Waiting to run
Mirror / mirror (push) Waiting to run

- vite-plugin-pwa with generateSW strategy (Workbox)
- manifest.webmanifest: name, short_name, display standalone, theme_color #e8a820
- Service worker: precaches JS/CSS/HTML shell; API routes network-first (60s);
  Google Fonts cache-first (1 year)
- Icons: 192 + 512px regular + maskable variants generated from App.vue bird SVG
- index.html: theme-color meta, apple-touch-icon, apple-mobile-web-app-* tags
  for iOS Safari homescreen support (iOS ignores the manifest icons array)
- autoUpdate mode: new versions install silently and activate on next navigation
This commit is contained in:
pyr0ball 2026-04-25 12:33:22 -07:00
parent e2c358c90a
commit 7e0722cc23
8 changed files with 4756 additions and 10 deletions

View file

@ -2,8 +2,13 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png" />
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#e8a820" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Kiwi" />
<title>Kiwi — Pantry Tracker</title> <title>Kiwi — Pantry Tracker</title>
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

File diff suppressed because it is too large Load diff

View file

@ -20,6 +20,7 @@
"@vue/tsconfig": "^0.8.1", "@vue/tsconfig": "^0.8.1",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.1.7", "vite": "^7.1.7",
"vite-plugin-pwa": "^1.2.0",
"vue-tsc": "^3.1.0" "vue-tsc": "^3.1.0"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -1,9 +1,79 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa'
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [
vue(),
VitePWA({
registerType: 'autoUpdate',
// generateSW strategy: Workbox builds the service worker at build time.
// autoUpdate means new versions install in the background and activate
// on next navigation — no "click to reload" prompt needed.
strategies: 'generateSW',
includeAssets: ['icons/icon-192.png', 'icons/icon-512.png', 'icons/maskable-192.png', 'icons/maskable-512.png'],
manifest: {
name: 'Kiwi — Pantry Tracker',
short_name: 'Kiwi',
description: 'Track your pantry, cut food waste, get recipe ideas from what you have.',
theme_color: '#e8a820',
background_color: '#1e1c1a',
display: 'standalone',
orientation: 'portrait',
scope: '/',
start_url: '/',
icons: [
{
src: '/icons/icon-192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/icons/icon-512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: '/icons/maskable-192.png',
sizes: '192x192',
type: 'image/png',
purpose: 'maskable',
},
{
src: '/icons/maskable-512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
},
workbox: {
// Precache the built JS/CSS/HTML shell. API calls are always network-first.
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
// API: network-first, fall back to cache for 1 minute
urlPattern: /^\/api\//,
handler: 'NetworkFirst',
options: {
cacheName: 'kiwi-api-cache',
expiration: { maxEntries: 50, maxAgeSeconds: 60 },
},
},
{
// Google Fonts: cache-first (fonts rarely change)
urlPattern: /^https:\/\/fonts\.(googleapis|gstatic)\.com\//,
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts',
expiration: { maxEntries: 10, maxAgeSeconds: 60 * 60 * 24 * 365 },
},
},
],
},
}),
],
base: process.env.VITE_BASE_URL ?? '/', base: process.env.VITE_BASE_URL ?? '/',
resolve: { resolve: {
alias: { alias: {