The WebSocket origin check compared the browser's Origin host, which
carries the port, against configured domains, which do not. publicOrigin
only ever matches domains[0], so every other domain fell through to that
comparison and was denied purely on the port: web.localhost:3000 never
matched web.localhost.
The result was a 403 on the HMR upgrade and a client reconnecting
forever, while the page itself loaded fine because HTTP routing resolves
the Host separately.
Compares hostnames now. Unrelated and lookalike-suffix origins are still
denied, and both cases are covered by tests.
Verified through a real gateway: the HMR socket opens on both localhost
and web.localhost, and a live edit reaches the browser.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>