Reload Block
Dev-only mechanism that intercepts page reloads in the playground so HMR doesn’t blow away in-progress state without consent. Implemented in screens/playground/PlaygroundUpdateGuard.tsx.
How it works
- On mount (DEV builds only), patches
Location.prototype.reloadwith aconfigurabledescriptor whose replacement value checks an internal_blockedflag. - While blocked, calls to
window.location.reload()are suppressed and apg:reload-blockedCustomEventis dispatched onwindowinstead of triggering navigation. - The original
reloadis captured viaLocation.prototype.reloadand.bind(window.location)before override, then stored at module scope so HMR re-renders of the component don’t lose it. - Install is idempotent —
installReloadBlockearly-returnstruewhen_blockedis already set. Wrapped intry/catchto handle browsers that refuse the override. - Uninstall restores the captured original via the same
Object.definePropertypath; module-level state means the patch survives component HMR.
UI
- A fixed-position amber badge (
#f59e0b, top-right,z-index: 999999) reading↻ Update available — click to reloadappears only when a reload has been blocked. - Clicking the badge calls
removeReloadBlock()then the captured_originalReload, performing the real navigation. - Badge is rendered conditionally on
pending && import.meta.env.DEV— never visible in production builds.
Vite HMR hook
- Belt-and-suspenders: subscribes to
import.meta.hot.on('vite:beforeFullReload', ...)and setspendingthere as well, so the badge appears even on HMR-initiated full reloads that don’t route throughlocation.reload().
Event contract
| Name | Target | Payload | Emitted when |
|---|---|---|---|
pg:reload-blocked | window | none | location.reload() is called while _blocked is true |
Listeners (the guard component itself) set pending = true, which renders the badge.
Production behavior
useEffect returns early when !import.meta.env.DEV, and the badge render is gated on the same flag. No patch installs and no UI appears in production.