Serve the client from one origin, and stop trusting a proxy to relay upgrades
Vite's dev proxy accepted the browser's WebSocket upgrade, opened the upstream connection, received the 101 — and never wrote it back downstream. The socket hung open forever: the server counted the client as present while the browser waited for a handshake that never came, leaving the editor locked read-only with nothing to explain why. The server now owns the single origin in both modes. It answers `/ws` itself and either serves the built client or forwards everything else to a running Vite, so development, production and a tunnel all present one host and port. Hot reload talks straight to Vite's own port rather than being relayed, which means nothing anywhere proxies a WebSocket. Browse localhost:8787. Which mode is which is now stated rather than sniffed. Testing for a `dist` directory would have quietly served stale files in development the moment somebody ran a build. The status the header showed was also wrong, and would have been wrong for any transport. A connection began life already recording `connecting`, so its first transition was swallowed and never announced. React mounts an effect, discards it and mounts again; the discarded connection's `closed` was therefore the last thing anyone said, and a replacement whose socket never opened left it standing forever. A connection now has no state until it has one.
Parent2890123
8 files changed+390-23
StackedSplit