Repository navigation
[lexical-website][lexical-y] Bug Fix: Write a block reorder as a move of the block Lexical moved - #9502
Merged
Conversation
zurfyx
requested review from
acywatson,
etrepum,
ivailop7 and
potatowagon
as code owners
October 28, 2026 23:30
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
lexical
@lexical/a11y
@lexical/clipboard
@lexical/code
@lexical/code-core
@lexical/code-prism
@lexical/code-shiki
@lexical/compiler
@lexical/devtools-core
@lexical/dragon
@lexical/eslint-plugin
@lexical/extension
@lexical/fast-check
@lexical/file
@lexical/hashtag
@lexical/headless
@lexical/history
@lexical/html
@lexical/internal
@lexical/link
@lexical/list
@lexical/mark
@lexical/markdown
@lexical/mdast
@lexical/offset
@lexical/overflow
@lexical/plain-text
@lexical/react
@lexical/rich-text
@lexical/selection
@lexical/table
@lexical/tailwind
@lexical/text
@lexical/utils
@lexical/y
@lexical/yjs
commit: |
etrepum
approved these changes
Oct 29, 2026
zurfyx
force-pushed
the
zurfyx/y-reorder-intent
branch
from
October 29, 2026 05:32
9d6f599 to
a1675b3
Compare
This was referenced Oct 29, 2026
This was referenced Oct 29, 2026
zurfyx
force-pushed
the
zurfyx/y-initial-state-binding
branch
from
October 29, 2026 10:47
cda2d59 to
70830bd
Compare
zurfyx
force-pushed
the
zurfyx/y-reorder-intent
branch
from
October 29, 2026 10:47
a1675b3 to
43df673
Compare
vjeux
approved these changes
Oct 29, 2026
zurfyx
force-pushed
the
zurfyx/y-initial-state-binding
branch
from
October 29, 2026 18:30
70830bd to
de27a67
Compare
… of the block Lexical moved
## Description
Yjs has no moves, so when a commit reorders blocks, `@lexical/y` keeps some blocks in place and deletes and re-inserts the others. Whatever a collaborator types in a re-inserted block at the same time is lost or stays at its old position. The writer chose the blocks to keep by block count and then by content size, and used Lexical's record of explicit moves (`EditorState#_movedNodes`) only to break exact ties. So a reorder was written as a move of whichever side held less content, not of the block the user moved:
- Peer A moves a two-item list above the paragraph `Another line` (`paragraph.insertBefore(list)`, as a block drag does) while peer B types `X` after `Another`. Every client and a fresh reader end with `list("Xitem one", "item two") / paragraph("Another line")`: B's text lands in a block nobody moved.
- Without concurrency, B's caret after `Another` jumps to the start of `item one` when A's move arrives.
- A table (1x2 or 2x2) or a list with more text than the paragraph, moved up or down past it, leaves B's `X` in a paragraph of its own at the paragraph's old place.
A short list, a quote or a paragraph moved the same way only passed because each held less content than the paragraph.
With this change, the block Lexical recorded as moved is the one rewritten, which is what `@lexical/yjs` already does (`getStableChildKeys`). `Writer#$writeStructure` splits the matched units into those the commit moved and the rest. A unit counts as moved when its node, or an element between it and the container being written (the list of a list item, the item that holds a nested list), is in the record. The rest keep their relative order, since only a move changes it, so all of them stay in place. A moved unit stays too where it is still in order between them (put back where it was, or indented). Block count and size now only decide where Lexical recorded nothing (an editor state set whole) and among moved units. Core already records every move made with `insertBefore`, `insertAfter`, `append` or `splice`, so it is unchanged. The stored layout does not change.
| With a concurrent move of a two-item list | Before | After |
| --- | --- | --- |
| B types in the paragraph the list passes | lands in `item one` | stays in the paragraph |
| B's caret in that paragraph | jumps into `item one` | stays |
| B types in a paragraph a moved table passes | lands in a paragraph of its own | stays |
| B types in the moved list itself | stays in the list | stays at the list's old place |
The last row is the documented Moves limit, which before applied only when the moved block was the smaller one. It covers text typed in the moved block at the same time, a collaborator's caret in it, and what a collaborator typed in it before the mover undoes the move. Two `BlocksAndLines` tests asserted the old trade-off (typing inside a dragged collapsible, and inside a paragraph moved down one slot, survived because the passed block was rewritten). They are now `test.fails`, next to new tests for typing in the block passed. The y.md Moves limit now says which block counts as moved, the fallback, and the undo case.
These are left as expected failures in `regressions/Moves.test.ts`:
- the moved block itself (above);
- a node put back where it was in the same update as a move (an insert next to the sibling it already has, a remove and a re-insert, a move away and back). Lexical's record counts such a node as moved too, so when a real move passes it, the smaller of the two is rewritten. Refining that record would be a core change shared with `@lexical/yjs`.
## Changeset
`.changeset/lexical-y-reorder-intent.md`: `[fix]` for `@lexical/y`, with a `Verify:` paragraph for the behavior change.
## Test plan
New: `packages/lexical-y/src/__tests__/unit/regressions/Moves.test.ts` (132 tests). It covers the S7-2 matrix with its controls, blocks of every shape moved up and down, the result of a recorded move (what the writer deletes), each move API, nested lists, tables and table rows, carets, the fallback with no record, and the remaining limits as `test.fails`, each with a convergence test. Two tests in `BlocksAndLines.test.ts` are updated as described above.
`./node_modules/.bin/vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/Moves.test.ts packages/lexical-y/src/__tests__/unit/BlocksAndLines.test.ts`
### Before
On `main` (0cd7448):
```
FAIL |unit| packages/lexical-y/src/__tests__/unit/regressions/Moves.test.ts > S7-2: a block moved up past the paragraph before it > S7-2 a two-item list moved up: a caret in the paragraph it passed stays there (ids 2,1)
AssertionError: expected 'list[bullet](listitem("Xitem one") li…' to be 'list[bullet](listitem("item one") lis…' // Object.is equality
Expected: "list[bullet](listitem("item one") listitem("item two")) / paragraph("AnotherX line")"
Received: "list[bullet](listitem("Xitem one") listitem("item two")) / paragraph("Another line")"
FAIL |unit| packages/lexical-y/src/__tests__/unit/regressions/Moves.test.ts > recorded moves of tables and table rows > a table moved up past a paragraph ‖ typing in the paragraph stays there (ids 1,2)
AssertionError: expected 'Another line' not to contain 'Another'
Test Files 2 failed (2)
Tests 72 failed | 108 passed | 8 expected fail (188)
```
Failures by group: S7-2 matrix 8, every shape 12 (long list, 1x2 and 2x2 tables, up and down), what a recorded move rewrites 10, each move API 8, nested lists 6, tables and rows 10, `BlocksAndLines` moves 8. The Moves limit group also counts 10, because those `test.fails` tests pass on `main`, where the passed block was rewritten instead.
### After
```
✓ |unit| packages/lexical-y/src/__tests__/unit/BlocksAndLines.test.ts (56 tests) 2085ms
✓ |unit| packages/lexical-y/src/__tests__/unit/regressions/Moves.test.ts (132 tests) 4828ms
Test Files 2 passed (2)
Tests 166 passed | 22 expected fail (188)
```
Also run with the change:
- `@lexical/y` unit tests: 149 files passed and 2 skipped; 5,027 tests passed, 38 expected fail, 2 skipped (on `main`: 148 files; 4,913 passed, 16 expected fail).
- Golden corpus: the fuzz's five lanes (no moves, block ids, gc, moves, block ids with moves), 250 seeds each, compared with `main` for every peer's Yjs state bytes, editor JSON, warnings, fuzz findings, node keys and selection. The three lanes without moves are identical on all 750 seeds. In each lane with moves, 14 of 250 seeds write different bytes (the moved block is rewritten instead of the block it passed), with no other difference. Two of them add a `loss(container)` finding: text typed inside a table while that table moved, which is the Moves limit.
- The soak repros: the original S7-2 matrix fails 8 of 36 on `main` and passes 36 of 36; the shapes matrix fails 8 of 20 on `main` and passes 20 of 20.
- `pnpm run ci-check` passes.
- Not run: browser-mode tests and the playground e2e specs. No collab Y e2e spec moves a block.
…ent joins into one line as a known race ## Description Take the blocks `[heading, quote, paragraph]`. Peer A deletes from the end of the heading to the start of the paragraph, across the quote. At the same time, peer B joins the paragraph into the quote (Backspace at its start). With one client id order, every client and a fresh reader end with the heading's and the paragraph's text in a quote, although neither edit retyped the first line. The same happens to a code block. A line is stored as its text followed by a marker that holds its type. So a join deletes the earlier line's marker and writes the earlier line's type onto the later line's marker, which now ends the joined line. Here both joins end at the paragraph's marker: A writes the heading's type there and B writes the quote's, and Yjs keeps one of the two writes by client id. The type is one value on that marker, so the two writes cannot both be kept. Keeping the earlier line's type would need each join to record which line it came from, and a follow-up to elect one. This commit records the race as a known limit and does not change what is stored: - `regressions/Joins.test.ts`: S7-22 for a heading and a code block, with a convergence test for both client id orders and a `test.fails` for the order that loses the first line's type. - y.md, Limits, Other races: one sentence for two joins at once into the same line. It is independent of the reorder fix in the previous commit, since a join reorders nothing. The new tests give the same results with and without that commit. ## Changeset None: a test and website docs only. ## Test plan ### Before The soak repro on `main` (0cd7448), `./node_modules/.bin/vitest --run --project unit L2-ConcurrentJoinIntoCode.test.ts`: ``` - "final": "heading", - "merged": "heading", + "final": "quote", + "merged": "quote", "mergedShape": "quote(t:\"const t44z = 1; // t46z note t47z\"[\"c1\"] t:\"t49z t50z t51z\")", Tests 2 failed | 2 passed (4) ``` ### After `./node_modules/.bin/vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/Joins.test.ts`, the same on `main` and on this branch (the behavior is unchanged): ``` ✓ |unit| packages/lexical-y/src/__tests__/unit/regressions/Joins.test.ts (8 tests) 388ms Tests 6 passed | 2 expected fail (8) ``` - `@lexical/y` unit tests with both commits: 150 files passed and 2 skipped; 5,033 tests passed, 40 expected fail, 2 skipped. - `pnpm run ci-check` passes.
…age as it is stored now
## Description
With the first commit of this PR, the block an update moved is the one written anew. When the same update first changed the content of a container inside that block (a list given an item, then moved into a box that moves too), the writer writes the box's container in place, and then writes the box anew, with its content, for its move. The document is right and every client converges, but the development self-check (`selfCheck: true`) compared the box's old container, which the move deleted, with the editor, and warned `self-check: container <key>: editor [...] != shared []`. On `main` the smaller list was written anew instead of the box, so this did not happen.
The self-check now compares only containers that are still their node's (`state.csByKey`). The box's new container is compared through the write's structural keys, as before.
## Changeset
None: this changes only the opt-in development self-check, and `.changeset/lexical-y-reorder-intent.md` already covers this PR.
## Test plan
New in `regressions/Moves.test.ts`: `a block written, then moved, in one update`, 4 tests (an empty box and a box holding a line, in both client id orders). They use `expectConverged`, which requires no self-check warning.
`./node_modules/.bin/vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/Moves.test.ts`
### Before
On the first two commits of this PR:
```
× a list given an item, moved into an empty box that moves too (ids 1,2)
× a list given an item, moved into a box that moves too (ids 1,2)
× a list given an item, moved into an empty box that moves too (ids 2,1)
× a list given an item, moved into a box that moves too (ids 2,1)
Tests 4 failed | 132 skipped (136)
```
Each fails on `expected [ Array(1) ] to deeply equal []`, the warning `self-check: container 13: editor [{"children":[{"children":[{"detail":0,...,"text":"z0",...}]...}]...] != shared []`. On `main` the same edits give no warning.
### After
```
Test Files 1 passed (1)
Tests 118 passed | 18 expected fail (136)
```
- `@lexical/y` unit tests: 150 files passed and 2 skipped; 5,037 tests passed, 40 expected fail, 2 skipped.
- `pnpm run ci-check` passes.
zurfyx
force-pushed
the
zurfyx/y-reorder-intent
branch
from
October 29, 2026 18:49
43df673 to
5e40824
Compare
zurfyx
added a commit
that referenced
this pull request
Oct 29, 2026
… does not know in a block it moves ## Description An older client of an app (one whose class lacks a field that a newer version of the class added) keeps that field when it edits a node (#9504). A block it moves was different: Yjs has no moves, so the writer deletes the moved block and writes it anew from what the client shows (#9502 chooses which block). The new copy started empty, so the moved block lost every field the client does not own, for every client and for a fresh reader, with no concurrency involved: - a line's fields on its marker (a note's `tone`), and the fields of its list and outer lists on the marker of every item; - a block node's or decorator's fields on its Y.Node, and those of everything it holds (lines, lists, inline nodes, slots), which are written anew too; - the same when the client splits such a line. With this change, a unit written anew takes what its node stored before the write: - **Lines.** A marker the write makes for a line that had one takes the fields the old marker stored that the client does not own (`$markerBefore`, read with `attrsBefore`: the attributes as they were before the transaction, which Yjs still holds for a marker the transaction deleted). A line that holds lines (stored as parts) reads each part's old marker, or the whole line's for a new part. Where a diff of a container gives a moved line the marker of another line there, the marker takes the moved line's fields and drops the other line's. - **Groups.** A list item new to its list, or written anew, takes the list's fields (`g:`) from the item next to it that kept its marker and was in the list before (#9504's rule, which now also requires the second part), so an item that joins another list takes that list's fields. Where no such item exists, the list moved with the item or lost all its items: the item takes the fields the list stored before, from its own old marker if it was in the list, else from another item's, else from an item the list had then (`$priorGroupItem`). So a list moved with an item added, swapped list items, and two lists an older client shows as one all keep their own fields. An outer level takes the fields of its item (`@`) from where that item's were stored, so a nested list moved into a new item takes none of the old item's. - **Nodes.** A node given new storage takes the fields its old Y.Node stored that the client does not own (`nodeBefore` in `$fillNode`). What each lookup finds is kept for the whole write, so a node written twice in one write (moved into a container that is itself written anew) still takes what it had before the first. Only names the client does not own are copied; it writes and deletes its own as before. Ownership now also counts a flat NodeState with no schema (a plain `parse` function) where the client writes it as a field: on a node stored as its own Y.Node, and on text (`jsonFieldsOf`). With #9504, such a state reset to its default was never deleted there, so every other client and a fresh reader kept the old value. On `main` it was deleted. Clients of one version store what they stored before, with one exception, in names that no class of theirs declares on a line (a list item's `checked` left on a paragraph): those now stay with the line when a diff gives it another line's marker, where before they stayed with the marker. Remaining limits, as `test.fails` and in y.md: - The text of a moved block is still written from what the client shows, so it loses the text formats, text-class fields and link fields the client does not know. Keeping them needs the old characters' formats carried into the new runs, character by character. - A nested list whose item moves into another list does not take that list's fields: the outer level would need them from the items of the list it joins. Not changed here: an item that keeps its marker while its list changes (an indent or outdent of a list's first item) keeps the list fields of its old place, as with #9504. ## Changeset `.changeset/lexical-y-moved-blocks-keep-fields.md`: `[fix]` for `@lexical/y`, with a `Verify:` paragraph. ## Test plan `regressions/SchemaSkew.test.ts`: #9504's `test.fails('limit: a block an older client moves keeps the fields it does not know')` is now a passing test. A new F2 group compares each move made by an older client with the same move made by a newer one: lines, lists, block nodes, decorators, nested content, nested lists and inline nodes, moved down, up and into a new container; splits; slots; items moved within and between lists; swapped list items; lists shown as one; lists moved with new items; nested lists moved into new or other items; moves into containers that move too; a line that gets another line's marker; a line holding lines. Another group checks a flat NodeState with no schema, reset in place and on a move. `./node_modules/.bin/vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/SchemaSkew.test.ts` ### Before On the merge of #9502 and #9504: ``` FAIL |unit| packages/lexical-y/src/__tests__/unit/regressions/SchemaSkew.test.ts > F2: an older client that moves a block keeps the fields it does not know > a block node moved down past a paragraph AssertionError: expected { root: { children: [ …(2) ], …(5) } } to deeply equal { root: { children: [ …(2) ], …(5) } } - "tone": "warm", + "tone": "", FAIL |unit| packages/lexical-y/src/__tests__/unit/regressions/SchemaSkew.test.ts > S7-3, F2: a flat NodeState with no schema > on a block node, reset, stays reset + "test_skew_memo": "old", Test Files 1 failed (1) Tests 41 failed | 66 passed | 4 expected fail (111) ``` ### After ``` ✓ |unit| packages/lexical-y/src/__tests__/unit/regressions/SchemaSkew.test.ts (111 tests) Test Files 1 passed (1) Tests 107 passed | 4 expected fail (111) ``` - `@lexical/y` unit tests: 151 files passed and 2 skipped; 5,144 tests passed, 44 expected fail, 2 skipped (on the merge: 151 files passed and 2 skipped; 5,094 tests passed, 42 expected fail, 2 skipped). - Golden corpus: the fuzz's five lanes (no moves, block ids, gc, moves, block ids with moves), 250 seeds each, compared with the merge for every peer's Yjs state bytes, editor JSON, warnings, fuzz findings, node keys and selection. Editor JSON, warnings, findings, keys and selection are identical on all 1,250 seeds. Bytes differ on 3 seeds in each lane without moves and 2 in each lane with moves, all from the exception above. - `pnpm run ci-check` passes.
This branch was successfully deployed
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.
Note
Stack, part 6 of 10. Based on #9508 (
zurfyx/y-initial-state-binding). Review only this PR's own commits.The
@lexical/yfixes touch the same writer, cursor, reader and binding internals, so they are one linear stack: each PR is based on the one below it, each conflict is resolved once, in the PR whose change causes it, and every CI run tests the real combination.Merge order: bottom first, #9509 → #9523 → #9522 → #9505 → #9508 → #9502 → #9504 → #9527 → #9507 → #9510. With squash merges, rebase the next PR onto
mainbefore merging it (git rebase --onto main <old base branch>).The stack
zurfyx/update-error-rollbackmainzurfyx/write-before-unread-changezurfyx/y-keep-chars-at-format-boundaryzurfyx/y-undo-member-closerzurfyx/y-initial-state-bindingzurfyx/y-reorder-intentzurfyx/y-preserve-unknown-attrszurfyx/y-move-keeps-unknown-fieldszurfyx/y-composition-holdbackzurfyx/y-incremental-read-marksFollow-ups for after the stack (not in it):
Unread(changes Yjs applied that the binding has not read) and [lexical-playground][lexical-website][lexical-y] Bug Fix: Hold back remote changes to the line being composed until the composition ends #9507'sHidden(changes held back while a line is composed) answer one question, and call sites combine them by hand. One owner module (isGone,isShown,attrOf/attrsOfand the write primitives), an item-level held overlay instead of thewas/nowmaps, and a lint or grep test that allows raw.deleted/getAttr(s)only in the Reader and in named provenance scans. [lexical-website][lexical-y] Bug Fix: Stop a format, comment or link from spreading to the end of the document after an undo or delete races an edit inside it #9505's run-opener scans (liveItemBefore,runOpener,restatedEnd) still read raw deletedness and move there too.$groupReference,$priorGroupItem, the nearest neighbour's marker before the write,priorGroupMarker), with separate predecessor stores for markers and nodes. One stored-predecessor lookup would replace it.Description
Yjs has no moves, so when a commit reorders blocks,
@lexical/ykeeps some blocks in place and deletes and re-inserts the others. Whatever a collaborator types in a re-inserted block at the same time is lost or stays at its old position. The writer chose the blocks to keep by block count and then by content size, and used Lexical's record of explicit moves (EditorState#_movedNodes) only to break exact ties. So a reorder was written as a move of whichever side held less content, not of the block the user moved:Another line(paragraph.insertBefore(list), as a block drag does) while peer B typesXafterAnother. Every client and a fresh reader end withlist("Xitem one", "item two") / paragraph("Another line"): B's text lands in a block nobody moved.Anotherjumps to the start ofitem onewhen A's move arrives.Xin a paragraph of its own at the paragraph's old place.A short list, a quote or a paragraph moved the same way only passed because each held less content than the paragraph.
With this change, the block Lexical recorded as moved is the one rewritten, which is what
@lexical/yjsalready does (getStableChildKeys).Writer#$writeStructuresplits the matched units into those the commit moved and the rest. A unit counts as moved when its node, or an element between it and the container being written (the list of a list item, the item that holds a nested list), is in the record. The rest keep their relative order, since only a move changes it, so all of them stay in place. A moved unit stays too where it is still in order between them (put back where it was, or indented). Block count and size now only decide where Lexical recorded nothing (an editor state set whole) and among moved units. Core already records every move made withinsertBefore,insertAfter,appendorsplice, so it is unchanged. The stored layout does not change.item oneitem oneThe last row is the documented Moves limit, which before applied only when the moved block was the smaller one. It covers text typed in the moved block at the same time, a collaborator's caret in it, and what a collaborator typed in it before the mover undoes the move. Two
BlocksAndLinestests asserted the old trade-off (typing inside a dragged collapsible, and inside a paragraph moved down one slot, survived because the passed block was rewritten). They are nowtest.fails, next to new tests for typing in the block passed. The y.md Moves limit now says which block counts as moved, the fallback, and the undo case.These are left as expected failures in
regressions/Moves.test.ts:@lexical/yjs.Also in this PR: two concurrent joins into one line (second commit)
Take the blocks
[heading, quote, paragraph]. Peer A deletes from the end of the heading to the start of the paragraph, across the quote, while peer B joins the paragraph into the quote. With one client id order, every client ends with the heading's and the paragraph's text in a quote (a code block too), although neither edit retyped the first line. A join deletes the earlier line's marker and writes the earlier line's type onto the later line's marker. Here both joins write the paragraph's marker, and Yjs keeps one of the two writes by client id. The type is one value on that marker, so the two writes cannot both be kept; keeping the earlier line's type would need each join to record which line it came from and a follow-up to elect one. The second commit records the race as a known limit (regressions/Joins.test.ts, withtest.failsfor the losing order, and a sentence under y.md Limits, Other races) and changes nothing stored. It is independent of the reorder fix: a join reorders nothing, and the new tests give the same results with and without it.Also in this PR: the self-check of a container a move gave new storage (third commit)
With the reorder fix, when one update first changes the content of a container inside the block it moves (a list given an item, then moved into a box that moves too), the writer writes the box's container in place and then writes the box anew for its move. The document is right, but the development self-check compared the box's old, deleted container with the editor and warned. It now compares only containers that are still their node's. New tests:
a block written, then moved, in one updateinregressions/Moves.test.ts(4 tests, which fail on the first two commits).Restack (October 29)
Rebased onto #9508 with no conflicts (it was based on a
mainone commit older).Changeset
.changeset/lexical-y-reorder-intent.md:[fix]for@lexical/y, with aVerify:paragraph for the behavior change.Test plan
New:
packages/lexical-y/src/__tests__/unit/regressions/Moves.test.ts(132 tests). It covers the S7-2 matrix with its controls, blocks of every shape moved up and down, the result of a recorded move (what the writer deletes), each move API, nested lists, tables and table rows, carets, the fallback with no record, and the remaining limits astest.fails, each with a convergence test. Two tests inBlocksAndLines.test.tsare updated as described above../node_modules/.bin/vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/Moves.test.ts packages/lexical-y/src/__tests__/unit/BlocksAndLines.test.tsBefore
On
main(0cd7448):Failures by group: S7-2 matrix 8, every shape 12 (long list, 1x2 and 2x2 tables, up and down), what a recorded move rewrites 10, each move API 8, nested lists 6, tables and rows 10,
BlocksAndLinesmoves 8. The Moves limit group also counts 10, because thosetest.failstests pass onmain, where the passed block was rewritten instead.After
Also run with the change:
@lexical/yunit tests: 149 files passed and 2 skipped; 5,027 tests passed, 38 expected fail, 2 skipped (onmain: 148 files; 4,913 passed, 16 expected fail).mainfor every peer's Yjs state bytes, editor JSON, warnings, fuzz findings, node keys and selection. The three lanes without moves are identical on all 750 seeds. In each lane with moves, 14 of 250 seeds write different bytes (the moved block is rewritten instead of the block it passed), with no other difference. Two of them add aloss(container)finding: text typed inside a table while that table moved, which is the Moves limit.mainand passes 36 of 36; the shapes matrix fails 8 of 20 onmainand passes 20 of 20.pnpm run ci-checkpasses.regressions/Joins.test.tsgives6 passed | 2 expected fail (8)onmainand on this branch; the soak repro it replaces fails 2 of 4 onmain(the quote wins). With both commits,@lexical/yunit tests: 150 files passed and 2 skipped; 5,033 tests passed, 40 expected fail, 2 skipped.@lexical/yunit tests: 150 files passed and 2 skipped; 5,037 tests passed, 40 expected fail, 2 skipped.pnpm run ci-checkpasses.