Two separate failures, one repro app. Vite 8.2.0, experimental.bundledDev: true.
The plugin in vite.config.ts drops the first WS message of one type sent to a client, standing in
for a message lost in transit.
pnpm install
pnpm dev:reload
- Open the page. It shows
VERSION-1. - Edit
no-boundary.js(change the string). Nothing accepts this module, so the client asks the server for a full reload. The plugin drops the server's reply. - Edit
dep.js, changingVERSION-1toVERSION-2.
Expected: step 3 either applies over HMR or reloads the page.
Actual: nothing happens, and nothing happens for any later edit either. No console error, no overlay. Only a manual refresh recovers.
requestFullReload sets reloadPending = true before it knows the server will honor the request
(bundledDevHmrClient.ts:301-302), and nothing ever clears it. Both applyPush (line 176) and
requestFullReload (line 301) early-return on it, so one lost message is terminal.
The loss is reachable without a plugin: the server's handler returns without replying when
this.clients.getId(client) misses (bundledDev.ts:155-156). Registration happens on
vite:client-connected (line 130), so a client that asks during a server restart can miss.
pnpm install
pnpm dev:patch
- Open the page. It shows
VERSION-1. - Edit
dep.js, changingVERSION-1toVERSION-2. The plugin drops that one patch. - Stop. Make no further edits.
Expected: the client or server notices the patch never arrived and recovers.
Actual: the page shows VERSION-1 forever. No error, no reload.
The sequence check lives inside applyPush (bundledDevHmrClient.ts:177), so it can only fire when
a later patch arrives. Editing dep.js again does recover, which hides this in normal use — a
human makes another edit within seconds.
It does not self-heal for an AI coding agent. A turn writes several new files that generate no patch, then one file that imports them all, which produces the only patch of the turn. Lose that and the preview is stale until the user refreshes, with nothing indicating why.
The server already knows: pendingPayloadFilenames holds every patch that was sent but never
served, and markPayloadDelivered clears it when the request completes (bundledDev.ts:333, 344).
Today that state only feeds the ship map so later chunks can re-include the missed modules. Nothing
reloads a client that will never get a later chunk.