When a new version is deployed, open tabs no longer silently run stale code until a manual F5. - Server stamps every response with X-App-Version (src/middleware/version.ts, onSend hook in server.ts). - Client bakes its build version via Vite define (__APP_VERSION__); apiFetch compares the header to it (no polling — piggybacks existing traffic) and latches a mismatch in a small store (appVersion.ts). - UpdateBanner: a persistent bottom Snackbar "Je k dispozici nová verze aplikace. — Obnovit" → location.reload(). User-initiated, so an in-progress form is never interrupted. - lazyWithReload: every React.lazy page reloads once if its hashed chunk 404s after a deploy (sessionStorage-guarded against loops), fixing the "old chunk gone" error screen. Approach chosen after researching current SPA practice (version header + banner beats polling / a service worker for a non-PWA admin app). Tests: +4 (server header, client store latch/notify). Full suite 669 pass, tsc -b clean, lint 0. Verified live via Playwright (header emitted; banner appears on a simulated mismatch). Detection applies to deploys from the NEXT release onward (current clients lack the detection code). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
36 lines
1018 B
TypeScript
36 lines
1018 B
TypeScript
import { useSyncExternalStore } from "react";
|
|
import Snackbar from "@mui/material/Snackbar";
|
|
import Button from "@mui/material/Button";
|
|
import { subscribeNewVersion, getNewVersion } from "../utils/appVersion";
|
|
|
|
/**
|
|
* Shows a persistent banner when the server reports a newer build than the one
|
|
* this tab is running (detected via the X-App-Version header in apiFetch). The
|
|
* reload is user-initiated — we never force it, so an in-progress form is safe.
|
|
*/
|
|
export default function UpdateBanner() {
|
|
const newVersion = useSyncExternalStore(
|
|
subscribeNewVersion,
|
|
getNewVersion,
|
|
() => null,
|
|
);
|
|
|
|
return (
|
|
<Snackbar
|
|
open={Boolean(newVersion)}
|
|
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
|
message="Je k dispozici nová verze aplikace."
|
|
action={
|
|
<Button
|
|
color="primary"
|
|
size="small"
|
|
variant="contained"
|
|
onClick={() => window.location.reload()}
|
|
>
|
|
Obnovit
|
|
</Button>
|
|
}
|
|
/>
|
|
);
|
|
}
|