18c4530721
Per Codex + Jay: the SW was added for nice-to-have PWA/offline caching, but it sat in the boot path and put first loads at risk (post-deploy tiny-chunk stalls of 4-10s — fast HTML, then delayed chunks). For a young site where a handful of visitors a day IS the audience, a broken first impression is a huge share of traffic. The site's value doesn't need offline caching; browser HTTP cache + the Cloudflare edge are enough. Removed cleanly (not just deleted — that strands the old worker on existing clients): - Delete src/service-worker.js → SvelteKit stops auto-registering. - static/service-worker.js is now a one-shot KILL SWITCH: takes over, wipes all caches, unregisters itself, no fetch handler (requests go straight to network/ browser cache). Served no-cache so existing clients pick it up. - app.html boot script unregisters any worker + clears caches on load, as a backstop so no returning visitor stays stuck on the old boot path. The boot seatbelt (timeout card, preloadError reload-once, telemetry) stays — that, not the SW, was the real blank-screen protection. Build clean, 11 vitest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
138 lines
7.5 KiB
HTML
138 lines
7.5 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<link rel="icon" href="%sveltekit.assets%/favicon.svg" />
|
|
<link rel="apple-touch-icon" href="%sveltekit.assets%/icon-192.png" />
|
|
<link rel="manifest" href="%sveltekit.assets%/manifest.webmanifest" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<meta name="theme-color" content="#0083ad" />
|
|
<meta name="description" content="Calm, constructive news worth your attention — and nothing that isn't." />
|
|
<title>Upbeat Bytes — calm, constructive news</title>
|
|
<link rel="canonical" href="https://upbeatbytes.com/" />
|
|
<meta property="og:site_name" content="Upbeat Bytes" />
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:title" content="Upbeat Bytes — calm, constructive news" />
|
|
<meta property="og:description" content="Calm, constructive news worth your attention — and nothing that isn't. Summarized, so you get the gist and go deeper only if you want." />
|
|
<meta property="og:url" content="https://upbeatbytes.com/" />
|
|
<meta name="twitter:card" content="summary" />
|
|
<meta name="twitter:title" content="Upbeat Bytes — calm, constructive news" />
|
|
<meta name="twitter:description" content="Calm, constructive news, summarized — get the gist, go deeper only if you want." />
|
|
%sveltekit.head%
|
|
<style>
|
|
#boot-fallback {
|
|
display: none; position: fixed; inset: 0; z-index: 9999;
|
|
align-items: center; justify-content: center; background: #f7f4ec;
|
|
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; padding: 24px;
|
|
}
|
|
#boot-fallback .bf { text-align: center; max-width: 360px; }
|
|
#boot-fallback img { height: 46px; width: auto; margin-bottom: 18px; }
|
|
#boot-fallback p { color: #4a5560; font-size: 1rem; line-height: 1.55; margin: 0 0 20px; }
|
|
#boot-fallback button {
|
|
background: #0083ad; color: #fff; border: none; border-radius: 999px;
|
|
padding: 12px 26px; font: inherit; font-weight: 600; font-size: 0.95rem; cursor: pointer;
|
|
}
|
|
</style>
|
|
<script>
|
|
// Reliability seatbelt: never let a slow/failed boot become a silent white
|
|
// screen. Show a calm recovery card if the app hasn't mounted, and reload
|
|
// once on a chunk/preload failure (e.g. a just-deployed hashed chunk).
|
|
(function () {
|
|
// The service worker was removed (it risked first loads). Actively
|
|
// unregister any worker a returning visitor still has + wipe its caches,
|
|
// so nobody stays stuck on the old boot path. Runs on every load; cheap.
|
|
if ('serviceWorker' in navigator) {
|
|
try {
|
|
navigator.serviceWorker.getRegistrations()
|
|
.then(function (rs) { rs.forEach(function (r) { r.unregister(); }); })
|
|
.catch(function () {});
|
|
if (self.caches && caches.keys) {
|
|
caches.keys().then(function (ks) { ks.forEach(function (k) { caches.delete(k); }); }).catch(function () {});
|
|
}
|
|
} catch (e) { /* best effort */ }
|
|
}
|
|
var sent = false;
|
|
function report(reason) {
|
|
if (sent) return; sent = true; // one beacon per page
|
|
try {
|
|
// The entry chunk's hashed filename identifies the build this page
|
|
// booted from — lets deploy-related errors correlate to a version.
|
|
var ver = '';
|
|
var pre = document.querySelector('link[rel="modulepreload"]');
|
|
if (pre) ver = (pre.getAttribute('href') || '').split('/').pop().slice(0, 60);
|
|
var b = new Blob([JSON.stringify({ reason: String(reason || 'unknown').slice(0, 500),
|
|
path: location.pathname, version: ver })], { type: 'application/json' });
|
|
navigator.sendBeacon && navigator.sendBeacon('/api/client-error', b);
|
|
} catch (e) { /* best-effort telemetry */ }
|
|
}
|
|
function showBoot(reason) {
|
|
if (window.__ubMounted) return; // app is running; in-app handles it
|
|
var el = document.getElementById('boot-fallback');
|
|
if (el) el.style.display = 'flex';
|
|
report(reason);
|
|
}
|
|
var timer = setTimeout(function () { showBoot('boot-timeout'); }, 10000);
|
|
// Svelte calls this once it has mounted (see +layout.svelte).
|
|
window.__ubBooted = function () {
|
|
window.__ubMounted = true;
|
|
clearTimeout(timer);
|
|
var el = document.getElementById('boot-fallback');
|
|
if (el && el.parentNode) el.parentNode.removeChild(el);
|
|
try { sessionStorage.removeItem('ub_reloaded'); } catch (e) {}
|
|
// Slow-but-successful boots (the "white screen, then it loaded" glitch)
|
|
// would otherwise leave no trace — beacon the timing so they're visible.
|
|
// performance.now() counts from navigation start, so a slow-arriving
|
|
// HTML document is included, not just slow JS.
|
|
try {
|
|
var ms = Math.round(performance.now());
|
|
if (ms > 4000) {
|
|
var nav = performance.getEntriesByType && performance.getEntriesByType('navigation')[0];
|
|
var detail = 'boot-slow: ' + ms + 'ms';
|
|
if (nav && nav.responseStart) detail += ' (html ' + Math.round(nav.responseStart) + 'ms)';
|
|
if (navigator.connection && navigator.connection.effectiveType)
|
|
detail += ' on ' + navigator.connection.effectiveType;
|
|
// Name the culprits: the 3 slowest resource fetches so far, with
|
|
// start→end so gaps (idle/SW churn) are distinguishable from slow
|
|
// downloads. size=0 usually means it came from the SW/cache.
|
|
var res = (performance.getEntriesByType('resource') || [])
|
|
.slice().sort(function (a, b) { return b.duration - a.duration; }).slice(0, 3)
|
|
.map(function (r) {
|
|
var name = r.name.replace(location.origin, '').slice(0, 80);
|
|
return name + ' ' + Math.round(r.startTime) + '→' + Math.round(r.responseEnd) + 'ms sz' + (r.transferSize || 0);
|
|
});
|
|
if (res.length) detail += ' | slowest: ' + res.join(' ; ');
|
|
report(detail);
|
|
}
|
|
} catch (e) { /* timing is best-effort */ }
|
|
};
|
|
addEventListener('vite:preloadError', function (e) {
|
|
report('preloadError: ' + ((e && e.payload && e.payload.message) || ''));
|
|
try {
|
|
if (!sessionStorage.getItem('ub_reloaded')) {
|
|
sessionStorage.setItem('ub_reloaded', '1');
|
|
e.preventDefault();
|
|
location.reload();
|
|
}
|
|
} catch (err) { /* ignore */ }
|
|
});
|
|
addEventListener('error', function (e) {
|
|
showBoot(e && (e.message || (e.error && e.error.message)) || 'error');
|
|
});
|
|
addEventListener('unhandledrejection', function (e) {
|
|
showBoot(e && e.reason && (e.reason.message || String(e.reason)) || 'rejection');
|
|
});
|
|
})();
|
|
</script>
|
|
</head>
|
|
<body data-sveltekit-preload-data="hover">
|
|
<div style="display: contents">%sveltekit.body%</div>
|
|
<div id="boot-fallback" role="alert" aria-live="polite">
|
|
<div class="bf">
|
|
<img src="%sveltekit.assets%/logo.svg" alt="Upbeat Bytes" />
|
|
<p>We had a little trouble loading. A quick refresh usually sorts it out.</p>
|
|
<button type="button" onclick="location.reload()">Refresh Upbeat Bytes</button>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|