Skip to content

chore: bump Lexical to 0.45.0#2975

Merged
huumn merged 12 commits into
stackernews:masterfrom
Soxasora:chore/bump-lexical-0.44.0
Jun 9, 2026
Merged

chore: bump Lexical to 0.45.0#2975
huumn merged 12 commits into
stackernews:masterfrom
Soxasora:chore/bump-lexical-0.44.0

Conversation

@Soxasora

@Soxasora Soxasora commented May 14, 2026

Copy link
Copy Markdown
Member

Description

Fixes #2976

Updates Lexical to v0.45.0 bringing new bug fixes and enhancements to the SN Editor
Relevant changes and adaptations:

lexical-code

  • breaking change: extract Prism code highlighting to @lexical/code-prism, with @lexical/code-core split out to avoid circular imports LEXICAL-#8198
    • we're temporarily switching to @lexical/code-core in conjunction with @lexical/code-shiki to reduce the bundle size and not ship Prism with the default @lexical/code dependency carried by Shiki. Lexical will remove Prism from @lexical/code later.
  • feat: Shift+Tab outdents space-indented code lines, not just tab-indented LEXICAL-#8445
    • automatic improvement to our Shiki code blocks

lexical-code-shiki

  • breaking change / feat: add CodeShikiExtension with signal-based { disabled, tokenizer } config LEXICAL-#8346
    • CodeShikiSNExtension now depends on configExtension(CodeShikiExtension, { tokenizer }) and hot-swaps output.tokenizer.value on theme change, replacing the old cleanup-and-re-register workaround in lib/lexical/exts/shiki.js.
    • shiki deps are now externalized, removing over 9MB of bundle bloat.
  • feat: allow null Tokenizer.defaultLanguage so unlanguaged code blocks round-trip through markdown without injecting a default fence tag LEXICAL-#8553
    • improvement, we now set null as the default language instead of forcing text and triggering syntax highlighting of basically nothing.

lexical

  • chore: stabilize @lexical/extension and NodeState APIs (no longer @experimental) LEXICAL-#8354
    • promoted @lexical/extension to a direct dependency as part of this bump

other SN fixes

  • adapt empty inline elements to NormalizeInlineElementsExtension behavior.
    • footnote backref node is now a simple DecoratorNode
    • autolink nodes are now created with a text node child
  • prevent accidental image uploads when pasting from rich text editors
  • prevent alignments from surfacing when pasting rich content. (reported by @sir-opti)
  • code blocks now have a starting bs-body-color to mitigate flashing <pre> -> <code> due to Shiki deps externalization

Summary of user-facing fixes and enhancements (can be used as a small changelog in the release notes)

enhancements:

- pressing backspace at block start (e.g. heading) will now replace an empty previous block (e.g. paragraph) with the current one. (some Android keyboards are not supported)
- code blocks now support outdenting space-indented lines
- adjacent links with identical attributes now get automatically merged
- improved triple-click selection, and transformations on such selections.

fixes:

- checklist items can now be toggled on mobile
- pasting from a rich text editor (e.g. Microsoft Word) no longer accidentally uploads a screenshot of the selection
- pasting code with multiple blank lines no longer breaks out of a code block
- caret is now kept above the on-screen keyboard on Android after Enter in lists
- general improvements to macOS text replacements on Chromium based browsers

List of fixes and observations

0.42.0 fixes

that pertain to our editor

lexical

  • fix: when the editor starts with an empty list item, ctrl+backspace (deleteWord) should replace the list with a paragraph LEXICAL-#8220
  • fix: consecutive linebreak insertion resets selection format LEXICAL-#8222

lexical-table

  • fix: improve nested table selection with monolithic pointer event handling LEXICAL-#8193
  • fix: prevent single-cell table selection after exiting table selection LEXICAL-#8195
  • fix: handle table selections crossing into or out of nested tables LEXICAL-#8234
    • sneditor current state: relevant if we keep nested tables supported, so this needs explicit QA rather than blind adoption

0.42.0 features, enhancements and changes

lexical

  • feat: LexicalEditor RootListener and EditableListener can return unregister callbacks LEXICAL-#8219

