Skip to content

Unexpected page refresh caused by Vite HMR disconnect handler in dev mode #6

@ashwin-pc

Description

@ashwin-pc

Problem

When using pi-web in dev mode, the page unexpectedly does a full browser reload when opening/returning to the page on an existing session. This is highly disruptive — the user sees their conversation, moves to type, and the page refreshes.

Root Cause

Vite's HMR client (node_modules/vite/dist/client/client.mjs) has a disconnect handler that unconditionally reloads the page whenever the HMR WebSocket closes:

if (payload.event === "vite:ws:disconnect") {
  if (hasDocument && !willUnload) {
    console.log(`[vite] server connection lost. Polling for restart...`);
    await waitForSuccessfulPing(url.href);
    location.reload();  // ← full page reload
  }
}

The HMR WebSocket can close due to:

  • Browser background tab throttling
  • TCP idle timeouts (especially through the supervisor proxy)
  • Network micro-interruptions

Since the server is still running, the ping succeeds immediately and location.reload() fires. This happens even though no files changed and no server restart occurred.

The application's own /ws WebSocket has proper reconnect logic (reconnect + replay missed events, no reload), but Vite's HMR client is separate and far more aggressive.

Proposed Solution

Add a PI_WEB_HMR environment variable to toggle HMR, and expose it as a runtime toggle via the supervisor:

  1. Supervisor holds hmrEnabled state (defaults from PI_WEB_HMR env var, defaults to "1" in dev)
  2. New supervisor endpoint: POST /api/hmr with { enabled: boolean } — updates the flag and restarts the child
  3. server.ts reads the flag and passes hmr: false or hmr: { server } to createViteServer()
  4. UI toggle in settings or status bar (e.g., "Enable HMR" checkbox, or /hmr slash command)

This lets developers enable HMR only when actively working on pi-web source, and disable it for normal usage — eliminating the spurious reloads.

Partial implementation already in place

server.ts already has:

const hmrEnabled = isDev && process.env.PI_WEB_HMR !== "0";
// ...
hmr: hmrEnabled ? { server } : false,

What remains:

  • Supervisor endpoint to toggle HMR and restart child
  • UI control (settings toggle or slash command)
  • Persist the preference (file or localStorage-driven env var)

Workaround

Set PI_WEB_HMR=0 before starting the supervisor to disable HMR entirely (loses hot reload for development).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions