Skip to content

Toggling a code block crashes when the selection spans nested structures#1110

Open
jorgemanrubia wants to merge 1 commit into
mainfrom
sentry-lexical-error-19
Open

Toggling a code block crashes when the selection spans nested structures#1110
jorgemanrubia wants to merge 1 commit into
mainfrom
sentry-lexical-error-19

Conversation

@jorgemanrubia

@jorgemanrubia jorgemanrubia commented Jun 10, 2026

Copy link
Copy Markdown
Member

Fixes BC3-JS-N7D7 — 562 events / 470 users since May 26 (153 / 149 in the last 3 days). Card: https://app.basecamp.com/2914079/buckets/41746046/card_tables/cards/9982983094

What it fixes

Clicking the code toolbar button with the selection spanning nested structures — a quote and its inner paragraphs, or nested list items — crashed with Lexical invariant #19 ("updateEditor: selection has been lost because the previously selected nodes have been removed and selection wasn't moved to another node"). Lexical rolls the update back, so the click silently did nothing. Every Sentry event goes through the same path: toolbar dispatchButtonCommandeditor.update → invariant thrown at commit.

How

Contents#toggleCodeBlock collects the nearest block element for every selected node, inserts the new code node after the last collected element, and hands all of them to CodeNodeInserter, which converts each to code text via node.remove() + $createTextNode(node.getTextContent()).

With nested structures the collected list contains both an element and its ancestor (e.g. [paragraph, quote, paragraphInsideQuote]). The code node gets inserted after the last element — inside the ancestor's subtree — so converting the ancestor detaches the code node along with it. The subsequent caret inserts and selectEnd() land in a detached tree; garbage collection purges those keys, and the pending selection fails Lexical's node-map check.

The fix filters the collected elements down to the outermost ones before picking the insertion point. Their getTextContent() already covers their descendants (the inserter already skips detached descendants), so the converted output is unchanged — the code node just can no longer be created inside an element that's about to be removed.

Failing-first regression tests at both layers, each verified to fail on main’s contents.js and pass with the fix: Playwright tests through the real toolbar button (test/browser/tests/formatting/code_block_navigation.test.js, "Code block conversion") and unit tests via the toolbar’s dispatch path (test/javascript/unit/editor/toggle_code_block.test.js, helpers promoted to editor_helper.js).

Overlap notes

Self-contained in contents.js#toggleCodeBlock; no overlap with #1106/#1107/#1108/#1109 diffs (different crash paths: arrow-nav race, drop/paste guards, paste invariant #66, paste invariants #211/#212).

While reproducing, a brute-force matrix also surfaced a separate non-#19 crash in the same command: a selection covering a horizontal divider throws "Expected node to have closest block element node" from #blockLevelElementsInSelection. Different error signature, different Sentry issue — left out of scope here.

@jorgemanrubia jorgemanrubia force-pushed the sentry-lexical-error-19 branch from 222934e to f9e4294 Compare June 10, 2026 18:44
@jorgemanrubia jorgemanrubia marked this pull request as draft June 10, 2026 18:44
Toggling a code block with a selection spanning nested structures (a quote
and its inner paragraphs, or nested list items) collected both an element
and its ancestor. The new code node was inserted after the last collected
element — inside the ancestor's subtree — and converting the ancestor
removed it, leaving the selection on detached nodes. Lexical then threw
invariant #19 ("selection has been lost") and rolled back the update,
so the button click did nothing.

Filter the collected block elements down to the outermost ones: their text
content already covers their descendants, and the code node inserted after
the last of them can no longer sit inside another element being converted.
@jorgemanrubia jorgemanrubia force-pushed the sentry-lexical-error-19 branch from f9e4294 to 92ece54 Compare June 10, 2026 18:52
@jorgemanrubia jorgemanrubia marked this pull request as ready for review June 11, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant