docs: specify AgendaMax PWA installability

This commit is contained in:
AgendaPro Dev
2026-07-27 15:12:58 -06:00
parent 3e056a0dbf
commit fe8b701e27
@@ -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.