Skip to content

[Breaking Change][lexical][lexical-utils][lexical-selection][lexical-website] Bug Fix: LexicalNode.replace no longer re-homes named slots - #8938

Merged
etrepum merged 1 commit into
facebook:mainfrom
etrepum:claude/slots-semantics-breaking-change-hoct0q
Aug 7, 2026
Merged

[Breaking Change][lexical][lexical-utils][lexical-selection][lexical-website] Bug Fix: LexicalNode.replace no longer re-homes named slots#8938
etrepum merged 1 commit into
facebook:mainfrom
etrepum:claude/slots-semantics-breaking-change-hoct0q

Conversation

@etrepum

@etrepum etrepum commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Description

Previously, LexicalNode.replace moved the replaced node's named slots onto the replacement node. Slots are tightly bound to their host node and are not necessarily portable across node types, so this re-homing is removed: the replaced node keeps its slot map. If it is reattached in the same update (the $wrapNodeInElement pattern) its slots come with it, and if it stays detached its slot subtrees are garbage-collected with it via the existing dual-channel slot GC. Anything built on replace(), such as $setBlocksType, now produces a slot-less replacement block.

This also fixes the bug where $wrapNodeInElement stripped the wrapped node's slots, because replace() re-homed them onto the wrapper.

  • Remove the re-home block (and its invariant) from LexicalNode.replace
  • Repoint the LexicalSlot tests that pinned the re-home at the new semantics, and add a wrap-pattern regression test
  • Add a $wrapNodeInElement slot-preservation test in @lexical/utils
  • Pin the new $setBlocksType behavior (converted hosts drop slots)
  • Document the node-bound slot lifecycle in the named-slots concepts doc

Closes #8936

Test plan

Before

host.replace(other) moved every named slot onto other; $wrapNodeInElement(host, ...) left the wrapped host slot-less. The tests replace(includeChildren) carries slots onto the replacement and replace carries slots onto a decorator host without includeChildren pinned that behavior.

After

Slots stay on the replaced node. Covered by the updated LexicalSlot tests (no transfer + GC of the detached host's slots, wrap pattern keeps slots), the new LexicalWrapNodeInElementSlots regression test, and the new $setBlocksType slot-host test. Full unit suite, browser tests for core/utils/selection, tsc, eslint, and prettier all pass.

… change: LexicalNode.replace no longer re-homes named slots

## Description

Previously, `LexicalNode.replace` moved the replaced node's named slots
onto the replacement node. Slots are tightly bound to their host node
and are not necessarily portable across node types, so this re-homing
is removed: the replaced node keeps its slot map. If it is reattached
in the same update (the `$wrapNodeInElement` pattern) its slots come
with it, and if it stays detached its slot subtrees are
garbage-collected with it via the existing dual-channel slot GC.
Anything built on `replace()`, such as `$setBlocksType`, now produces a
slot-less replacement block.

This also fixes the bug where `$wrapNodeInElement` stripped the wrapped
node's slots, because `replace()` re-homed them onto the wrapper.

- Remove the re-home block (and its invariant) from `LexicalNode.replace`
- Repoint the LexicalSlot tests that pinned the re-home at the new
  semantics, and add a wrap-pattern regression test
- Add a `$wrapNodeInElement` slot-preservation test in `@lexical/utils`
- Pin the new `$setBlocksType` behavior (converted hosts drop slots)
- Document the node-bound slot lifecycle in the named-slots concepts doc

Closes facebook#8936

## Test plan

### Before

`host.replace(other)` moved every named slot onto `other`;
`$wrapNodeInElement(host, ...)` left the wrapped host slot-less. The
tests `replace(includeChildren) carries slots onto the replacement` and
`replace carries slots onto a decorator host without includeChildren`
pinned that behavior.

### After

Slots stay on the replaced node. Covered by the updated LexicalSlot
tests (no transfer + GC of the detached host's slots, wrap pattern
keeps slots), the new `LexicalWrapNodeInElementSlots` regression test,
and the new `$setBlocksType` slot-host test. Full unit suite, browser
tests for core/utils/selection, tsc, eslint, and prettier all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview Aug 6, 2026 12:57am
lexical-playground Ready Ready Preview Aug 6, 2026 12:57am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 6, 2026
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Aug 6, 2026
@etrepum
etrepum marked this pull request as ready for review August 6, 2026 06:06
@etrepum
etrepum added this pull request to the merge queue Aug 7, 2026
Merged via the queue into facebook:main with commit d319fce Aug 7, 2026
50 checks passed
@etrepum etrepum mentioned this pull request Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Slots are removed when $wrapNodeInElement is called

3 participants