Repository navigation
[lexical-website] Documentation Update: Add the limit of text nodes made again with other text to the @lexical/y docs - #9556
Conversation
…ade again with other text to the @lexical/y docs ## Description When an update replaces a line's text nodes with new ones and the line's text changed (an AI rewrite that makes a block's text nodes again from their JSON with one word changed, or removes a block's children and appends one new text node), the binding writes a replaced node whose text is not all there unchanged again whole: a removed node's text is aligned with what replaced it whole or not at all. A collaborator's concurrent letter typed into that text, which the rewriting client had not seen, then stays where it was and shows in front of the rewritten text, out of its word (for example `zt63t71z t13z t81z t15z` where the collaborator typed the `z` of `t63z`). The concurrent typed text is kept and every client converges. The "Edits made by code" section already says that text made where the line's text changed is new where it is put, and points to Limits for replaced blocks, but Limits did not list the case. This adds it there, with what to do instead (edit the changed words in place with `spliceText` or `insertText`, which writes only the changed characters). Aligning a removed node's text in part instead (by the common start and end, as a text set is) was tried and is not taken here: it fixes this case but breaks cases the whole-or-nothing rule protects in the existing suite (a peer's concurrent delete written back, a character duplicated after an entity move and a set, a collaborator's typing moved to the start of a rewritten line). ## Test plan Documentation only; `pnpm run ci-check` passes (prettier, website typecheck). The anchor it links (`#edits-made-by-code-agents-tools`) is the slug of the existing "Edits made by code (agents, tools)" heading.
|
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: |
## Description This commit changes no file. The repository's review bot reviews a pull request when its branch is pushed; this branch was last pushed before the bot started reviewing the repository, so it had no review yet.
| that outdents an item of such a list is a delete in the nested list's | ||
| container and an insert in the list's: text a collaborator types in those | ||
| items at the same time is lost (only lists stored in that older shape). | ||
| - **Text nodes made again with other text.** Where an update replaces a |
There was a problem hiding this comment.
IIRC the text merge transform is going to splice TextNode back together at least in a left-biased way if the other matching conditions are met before the update is committed and then relayed to collab, so a split-then-join should be indistinguishable from a setTextContent splice unless there's something blocking the merge altogether or the changed content isn't a prefix. We could add a different bias in $mergeTextNodes to prefer preserving an existing node on merge even if it is on the right.
There was a problem hiding this comment.
And if the problem is that it doesn't call TextNode.spliceText (after the bias heuristic is changed), we could probably have TextNode.mergeWithSibling call it?
There was a problem hiding this comment.
This looks good to me. The new Limits bullet matches how the @lexical/y binding behaves today: a text node that is made again with changed text is deleted and inserted whole, what a collaborator types into it at the same time is kept and every client converges, and TextNode#spliceText or the selection's insertText write only the characters they replace. Three optional, non-blocking wording suggestions, all in packages/lexical-website/docs/collaboration/y.md:
-
Unchanged nodes between two changed ones are rewritten too (lines 816-818). “a replaced node whose text is not all there unchanged is written again whole” reads as if a remade node whose text did not change keeps its characters. It does only where it lines up from the start or the end of the replaced text: with
aa·bb·ccremade asaX·bb·cY, the whole lineaa bb ccis deleted andaX bb cYinserted, and aza collaborator types inside the unchangedbbat the same time lands in front of the line. With only the last word changed, onlyccis rewritten and thezstays in its word. So an update that changes two words of a block rewrites everything between them. Possible wording: “…every replaced node from the first whose text changed to the last, also one whose text did not change, is written again whole…”. -
Concurrent formatting at the end of the replaced text (lines 822-823). The bullet says the collaborator's formatting of that text “is not kept”. Formatting that reaches the end of the replaced text can instead cover all of the new text, on every client. For example, with the line
t63 t71 t13 t81 t15made again as one node witht72, a collaborator who boldst15at the same time leaves the whole rewritten line bold when the author has the lower client id; in a linehead·t63 t71·t13 tailwhose nodes are remade from JSON, boldingt71makes all of the newt63 t72bold whichever id is lower. Formatting that ends inside the text (boldingt63) is dropped, as written. The page's “Other races” bullet already describes a related case, text typed at the start of a range a collaborator formats at the same time. Possible wording: “…is not kept where it was; formatting that reaches the end of the replaced text can cover all of the new text instead.” ThesetTextContentsentence under “Edits made by code” (lines 684-686) could take the same clause. -
Typing at the very end of the replaced text (lines 819-821). “lands outside the new text, before it” holds for a letter typed inside the text; one typed at the very end of the replaced text lands before or after the new text depending on client ids (
ztyped aftert15givest63 t72 t13 t81 t15zwhen the author has the lower id). Possible wording: “outside the new text: before it, or after it where it was typed at the very end of the replaced text (by the order Yjs gives the two inserts)”.
On the open thread about merge bias: the binding already replays a text-node merge with both nodes' characters (the merge entry in packages/lexical-y/src/internal/TextEdits.ts), so in a split-then-join only the part that was made again is rewritten whole, whichever node survives the merge. That looks like a separate follow-up rather than something this page needs to wait for.
No changeset is needed for a website-only change (CONTRIBUTING.md, “Changesets”).
| block's children and appends one new text node), a replaced node whose | ||
| text is not all there unchanged is written again whole: its characters are | ||
| deleted, and the new text is inserted. What a collaborator types into that |
There was a problem hiding this comment.
Optional, non-blocking: a remade node whose text did not change keeps its characters only where it lines up from the start or the end of the replaced text. With aa · bb · cc remade as aX · bb · cY, the whole line aa bb cc is deleted and aX bb cY inserted, and a z a collaborator types inside the unchanged bb at the same time lands in front of the line; with only the last word changed, only cc is rewritten. So an update that changes two words of a block rewrites everything between them. Possible wording: “…every replaced node from the first whose text changed to the last, also one whose text did not change, is written again whole…”.
| text at the same time, which the update's author has not seen, stays where | ||
| it was, so it lands outside the new text, before it (a letter typed at the | ||
| end of a word ends up in front of the rewritten text, out of its word), |
There was a problem hiding this comment.
Optional, non-blocking: a letter typed at the very end of the replaced text can land after the new text instead: z typed after t15 (the end of the line) gives t63 t72 t13 t81 t15z when the author has the lower client id, and zt63 t72 t13 t81 t15 otherwise. Possible wording: “outside the new text: before it, or after it where it was typed at the very end of the replaced text (by the order Yjs gives the two inserts)”.
| and the collaborator's formatting of that text at the same time is not | ||
| kept. The concurrent typed text is kept, and every client shows the same |
There was a problem hiding this comment.
Optional, non-blocking: formatting that reaches the end of the replaced text is not dropped but can cover all of the new text. With t63 t71 t13 t81 t15 made again as one node with t72, a collaborator bolding t15 at the same time leaves the whole rewritten line bold on every client when the author has the lower client id; in head · t63 t71 · t13 tail remade from JSON, bolding t71 makes all of the new t63 t72 bold in either id order. Formatting that ends inside the text (bolding t63) is dropped, as written. Possible wording: “…is not kept where it was; formatting that reaches the end of the replaced text can cover all of the new text instead.” The “Other races” bullet already describes a related case, text typed at the start of a range a collaborator formats at the same time.
Description
When an update replaces a line's text nodes with new ones and the line's text changed (an AI rewrite that makes a block's text nodes again from their JSON with one word changed, or removes a block's children and appends one new text node), the binding writes a replaced node whose text is not all there unchanged again whole: a removed node's text is aligned with what replaced it whole or not at all. A collaborator's concurrent letter typed into that text, which the rewriting client had not seen, then stays where it was and shows in front of the rewritten text, out of its word (for example
zt63t71z t13z t81z t15zwhere the collaborator typed thezoft63z). The concurrent typed text is kept and every client converges.The "Edits made by code" section already says that text made where the line's text changed is new where it is put, and points to Limits for replaced blocks, but Limits did not list the case. This adds it there, with what to do instead (edit the changed words in place with
spliceTextorinsertText, which writes only the changed characters).Aligning a removed node's text in part instead (by the common start and end, as a text set is) was tried and is not taken here: it fixes this case but breaks cases the whole-or-nothing rule protects in the existing suite (a peer's concurrent delete written back, a character duplicated after an entity move and a set, a collaborator's typing moved to the start of a rewritten line).
Test plan
Documentation only;
pnpm run ci-checkpasses (prettier, website typecheck). The anchor it links (#edits-made-by-code-agents-tools) is the slug of the existing "Edits made by code (agents, tools)" heading.