Problem
When opening 2 Studio tabs on localhost, the server appears to hang. The event loop shows as idle — the issue is Chrome's HTTP/1.1 limit of 6 concurrent connections per origin.
Each tab opens 2 long-lived SSE connections:
EventSource('/events') — live events (client-id.tsx)
EventSource('/__webpack_hmr') — webpack hot middleware (hot-middleware-client/client.ts)
With 2 tabs, that's 4 persistent connections, leaving only 2 slots for API calls, assets, and chunk loading. Any burst of concurrent requests queues behind the limit.
Confirmed: using the network URL (e.g. 192.168.x.x:3123) instead of localhost for the second tab works around the issue, because Chrome treats them as different origins.
Proposed fix
Merge the two SSE streams into a single EventSource('/events') connection per tab.
- Server: Have the HMR middleware publish through
liveEventsServer.sendEventToClient() instead of maintaining its own SSE client list. Add a new EventSourceEvent variant: {type: 'hmr', payload: HotMiddlewareMessage}.
- Client: In
client-id.tsx, dispatch hmr events to the HMR handler. In hot-middleware-client/client.ts, subscribe to the shared EventSource instead of opening a new one.
- The HMR "send latest stats on connect" behavior would move into the live-events router's connect handler (similar to how initial undo/redo state is sent on connect).
- The HMR heartbeat (10s interval) can be adopted by the shared stream.
This halves the long-lived connections (2 tabs = 2 SSE instead of 4), leaving 4 slots for regular requests.
Complexity
Medium-low. The HMR middleware's own client management (createEventStream) gets removed. Its webpack hooks and publishStats logic stay. Main work is wiring publish through the shared event system and subscribing the HMR client to the shared EventSource.
Problem
When opening 2 Studio tabs on
localhost, the server appears to hang. The event loop shows as idle — the issue is Chrome's HTTP/1.1 limit of 6 concurrent connections per origin.Each tab opens 2 long-lived SSE connections:
EventSource('/events')— live events (client-id.tsx)EventSource('/__webpack_hmr')— webpack hot middleware (hot-middleware-client/client.ts)With 2 tabs, that's 4 persistent connections, leaving only 2 slots for API calls, assets, and chunk loading. Any burst of concurrent requests queues behind the limit.
Confirmed: using the network URL (e.g.
192.168.x.x:3123) instead oflocalhostfor the second tab works around the issue, because Chrome treats them as different origins.Proposed fix
Merge the two SSE streams into a single
EventSource('/events')connection per tab.liveEventsServer.sendEventToClient()instead of maintaining its own SSE client list. Add a newEventSourceEventvariant:{type: 'hmr', payload: HotMiddlewareMessage}.client-id.tsx, dispatchhmrevents to the HMR handler. Inhot-middleware-client/client.ts, subscribe to the shared EventSource instead of opening a new one.This halves the long-lived connections (2 tabs = 2 SSE instead of 4), leaving 4 slots for regular requests.
Complexity
Medium-low. The HMR middleware's own client management (
createEventStream) gets removed. Its webpack hooks andpublishStatslogic stay. Main work is wiring publish through the shared event system and subscribing the HMR client to the shared EventSource.