Rebuild realtime on one RealtimeSession; callable bool.room (named rooms) - #27
Merged
Merged
Conversation
…oms)
The rewrite Jack asked for, built as a structured refactor rather than a
blank page: the public API and the behavior tests are the spec (both
survived five emergency releases unchanged), and the internals reshape
around one RealtimeSession.
Architecture:
- session.ts (new, ~300 lines): THE connection machine — mint with a
timeout race, setAuth, refresh at 75% TTL, backoff ladder, wake
reconnect, generation counter, identity-guarded teardown. Every scar
from the July 2026 fires is written exactly once, each with a comment
naming the production failure that earned it; session.test.ts pins one
test per scar. Consumers register ChannelGroups and contain zero
connection code.
- realtime.ts (280 → 118): the doorbell is now pure fan-out.
- room.ts (722 → ~530 incl. the new API): pure membership ∪ state +
throttle/coalescing/hydration/stable-identities logic.
- client.ts: ONE session shared by the doorbell and every room — one
socket, one wristband refresh, no more duplicate lifecycles.
API — the one addition, decided with Jack against the convention survey
(Liveblocks/PartyKit/Yjs): bool.room is now CALLABLE. `bool.room` stays
the app-wide default room (every existing example works verbatim);
`bool.room("game:4")` returns a scoped room with the identical surface,
memoized per id, topic bool:<schema>:room:<id>. Ids are validated loudly
([A-Za-z0-9][A-Za-z0-9:_.-]{0,63}) — silent normalization would put two
"different" ids in one room. Rejected on purpose: provider components,
join()/leave() ceremony, useUpdateMyPresence-weight names, selectors —
recorded in room.ts's header.
Behavior changes beyond structure: NONE. All 214 pre-rewrite tests pass
with only harness-internal edits, except one test that pinned the old
"any failed track() reconnects" rule — deliberately re-pinned to the
rate-shaped join-beacon contract (that rule is what amplified the
presence meltdown into a reconnect storm).
Not published yet: next.7 waits for the platform policy v4 (scoped-topic
send/recv) and a real two-browser verification, per the proof gate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
The rewrite: same public API, same tests, new internals. Stacked on
jack/room-batteryso the diff shows only the rewrite.Why a rewrite
The doorbell and the room each carried a private copy of the connection machine. Both of the 2026-07-29 production fires (sync-CLOSED stack overflow, missing wake reconnect) had to be diagnosed and fixed twice — once per copy. That's structural, not incidental.
Architecture
session.ts(new) — THE connection machine: mint (10s timeout race) → setAuth → open channel groups → refresh at 75% TTL; backoff ladder; wake reconnect; generation counter; identity-guarded teardown. Every scar written once, each comment naming the production failure that earned it.session.test.tspins one test per scar.realtime.ts280 → 118 — the doorbell is pure fan-out.room.ts722 → ~530 — pure membership ∪ state (throttle, frame-coalescing, stable identities, hydration), zero connection code.client.ts— ONE session, one socket, one wristband refresh under everything.The API addition (decided with Jack against the Liveblocks/PartyKit/Yjs survey)
bool.roomis now callable:bool.roomstays the app-wide default room (every existing example works verbatim);bool.room("game:4")returns a scoped room with the identical surface, memoized per id, topicbool:<schema>:room:<id>, ids validated loudly. Rejected on purpose (recorded inroom.ts's header): providers, join/leave ceremony, selectors,useUpdateMyPresence-weight names.Behavior changes
None, except one deliberate re-pin: the old "any failed
track()forces a reconnect" rule is gone — it's what amplified the presence rate-limit into a reconnect storm. The join beacon is once-per-join; failure warns; genuinely dead channels heal via terminal states.Verification
tsccleanPairs with platform PR (policy v4). Published as
0.4.0-next.7.🤖 Generated with Claude Code