From fa8437d03a1241d2282e5ca869d3b84a218f994a Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 24 Jan 2026 19:06:20 -0300 Subject: [PATCH] fix: broadcast full CRDT state on connect and when new user joins CRDT broadcasts are fire-and-forget, so any updates missed during a disconnection were permanently lost. Now when a client connects (or reconnects), it broadcasts its full Yjs doc state. When an existing client sees a new user join, it also broadcasts its full state. Yjs merges handle deduplication automatically, so this converges all clients. Co-Authored-By: Claude Opus 4.5 --- src/app/editor/[projectId]/FlowchartEditor.tsx | 2 ++ src/lib/collaboration/crdt.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/app/editor/[projectId]/FlowchartEditor.tsx b/src/app/editor/[projectId]/FlowchartEditor.tsx index 8f7cc67..879df51 100644 --- a/src/app/editor/[projectId]/FlowchartEditor.tsx +++ b/src/app/editor/[projectId]/FlowchartEditor.tsx @@ -605,6 +605,8 @@ function FlowchartEditorInner({ projectId, projectName, userId, userDisplayName, ...prev, { id: nanoid(), displayName: user.displayName, type: 'join', color: getUserColor(user.userId) }, ]) + // Send full CRDT state so the joining client gets caught up + crdtManager.broadcastFullState() }, onPresenceLeave: (user) => { setCollaborationNotifications((prev) => [ diff --git a/src/lib/collaboration/crdt.ts b/src/lib/collaboration/crdt.ts index eedf8d8..6b64c6b 100644 --- a/src/lib/collaboration/crdt.ts +++ b/src/lib/collaboration/crdt.ts @@ -63,6 +63,19 @@ export class CRDTManager { /** Connect to a Supabase Realtime channel for outbound broadcasts */ connectChannel(channel: RealtimeChannel): void { this.channel = channel + // Broadcast full state so other clients merge any updates they missed + this.broadcastFullState() + } + + /** Broadcast the full Yjs document state to sync all connected clients */ + broadcastFullState(): void { + if (!this.channel || this.isDestroyed) return + const state = Y.encodeStateAsUpdate(this.doc) + this.channel.send({ + type: 'broadcast', + event: BROADCAST_EVENT, + payload: { update: Array.from(state) }, + }) } /** Apply a remote CRDT update received via broadcast */