You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Bug: [lexical-y] A write that fails after inserting part of a change is recovered by inserting that content again #9775
When a local write throws part way, @lexical/y rewrites every container
in a fresh transaction (RECOVERED_WRITE_WARNING). The rewrite diffs the
editor against the binding's record of the document, which is updated only
when a container's write completes. Content the failed attempt had already
inserted is in the document but not in that record, so the recovery
inserts it again: the document stores the text twice while the editor that
made the change shows it once. A fresh client loading the stored state
shows both copies.
Reproduction
On main (gc on or off):
Bind an editor to a document whose root holds one paragraph abc.
Register a NodeState (createState) whose unparse throws once while
the binding's write transaction is open (doc._transaction.origin === binding).
In one editor.update, append a paragraph mine with that state set.
The paragraph's text and end marker are inserted before its marker fields
(which run the serializer) are computed; the serializer throws, the
recovery runs.
Expected
The document stores abcmine once, as the editor shows.
Actual
The document stores abcminemine; Y.encodeStateAsUpdate loaded into a
fresh Y.Doc reads abcminemine. The editor shows abcmine.
Controls
The same edit without a throwing serializer stores abcmine.
A serializer that throws before anything is inserted (no partial write)
stores abcmine.
Any failure inside the writer after it inserted content and before the
container's record is updated reproduces it (for example a block node's
serializer, run after the line before it was inserted, or a nested
container's write).
Notes
A fix needs the recovery to see what the failed attempt inserted: rebuild
the affected containers' record from the document before the rewrite (the
reader's unit parser can do it), treating only units made of items the
failed writer inserted as its own, so an app's write inside the binding's
transaction is neither deleted nor duplicated.
More shapes that reproduce it on main, all with a NodeState unparse that throws once inside the binding's write transaction: an inline decorator's state (its text before it is already inserted), the state of a paragraph or block inside a newly inserted container (the container is already inserted), a table cell's state in a new table, and a named slot's value (the slot is already inserted). In each case the recovery stores the partly written content a second time; the editor shows it once.
Activity
[lexical-y] Bug Fix: An app serializer that throws during a write run…
zurfyx commentedon Nov 4, 2026
More shapes that reproduce it on main, all with a NodeState
unparsethat throws once inside the binding's write transaction: an inline decorator's state (its text before it is already inserted), the state of a paragraph or block inside a newly inserted container (the container is already inserted), a table cell's state in a new table, and a named slot's value (the slot is already inserted). In each case the recovery stores the partly written content a second time; the editor shows it once.[lexical-y] Chore: Pin main's duplicated recovery after a failed writ…
[lexical-y] Chore: Pin main's duplicated recovery after a failed writ…
[lexical-y] Chore: Pin main's duplicated recovery after a failed writ…
[lexical-y] Chore: Cells for what a composer's hold refuses: a local …
[lexical-y] Bug Fix: Refuse a local change that would delete what a c…
[lexical-y] Chore: Cells for what a composer's hold refuses: a local …
[lexical-y] Bug Fix: Refuse a local change that would delete what a c…
[lexical-y] Chore: Cells for what a composer's hold refuses: a local …
[lexical-y] Bug Fix: Refuse a local change that would delete what a c…
[lexical-y] Chore: Cells for what a composer's hold refuses: a local …
[lexical-y] Bug Fix: Refuse a local change that would delete what a c…
[lexical-y] Chore: Cells for what a composer's hold refuses: a local …