/** * Surviving a deploy that lands while a tab is open. * * Every app on this stack is a Vite SPA with `lazy()` routes, deployed on push. A build replaces * every hashed asset underneath whatever tabs are already open, and those tabs go on running * yesterday's bundle. It surfaces two ways, and only one of them looks like a missing file: * * 1. The next navigation asks for a chunk whose hashed name is gone. React's lazy boundary throws, * and the person gets a white screen on a click that worked a minute ago. 2. The old bundle reads a * field the API has since renamed, and throws. That one arrives as a plain `TypeError` with nothing * in it about a deploy: no file, no hash, no signal. * * So there are two detectors here and they are not interchangeable. {@link isChunkLoadError} * matches the message, which is all you have for a failure that never reaches a boundary. {@link * isStaleBuild} asks the server for evidence rather than a guess. It is the only one that catches * case 2 at all. * * **The deepest fix is not in this file, and it is worth more than everything in it.** A static * host answers a missing `/assets/x.js` with the SPA fallback, `index.html`, at a 200, as * `text/html`. That is what the browser's "MIME type text/html" module-script refusal actually is. * Hosts then match cache rules against the REQUEST PATH rather than the outcome, so the `immutable, * max-age=31536000` rule written for hashed assets gets applied to that HTML body: one poisoned * asset URL, at the edge and in every browser that touched it, for a year. One adopter removed the * cause instead of recovering from it: no blanket fallback over `/assets/`, and an edge function * that refuses to answer an asset path with `text/html`. That turns a dead chunk into a real, * uncacheable 404 and lets everything below be recovery rather than the plan. A package cannot ship * that; it can refuse to let it go unwritten. */ /** * One stamp for every reason to reload, which is the part that is easy to get wrong. * * A tab can decide to reload because a chunk 404'd, because the server says it is running an old * build, or from a guard inlined in `index.html` that runs before any module does. That is three * detectors for ONE predicament, and giving each its own key hands a broken deploy three reloads * a minute to take turns with. The guard belongs to the tab, not to the reason. * * Exported because of the third caller: an inline `