[lexical-yjs] Bug Fix: Don't sync root textFormat/textStyle - #8940
Merged
Conversation
patrick-atticus
requested review from
acywatson,
etrepum,
fantactuka,
ivailop7,
potatowagon and
zurfyx
as code owners
August 6, 2026 06:49
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
etrepum
approved these changes
Aug 7, 2026
Merged
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.
Description
ElementNode.__textFormatand__textStyleare synced for every element, including the root. On the root this produces a stream of pointless updates to the shared document.The reconciler derives these from the first
TextNodeof an element's subtree (reconcileTextFormat/reconcileTextStyle), so an empty block remembers the formatting the next typed character inherits.The root can't be typed into, so the value serves no purpose there. It also never settles: the reconciler sets it from the first text node it meets while rendering, and a partial re-render only covers the part that changed. Loading the document sets the root from the first block, then editing a block further down replaces it with that block's formatting. Every flip is a Yjs update sent to every collaborator, and every client rewrites it on load.
It also isn't limited to users who can edit:
reconcileTextFormatis gated on!activeEditorStateReadOnly, noteditor.isEditable(), so read-only clients emit these updates too. This was how I found the issue - read-only clients attempting to edit docs and causing errors.Lexical already treats the value as non-persistable on the root;
ElementNode.exportJSONskips it there, and for any element withTextNodechildren, "Only persist for cases when there are no TextNode children from which these would be set on reconcile (#7968)". This adds the two properties torootExcludedPropertiesalongside__cachedTextso the binding agrees. The reconciler still sets it locally, and existing documents keep their stored values, just ignored.Blocks are unchanged: an empty block has no text to derive from, so its value is real state and still syncs.
Related: #7968, #7971
Test plan
No automated test:
Collaboration.test.tscan't reproduce it, becauseactiveEditorStateReadOnlyis true in that harness so the reconciler never derives the value. There's also no existing coverage ofrootExcludedPropertiesto extend. Guidance welcome.Verified in a browser. Open a document whose first text run is formatted, then edit a differently formatted block and reload.
Before
A new update every time the value flips.
After
No updates. The root still holds the value locally, only the sharing is removed. Empty blocks still round-trip their
textFormat/textStyle.