Fix PWA install hint dismiss after cached HTML/JS mismatch.

Restore button id, bind dismiss at document level, force hide inline, and fetch pwa.js/style.css network-first.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-20 15:37:30 +02:00
parent 6ba4cf447f
commit 0e9dbf1735
4 changed files with 45 additions and 17 deletions
+22 -14
View File
@@ -4,7 +4,8 @@ const Pwa = (() => {
const DISMISS_KEY = "pwa-hint-dismissed"; const DISMISS_KEY = "pwa-hint-dismissed";
let deferredPrompt = null; let deferredPrompt = null;
let refreshInstallHint = null; let refreshInstallHint = null;
let hintBound = false; let dismissListenerBound = false;
let initialized = false;
function isStandalone() { function isStandalone() {
return window.matchMedia("(display-mode: standalone)").matches return window.matchMedia("(display-mode: standalone)").matches
@@ -44,40 +45,42 @@ const Pwa = (() => {
} }
function hideHint(hint, { persist = true } = {}) { function hideHint(hint, { persist = true } = {}) {
if (!hint) return;
if (persist) { if (persist) {
try { try {
localStorage.setItem(DISMISS_KEY, "1"); localStorage.setItem(DISMISS_KEY, "1");
} catch { } catch {
/* private mode with storage blocked still hide for this session */ /* storage blocked still hide for this session */
} }
} }
hint.hidden = true; hint.hidden = true;
hint.classList.add("is-dismissed"); hint.classList.add("is-dismissed");
hint.style.setProperty("display", "none", "important");
} }
function showHint(hint) { function showHint(hint) {
if (!hint) return;
hint.hidden = false; hint.hidden = false;
hint.classList.remove("is-dismissed"); hint.classList.remove("is-dismissed");
hint.style.removeProperty("display");
} }
function bindHintActions(hint) { function bindDismissListener() {
if (hintBound) return; if (dismissListenerBound) return;
hintBound = true; dismissListenerBound = true;
hint.addEventListener("click", (event) => { document.addEventListener("click", (event) => {
if (event.target.closest(".pwa-hint-dismiss")) { if (!event.target.closest("#pwa-hint-dismiss, .pwa-hint-dismiss")) return;
event.preventDefault(); event.preventDefault();
hideHint(hint); event.stopPropagation();
} hideHint(document.getElementById("pwa-install-hint"));
}); }, true);
} }
function setupInstallHint() { function setupInstallHint() {
const hint = document.getElementById("pwa-install-hint"); const hint = document.getElementById("pwa-install-hint");
if (!hint) return null; if (!hint) return null;
bindHintActions(hint);
if (isStandalone()) { if (isStandalone()) {
hideHint(hint, { persist: false }); hideHint(hint, { persist: false });
return null; return null;
@@ -89,9 +92,10 @@ const Pwa = (() => {
const body = document.getElementById("pwa-hint-body"); const body = document.getElementById("pwa-hint-body");
const installBtn = document.getElementById("pwa-install-btn"); const installBtn = document.getElementById("pwa-install-btn");
const dismissBtn = hint.querySelector(".pwa-hint-dismiss"); const dismissBtn = document.getElementById("pwa-hint-dismiss");
const updateHintText = () => { const updateHintText = () => {
if (typeof t !== "function") return;
const title = hint.querySelector("[data-i18n='pwa.hintTitle']"); const title = hint.querySelector("[data-i18n='pwa.hintTitle']");
if (title) title.textContent = t("pwa.hintTitle"); if (title) title.textContent = t("pwa.hintTitle");
if (body && !deferredPrompt) body.textContent = t(platformHintKey()); if (body && !deferredPrompt) body.textContent = t(platformHintKey());
@@ -129,6 +133,8 @@ const Pwa = (() => {
} }
async function init() { async function init() {
if (initialized) return;
initialized = true;
refreshInstallHint = setupInstallHint(); refreshInstallHint = setupInstallHint();
await registerServiceWorker(); await registerServiceWorker();
} }
@@ -137,6 +143,8 @@ const Pwa = (() => {
refreshInstallHint?.(); refreshInstallHint?.();
} }
bindDismissListener();
return { init, refreshHint }; return { init, refreshHint };
})(); })();
+21 -1
View File
@@ -1,4 +1,4 @@
const CACHE = "if-viewer-static-v5"; const CACHE = "if-viewer-static-v6";
const ASSETS = [ const ASSETS = [
"/static/style.css", "/static/style.css",
"/static/favicon.svg", "/static/favicon.svg",
@@ -10,6 +10,11 @@ const ASSETS = [
"/static/locales/de.json", "/static/locales/de.json",
]; ];
const NETWORK_FIRST = new Set([
"/static/pwa.js",
"/static/style.css",
]);
self.addEventListener("install", (event) => { self.addEventListener("install", (event) => {
event.waitUntil( event.waitUntil(
caches.open(CACHE) caches.open(CACHE)
@@ -33,6 +38,21 @@ self.addEventListener("fetch", (event) => {
if (url.pathname.includes("/api/")) return; if (url.pathname.includes("/api/")) return;
if (!url.pathname.startsWith("/static/")) return; if (!url.pathname.startsWith("/static/")) return;
if (NETWORK_FIRST.has(url.pathname)) {
event.respondWith(
fetch(event.request)
.then((response) => {
if (response.ok) {
const copy = response.clone();
caches.open(CACHE).then((cache) => cache.put(event.request, copy));
}
return response;
})
.catch(() => caches.match(event.request))
);
return;
}
event.respondWith( event.respondWith(
caches.match(event.request).then((cached) => cached || fetch(event.request)) caches.match(event.request).then((cached) => cached || fetch(event.request))
); );
+1 -1
View File
@@ -3,4 +3,4 @@
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-icon" href="/static/icon-192.png"> <link rel="apple-touch-icon" href="/static/icon-192.png">
<script src="/static/pwa.js" defer></script> <script src="/static/pwa.js?v=7" defer></script>
+1 -1
View File
@@ -5,6 +5,6 @@
</div> </div>
<div class="pwa-hint-actions"> <div class="pwa-hint-actions">
<button type="button" id="pwa-install-btn" class="viewer-copy-btn" hidden data-i18n="pwa.install">Install</button> <button type="button" id="pwa-install-btn" class="viewer-copy-btn" hidden data-i18n="pwa.install">Install</button>
<button type="button" class="pwa-hint-dismiss" aria-label="Dismiss">×</button> <button type="button" id="pwa-hint-dismiss" class="pwa-hint-dismiss" aria-label="Dismiss">×</button>
</div> </div>
</div> </div>