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) <noreply@anthropic.com>
This commit is contained in:
Arthur Belleville 2026-04-02 18:50:51 +02:00
parent 949888ec63
commit 61ec7e44cf
No known key found for this signature in database

View file

@ -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
<meta name="theme-color" content="#...">
<link rel="apple-touch-icon" href="/pwa-icons/apple-touch-icon-180x180.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
```
Note: The manifest `<link>` 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
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
```
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