From 61ec7e44cf8be64e0f3a443eae39c928626c9a6a Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 2 Apr 2026 18:50:51 +0200 Subject: [PATCH] docs: add PWA design spec for apps/main Covers installable PWA with vite-plugin-pwa, icon generation, custom install prompt, mobile polish, and deployment strategy. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../specs/2026-04-02-pwa-design.md | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-02-pwa-design.md diff --git a/docs/superpowers/specs/2026-04-02-pwa-design.md b/docs/superpowers/specs/2026-04-02-pwa-design.md new file mode 100644 index 0000000..4577d22 --- /dev/null +++ b/docs/superpowers/specs/2026-04-02-pwa-design.md @@ -0,0 +1,180 @@ +# PWA Design Spec: apps/main + +**Date**: 2026-04-02 +**Goal**: Make apps/main installable as a native-feeling PWA with fast repeat loads, focused on mobile experience polish. No offline data, no push notifications. +**Approach**: `vite-plugin-pwa` (Workbox-based) integrated into the existing Vite build. + +--- + +## 1. Web App Manifest & Icons + +### Manifest + +Generated by `vite-plugin-pwa` from config in `vite.config.ts`. Key properties: + +| Property | Value | +|----------|-------| +| `name` | XTablo | +| `short_name` | XTablo | +| `start_url` | `/` | +| `display` | `standalone` | +| `theme_color` | Matched to app header (from Tailwind config) | +| `background_color` | Matched to app background | +| `orientation` | `any` | +| `scope` | `/` | + +### Icons + +Generated from existing `icon.jpg` (production) and `staging_icon.jpg` (staging) using a one-time script (e.g., `sharp`). Output committed to `apps/main/public/pwa-icons/`. + +Required sizes: +- `16x16`, `32x32` — favicons +- `180x180` — Apple touch icon +- `192x192` — Android standard +- `512x512` — Android standard +- `512x512 maskable` — Android adaptive icon + +### HTML Meta Tags + +Added to `apps/main/index.html`: + +```html + + + + +``` + +Note: The manifest `` is injected automatically by `vite-plugin-pwa`. + +--- + +## 2. Service Worker & Caching Strategy + +### Plugin Configuration + +`vite-plugin-pwa` in `vite.config.ts` with: +- `registerType: 'autoUpdate'` — silent background updates, no user prompt +- `workbox.globPatterns` — precache all build outputs (JS, CSS, fonts, images) + +### Caching Rules + +| Resource | Strategy | Notes | +|----------|----------|-------| +| App shell (HTML, JS, CSS) | Precache | Auto-generated by Workbox from build manifest | +| Static assets (icons, logos) | Precache | Included in build output | +| API calls (`/api/v1/*`) | Network only | Not cached; errors handled by React Query | +| Supabase calls | Network only | Not cached; errors handled by React Query | + +### SW Registration + +In `main.tsx`, using `registerSW` from `virtual:pwa-register`. Handles registration lifecycle and auto-update. + +### Update Flow + +1. User opens/refreshes the app +2. SW detects new precache manifest in background +3. New assets downloaded silently +4. New version activates on next navigation/refresh + +No "update available" toast — the app always requires connectivity, so silent updates are sufficient. + +--- + +## 3. Install Prompt UX + +### Custom Install Prompt + +A React hook `useInstallPrompt` that: +- Listens for the `beforeinstallprompt` event +- Stores the deferred prompt event +- Exposes `canInstall: boolean` and `promptInstall(): void` + +### UI + +- Dismissible banner shown to authenticated users on the dashboard +- Dismissal persisted in `localStorage` (key: `pwa-install-dismissed`) +- Hidden when app is already running in standalone mode (detected via `window.matchMedia('(display-mode: standalone)')`) + +### iOS Fallback + +Safari does not support `beforeinstallprompt`. For iOS Safari users: +- Detect via user agent +- Show manual instruction: "Tap Share > Add to Home Screen" +- Same dismissal logic applies + +--- + +## 4. Mobile Polish & Standalone Experience + +### Viewport & Safe Areas + +Update viewport meta tag: +```html + +``` + +Add CSS safe area padding on the app shell: +```css +padding-top: env(safe-area-inset-top); +padding-bottom: env(safe-area-inset-bottom); +padding-left: env(safe-area-inset-left); +padding-right: env(safe-area-inset-right); +``` + +### Splash Screens + +Simple approach — no custom splash images. Use `theme_color` + `background_color` in the manifest for Android. iOS uses `apple-mobile-web-app-status-bar-style` for a clean launch. + +### Status Bar + +- Android: Controlled by `theme_color` in manifest +- iOS: `apple-mobile-web-app-status-bar-style: default` + +### Navigation + +Standalone mode has no browser back button. The existing React Router navigation with in-app back buttons handles this. Verify no dead-end flows exist. + +--- + +## 5. Environment & Deployment + +### Environment Differences + +| Aspect | Staging | Production | +|--------|---------|------------| +| Icon source | `staging_icon.jpg` | `icon.jpg` | +| Manifest name | XTablo (Staging) | XTablo | +| All other config | Same | Same | + +### Cloudflare Workers + +No changes to `wrangler.toml`. The SW file and manifest are static assets in `dist/`, served by Cloudflare like any other file. SPA routing (`not_found_handling = "single-page-application"`) is unaffected. + +### Build Pipeline + +- `vite-plugin-pwa` runs as part of normal `pnpm build` — no separate step +- Icon generation is a one-time script, output committed to repo + +### Local Testing + +- `pnpm dev`: SW disabled (standard behavior for vite-plugin-pwa) +- `pnpm build && pnpm preview`: Full PWA experience with active SW + +### Impact + +Fully additive. Users who don't install the PWA see no changes. The app works identically in a regular browser tab. + +--- + +## Out of Scope + +- Offline data access (all data requires connectivity) +- Push notifications (deferred to future work) +- Background sync +- Custom splash screen images (using color-based approach instead) + +## Dependencies + +- `vite-plugin-pwa` — Vite PWA integration +- `sharp` (dev dependency) — icon generation script