Add a mobile banner nudging an install of the PWA #21

Merged
kyuno merged 2 commits from feature/pwa-install-prompt into master 2026-09-17 21:22:36 +02:00
Owner

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 like toast.ts (no per-route/per-party state), with one real difference: beforeinstallprompt can 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.ts does that (and calls its initPwaInstallPrompt()) — and since ES module imports all evaluate before an entry file's own top-level code runs, that listener is live well before app.ts's boot() even starts its async login check.

Two real install paths, one banner:

  • Chrome/Edge/Android WebView: captures the real event, "Installer" calls the real prompt(); only an 'accepted' outcome removes the banner (a picker 'dismissed' just means not right now).
  • iOS Safari: no such event exists — detected via user-agent, shows manual "Partager → Sur l'écran d'accueil" instructions instead, no button.

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 with Shell.css's already-stacked .mobileTopBar/.libraryHeader pair.

Test plan

  • npm run typecheck (client) — clean
  • npm run build (client) — clean
  • No real device install flow is triggerable in this sandbox, so checked everything around that boundary directly: a synthetic beforeinstallprompt-shaped event (real Event, stubbed prompt/userChoice) into real compiled CSS/JS confirmed the banner + install button appear, the button calls the stub prompt() and removes the banner only on 'accepted', and the close button both removes it and blocks a second dispatched event from reopening it. Overriding navigator.userAgent to 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.
  • Not verified against a real mobile browser's actual install flow — flagged the same way in docs/architecture.md.

🤖 Generated with Claude Code

## 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 like `toast.ts` (no per-route/per-party state), with one real difference: `beforeinstallprompt` can 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.ts` does that (and calls its `initPwaInstallPrompt()`) — and since ES module imports all evaluate before an entry file's own top-level code runs, that listener is live well before `app.ts`'s `boot()` even starts its async login check. Two real install paths, one banner: - **Chrome/Edge/Android WebView**: captures the real event, "Installer" calls the real `prompt()`; only an 'accepted' outcome removes the banner (a picker 'dismissed' just means not right now). - **iOS Safari**: no such event exists — detected via user-agent, shows manual "Partager → Sur l'écran d'accueil" instructions instead, no button. 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 with `Shell.css`'s already-stacked `.mobileTopBar`/`.libraryHeader` pair. ## Test plan - [x] `npm run typecheck` (client) — clean - [x] `npm run build` (client) — clean - [x] No real device install flow is triggerable in this sandbox, so checked everything around that boundary directly: a synthetic `beforeinstallprompt`-shaped event (real `Event`, stubbed `prompt`/`userChoice`) into real compiled CSS/JS confirmed the banner + install button appear, the button calls the stub `prompt()` and removes the banner only on 'accepted', and the close button both removes it and blocks a *second* dispatched event from reopening it. Overriding `navigator.userAgent` to 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. - Not verified against a real mobile browser's actual install flow — flagged the same way in docs/architecture.md. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 1 commit 2026-09-16 23:30:04 +02:00
Add a mobile banner nudging an install of the PWA
Some checks failed
Publier l'image Docker / test (pull_request) Failing after 4s
Publier l'image Docker / publish (pull_request) Has been skipped
7f0dbc28e3
The PWA already existed but nothing told a mobile visitor it could be
installed - Chrome own default mini-infobar is easy to miss, and
Safari has no browser-driven prompt at all. New pwaInstall.ts (self-
mounting like toast.ts) captures beforeinstallprompt as early as
possible (a top-level side effect of importing the module from
Shell.ts, live before boot() even starts its async login check, since
that event never replays for a listener that attached too late) and
shows a banner with a real Installer button on Chrome/Edge/Android, or
manual Share-then-home-screen instructions on iOS Safari where no such
event exists at all. Mobile-only, suppressed once already running
standalone, and a close click remembers not to ask again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno added 1 commit 2026-09-17 21:22:28 +02:00
Merge remote-tracking branch 'origin/master' into feature/pwa-install-prompt
Some checks failed
Publier l'image Docker / test (pull_request) Failing after 4s
Publier l'image Docker / publish (pull_request) Has been skipped
d79b49f34d
# Conflicts:
#	README.md
#	docs/architecture.md
kyuno merged commit 9cb25f4dc0 into master 2026-09-17 21:22:36 +02:00
kyuno deleted branch feature/pwa-install-prompt 2026-09-17 21:22:36 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/watchparty#21
No description provided.