Add a mobile banner nudging an install of the PWA #21
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/pwa-install-prompt"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
The PWA (see "Progressive Web App" in docs/architecture.md) already existed, but nothing told a mobile visitor it could be installed — Chrome's own default mini-infobar is easy to miss/dismiss, and Safari has no browser-driven prompt whatsoever.
New
pwaInstall.ts, self-mounting liketoast.ts(no per-route/per-party state), with one real difference:beforeinstallpromptcan fire before any UI exists to react to it, and the browser never replays a missed one. The listener is a top-level side effect of importing the module —shell/Shell.tsdoes that (and calls itsinitPwaInstallPrompt()) — and since ES module imports all evaluate before an entry file's own top-level code runs, that listener is live well beforeapp.ts'sboot()even starts its async login check.Two real install paths, one banner:
prompt(); only an 'accepted' outcome removes the banner (a picker 'dismissed' just means not right now).Mobile-only (
matchMedia('(max-width: 720px)'), this app's own existing breakpoint), suppressed once already running standalone, and a close click remembers not to ask again (localStorage). Rendered as a plain normal-flow banner at the very top of<body>— not fixed/sticky — specifically to avoid coordinating yet another offset withShell.css's already-stacked.mobileTopBar/.libraryHeaderpair.Test plan
npm run typecheck(client) — cleannpm run build(client) — cleanbeforeinstallprompt-shaped event (realEvent, stubbedprompt/userChoice) into real compiled CSS/JS confirmed the banner + install button appear, the button calls the stubprompt()and removes the banner only on 'accepted', and the close button both removes it and blocks a second dispatched event from reopening it. Overridingnavigator.userAgentto an iPhone string confirmed the iOS-flavored copy with no button. A ≥900px-wide<iframe>confirmed the banner never renders there regardless of the event firing.🤖 Generated with Claude Code