The Add-tile menu would stop appearing once enough tiles were on the
dashboard — a different count each time. The button's handler awaited
GET /api/modules before revealing the menu, and that request could never
complete: browsers allow about six HTTP/1.1 connections per host, an open
EventSource holds one for as long as it lives, and every stream-fed tile
(ProPresenter, Timers, PCO Plan, ProdCom) opened its own. With the shell's
own /api/events that is the whole allowance at five or six such tiles, and
every request after that — the picker's module list, a new tile's /state,
its stream — sat in the browser's queue for a connection that was never
handed back. The count varied because a stream in its 3-second retry loop
frees a connection for a moment, and a clock tile holds none.
Reproduced with six stream-fed tiles: the sixth stream and the picker's
/api/modules both pend forever, and the menu stays hidden after the click.
Three changes, all in the shell:
- Dropdowns open the moment they are clicked, from what the page already
knows, and slot in what their rebuild fetches when it lands. A rebuild
that fails leaves the previous contents rather than an invisible menu,
and a generation token stops a late fetch from repainting a menu that has
since been closed or reopened (the old await also let two quick clicks
cancel each other out). The + picker repaints only when the module list
actually changed; the layout menu shows Save and Reset at once and
inserts the server's layouts above Reset when they arrive.
- Tiles asking for the same stream share one connection. moduleApi.sse()
now subscribes to a shell-wide pool with one EventSource per URL, fanning
events out to every subscriber and reconnecting on the same 3-second
timer while anyone listens; a tile joining an open stream is told `open`
straight away, so its backfill runs as before. Six tiles of three modules
now hold three connections instead of six. Modules see no difference.
- /api/modules and /api/layouts fetches give up after 8 s instead of
pending forever, so nothing awaiting them can hang.
Verified in the browser pane against a scratch server: the same six-tile
layout now opens the picker instantly with all five modules listed and
/api/modules completes; with the pool deliberately starved (three extra
EventSources) the picker still opens from its cached list.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>