dashboard: address argus nits — bfcache restore + worker IIFE (#448)

mara on #453: address argus's two yellow nits.

bfcache restore gap

previously openStream registered a pagehide unsubscribe but never
re-subscribed on pageshow, so a bfcache restore left the consumer's
onmessage bound but no events flowing.

fix: maintain a registry of live subscriptions (Map<url, target,
route>). bind page lifecycle hooks once:

- pagehide: unsubscribe every URL, drop route listeners, invalidate
  the cached SharedWorker port (it may be collected if all other
  tabs closed while this page was frozen).
- pageshow { persisted: true }: get a fresh port via getSharedPort
  (creates a new SharedWorker if needed), re-attach every route
  listener, re-subscribe to every URL. target.readyState resets to
  CONNECTING so the worker's synthetic open after subscribe fires
  the consumer's onStreamOpen and triggers a refresh.

worker bundle format

stream-worker.js was bundled as format: 'esm' but loaded as
classic via new SharedWorker(url, name). today's worker has no
imports/exports so the ESM bundle is syntactically valid as a
classic script; argus's concern was that a future contributor
adding an import would silently break things.

fix: switched build.mjs to format: 'iife'. esbuild now wraps the
worker output in (() => { ... })(); any future import statement
would surface as a build error rather than ship broken code.
verified output starts with the IIFE wrapper.

other

- target.close() now reads _sharedPort lazily so close-after-bfcache
  (port may have been recreated) doesn't try to postMessage on a
  stale reference.
- _activeSubs.delete on close keeps the registry honest if a
  consumer ever explicitly closes a stream (none do today, but the
  shape stays correct).

validation: npm run build clean. stream-worker.js: 1.8 kb → 1.9 kb
(IIFE wrapper). common.js bfcache logic adds ~30 LOC inside the
existing module — bundle deltas negligible.
This commit is contained in:
iris 2026-05-26 01:25:49 +02:00 committed by Mara
commit b4b7ccf88c
2 changed files with 83 additions and 21 deletions

View file

@ -49,14 +49,19 @@ await build({
// scripts run in a different global (`self` is the worker scope, no
// `window`) so they can't be inlined into app.js / flow.js. Output is
// at `static/stream-worker.js`; common.js's `openStream` references
// `/static/stream-worker.js` as the SharedWorker URL. ES-module-shaped
// (so a future `import` from within the worker can pull in shared
// utilities), browser target — webworker target subset.
// `/static/stream-worker.js` as the SharedWorker URL. `format: 'iife'`
// matches the classic-script load (`new SharedWorker(url, name)` with
// no `{ type: 'module' }`); Firefox is the #448 target and module
// SharedWorker support there is patchy, so keeping the worker as a
// classic script + IIFE bundle is the compatible default. argus nit
// on #453: if a future contributor adds an `import` to this bundle,
// the IIFE format will surface it as a build error rather than
// silently shipping broken code.
await build({
entryPoints: [src('stream-worker.js')],
outdir: staticDir(''),
bundle: true,
format: 'esm',
format: 'iife',
platform: 'browser',
target: ['es2022'],
sourcemap: true,