lexical-code

  • breaking change: extract Prism code highlighting to @lexical/code-prism, with @lexical/code-core split out to avoid circular imports LEXICAL-#8198
    • we're temporarily switching to @lexical/code-core in conjunction with @lexical/code-shiki to reduce the bundle size and not ship Prism with the default @lexical/code dependency carried by Shiki. Lexical will remove Prism from @lexical/code later.

lexical-link

  • enhance: merge adjacent LinkNodes with identical attributes LEXICAL-#8236
    • actually nice enhancement

0.42.0 divergences

lexical-link

  • fix: enable autolink matching when it becomes unlinked LEXICAL-#8165
    • we do not use Lexical's stock AutoLinkExtension; sneditor has a custom autolink pipeline that reacts to AutoLinkNode and can transform it into mentions, embeds, media, or links, so this needs a separate check in our implementation

lexical-rich-text

  • fix: use writable node in HeadingNode.setTag LEXICAL-#8235
    • we replace HeadingNode with SNHeadingNode and do not currently call setTag directly

0.43.0 fixes

that pertain to our editor

lexical

  • fix: exclude Android WebView from IS_SAFARI browser detection LEXICAL-#8267
    • we gate our Apple-specific Backspace/Enter patches in lib/lexical/exts/apple.js on (IS_IOS || IS_SAFARI || IS_APPLE_WEBKIT). The patch was always meant to be Apple-only, so excluding Android WebViews (which spoof Safari in their UA) correctly narrows our targeting.
  • fix: merge TextNodes when __state contains a different number of default values LEXICAL-#8273
    • automatic; we don't use NodeState yet but the bug could surface once we do
  • fix: replace $insertNodes with $insertNodeIntoLeaf for consistent DecoratorNode behavior inside MarkNode LEXICAL-#8266
    • automatic; relevant because we have many decorator nodes (Media, Embed, Math, Gallery, mentions, footnotes)

lexical-code

  • fix: remove usage of skipTransforms in CodeHighlighterPrism and CodeHighlighterShiki LEXICAL-#8254
    • relevant since we use Shiki; the new CodeShikiExtension we adopted in 0.44 is built on top of this cleanup

