8a2efdfeef
initHmrBridge registered three Vite HMR listeners (vite:error, vite:beforeUpdate, vite:beforeFullReload) with no idempotency guard and is called once per createApp() (dev block in render.ts). createApp() can run multiple times in one dev process — two apps, an app that unmounts and is re-created, a tool that restarts the UI, or a test run — and Vite's Node HMR runtime APPENDS listeners with no dedup, so N calls leaked N copies of every handler permanently. Every later HMR event then ran each handler N times. Add a module-level boolean guard so the listeners register at most once for the module's lifetime, regardless of how many times initHmrBridge is called. Also parameterize the hot context (defaulting to import.meta.hot) so the body is reachable under vitest, where import.meta.hot is undefined. HotContext is a local structural type and import.meta.hot is read via a structural cast so the module type-checks even when imported directly from runtime-tests, whose tsconfig doesn't pick up env.d.ts's ambient ImportMeta.hot augmentation. Out of scope: the setTimeout stale-timer in the vite:beforeUpdate handler is a separate bug left exactly as-is. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>