feat(sw): offer a reload when a newer version is ready
The shell is served cache-first, which is what makes the app work on a train and also what makes a deploy invisible: a reader with the tab open — the reader this app is built for — keeps running the bundle they first loaded, so a new game or a corrected date reaches their device and sits there with nothing saying why the page looks unchanged. An old app shown as current is the same failure as old events shown as current. So the worker now installs quietly and waits instead of calling skipWaiting(), the page notices it waiting and says so, and the reader's tap sends the skip-waiting message and reloads on controllerchange. The app never reloads itself: someone may be mid-way through typing in one of their own events, and the notice says what a reload costs (their place on the page) and what it does not (marks and notes live in localStorage). Detection is derived rather than remembered. build:static grew into a script that stamps sw.js with a hash of the built shell, because the browser only offers a worker whose bytes differ, and the predecessor — a hand-bumped CACHE_VERSION — had already been forgotten once. The feed is deliberately not part of that hash: it changes twice a day, needs no reload, and announcing it would teach readers to dismiss the notice unread. The cache name stays put for the same reason a per-build one would be wrong — it holds the feed an offline reader is reading. A first install is not an update and stays silent. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7130a94b21
commit
a4ab5aa36c
@@ -11,7 +11,9 @@ import { Welcome } from "./components/Welcome.tsx";
|
||||
import { Colophon } from "./components/Colophon.tsx";
|
||||
import { Legend } from "./components/Legend.tsx";
|
||||
import { Toast } from "./components/Toast.tsx";
|
||||
import { UpdateNotice } from "./components/UpdateNotice.tsx";
|
||||
import { KEYS } from "./state/storage.ts";
|
||||
import { useAppUpdate } from "./state/useAppUpdate.ts";
|
||||
import { useMarkSet } from "./state/useMarkSet.ts";
|
||||
import { useProgress } from "./state/useProgress.ts";
|
||||
import { useDailyLog, type DailyLogMap } from "./state/useDailyLog.ts";
|
||||
@@ -553,10 +555,26 @@ export function App() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Every screen goes through here, which is why the update notice lives here
|
||||
* rather than beside the list: a reader who is being told "events unavailable"
|
||||
* or is still picking their games needs the offer at least as much as one
|
||||
* reading a calendar — a bundle too old for the feed it just downloaded
|
||||
* (`fetchFeed`'s schemaVersion refusal) lands on exactly that error screen, and
|
||||
* a reload is the fix.
|
||||
*/
|
||||
function Shell({ children }: { children: React.ReactNode }) {
|
||||
const update = useAppUpdate();
|
||||
return (
|
||||
<div className="mx-auto min-h-full max-w-2xl border-hairline sm:border-x">
|
||||
{children}
|
||||
{update.available && (
|
||||
<UpdateNotice
|
||||
applying={update.applying}
|
||||
onApply={update.apply}
|
||||
onDismiss={update.dismiss}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user