Skip to content

fix(server): a WS board subscriber bootstraps the current board - #97

Merged
EdmondDantes merged 1 commit into
mainfrom
fix/ws-board-bootstrap
Jul 27, 2026
Merged

fix(server): a WS board subscriber bootstraps the current board#97
EdmondDantes merged 1 commit into
mainfrom
fix/ws-board-bootstrap

Conversation

@EdmondDantes

Copy link
Copy Markdown
Contributor

What

TODO item 1b. Over the WebSocket transport, a client that subscribes to a board room (project/{key}/issues) received nothing — it could not arrive at a correct board from subscribe alone.

  • wsSubscribe() replayed the journal only for a run-trace topic (project/{key}/run/{id}/trace); a board topic fell through the regex and returned having sent nothing.
  • broadcastBoards() diffs against a $sent cursor that is server-global, not per-connection — so an issue unchanged since the server last published it is never re-sent, and a room a client joins later never receives it.

The SSE stream doesn't have this bug because its $sentSnapshots cursor is a per-connection local, so its first tick dumps the whole board to each new subscriber.

Fix

On a board subscribe, dump the current board to that socket — the same first-tick dump SSE does, now available to WS. subscribe stays first so nothing published mid-dump is lost; the client keys by issue id, so the overlap with broadcastBoards' next tick is idempotent.

Verified live

No unit test — this surface has none and needs the server extension (TrueAsync\HttpServer + WebSocket). Verified with a minimal WS client against a running server: subscribing to a project's board room now delivers one {"kind":"issue","data":{…}} frame per current issue immediately, where before it delivered nothing until an issue changed.

-- upgraded --
frame 1: {"topic":"project/…/issues","kind":"issue","data":{"id":3,…}}
frame 2: {"…","kind":"issue","data":{"id":4,…}}
…                                          (the full current board, on subscribe)

Left for TODO 1d

The residual ordering — a stale snapshot value racing a concurrent change delivered on the same socket during the sub-millisecond dump — is what 1d's per-message cursor/version closes (boards have no seq to de-dupe by, unlike traces). This change is strictly better than the current "nothing sent" and introduces no new bug; the board self-corrects on the issue's next change.

Checks

composer qa green — cs, PHPStan level 8, Testo (463 tests, unchanged: no test covers this WS path).

wsSubscribe replayed the journal only for a run-trace topic; a board room (project/{key}/issues) fell through and returned having sent nothing, so a late subscriber saw an empty board until an issue next changed — broadcastBoards diffs against a server-global cursor, not per-connection, so an unchanged issue is never re-sent. Now a board subscribe dumps the current board to that socket, the same first-tick dump the SSE stream does per connection. Subscribe stays first so nothing published mid-dump is lost; the client keys by issue id. Verified live with a WS client (this surface has no unit tests; it needs the server extension). The residual stale-snapshot-vs-concurrent-change ordering is TODO 1d's per-message cursor.
@EdmondDantes
EdmondDantes merged commit 5610633 into main Jul 27, 2026
2 checks passed
@EdmondDantes
EdmondDantes deleted the fix/ws-board-bootstrap branch July 27, 2026 20:13
EdmondDantes added a commit that referenced this pull request Jul 27, 2026
1a: an idle WS connection is NOT torn down at 15s — measured against a running server, the keepalive PING arrives at ~30s and the connection stays open past 75s, so setReadTimeout(15) does not apply after the upgrade. 1b: the board-bootstrap defect is fixed; what remains is the ordering half, which is 1d's per-message cursor.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant