fix: 🐛 Gerber demo: survive a stale cross-origin-isolation state

The demo showed "Live demo unavailable in this browser" on fully capable
browsers (e.g. Chrome) whenever a page load wasn't cross-origin isolated —
a soft SPA navigation, or a visit cached from before the COOP/COEP headers
went live. The old reload guard used a sticky sessionStorage flag that, once
set during a non-isolated load, never cleared, so it skipped the recovery
reload and showed a misleading "unsupported browser" message.

- Reload guard now uses a timestamp, not a sticky flag: a stale flag from an
  earlier visit no longer permanently strands the demo (reload loop still
  prevented within a 10s debounce).
- Non-isolated pages now open the standalone viewer (/gerber-demo/, isolated
  on its own and always works) in a new tab instead of dead-ending.
- The "needs a recent browser" note only shows when provably incapable
  (isolated but missing SharedArrayBuffer/WebGL2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Viktor Vaczi 2026-06-18 14:33:55 +02:00
commit 429ac206f9

View file

@ -56,6 +56,7 @@ const caption =
<script> <script>
const BOOT_SRC = "/gerber-demo/index.html"; const BOOT_SRC = "/gerber-demo/index.html";
const RELOAD_KEY = "gerber-demo-coi-reload"; const RELOAD_KEY = "gerber-demo-coi-reload";
const RELOAD_DEBOUNCE_MS = 10000;
function hasWebGL2(): boolean { function hasWebGL2(): boolean {
try { try {
@ -65,7 +66,8 @@ const caption =
} }
} }
function launch(root: HTMLElement): void { // Best path: embed the viewer inline (only when this page is cross-origin isolated).
function launchInline(root: HTMLElement): void {
if (root.dataset.state === "loading" || root.dataset.state === "ready") return; if (root.dataset.state === "loading" || root.dataset.state === "ready") return;
root.dataset.state = "loading"; root.dataset.state = "loading";
@ -95,54 +97,65 @@ const caption =
frame?.appendChild(iframe); frame?.appendChild(iframe);
} }
function showFallback(root: HTMLElement): void { // Genuinely incapable browser: cross-origin isolated, yet no SharedArrayBuffer
// or WebGL2. The only true dead-end — show the poster + the "recent browser"
// note. (We can ONLY assert this when isolated; on a non-isolated page
// SharedArrayBuffer is always undefined regardless of the browser's support.)
function showUnsupported(root: HTMLElement): void {
root.dataset.state = "unsupported"; root.dataset.state = "unsupported";
const launchEl = root.querySelector<HTMLElement>(".gerber-demo__launch"); const launchEl = root.querySelector<HTMLElement>(".gerber-demo__launch");
const fallback = root.querySelector<HTMLElement>(".gerber-demo__fallback");
// Keep the poster (it's the <a> background); hide the play affordance, show note.
launchEl?.querySelector(".gerber-demo__play")?.setAttribute("hidden", ""); launchEl?.querySelector(".gerber-demo__play")?.setAttribute("hidden", "");
launchEl?.querySelector(".gerber-demo__cta")?.setAttribute("hidden", ""); launchEl?.querySelector(".gerber-demo__cta")?.setAttribute("hidden", "");
launchEl?.querySelector(".gerber-demo__sub")?.setAttribute("hidden", ""); launchEl?.querySelector(".gerber-demo__sub")?.setAttribute("hidden", "");
if (launchEl) { if (launchEl) {
launchEl.removeAttribute("href"); // not clickable launchEl.removeAttribute("href");
launchEl.style.cursor = "default"; launchEl.style.cursor = "default";
} }
const fallback = root.querySelector<HTMLElement>(".gerber-demo__fallback");
if (fallback) fallback.hidden = false; if (fallback) fallback.hidden = false;
} }
function initDemo(): void { function initDemo(): void {
const root = document.querySelector<HTMLElement>(".gerber-demo"); const root = document.querySelector<HTMLElement>(".gerber-demo");
if (!root) return; // page without the demo if (!root) return; // page without the demo
const launchEl = root.querySelector<HTMLAnchorElement>(".gerber-demo__launch");
const isolated = window.crossOriginIsolated === true; if (window.crossOriginIsolated === true) {
// Soft SPA navigation lands here NOT cross-origin isolated; force one real
// load so the route's COOP/COEP headers apply. Guard against reload loops.
if (!isolated) {
if (!sessionStorage.getItem(RELOAD_KEY)) {
sessionStorage.setItem(RELOAD_KEY, "1");
location.reload();
return;
}
// Already retried and still not isolated → genuinely unsupported (or no
// headers, e.g. a preview without vercel.json). Fall back.
} else {
sessionStorage.removeItem(RELOAD_KEY); sessionStorage.removeItem(RELOAD_KEY);
} if (typeof SharedArrayBuffer !== "undefined" && hasWebGL2()) {
root.dataset.state = "idle";
const supported = launchEl?.addEventListener("click", (e) => {
isolated && typeof SharedArrayBuffer !== "undefined" && hasWebGL2(); e.preventDefault(); // intercept the href; embed inline instead
launchInline(root);
if (!supported) { });
showFallback(root); } else {
showUnsupported(root);
}
return; return;
} }
const launchEl = root.querySelector<HTMLElement>(".gerber-demo__launch"); // NOT cross-origin isolated — usually because a soft SPA navigation delivered
launchEl?.addEventListener("click", (e) => { // this page (the route's COOP/COEP only take effect on a real load). Reload
e.preventDefault(); // intercept the href; embed inline instead // ONCE to get the isolated version so we can embed inline. Guard with a
launch(root); // TIMESTAMP, not a sticky flag: a stale flag from an earlier visit must not
}); // permanently strand the demo, but two reloads within the debounce window
// would be a loop — so we only suppress a reload we *just* did.
const last = Number(sessionStorage.getItem(RELOAD_KEY) || "0");
if (Date.now() - last > RELOAD_DEBOUNCE_MS) {
sessionStorage.setItem(RELOAD_KEY, String(Date.now()));
location.reload();
return;
}
// Reloaded moments ago and STILL not isolated (stale cache, missing headers,
// a preview without vercel.json…). Don't dead-end and don't blame the
// browser: the standalone viewer page is isolated on its own, so let the
// button open it in a new tab — capable browsers get the full viewer there.
if (launchEl) {
launchEl.target = "_blank";
launchEl.rel = "noopener";
root.dataset.state = "external";
}
} }
// Run on first load and on every SPA navigation (ClientRouter). // Run on first load and on every SPA navigation (ClientRouter).