/** * [Gitea #116] PWA install hint — pure decision logic. * * Chromium fires `beforeinstallprompt`; iOS Safari never prompts and only * installs via Share → "Add to Home Screen", so people on phones simply never * find it. The hint is shown once the user has come back at least once (second * visit), never inside the desktop app or an already-installed PWA, and snoozes * for 30 days after a dismissal. */ export const INSTALL_HINT_MIN_VISITS = 2; export const INSTALL_HINT_SNOOZE_MS = 30 * 24 * 60 * 60 * 1000; export type InstallHintInput = { /** Running inside the Tauri desktop app. */ isTauri: boolean; /** `display-mode: standalone` or iOS `navigator.standalone` — already installed. */ isStandalone: boolean; /** Distinct app visits recorded so far, including this one. */ visits: number; /** Epoch ms of the last dismissal, or null. */ dismissedAt: number | null; now: number; }; export const shouldShowInstallHint = ({ isTauri, isStandalone, visits, dismissedAt, now, }: InstallHintInput): boolean => { if (isTauri || isStandalone) return false; if (visits < INSTALL_HINT_MIN_VISITS) return false; if (dismissedAt !== null && now - dismissedAt < INSTALL_HINT_SNOOZE_MS) return false; return true; }; export type InstallHintKind = 'prompt' | 'ios-share' | 'none'; /** * Which hint to show: a real install prompt when the browser handed us a * deferred `beforeinstallprompt`, the manual Share-sheet instructions on iOS * Safari, or nothing (Firefox desktop and friends have no install path — a * hint there would just be noise). */ export const installHintKind = ( hasDeferredPrompt: boolean, isIosSafari: boolean, ): InstallHintKind => { if (hasDeferredPrompt) return 'prompt'; if (isIosSafari) return 'ios-share'; return 'none'; };