From e5aba6b85300b84f2bb0d1dc05b13fa1ca342c19 Mon Sep 17 00:00:00 2001 From: ella Date: Tue, 5 May 2026 18:41:38 +0200 Subject: [PATCH] RTC: Attach sync observers after hydrating persisted CRDT doc Loading an entity ran the record-map observer's _updateEntityRecord on the same applyUpdateV2 that hydrated the live ydoc from the persisted CRDT doc. Because the live ydoc has no CRDT_DOC_META_PERSISTENCE_KEY, the blocks filter in getPostChangesFromCRDTDoc unconditionally reports blocks as changed, which then triggered a redundant deserializeBlockAttributes pass and an editRecord dispatch whose blocks already matched the editor's parsed content. Attaching observers after applyPersistedCrdtDoc avoids the spurious fire. Providers connect asynchronously, so no peer update can land in the synchronous window before the observers are attached. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/sync/src/manager.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/sync/src/manager.ts b/packages/sync/src/manager.ts index dcca0c5d9970e6..7444f1c4b9b318 100644 --- a/packages/sync/src/manager.ts +++ b/packages/sync/src/manager.ts @@ -286,15 +286,19 @@ export function createSyncManager( debug = false ): SyncManager { } ) ); - // Attach observers. - recordMap.observeDeep( onRecordUpdate ); - stateMap.observe( onStateMapUpdate ); - // Initialize the Yjs document with the necessary CRDT state. initializeYjsDoc( ydoc ); // Get and apply the persisted CRDT document, if it exists. + // Observers are attached after hydration so the applyUpdateV2 inside + // _applyPersistedCrdtDoc does not trigger _updateEntityRecord with the + // just-loaded state, which would dispatch a redundant editRecord whose + // blocks already match the editor's parsed content. internal.applyPersistedCrdtDoc( objectType, objectId, record ); + + // Attach observers. + recordMap.observeDeep( onRecordUpdate ); + stateMap.observe( onStateMapUpdate ); } /**