diff --git a/docs/superpowers/specs/2026-07-27-pwa-installability-design.md b/docs/superpowers/specs/2026-07-27-pwa-installability-design.md new file mode 100644 index 0000000..636bc16 --- /dev/null +++ b/docs/superpowers/specs/2026-07-27-pwa-installability-design.md @@ -0,0 +1,174 @@ +# AgendaMax PWA Installability + +## Goal + +Make AgendaMax installable as a web app on mobile and desktop browsers, with a +native installation prompt where the browser supports it and an explicit iOS +installation guide where Apple does not expose that prompt API. + +## Decisions + +- Use a manually maintained manifest and service worker instead of adding + `vite-plugin-pwa`. This keeps the caching policy visible and avoids a new + build-time dependency for a small, already stable Vite app. +- Use an online-first data model. The service worker may recover the application + shell after a previous visit, but it must never cache `/api` responses or + attempt offline writes for appointments, clients, cash, or settings. +- Keep the existing AgendaMax visual language and Spanish copy. Installation is + an optional secondary action, not a permanent mobile banner. +- Require HTTPS in deployed environments. `localhost` remains valid for local + testing because browsers treat it as a secure context. + +## Current Context + +- Frontend: React 18, TypeScript, Vite, Tailwind CSS, React Router. +- Production serving: Express serves `dist/` and falls back to `index.html` for + non-API routes. +- Existing brand assets: `public/favicon.svg`, `#3b66ff` theme color, AgendaMax + name and current Spanish UI. +- Existing entry points: `src/main.tsx`, `src/App.tsx`, `LoginPage`, + `AppShell`, and `AdminShell`. +- There is no manifest, service worker, install prompt handling, or iOS-specific + home-screen metadata today. + +## Architecture + +### Static PWA assets + +Add the following public assets, copied by Vite into `dist/`: + +- `manifest.webmanifest` with `name` and `short_name` set to AgendaMax, + `start_url` `/`, `scope` `/`, `display` `standalone`, portrait orientation, + brand colors, and PNG icons at 192x192 and 512x512. +- `sw.js` with a versioned cache name. +- PNG icons derived from the existing AgendaMax favicon mark. Keep the SVG + favicon unchanged for browser tabs. + +Update `index.html` with the manifest link, iOS home-screen metadata, an +explicit mobile web app title, `viewport-fit=cover`, and the existing theme and +description metadata. + +### Service worker + +Register the worker from `src/main.tsx` only for production builds, using +`updateViaCache: "none"` so a deployed worker is checked promptly. + +The worker has these rules: + +- On install, cache only the stable shell/bootstrap assets and call + `skipWaiting`. +- On navigation requests, use network-first and fall back to the cached root + document. This allows deployments to deliver fresh HTML while preserving a + previously visited shell during a temporary outage. +- On same-origin static GET requests, use cache-first for immutable or hashed + assets and add successful responses to the runtime cache. +- Bypass `/api/`, non-GET requests, cross-origin requests, and requests with + credentials or other conditions that could expose tenant data. +- On activation, remove caches from older versions and call `clients.claim`. + +The worker is deliberately not a data-sync layer. If an API request fails, the +existing application error/loading behavior remains authoritative. + +### Install state and UI + +Create a small reusable install hook/component with these states: + +1. `unsupported`: no UI. +2. `available`: show an install action backed by `beforeinstallprompt`. +3. `ios-instructions`: show a short dialog explaining Safari's Share then Add + to Home Screen flow. +4. `installed`: no UI, detected from `display-mode: standalone` and + `navigator.standalone`. + +The hook must retain the deferred Android prompt only until it is used, handle +`appinstalled`, and avoid showing the CTA repeatedly after a user dismisses it +within the current browser session. + +Render the reusable action in: + +- `LoginPage`, so a user can install before authentication; +- the authenticated business mobile header/sidebar in `AppShell`; +- the authenticated platform-admin mobile header/sidebar in `AdminShell`. + +The action should remain a compact secondary button with a download icon. The +iOS dialog is instructional and must not claim that a native prompt is +available. It must not reserve a persistent bottom band or interfere with +existing mobile navigation. + +Use safe-area-aware spacing where the install dialog or mobile controls touch a +screen edge. + +## Data Flow + +1. Browser loads the Vite entry document. +2. Production client registers `/sw.js`; the worker installs and claims future + navigations. +3. The install hook evaluates browser capability and standalone state. +4. Android/Chromium emits `beforeinstallprompt`; the hook stores the event and + exposes the CTA. +5. User selects the CTA; the hook calls the native prompt and handles the + resulting `appinstalled` or dismissal event. +6. iOS Safari is identified when not standalone; the CTA opens the instruction + dialog and does not attempt an unsupported API. +7. All authenticated API requests continue to use the existing `fetch`/auth + path and always go to the network. + +## Error Handling and Compatibility + +- Failure to register the service worker is non-fatal and must not prevent the + React application from rendering; log a concise warning in development only. +- Missing or invalid install events result in hidden UI, not an exception. +- Existing browsers that cannot install PWAs continue to work as normal web + pages. +- iOS installation is supported through Safari's system flow. Other iOS + browsers may render the app but are not promised a direct install action. +- The service worker must not intercept API errors, mutate auth tokens, or + serve one user's tenant data to another user. + +## Testing and Verification + +### Automated + +- `npm run typecheck` passes. +- `npm run build` passes and emits the manifest, worker, icons, and metadata in + `dist/`. +- Add a Playwright check for Chromium's deferred install event and `prompt()`. +- Add a Playwright check for iOS-style user-agent/standalone detection and the + instructional dialog. +- Add checks that unsupported and already-installed states do not render the + CTA. +- Verify the production Express server returns status 200 for the manifest, + worker, and icon paths, while SPA fallback still serves client routes. +- Run `npm run audit:visual` and confirm no horizontal overflow at 375, 768, + 1280, or 1536 pixels. + +### Manual device acceptance + +- Android Chrome on HTTPS displays the native install action and opens + AgendaMax in standalone mode with the correct icon/name. +- iPhone Safari on HTTPS shows the two-step installation guide and the added + home-screen app opens without browser chrome. +- After a prior visit, temporarily disabling the network still allows the + shell to load; API-backed data correctly remains unavailable rather than + showing stale cached data. +- A new deployment replaces the old worker/cache without requiring the user to + clear browser storage manually. + +## Scope Exclusions + +- No offline appointment creation, edits, queued writes, conflict resolution, + or background synchronization. +- No native App Store/Play Store packaging. +- No change to authentication, tenant isolation, API routes, or database schema. + +## Acceptance Criteria + +- AgendaMax has a valid install manifest and 192x192/512x512 icons. +- Android/Chromium exposes an in-app install action when the native event is + available. +- iOS Safari exposes clear Share -> Add to Home Screen instructions. +- Installed standalone mode hides the install action. +- The service worker is registered in production, preserves the shell after a + prior visit, and never caches `/api` or non-GET requests. +- Production HTTPS serving works through the existing Express/Docker path. +- Typecheck, build, PWA behavior checks, and visual audit pass.