# 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.