Replace the whole member state only when its version is at least the held version. Insert messages by id when absent, even from stale updates; render and gap-check by seq, never by version. Full rows apply their peer card with their state; unknown conversations fetch a full row. A reply without a version keeps held state and fetches a versioned copy. Every applied state gap-checks the open thread's visible horizon (1 for a pending receiver, otherwise lastSeq). Pause to hold events, or drop them to simulate lost events; foreground, reconnect and inbox open refresh lost state.
The missing-message control sends sample text in the open thread. A 1411 refusal offers to send that text without a quote under a new key. Retry same key preserves the original body and reply target, including after a timeout or temporary error.
Fetch a list or the open conversation and hold its response without applying it. Read on another tab (AE5), or Accept here (AE10), then apply the held response: an older version must leave the state and peer card unchanged.
Any method and payload over the connection, e.g. accept your own request (1403), send as the receiver of a pending request (1409), an unknown method or a bad payload (1401).
Serve this file over http (for example npx http-server test -p 8765) rather than opening it from disk, and open one tab per user (the login is kept per tab).
Gateway calls blocked by the browser: with NODE_ENV=development every origin is allowed; under any other NODE_ENV the page's origin must be listed in ALLOWED_ORIGINS in the gateway's process environment.
Connection fails with only "transport closed" and no close code: the chat server refuses browser connections whose origin it does not know. Set CHAT_CENTRIFUGO_ALLOWED_ORIGINS to this page's origin (scheme://host[:port], no path, no trailing slash, never *; space-separated for several), e.g. CHAT_CENTRIFUGO_ALLOWED_ORIGINS=http://localhost:8765, in .env for docker-compose.yml or .env.test for the test stack, then recreate the chat server container(s). Do not set CENTRIFUGO_CLIENT_ALLOWED_ORIGINS directly: the compose files set it from the CHAT_ key, empty when that is unset. The login itself needs nothing extra: the token travels inside the connect command and the server forwards it as the Authorization header.
Reads send only from a visible thread on a focused page, with one command in flight per conversation and the highest displayed seq queued. Pending reads retry after reconnect; a capped reply never causes a resend. The Requests dot turns on from applied unread-request state and clears only from the summary route, refreshed on open, foreground, reconnect and request changes (at most once a second). Ignore, Block and the archived requests list are not built yet.