From 64dff466085d8701ca5d7ccdd6e98c573452d1ba Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 4 Sep 2026 23:51:57 +0000 Subject: [PATCH 1/2] fix(pwa): require native install prompt Co-authored-by: Roshan Raj Mishra --- CHANGELOG.md | 7 +++++++ README.md | 4 ++-- index.html | 2 +- js/pwa-install.js | 33 ++++++++++++++++++++------------- js/ui.js | 2 +- scripts/check-pwa.mjs | 12 +++++++++++- specs/pwa.md | 7 ++++--- sw.js | 2 +- 8 files changed, 47 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d630617..cdbe974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to FloRo Sign Controller are documented here. Version numbers match `APP_VERSION` in `js/ui.js`. Bump `CACHE_NAME` in `sw.js` when static assets change so installed PWAs pick up updates. +## [2.2.2] - 2026-09-05 + +### Fixed +- Match Health Hub's native installation contract: Android and desktop show **Install** only while Chromium supplies `beforeinstallprompt`, and clicking it calls `prompt()` directly. +- Remove the Android menu-help route that could lead users to **Create shortcut**, which is a Chrome bookmark and not a standalone PWA. +- Hide Settings > Install App when no native Chromium prompt exists; iOS and already-installed app guidance remain available. + ## [2.2.1] - 2026-09-05 ### Fixed diff --git a/README.md b/README.md index d97d7fb..bab6be9 100644 --- a/README.md +++ b/README.md @@ -116,11 +116,11 @@ See [`specs/pwa.md`](specs/pwa.md). Short version: **Install** calls the browser | Platform | Banner | Install button | |----------|--------|----------------| -| **Android (Chrome/Edge)** | Native prompt when available; otherwise immediate honest guidance | Native **Install**, or clearly labeled **Install help** | +| **Android (Chrome/Edge)** | Native prompt when available; otherwise immediate status guidance | Native **Install** only—never a browser shortcut | | **iOS (Safari)** | On first visit (Share → Add to Home Screen copy) | Hidden (Apple has no auto-install) | | **Desktop** | Hidden until `beforeinstallprompt` | Native **Install** when Chrome/Edge offers it | -Dismiss **Not now** stores `floro_install_dismissed_v6` for **7 days**. Settings > **Install App** still works (native prompt, or a help sheet). On Android, an installed app appears in the app drawer as **FloRo Sign**; the launcher may not automatically pin it to the Home screen. +Dismiss **Not now** stores `floro_install_dismissed_v7` for **7 days**. On Android, **Install** appears only when Chrome offers the native standalone-app prompt; FloRo never routes users through **Create shortcut**. An installed app appears in the app drawer as **FloRo Sign**; the launcher may not automatically pin it to the Home screen. ## Assets diff --git a/index.html b/index.html index 782794d..e0660e3 100644 --- a/index.html +++ b/index.html @@ -298,7 +298,7 @@

Activity Log

FloRo Sign Controller

-

v2.2.1

+

v2.2.2

diff --git a/js/pwa-install.js b/js/pwa-install.js index 0b993a2..aa93a36 100644 --- a/js/pwa-install.js +++ b/js/pwa-install.js @@ -1,6 +1,6 @@ /** Native PWA install with honest manual and already-installed fallbacks. */ -export const DISMISS_KEY = 'floro_install_dismissed_v6'; +export const DISMISS_KEY = 'floro_install_dismissed_v7'; const DISMISS_TTL_MS = 7 * 24 * 60 * 60 * 1000; export function isStandalone() { @@ -63,7 +63,7 @@ function installHelpText(hasNativePrompt, installedPwa) { return 'Open this page in Safari, tap Share, then Add to Home Screen.'; } if (isAndroid() && !hasNativePrompt) { - return 'Chrome has not offered one-tap install yet. You can still install from the Chrome menu.'; + return 'Waiting for Chrome’s app installer. Do not create a shortcut—it opens as a browser tab.'; } return 'Install FloRo so it opens full screen, not as a browser tab.'; } @@ -112,13 +112,22 @@ export function setupPwaInstall({ btnInstall.classList.toggle('hidden', !hasNativePrompt || installedPwa); } if (btnInstallHelp) { - btnInstallHelp.textContent = installedPwa ? 'Find app' : 'Install help'; + btnInstallHelp.textContent = installedPwa ? 'Find app' : 'How to install'; btnInstallHelp.classList.toggle( 'hidden', - hasNativePrompt || (!installedPwa && !isAndroid() && !isIOS()) + hasNativePrompt || (!installedPwa && !isIOS()) ); } btnDismiss?.classList.remove('hidden'); + syncSettingsInstall(); + } + + function syncSettingsInstall() { + const section = settingsBtn?.closest('.settings-section'); + if (!section) return; + const canInstallOrHelp = + !isStandalone() && (Boolean(deferredPrompt) || installedPwa || isIOS()); + section.classList.toggle('hidden', !canInstallOrHelp); } function renderModalSteps(items) { @@ -160,14 +169,14 @@ export function setupPwaInstall({ return; } - modalTitle.textContent = 'Install FloRo Sign'; + modalTitle.textContent = 'App install not ready'; modalDesc.textContent = - 'Chrome can delay or temporarily suppress its one-tap prompt. Manual installation is always available:'; + 'Chrome has not made native app installation available yet. A shortcut is only a bookmark and will keep the browser bar.'; renderModalSteps([ 'Open this page directly in Chrome or Edge, not an in-app browser', - 'Tap the three-dot Chrome menu', - 'Tap Add to Home screen (or Install and create shortcut), then choose Install app', - 'After installation, swipe up and search your app drawer for FloRo Sign', + 'Keep the page open briefly and interact with it so Chrome can finish checking the app', + 'Reload the page and use FloRo’s Install button when it appears', + 'Do not choose Create shortcut from Chrome’s menu', ]); } @@ -217,6 +226,7 @@ export function setupPwaInstall({ } else { hideBanner(); } + syncSettingsInstall(); window.addEventListener('appinstalled', () => { installedPwa = true; @@ -224,6 +234,7 @@ export function setupPwaInstall({ deferredPrompt = null; window.__floroDeferredInstall = null; showBanner(); + syncSettingsInstall(); }); btnInstall?.addEventListener('click', async () => { @@ -256,10 +267,6 @@ export function setupPwaInstall({ if (e.target === modal) closeHelpSheet(); }); - if (settingsBtn && isStandalone()) { - settingsBtn.closest('.settings-section')?.classList.add('hidden'); - } - detectInstalledPwa().then((installed) => { if (!installed || isStandalone()) return; installedPwa = true; diff --git a/js/ui.js b/js/ui.js index 7e5b4c2..77c16b9 100644 --- a/js/ui.js +++ b/js/ui.js @@ -1,6 +1,6 @@ import { formatModeLabel, getModeName, MODE_COUNT } from './mode-names.js'; -export const APP_VERSION = '2.2.1'; +export const APP_VERSION = '2.2.2'; export function createLogger(consoleBody) { return function log(message, type = 'info') { diff --git a/scripts/check-pwa.mjs b/scripts/check-pwa.mjs index 68dc8ea..76de08d 100644 --- a/scripts/check-pwa.mjs +++ b/scripts/check-pwa.mjs @@ -42,7 +42,7 @@ if (!html.includes('id="btn-install"') || !html.includes('install-banner-action- fail('Install button must start hidden until a native prompt exists'); } if (!html.includes('id="btn-install-help"')) { - fail('Android needs an honest manual-install fallback when Chrome withholds prompt()'); + fail('install help is required for iOS and already-installed app guidance'); } if (!html.includes("updateViaCache: 'none'")) { fail('service worker registration must bypass stale HTTP caches'); @@ -138,6 +138,16 @@ if (!nativeInstallHandler.includes('triggerNativeInstall') || if (!pwaSrc.includes("btnInstall.classList.toggle('hidden', !hasNativePrompt || installedPwa)")) { fail('Install button visibility must follow hasNativePrompt'); } +if (!pwaSrc.includes("hasNativePrompt || (!installedPwa && !isIOS())")) { + fail('Android must not show a manual install action without the native prompt'); +} +if (!pwaSrc.includes('syncSettingsInstall')) { + fail('Settings install action must be hidden until native install or valid platform help exists'); +} +if (pwaSrc.includes('Install and create shortcut') || + pwaSrc.includes('Tap the three-dot Chrome menu')) { + fail('Android install guidance must never route users into Chrome shortcut creation'); +} if (!process.exitCode) { console.log('check-pwa: ok'); diff --git a/specs/pwa.md b/specs/pwa.md index 024251a..ded0797 100644 --- a/specs/pwa.md +++ b/specs/pwa.md @@ -61,15 +61,15 @@ Module: `js/pwa-install.js`. Early capture in `index.html` (before other scripts | Situation | Banner | Install button | Click | |-----------|--------|----------------|-------| | `beforeinstallprompt` fired | Show | Show | `event.prompt()` only | -| Android, no native event | Show immediately (honest copy) | **Install help** | Chrome menu instructions | +| Android, no native event | Show immediately (honest copy) | Hidden | Wait for native eligibility; never create a shortcut | | Android, PWA already installed | Show find-app copy | **Find app** | App-drawer instructions | | iOS, not standalone | Show (Share → A2HS copy) | **Install help** | Share → Add to Home Screen instructions | | Desktop, no event | Hidden | Hidden | Settings help sheet only | | Already standalone | Hidden | Hidden | Settings install row hidden | -Dismiss **Not now** writes `localStorage` `floro_install_dismissed_v6` with a 7-day TTL. Settings → **Install App** ignores dismiss: native `prompt()` if held, otherwise the help sheet. A self-reference in `related_applications` lets supporting Chrome versions distinguish “prompt unavailable” from “already installed.” Android launchers may install into the app drawer without pinning a Home screen icon, so the installed help names both **FloRo Sign** and the old **FloRo** short name. +Dismiss **Not now** writes `localStorage` `floro_install_dismissed_v7` with a 7-day TTL. On Android and desktop, Settings → **Install App** is visible only while a native prompt is held or an existing installation was detected. It never directs users into Chrome’s **Create shortcut** flow, which creates a browser bookmark instead of a standalone app. A self-reference in `related_applications` lets supporting Chrome versions distinguish “prompt unavailable” from “already installed.” Android launchers may install into the app drawer without pinning a Home screen icon, so the installed help names both **FloRo Sign** and the old **FloRo** short name. -`preventDefault` on `beforeinstallprompt` is required to call `prompt()` later. The in-page **Install** button must call that event directly. If Chrome does not emit the event (for example, after a recent dismissal), the separately labeled **Install help** action may explain the manual menu route; it must never pretend to invoke native installation. +`preventDefault` on `beforeinstallprompt` is required to call `prompt()` later. The in-page **Install** button must call that event directly. If Chrome does not emit the event, FloRo must not offer an Android install action. **Create shortcut** is not an installation and must never be presented as a fallback. ## Verification @@ -83,5 +83,6 @@ Manual: Chrome or Edge on Android, https://rawshn.com/sign-controller/, tap **In | Date | Change | |------|--------| +| 2026-09-05 | Removed Android shortcut flow; only native `beforeinstallprompt` can expose Install | | 2026-09-05 | Register SW immediately, detect existing installs, add honest manual fallback, and make cached launches fast | | 2026-09-05 | Reimplemented to match Health Hub: native prompt only, static manifest, relative scope | diff --git a/sw.js b/sw.js index faa3c22..db94448 100644 --- a/sw.js +++ b/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'floro-controller-v62'; +const CACHE_NAME = 'floro-controller-v63'; const ASSETS = [ './', './index.html', From ca0b2aa56e0a6364c3b69b9ae51e3bb8a2199e87 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 4 Sep 2026 23:55:16 +0000 Subject: [PATCH 2/2] fix(pwa): match Health Hub installer exactly Co-authored-by: Roshan Raj Mishra --- CHANGELOG.md | 4 ++- index.html | 1 - js/app.js | 1 - js/pwa-install.js | 66 ++++++------------------------------------- manifest.webmanifest | 8 +----- scripts/check-pwa.mjs | 22 +++++++-------- specs/pwa.md | 11 +++----- sw.js | 2 +- 8 files changed, 27 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdbe974..dfd06f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ All notable changes to FloRo Sign Controller are documented here. Version number ### Fixed - Match Health Hub's native installation contract: Android and desktop show **Install** only while Chromium supplies `beforeinstallprompt`, and clicking it calls `prompt()` directly. - Remove the Android menu-help route that could lead users to **Create shortcut**, which is a Chrome bookmark and not a standalone PWA. -- Hide Settings > Install App when no native Chromium prompt exists; iOS and already-installed app guidance remain available. +- Hide Settings > Install App when no native Chromium prompt exists; iOS guidance remains available. +- Remove the separate install-help control, self-referential `related_applications`, and installed-app detection for strict Health Hub parity. +- Restore the canonical production manifest ID used by Health Hub's identity pattern. ## [2.2.1] - 2026-09-05 diff --git a/index.html b/index.html index e0660e3..baa8b9c 100644 --- a/index.html +++ b/index.html @@ -249,7 +249,6 @@

Install FloRo so it opens full screen, not as a browser tab.

-
diff --git a/js/app.js b/js/app.js index 56a9502..e98b2cb 100644 --- a/js/app.js +++ b/js/app.js @@ -1190,7 +1190,6 @@ setupPwaInstall({ bannerTitle: document.getElementById('install-banner-title'), bannerSubtitle: document.getElementById('install-banner-subtitle'), btnInstall: document.getElementById('btn-install'), - btnInstallHelp: document.getElementById('btn-install-help'), btnDismiss: document.getElementById('btn-dismiss-install'), settingsBtn: document.getElementById('btn-settings-install'), modal: document.getElementById('install-modal'), diff --git a/js/pwa-install.js b/js/pwa-install.js index aa93a36..0b0aaea 100644 --- a/js/pwa-install.js +++ b/js/pwa-install.js @@ -43,22 +43,7 @@ function shouldSuppressBanner() { return isStandalone() || isDismissed(); } -async function detectInstalledPwa() { - if (isStandalone()) return true; - if (!('getInstalledRelatedApps' in navigator)) return false; - - try { - const apps = await navigator.getInstalledRelatedApps(); - return apps.some((app) => app.platform === 'webapp'); - } catch { - return false; - } -} - -function installHelpText(hasNativePrompt, installedPwa) { - if (installedPwa) { - return 'FloRo Sign is installed. Find it in your Android app drawer; Chrome may not add a Home screen icon.'; - } +function installHelpText(hasNativePrompt) { if (isIOS() && !hasNativePrompt) { return 'Open this page in Safari, tap Share, then Add to Home Screen.'; } @@ -73,7 +58,6 @@ export function setupPwaInstall({ bannerTitle, bannerSubtitle, btnInstall, - btnInstallHelp, btnDismiss, settingsBtn, modal, @@ -85,7 +69,6 @@ export function setupPwaInstall({ log, }) { let deferredPrompt = window.__floroDeferredInstall || null; - let installedPwa = isStandalone(); function showBanner() { if (!bannerEl || shouldSuppressBanner()) return; @@ -102,21 +85,14 @@ export function setupPwaInstall({ function syncBanner() { const hasNativePrompt = Boolean(deferredPrompt); if (bannerTitle) { - bannerTitle.textContent = installedPwa ? 'FloRo Sign is installed' : 'Install FloRo Sign'; + bannerTitle.textContent = 'Install FloRo Sign'; } if (bannerSubtitle) { - bannerSubtitle.textContent = installHelpText(hasNativePrompt, installedPwa); + bannerSubtitle.textContent = installHelpText(hasNativePrompt); } if (btnInstall) { btnInstall.textContent = 'Install'; - btnInstall.classList.toggle('hidden', !hasNativePrompt || installedPwa); - } - if (btnInstallHelp) { - btnInstallHelp.textContent = installedPwa ? 'Find app' : 'How to install'; - btnInstallHelp.classList.toggle( - 'hidden', - hasNativePrompt || (!installedPwa && !isIOS()) - ); + btnInstall.classList.toggle('hidden', !hasNativePrompt); } btnDismiss?.classList.remove('hidden'); syncSettingsInstall(); @@ -126,7 +102,7 @@ export function setupPwaInstall({ const section = settingsBtn?.closest('.settings-section'); if (!section) return; const canInstallOrHelp = - !isStandalone() && (Boolean(deferredPrompt) || installedPwa || isIOS()); + !isStandalone() && (Boolean(deferredPrompt) || isIOS()); section.classList.toggle('hidden', !canInstallOrHelp); } @@ -146,18 +122,6 @@ export function setupPwaInstall({ modalSteps?.classList.remove('hidden'); modalAction?.classList.add('hidden'); - if (installedPwa) { - modalTitle.textContent = 'Find FloRo Sign'; - modalDesc.textContent = - 'Android installs the app in your app drawer, but your launcher may not add it to the Home screen.'; - renderModalSteps([ - 'Swipe up from your Home screen to open the app drawer', - 'Search for FloRo Sign (older installs may appear as FloRo)', - 'Long-press the app icon and drag it to your Home screen', - ]); - return; - } - if (isIOS()) { modalTitle.textContent = 'Add to Home Screen'; modalDesc.textContent = 'iPhone and iPad cannot auto-install. In Safari:'; @@ -186,13 +150,10 @@ export function setupPwaInstall({ async function triggerNativeInstall() { if (!deferredPrompt) return false; - let accepted = false; try { deferredPrompt.prompt(); const { outcome } = await deferredPrompt.userChoice; if (outcome === 'accepted') { - accepted = true; - installedPwa = true; log?.('User installed FloRo Sign.', 'success'); } } catch (err) { @@ -202,15 +163,14 @@ export function setupPwaInstall({ deferredPrompt = null; window.__floroDeferredInstall = null; closeHelpSheet(); - if (accepted) showBanner(); - else hideBanner(); + hideBanner(); + syncSettingsInstall(); return true; } function capturePrompt(event) { event.preventDefault(); deferredPrompt = event; - installedPwa = false; window.__floroDeferredInstall = event; showBanner(); } @@ -229,11 +189,10 @@ export function setupPwaInstall({ syncSettingsInstall(); window.addEventListener('appinstalled', () => { - installedPwa = true; log?.('FloRo Sign installed.', 'success'); deferredPrompt = null; window.__floroDeferredInstall = null; - showBanner(); + hideBanner(); syncSettingsInstall(); }); @@ -241,8 +200,6 @@ export function setupPwaInstall({ await triggerNativeInstall(); }); - btnInstallHelp?.addEventListener('click', openHelpSheet); - btnDismiss?.addEventListener('click', () => { try { localStorage.setItem(DISMISS_KEY, String(Date.now())); @@ -267,11 +224,4 @@ export function setupPwaInstall({ if (e.target === modal) closeHelpSheet(); }); - detectInstalledPwa().then((installed) => { - if (!installed || isStandalone()) return; - installedPwa = true; - deferredPrompt = null; - window.__floroDeferredInstall = null; - showBanner(); - }); } diff --git a/manifest.webmanifest b/manifest.webmanifest index a75b601..d3b9df8 100644 --- a/manifest.webmanifest +++ b/manifest.webmanifest @@ -4,19 +4,13 @@ "description": "Control your FloRo neon sign over Bluetooth", "start_url": "./", "scope": "./", - "id": "./", + "id": "https://rawshn.com/sign-controller/", "display": "standalone", "display_override": ["standalone"], "orientation": "portrait", "background_color": "#020D0A", "theme_color": "#020D0A", "prefer_related_applications": false, - "related_applications": [ - { - "platform": "webapp", - "url": "./manifest.webmanifest" - } - ], "launch_handler": { "client_mode": ["navigate-existing", "auto"] }, diff --git a/scripts/check-pwa.mjs b/scripts/check-pwa.mjs index 76de08d..898d653 100644 --- a/scripts/check-pwa.mjs +++ b/scripts/check-pwa.mjs @@ -41,8 +41,8 @@ if (!html.includes('beforeinstallprompt')) { if (!html.includes('id="btn-install"') || !html.includes('install-banner-action-primary hidden')) { fail('Install button must start hidden until a native prompt exists'); } -if (!html.includes('id="btn-install-help"')) { - fail('install help is required for iOS and already-installed app guidance'); +if (html.includes('id="btn-install-help"')) { + fail('do not expose a separate install-help action; match Health Hub native prompt flow'); } if (!html.includes("updateViaCache: 'none'")) { fail('service worker registration must bypass stale HTTP caches'); @@ -68,8 +68,8 @@ if (manifest) { if (manifest.start_url !== './' || manifest.scope !== './') { fail('manifest start_url and scope must be "./" (portable across localhost and /sign-controller/)'); } - if (manifest.id !== './') { - fail('manifest id must be "./" so it resolves consistently on production and localhost'); + if (manifest.id !== 'https://rawshn.com/sign-controller/') { + fail('manifest id must be the canonical production identity'); } if (manifest.display !== 'standalone') { fail('manifest display must be standalone'); @@ -89,10 +89,8 @@ if (manifest) { if (!icons.some((i) => i.sizes === '192x192') || !icons.some((i) => i.sizes === '512x512')) { fail('manifest needs 192x192 and 512x512 icons'); } - if (!manifest.related_applications?.some( - (app) => app.platform === 'webapp' && app.url === './manifest.webmanifest' - )) { - fail('manifest must self-reference so Android can detect an existing installation'); + if (manifest.related_applications) { + fail('do not self-reference related applications; it is not part of Health Hub installation'); } } @@ -135,15 +133,15 @@ if (!nativeInstallHandler.includes('triggerNativeInstall') || nativeInstallHandler.includes('openHelpSheet')) { fail('Install button click must call prompt() only; do not open the help sheet'); } -if (!pwaSrc.includes("btnInstall.classList.toggle('hidden', !hasNativePrompt || installedPwa)")) { +if (!pwaSrc.includes("btnInstall.classList.toggle('hidden', !hasNativePrompt)")) { fail('Install button visibility must follow hasNativePrompt'); } -if (!pwaSrc.includes("hasNativePrompt || (!installedPwa && !isIOS())")) { - fail('Android must not show a manual install action without the native prompt'); -} if (!pwaSrc.includes('syncSettingsInstall')) { fail('Settings install action must be hidden until native install or valid platform help exists'); } +if (pwaSrc.includes('getInstalledRelatedApps')) { + fail('do not let installed-app detection clear a valid deferred native prompt'); +} if (pwaSrc.includes('Install and create shortcut') || pwaSrc.includes('Tap the three-dot Chrome menu')) { fail('Android install guidance must never route users into Chrome shortcut creation'); diff --git a/specs/pwa.md b/specs/pwa.md index ded0797..a191d93 100644 --- a/specs/pwa.md +++ b/specs/pwa.md @@ -30,14 +30,12 @@ No `` tag. Relative URLs resolve against the page URL (`/` locally, `/sign |-------|--------| | `name` / `short_name` | FloRo Sign Controller / FloRo Sign | | `start_url` / `scope` | `./` | -| `id` | `./` (resolves to the canonical scope in production and localhost in development) | +| `id` | `https://rawshn.com/sign-controller/` (canonical production identity) | | `display` | `standalone` | | `display_override` | `["standalone"]` | | `launch_handler.client_mode` | `["navigate-existing", "auto"]` (array, not a string) | | `icons` | `./icons/icon-192.png` (`any`), `./icons/icon-512.png` (`any` + `maskable`) | | `theme_color` / `background_color` | `#020D0A` | -| `related_applications` | Self-reference used to detect an existing Android installation | - `prefer_related_applications` stays `false` so Chrome does not skip the install UI looking for a Play Store app. ## Service worker @@ -62,12 +60,11 @@ Module: `js/pwa-install.js`. Early capture in `index.html` (before other scripts |-----------|--------|----------------|-------| | `beforeinstallprompt` fired | Show | Show | `event.prompt()` only | | Android, no native event | Show immediately (honest copy) | Hidden | Wait for native eligibility; never create a shortcut | -| Android, PWA already installed | Show find-app copy | **Find app** | App-drawer instructions | -| iOS, not standalone | Show (Share → A2HS copy) | **Install help** | Share → Add to Home Screen instructions | -| Desktop, no event | Hidden | Hidden | Settings help sheet only | +| iOS, not standalone | Show (Share → A2HS copy) | Hidden | Settings retains Share → Add to Home Screen help | +| Desktop, no event | Hidden | Hidden | Settings install row hidden | | Already standalone | Hidden | Hidden | Settings install row hidden | -Dismiss **Not now** writes `localStorage` `floro_install_dismissed_v7` with a 7-day TTL. On Android and desktop, Settings → **Install App** is visible only while a native prompt is held or an existing installation was detected. It never directs users into Chrome’s **Create shortcut** flow, which creates a browser bookmark instead of a standalone app. A self-reference in `related_applications` lets supporting Chrome versions distinguish “prompt unavailable” from “already installed.” Android launchers may install into the app drawer without pinning a Home screen icon, so the installed help names both **FloRo Sign** and the old **FloRo** short name. +Dismiss **Not now** writes `localStorage` `floro_install_dismissed_v7` with a 7-day TTL. On Android and desktop, Settings → **Install App** is visible only while a native prompt is held. It never directs users into Chrome’s **Create shortcut** flow, which creates a browser bookmark instead of a standalone app. As in Health Hub, FloRo does not use `related_applications` or installed-app detection as part of installation. `preventDefault` on `beforeinstallprompt` is required to call `prompt()` later. The in-page **Install** button must call that event directly. If Chrome does not emit the event, FloRo must not offer an Android install action. **Create shortcut** is not an installation and must never be presented as a fallback. diff --git a/sw.js b/sw.js index db94448..303dc52 100644 --- a/sw.js +++ b/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'floro-controller-v63'; +const CACHE_NAME = 'floro-controller-v64'; const ASSETS = [ './', './index.html',