Skip to content

feat(webapp): the editor joins the document the hub is holding - #242

Merged
ssowonny merged 1 commit into
mainfrom
feat/client-joins-the-held-document
Sep 20, 2026
Merged

ssowonny merged 1 commit into
mainfrom
feat/client-joins-the-held-document

Conversation

@ssowonny

Copy link
Copy Markdown
Contributor

TL;DR

  • The client stops being a Yjs provider: y-websocket replaces the hand-rolled SSE-down/POST-up transport when the hub holds the document.
  • What that buys is the part that was hand-rolled badly — a state-vector handshake instead of replaying the whole room log on every reconnect.
  • Nothing seeds client-side any more. The hub built the document from the file before anyone attached, so the seed claim and its grace timer are unreachable.
  • Verified in a real browser, not only in tests.

y-websocket, not @hocuspocus/provider

The PRD named Hocuspocus. ygo speaks y-websocket natively and Hocuspocus only behind a server flag — so this is one fewer thing that has to agree, for the same gain. Recorded as a deviation in the PRD.

The client is told, not left to guess

/api/config now carries collab.held.

A hub too old to serve the route and a proxy that refuses to upgrade a websocket fail in exactly the same way. Guessing wrong means either an editor waiting for a document nobody is going to send, or two clients seeding two documents of one file. The hub knows; it says.

Editor and VisualEdit read it through useConfig rather than threading a prop down four components — staleTime: Infinity, so it's a cache hit.

Two things that had to go quiet

  • Client-side seeding, on the held path. The document already is the file.
  • The relay's update and awareness POSTs. The socket carries both, so posting them too was the same bytes twice at a route that only serves GET — which is how I found it: 405s in a browser console.

Still here, deliberately

Both transports live in collab.ts. Deleting the relay, onSolo and the compensations is Stage 4 — this is what "the old relay stays reachable for a release" means in practice.

Verification

Driven with a real browser as well as the suite, because three of the failures on the way here were invisible to unit tests: a route that didn't match the URL y-websocket builds, a client dialling the relay's path, and a hub still serving a bundle compiled before any of it existed.

  • Browser probe: websocket opens, editor mounts, no console errors.
  • 25 editor e2e pass — including two people editing different paragraphs both land and the URL is the invitation: a teammate lands in the same document, both now running through the hub-held document.

🤖 Generated with Claude Code

The client stops being a Yjs provider. y-websocket replaces the hand-rolled
SSE-down/POST-up transport on hubs that hold the document, which buys the part
that was hand-rolled badly: a state-vector handshake instead of replaying the
entire room log on every reconnect, and backoff that is somebody else's
problem.

y-websocket rather than @hocuspocus/provider, which the plan named: ygo speaks
y-websocket natively and Hocuspocus only behind a server flag, so this is one
fewer thing that has to agree.

The client is TOLD, not left to guess. /api/config carries collab.held,
because a hub too old to serve the route and a proxy that refuses to upgrade a
websocket fail in exactly the same way — and guessing wrong means either an
editor waiting for a document nobody is going to send, or two clients seeding
two documents of one file. Editor and VisualEdit read it through useConfig
rather than a prop threaded down four components; it is cached with staleTime
Infinity, so it is a cache hit.

Nothing seeds client-side any more on that path: the hub built the document
from the file before anyone attached. The seed claim, its grace timer and the
blank-document failure it papered over are all unreachable there.

The relay's own update and awareness POSTs go silent on the held path. The
socket carries both, so posting them as well was the same bytes twice at a
route that only serves GET — which is exactly how it was found, as 405s in a
browser console.

Both transports still live in collab.ts. Deleting the relay is Stage 4, which
is what "the old relay stays reachable for a release" means in practice.

Verified end to end with a browser, not only in tests: websocket opens, editor
mounts, no console errors. 25 editor e2e pass, including two people editing
different paragraphs of one document and a teammate landing in it from a
pasted URL.

Stage 2 of docs/collab-provider-prd.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ssowonny
ssowonny merged commit 5827ace into main Sep 20, 2026
3 checks passed
@ssowonny
ssowonny deleted the feat/client-joins-the-held-document branch September 20, 2026 07:46
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