Reload when the server is running a newer build #119

Closed
opened 2026-08-28 05:21:30 +00:00 by jcoffey-dev · 0 comments
Owner

Closes the gap found while switching production to immutable: a deploy signs everyone out, but it does not get them onto the new build.

client.handleUnauthenticated() sets state to anonymous and renders the sign-in view from the bundle already in memory. No reload happens — the only location.reload() in the client is the manual Refresh menu item (AppShell.tsx:106). So after a deploy a user signs back in and carries on running old JS against the new server until they happen to hard-refresh.

The pieces already existed

  • index.html is served no-cache, assets under it are content-hashed and immutable (static.ts:62,85) — a reload is sufficient
  • Vite bakes the build's version in as APP_VERSION (vite.config.ts:12)
  • /api/health reports the server's version

Only the comparison was missing.

Design choices

On a 401, not on a timer. That is the moment it matters, and it costs one small request.

Compare versions, don't reload on every 401. An ordinary session expiry still lands on the sign-in form with the page intact.

Check before showing the sign-in form, not after. Reloading a form someone has already started typing into would throw the password away.

Any unclear answer leaves the page alone. Unreachable server, non-OK response, missing version → no reload. Losing what is on screen is worse than staying stale.

Reload once per version. A server that keeps reporting a version the bundle does not match — stale proxy cache, half-finished deploy — cannot put the tab in a reload loop.

Verified in a real browser, not just unit tests

Built bundle + mock upstream, signed in, planted a marker on window, then restarted the server on a different version (dropping the in-memory session, exactly as an immutable deploy does), then clicked a folder like a user would:

markerStillThere: "GONE — the page reloaded"
navType:          "reload"

The loop guard got tested for free: the static bundle stayed 2.16.118 while the server kept reporting 2.16.999, so the mismatch was permanent. Three further forced 401s produced no second reload — page up 23s across all of them.

Unit tests cover the six branches: mismatch reloads, match doesn't, once-per-version, guard clears when versions agree again, unreachable server, bad response / missing version.

npm run typecheck, npm test (344 web + 80 server), npm run build all clean.

Merged 2026-08-27 as coffey-labs/ihasmail@a3dc7e017c

Rebuilt from: git history, session transcript.

Closes the gap found while switching production to immutable: **a deploy signs everyone out, but it does not get them onto the new build.** `client.handleUnauthenticated()` sets state to `anonymous` and renders the sign-in view from the bundle already in memory. No reload happens — the only `location.reload()` in the client is the manual Refresh menu item (`AppShell.tsx:106`). So after a deploy a user signs back in and carries on running **old JS against the new server** until they happen to hard-refresh. ### The pieces already existed - `index.html` is served `no-cache`, assets under it are content-hashed and `immutable` (`static.ts:62,85`) — a reload is sufficient - Vite bakes the build's version in as `APP_VERSION` (`vite.config.ts:12`) - `/api/health` reports the server's version Only the comparison was missing. ### Design choices **On a 401, not on a timer.** That is the moment it matters, and it costs one small request. **Compare versions, don't reload on every 401.** An ordinary session expiry still lands on the sign-in form with the page intact. **Check before showing the sign-in form, not after.** Reloading a form someone has already started typing into would throw the password away. **Any unclear answer leaves the page alone.** Unreachable server, non-OK response, missing version → no reload. Losing what is on screen is worse than staying stale. **Reload once per version.** A server that keeps reporting a version the bundle does not match — stale proxy cache, half-finished deploy — cannot put the tab in a reload loop. ### Verified in a real browser, not just unit tests Built bundle + mock upstream, signed in, planted a marker on `window`, then restarted the server on a different version (dropping the in-memory session, exactly as an immutable deploy does), then clicked a folder like a user would: ``` markerStillThere: "GONE — the page reloaded" navType: "reload" ``` The loop guard got tested for free: the static bundle stayed 2.16.118 while the server kept reporting 2.16.999, so the mismatch was permanent. Three further forced 401s produced **no second reload** — page up 23s across all of them. Unit tests cover the six branches: mismatch reloads, match doesn't, once-per-version, guard clears when versions agree again, unreachable server, bad response / missing version. `npm run typecheck`, `npm test` (344 web + 80 server), `npm run build` all clean. **Merged** 2026-08-27 as coffey-labs/ihasmail@a3dc7e017c14 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.