fix(sw, assets, install): bypass HTTP cache + revalidate assets + mobile-only install banner (t-paliad-043 step 3)
After step 2 deployed the IIFE-wrapped bundles, m's browser still saw
the broken page because /assets/projects.js was being served from the
local HTTP cache (no Cache-Control, just heuristic freshness from
Last-Modified). Even after the new SW activated and cleared its own
caches, its cacheFirst handler did `fetch(req)` which goes through the
browser HTTP cache — re-fed the SW cache from the stale bundle and the
loop perpetuated forever.
Three mutually reinforcing fixes:
1. SW cacheFirst now does `fetch(req, { cache: "reload" })` for the
network leg. Forces the network fetch to bypass the browser's HTTP
cache, so the SW always seeds its own cache from a true network read.
2. Go static handlers for /assets/* and /icons/* set
`Cache-Control: no-cache, must-revalidate`. Combined with the
Last-Modified that http.FileServer already emits, browsers send
If-Modified-Since and the server replies 304 when unchanged — fast
for repeat loads, fresh on every deploy. Users without a SW (or after
the kill-switch unregistered theirs) now also pick up new bundles
immediately.
3. pwa-install.ts gates the install banner on
`(min-width: 768px)` — same breakpoint the BottomNav and other
mobile-shell elements use. Desktop partners no longer get an install
prompt covering their work area.
This commit is contained in:
@@ -56,7 +56,12 @@ async function cacheFirst(req) {
|
||||
const cached = await cache.match(req);
|
||||
if (cached) return cached;
|
||||
try {
|
||||
const res = await fetch(req);
|
||||
// cache: "reload" forces the network leg to BYPASS the browser HTTP
|
||||
// cache. Without this, a stale /assets/projects.js sitting in the
|
||||
// browser's disk cache from a previous deploy would be returned to us,
|
||||
// we'd cache it again, and the user would be stuck on the old bundle
|
||||
// forever — exactly the failure mode that caused t-paliad-043.
|
||||
const res = await fetch(req, { cache: "reload" });
|
||||
if (res && res.ok) cache.put(req, res.clone());
|
||||
return res;
|
||||
} catch (err) {
|
||||
|
||||
@@ -22,6 +22,12 @@ let deferredPrompt: BeforeInstallPromptEvent | null = null;
|
||||
export function initInstallPrompt(): void {
|
||||
if (localStorage.getItem(DISMISS_KEY) === "1") return;
|
||||
|
||||
// The install prompt is part of the PWA mobile shell — same audience as
|
||||
// the BottomNav. On desktop, partners use the sidebar nav and don't want
|
||||
// an install banner overlapping their work area. Gate on the same 768px
|
||||
// breakpoint as global.css's mobile media queries.
|
||||
if (window.matchMedia("(min-width: 768px)").matches) return;
|
||||
|
||||
// Skip when already running as an installed PWA.
|
||||
if (window.matchMedia("(display-mode: standalone)").matches) return;
|
||||
// Safari iOS exposes navigator.standalone for the same signal.
|
||||
|
||||
Reference in New Issue
Block a user