lexical-link

  • fix: preserve cursor position when merging adjacent identical links LEXICAL-#8309
    • automatic; pairs with the 0.42 link merge enhancement (LEXICAL-#8236)

0.43.0 features, enhancements and changes

lexical

  • breaking change: use asynchronous parent editor delegation when needed in nested editors LEXICAL-#8308
    • we have no nested editors (MathNode is a DecoratorNode that renders KaTeX directly, not a nested Lexical editor), so no impact

lexical-react

  • feat: add @lexical/react/useExtensionSignalValue hook for reading extension signals from React LEXICAL-#8286
    • newly relevant: our migrated CodeShikiSNExtension now exposes output.tokenizer and output.disabled as signals via CodeShikiExtension. If we ever want to drive UI off them (e.g. a syntax-highlighting toggle), this is the hook we'd use

0.44.0 fixes

that pertain to our editor

lexical-code

  • breaking change: move code-block escape logic from CodeNode.insertNewAfter into a KEY_ENTER_COMMAND listener registered by CodeExtension; fixes paste-with-blank-lines incorrectly escaping the code block LEXICAL-#8360
    • we adopted the new CodeShikiExtension (which declares CodeExtension as a dependency), so the new Enter listener is auto-wired. Our ApplePatchExtension KEY_ENTER_COMMAND listener returns false unless the top block is $isUnwritable, so it does not race the new Lexical listener

lexical-list

  • fix: ensure ListItemNode always has a ListNode parent (orphans are merged or floated to root) LEXICAL-#8382
  • fix: toggle checklist items on mobile tap (iOS Safari / Android Chrome) via pointerup LEXICAL-#8390
  • fix: merge nested lists into parent <ul> during HTML export, eliminating duplicate ordered-list numbering LEXICAL-#8313
    • automatic improvement to $generateHtmlFromNodes, which we use in lib/lexical/server/html.js for SSR

lexical-html

  • feat: inline CSS from <style> tags during HTML import (fixes paste from Excel, Outlook, etc.) LEXICAL-#8326
    • automatic improvement to clipboard paste handling

lexical

  • fix: fall back to non-shifted key matching for Option+number shortcuts on macOS LEXICAL-#8361
    • relevant to our ShortcutsPlugin keyboard handling, especially on Mac layouts where Option+number produces alternate glyphs
  • fix: workaround for synchronous Firefox focus edge case where deferred callbacks were silently discarded LEXICAL-#8356
    • automatic

lexical-react

  • fix: prevent error when editorRef is null in LexicalEditorRefPlugin LEXICAL-#8329
    • we use EditorRefPlugin in both components/editor/editor.js and components/editor/reader.js; previously a transient null ref could throw

0.44.0 features, enhancements and changes

lexical-code-shiki

  • breaking change / feat: add CodeShikiExtension with signal-based { disabled, tokenizer } config; legacy CodeHighlighterShikiExtension kept as a deprecated shim LEXICAL-#8346
    • we adopted it, CodeShikiSNExtension now depends on configExtension(CodeShikiExtension, { tokenizer }) and hot-swaps output.tokenizer.value on theme change, replacing the old cleanup-and-re-register dance in lib/lexical/exts/shiki.js.

lexical-code

  • chore: upgrade shiki to ^4.0.2 LEXICAL-#8330
    • automatic; we get whatever Shiki improvements ship with this bump

lexical

  • breaking change: deprecate @lexical/offset OffsetView; export $createChildrenArray from lexical LEXICAL-#8350
    • n/a; we don't import @lexical/offset
  • feat: COMMAND_PRIORITY_BEFORE_* priorities for last-registered-called-first ordering LEXICAL-#8375
    • opt-in; could simplify the few places where we currently rely on registration order to win
  • chore: stabilize @lexical/extension and NodeState APIs (no longer @experimental) LEXICAL-#8354
    • we promoted @lexical/extension to a direct dependency as part of this bump
  • refactor: replace runtime cssText / setAttribute('style', …) writes with property-based style updates (CSP-safe) LEXICAL-#8372
    • automatic; relevant if we ever tighten our CSP

lexical-link

  • feat: allow custom punctuation for AutoLink boundaries LEXICAL-#8378
    • opt-in and not needed today; our custom autolink pipeline runs off AutoLinkNode creation rather than the upstream boundary regex

lexical-clipboard

  • feat: $handleRichTextDrop and $handlePlainTextDrop shared drop handlers wired by default in registerRichText / registerPlainText LEXICAL-#8373
    • our MarkdownTextExtension registers DROP/DRAGSTART/DRAGOVER at COMMAND_PRIORITY_NORMAL with return true, and FileUploadPlugin at COMMAND_PRIORITY_HIGH, both preempting Lexical's new defaults at COMMAND_PRIORITY_EDITOR. No action needed but worth a manual QA pass on file drag-and-drop

0.44.0 divergences

lexical-react

  • fix: LexicalExtensionEditorComposer no longer disposes the editor on unmount; lifetime is the caller's responsibility LEXICAL-#8377
    • we use LexicalExtensionComposer (not LexicalExtensionEditorComposer), and useHeadlessBridge already disposes manually via bridge.dispose(), so this fix doesn't apply to us

0.45.0 fixes

that pertain to our editor

lexical

  • fix: handle triple-click overselection in $setBlocksType LEXICAL-#8517
    • relevant; commands/formatting/blocks.js calls $setBlocksType for paragraph/heading/quote/code conversions, so a triple-click selection that overshoots into the next block no longer corrupts the conversion
  • fix: workarounds for buggy browser behavior around macOS text replacements LEXICAL-#8417
    • applies only to Chromium based browsers on macOS, no regressions
  • fix: handle iOS 10-key Korean IME deleteContentBackward with targetRange LEXICAL-#8475
    • automatic mobile IME fix
  • fix: keep caret above the on-screen keyboard on Android after Enter in lists LEXICAL-#8486
    • automatic mobile fix
  • fix: respect CSS display style in isBlockDomNode / isInlineDomNode LEXICAL-#8428
    • automatic; improves block/inline detection on paste/HTML import

lexical-rich-text

  • fix: insert a paragraph on Enter for a block DecoratorNode NodeSelection LEXICAL-#8526
    • we do this ourselves, see divergences below

lexical-code-core

  • fix: detect nested <br> elements in pasted code LEXICAL-#8487
    • cleans up multi-line code pasted into our Shiki code blocks

lexical-table

  • fix: attach the window pointerdown handler when the root element is set after register LEXICAL-#8492
    • we register TableExtension and our root element is set by the composer, so this ordering fix keeps table pointer selection working
  • fix: prevent crash when moving selection with arrow key outside of a nested table LEXICAL-#8502

0.45.0 features, enhancements and changes

lexical-extension / lexical-rich-text / lexical-plain-text

  • breaking change: registerRichText / registerPlainText now register a transform that removes empty inline elements; also exported as NormalizeInlineElementsExtension LEXICAL-#8497
    • we adapted to this (see "other SN fixes" at the top): the footnote backref is now a simple DecoratorNode and autolink nodes are created with a TextNode child so they aren't stripped as empty inline wrappers

lexical / lexical-rich-text

  • breaking change: Backspace at the start of a non-empty block now preserves the current block's type instead of folding it into the previous block; new $mergeBlockBackward helper exposed on RangeSelection LEXICAL-#8493
    • the title of this change is confusing. this fixes this issue:
      • "When a HeadingNode (or any non-empty block ElementNode) sits directly after an empty ParagraphNode, pressing Backspace at the heading's start merges the heading's children into the empty paragraph and removes the heading."
      • so instead of losing the heading we just replace the paragraph with the heading (or any other block) on backspace.

lexical

  • breaking change: replace / insertBefore / insertAfter (and other removeFromParent callers) now adjust the selection to follow the moved node LEXICAL-#8501
    • we re-set selection ourselves in the affected flows (autolink.js, commands/links.js AutoLink unwrap loop, gallery.js, decorator-click-zones.js, heading.jsx), so impact is limited
  • breaking change: $getReconciledDirection now walks through shadow roots when resolving dir LEXICAL-#8479
    • we don't rely on dir inheritance.
  • breaking change: generalized DOMSlot and a new DOMRender override surface; markSelection rewritten on top LEXICAL-#8519
    • our custom nodes use the legacy createDOM/updateDOM/exportDOM/importDOM (all still supported) and none override getDOMSlot
  • feat: ElementNode now uniformly imports/exports the data-lexical-indent attribute LEXICAL-#8536
    • additive; only touches copy/paste HTML
  • feat: detect infinite recursion in update listeners and transforms (throws instead of hanging) LEXICAL-#8542
    • automatic safety net; relevant and welcomed because we register several node transforms

lexical-list

  • feat: $setFormatFromDOM; ListItemNode.importDOM now imports text-align / dir alignment from pasted DOM LEXICAL-#8460
    • relevant: more alignment will now be imported on pasted list items
    • we implemented the NoAlignmentExtension that strips alignment from ListNode / ListItemNode, so this reinforces (rather than breaks) our "no alignment" stance. See dir caveat in divergences

lexical-history

  • feat: canUndo / canRedo ReadonlySignals on HistoryExtension output LEXICAL-#8465
    • opt-in; could drive enabled/disabled state for toolbar undo/redo buttons without polling historyState
  • feat: maxDepth option to bound the undo stack (FIFO eviction) LEXICAL-#8537
    • opt-in; useful for capping memory on long compose sessions

lexical-react

  • feat: optional async onClose for LexicalTypeaheadMenuPlugin LEXICAL-#8489
    • opt-in; we use LexicalTypeaheadMenuPlugin for mentions (components/editor/plugins/mentions.js), so this is available if a close handler ever needs to await work

lexical-html / lexical-clipboard

  • feat: DOMImportExtension (experimental), a composable rule-based replacement for importDOM, plus ClipboardDOMImportExtension that routes text/html pastes through it LEXICAL-#8528
    • opt-in and future-facing; legacy importDOM still works. Could eventually replace our paste-handling overrides with explicit per-source rules
  • feat: disabledForEditor / disabledForSession predicates on domOverride LEXICAL-#8575
    • opt-in; only relevant if we adopt the new DOMRender override surface

lexical-markdown

  • feat: $convertSelectionToMarkdownString serializes only the current selection LEXICAL-#8395
    • we already have this feature though our MDAST pipeline and transformer bridge.

0.45.0 divergences

lexical-rich-text

  • fix: insert a paragraph on Enter for a block DecoratorNode NodeSelection LEXICAL-#8526
    • our decorators are usually wrapped in paragraphs, so this doesn't apply. anyway we already do this ourselves: useDecoratorNodeSelection (components/editor/hooks/use-decorator-selection.js) registers a KEY_ENTER_COMMAND handler at COMMAND_PRIORITY_EDITOR that inserts a paragraph after the block, and ApplePatchExtension covers the $isUnwritable case.

lexical / lexical-rich-text / lexical-list

  • fix: import the dir attribute in importDOM LEXICAL-#8412
    • our NoAlignmentExtension strips text-align (element format) but not dir. Pasted RTL content could now carry a dir onto paragraphs/headings/list items.
    • we probably don't need to take action here, RTL content is not supported but it shouldn't be forbidden either (?)

Checklist

Are your changes backward compatible? Please answer below:

Yes, breaking changes have been handled.

On a scale of 1-10 how well and how have you QA'd this change and any features it might affect? Please answer below:

7, breaking changes have been manually verified and handled, current behavior is preserved if not improved.

Did you use AI for this? If so, how much did it assist you?

Yes, list of breaking changes

@socket-security

socket-security Bot commented May 14, 2026

Copy link
Copy Markdown

@Soxasora Soxasora marked this pull request as ready for review May 14, 2026 14:30
@Soxasora Soxasora changed the title chore: bump Lexical to 0.44.0, handle breaking changes chore: bump Lexical to 0.44.0 May 14, 2026
@sir-opti

sir-opti commented May 15, 2026

Copy link
Copy Markdown
Contributor

On Android I found one minor thing: when I paste centered text from a website in compose, it shows as centered. If then I switch to write and back, it is no longer centered. The same happens after posting straight.

I think that this is a sideeffect of

feat: inline CSS from <style> tags during HTML import (fixes paste from Excel, Outlook, etc.) LEXICAL-#8326
automatic improvement to clipboard paste handling


  1. Tested on both android and desktop chrome, no findings for:
    1. 3x CR to exit codeblocks
    2. If list shenanigans still work mixed ol/ul
    3. footnotes
    4. Pasting images
  2. Tested in MacOS, also no findings:
    1. option+number and that works, as I get ¡™£¢∞§¶•

@Soxasora

Copy link
Copy Markdown
Member Author

when I paste centered text from a website in compose, it shows as centered. If then I switch to write and back, it is no longer centered.

Hmm, we disabled extra alignments almost everywhere, so an alignment just gets ignored by our MDAST pipeline.
But as you noticed, extra formatting can still leak via copy/paste!

I'll look at this in the coming days, I'm waiting for Lexical 0.45.0 to drop as it'll fix the Shiki bundle bloat.

Thank you so much!

@Soxasora Soxasora marked this pull request as draft May 28, 2026 14:28
@Soxasora Soxasora changed the title chore: bump Lexical to 0.44.0 chore: bump Lexical to 0.45.0 May 29, 2026
Soxasora added 5 commits May 30, 2026 00:31
behavior.

`NormalizeInlineElementsExtension` is active by
default in Lexical 0.45.0
This extension automatically removes empty inline
elements from the editor state.

- fix: append href text to empty autolink nodes on paste
- refactor: promote backref node to DecoratorNode
…tors

if `clipboardData` contains text/plain AND text/html,
don't let the upload's PASTE_COMMAND handler only paste
the image. instead bail out and prefer text.
@Soxasora Soxasora marked this pull request as ready for review June 2, 2026 13:51
@Soxasora

Soxasora commented Jun 2, 2026

Copy link
Copy Markdown
Member Author

It's a very big jump in terms of changes, and every change has been subjected to careful QA. Other than ours and Lexical's fixes, I added a list of user-facing changes that can be used in the release notes.

Every Lexical change is accompanied by reasoning, explanations and comparisons to our own editor. It's a really long list so it's inside of a collapsed section.

@Soxasora Soxasora requested a review from huumn June 2, 2026 13:55
@sir-opti

sir-opti commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

1. Minor: Check lists align different than other lists

  • Found on both Android (Vanadium) and macOS (Chrome)
  • Alignment is off on both compose mode and as Item after posting
* [ ] check

***

* bullet

***

1. numbered
Screenshot 2026-06-02 at 12 27 51

2. Unsure: nested tables don't seem to work?

From clipboard:

Steps to repro:

  1. Source nested table: https://jsfiddle.net/uzhreL5m/ (simple nested table inside a single cell table)
  2. Copy the rendered html
  3. Paste in write or compose mode
  4. Escapes the markdown

From write:

|  abc | def | 
| :--: | :-- | 
| | ghi | jkl | 
  | :--: | --: | 
  | 123 | 456 |  | 789 |

gets turned into

|  abc | def |     |     |
| :--: | :-- | --- | --- |
|      | ghi | jkl |     |
| :--: | --: |     |     |
|  123 | 456 |     | 789 |

3. (edit) On Android/Vanadium header backspace at block start removes the header tag

paragraph


## header

when backspaced in compose, removes header markings ##


Enhancement/fix tests

test macOS Chrome Android Vanadium
pressing backspace at block start (3)
code blocks now support outdenting N/A
adjacent links get automatically merged
improved triple-click
checklist items toggles on mobile N/A
rich text editor screenshot
pasting code with multiple blank lines
code blocks no longer flash pink 1 1
caret kept above keyboard Android N/A
nested tables selection (2) (2)

Footnotes

  1. unable to reproduce 2

@Soxasora

Soxasora commented Jun 3, 2026

Copy link
Copy Markdown
Member Author
  1. Unsure: nested tables don't seem to work?

I forgot to remove it from the changelog! Nested tables are by default disabled, and I never enabled them because we don't support tables in compose to begin with. They're tricky to work with, and require lots of CSS work to even make them "work".

I'll revisit nested tables when we'll add support for tables in compose. Sorry! I'll edit the changelog now.

  1. Minor: Check lists align different than other lists

That's a day one bug, sadly check lists are kind of a CSS hack (to avoid using the standard browser checkboxes and handle them via Lexical) so I have to check this separately, maybe in the enhancement pack 2?

  1. On Android/Vanadium header backspace at block start removes the header tag

I can't reproduce this but I'll try with Vanadium if I can, are you sure that you were on the local instance on the right branch? I forget all the time lol.

Oh also the previous paragraph must be empty otherwise the heading becomes part of the paragraph as expected.


code blocks no longer flash pink ... unable to reproduce

I also need to remove this from the changelog. The pink flash doesn't happen in prod because Shiki is already fully loaded (9 MB of syntax highlighting), but now that Shiki's dependencies got externalized we benefit from lazy-loading and have to "patch" the pink flash.

You can reproduce this by removing bs-body-color from text.scss on the .sn-code-block class, then fully reload (shift+cmd+R or shift+F5) a page containing a code block.

The fix is naive, we're just enforcing a bs-body-color (white or black depending on the current theme) so that it's consistent to what we have in prod.


Thank you so much for the QA!! I'm going to check the Android block replacement, amazing work!

@sir-opti

sir-opti commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

I can't reproduce this but I'll try with Vanadium if I can

sr_android_issue_2975.mp4

are you sure that you were on the local instance on the right branch?

Yeah, running 119013

@Soxasora

Soxasora commented Jun 3, 2026

Copy link
Copy Markdown
Member Author

Reproduced with AOSP keyboard, didn't expect this to be yet another IME bug but oh well. I'll see if it's a quick thing, otherwise it'd be better to solve this with another PR (this issue being maybe out of scope, introduced by Lexical and matches prod behavior).

@huumn huumn merged commit 3ac8da0 into stackernews:master Jun 9, 2026
6 checks passed
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.

Lexical code highlighting takes half of the main bundle size

3 participants