Files
hedena_gold/docs/superpowers/specs/2026-10-15-pwa-install-prompt-design.md
T
s.esmaeiliandCursor 3add0683b3 Polish Hedena brand, home layout, and user-app chrome.
Switch the customer app to IRANSans, restyle home featured/newest and cart note sheet, refresh brand icons across apps, and add the PWA install prompt.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 00:04:13 +03:30

7.0 KiB
Raw Blame History

PWA install prompt + platform guides

Date: 2026-10-15
App: apps/user-app

Goal

Show a brand-focused centered install modal automatically after first useful visit, with separate Android-web and iOS install guides. Do not nag: if the user dismisses, wait 7 days before offering again (only while not installed). Once installed, never show again.

Decisions (locked)

Topic Choice
Entry Auto prompt after short delay (not a permanent settings entry in v1)
UI Centered modal, brand-first, one primary CTA
Re-offer 7 days after dismiss, only if not installed
Installed Never show again
iOS / no native prompt Same modal shell → visual step guide for that platform
Guides Separate copy/steps for Android web vs iOS
Style Follow existing hg-dialog / DialogVant island pattern

Out of scope

  • Admin panel PWA install UX
  • Push-notification permission prompts
  • Changing vite-plugin-pwa manifest / SW strategy (already present)
  • Persistent “Install app” row in profile/menu (can add later)
  • Desktop-specific illustrated guide (desktop Chrome may still get native prompt or Android-style fallback text)

Architecture

Three pieces in user-app:

  1. usePwaInstall (composable)

    • Listen for beforeinstallprompt, stash the event, expose promptInstall()
    • Detect installed / standalone (display-mode: standalone, navigator.standalone on iOS)
    • Detect platform for guide content: ios | android (default non-iOS mobile/desktop to android-style guide)
    • Read/write persistence; expose shouldShowPrompt, open(), dismiss(), markInstalled()
    • After ~4s on eligible routes, set visible if shouldShowPrompt
  2. InstallPromptModal.vue

    • Centered van-dialog with hg-dialog styling (or thin wrapper consistent with DialogVant)
    • Modes: offer | guide
    • Offer: brand logo, title, one line, primary CTA, subtle close
    • Guide: platform-specific steps; secondary “متوجه شدم” dismisses
  3. Mount in App.vue

    • Sibling of AppUpdateGate
    • Skip while route uses FullPage layout (login/register and similar)

Existing SW registration via useRegisterSW stays as-is; this feature only adds install UX.

Show / hide rules

Show only when all are true:

  1. Not already installed (standalone / navigator.standalone / persisted installed)
  2. dismissedAt is missing, or older than 7 days
  3. Current route is not meta.layout === 'FullPage'
  4. Initial delay of ~4 seconds after app shell ready has elapsed
  5. Document is visible (document.visibilityState === 'visible') — optional soft guard

On dismiss (×, overlay, or “متوجه شدم”):

  • Set pwaInstall.dismissedAt = Date.now()
  • Close modal; reset mode to offer for next open

On successful native install (appinstalled event or user accepted prompt()):

  • Set pwaInstall.installed = true
  • Clear need to re-offer; never show again

On later visit already in standalone:

  • Treat as installed (set installed if not set); never show

Persistence

localStorage keys (single JSON object preferred, matching cart/appearance patterns):

{
  "dismissedAt": null,
  "installed": false
}

Key name: pwaInstall (or hg.pwaInstall if namespacing is preferred in implementation).

Constants:

  • REOFFER_MS = 7 * 24 * 60 * 60 * 1000
  • INITIAL_DELAY_MS = 4000

Modal content

Offer mode (both platforms)

Element Copy / asset
Media brand-logo (existing public/assets path)
Title نصب اپلیکیشن هدنا گلد
Body دسترسی سریع‌تر و تجربهٔ شبیه اپ واقعی
Primary CTA If deferred prompt ready: نصب اپ — else: مشاهده راهنما
Close Small ×; no loud secondary cancel

CTA behavior:

  • Deferred prompt available → event.prompt(); on accept → markInstalled(); on dismiss of browser UI only → keep our dismissedAt unset unless user also closed our modal (browser decline alone does not start the 7-day clock; closing our modal does)
  • No deferred prompt → switch modal to guide for detected platform (do not dismiss yet)

Guide — Android web

Title: راهنمای نصب

  1. منوی مرورگر (⋮) را باز کنید
  2. گزینهٔ «نصب برنامه» یا «Add to Home screen» را بزنید
  3. نصب را تأیید کنید

Footer CTA: متوجه شدم → dismiss + 7-day clock

Guide — iOS

Title: راهنمای نصب

  1. دکمهٔ Share (مربع با فلش رو به بالا) را بزنید
  2. «Add to Home Screen» / افزودن به صفحهٔ اصلی را انتخاب کنید
  3. Add را تأیید کنید

Footer CTA: متوجه شدم → dismiss + 7-day clock

Guides use short icon+text rows (no long paragraphs). Illustrations may be simple inline SVG icons (share / menu / home); no new marketing photography required for v1.

Platform detection (guide only)

  • iOS: iPhone/iPad/iPod UA, or iPadOS desktop UA with touch — use iOS guide
  • Else: Android-web guide (covers Android Chrome without prompt, and as fallback elsewhere)

Native beforeinstallprompt path is independent of guide platform: if the event exists, offer CTA installs directly regardless of guide branch.

Error / edge cases

Case Behavior
prompt() rejects / user cancels browser sheet Stay closed or return to offer only if still open; do not set installed; if our modal was closed by user earlier, 7-day rule already applies
Already installed mid-session (appinstalled) Close modal; set installed
Private mode / localStorage throws Fail soft: skip auto-show (no crash); optional in-memory session-only suppress
User opens app from home screen Never show
Update gate (AppUpdateGate) visible Do not stack: keep install modal closed while the update gate is open; schedule or re-check after it closes
HTTPS / installability not met No deferred prompt → auto path still can show offer → guide

Testing (manual)

  1. Fresh profile, Android Chrome (installable): after ~4s modal → نصب اپ → browser prompt → accept → never again
  2. Same, dismiss modal → gone; advance clock / spoof dismissedAt −8 days → shows again
  3. iOS Safari: after ~4s → مشاهده راهنما → iOS steps → متوجه شدم → 7-day silence
  4. Standalone launch: no modal
  5. Login (FullPage): no modal until leaving that layout and delay elapses on an eligible view

File sketch

apps/user-app/src/composables/usePwaInstall.ts
apps/user-app/src/components/pwa/InstallPromptModal.vue
apps/user-app/src/App.vue  (mount modal)

Optional tiny helpers for UA / display-mode can live next to the composable.

Success criteria

  • Brand-first centered modal, one primary CTA
  • Android and iOS guides are distinct
  • Auto once per window of eligibility; 7-day re-offer only if not installed
  • Installed / standalone users never see it
  • No change to existing SW/update-gate behavior beyond coexistence