Pasting into a blank list item inside a blockquote crashes with Lexical error #66#1108
Pasting into a blank list item inside a blockquote crashes with Lexical error #66#1108jorgemanrubia wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes a Lexical paste crash when the caret is on a blank (but not empty) list item inside a blockquote by preventing EarlyEscapeListItemNode from treating paste-driven insertNewAfter calls as an “escape” gesture, and adds a regression test to ensure both “no crash” and “content preserved” behavior.
Changes:
- Guard
EarlyEscapeListItemNode#shouldEscapewith$hasUpdateTag(PASTE_TAG)so paste flows defer to default list-item behavior. - Add a vitest regression test covering the crash path and verifying pasted content remains in the document.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| test/javascript/unit/editor/paste_into_quoted_list.test.js | Adds regression coverage for pasting block content into a blank quoted list item (crash + content preservation). |
| src/nodes/early_escape_list_item_node.js | Prevents list-item “escape” behavior during paste updates to avoid detached-node errors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const quote = $getRoot().getChildren().find($isQuoteNode) | ||
| const list = quote.getChildren().find($isListNode) | ||
| const blankListItem = list.getChildren().findLast($isListItemNode) | ||
| blankListItem.append($createLineBreakNode()) // As after Shift+Enter on an empty list item | ||
| blankListItem.select() |
There was a problem hiding this comment.
🤖 Addressed — the regression test now lives in the browser suite (test/browser/tests/paste/paste_into_quoted_list.test.js) and drives a real collapsed caret through actual editor interaction (Enter then Shift+Enter on the quoted list), so the selection shape matches the production scenario exactly. The unit test file this comment was on has been deleted.
Pasting block content with the cursor on a blank list item inside a blockquote crashed with Lexical error #66 ("Expected node to have a parent"). Lexical's RangeSelection.insertNodes calls insertParagraph, which triggered EarlyEscapeListItemNode's early-escape behavior: it removed the list item that insertNodes still references, so the follow-up insertRangeAfter called insertAfter on a detached node and threw. The pasted content was lost too. Guard the escape with PASTE_TAG, mirroring EarlyEscapeCodeNode: pasting is not an escape gesture, so defer to the default list item behavior.
207c259 to
3f36003
Compare
|
Another lexxy consumer (Rails, lexxy 0.9.12.beta) hitting #66 at the same We insert a saved-signature HTML snippet by dispatching a synthetic We can't tell from the Sentry event whether the caret was on a blank list item inside a blockquote (your exact repro) — if it was, treat this as a +1; if not, there may be a second paste-driven |
Fixes BC3-JS-N7CJ — 228 events / 186 users in 3 days. Card: https://app.basecamp.com/2914079/buckets/41746046/card_tables/cards/9982981354
What it fixes
Pasting block content with the cursor on a blank list item inside a blockquote crashed with Lexical error #66 ("Expected node %s to have a parent"), and the pasted content was lost. Every Sentry event goes through the same path:
Contents#insertDOM→insertAtCursor→RangeSelection.insertNodes.Reproduction: in a blockquote, Shift+Enter on an empty list item (leaving a list item whose only child is a line break), then paste rich text.
How
Lexical's
RangeSelection.insertNodescapturesfirstBlock(the list item), callsinsertParagraph→block.insertNewAfter(...), then inserts the pasted blocks withinsertRangeAfter(firstBlock, ...).EarlyEscapeListItemNode#insertNewAftertreated this internal call as an escape gesture:#escapeFromListremoved the list item, soinsertRangeAftercalledinsertAfteron a detached node andgetParentOrThrowthrew #66.The fix guards
#shouldEscapewith$hasUpdateTag(PASTE_TAG), mirroring the guardEarlyEscapeCodeNodealready has. Pasting is not an escape gesture; during paste the node defers to the default list item behavior, so the pasted content lands in the list and the escape UX on Enter is unchanged.Includes a Playwright regression test at
test/browser/tests/paste/paste_into_quoted_list.test.jsthat recreates the production scenario with a real collapsed caret (Enter, then Shift+Enter on the quoted list, then paste). Red/green verified: it fails on main'searly_escape_list_item_node.js, passes with the fix.Overlap notes
sentry-lexical-error-63for BC3-JS-N7E1) touch other crash paths (selection.js,contents.js,node_inserter.js,uploader.js). This fix is self-contained inearly_escape_list_item_node.jsand doesn't overlap their diffs.