docs/solutions/logic-errors/2026-05-09-native-list-fragment-paste-must-run-before-generic-fragment-unwrapping.md
Lexical's native list copy/paste tests exposed shapes that Slate's generic fragment insertion path could not model cleanly. The generic path was built to merge inline/text leaves into the target block, so it either unwrapped copied list items into paragraphs or left promoted paragraphs trapped inside a list.
12one and two45 instead of splitting the paragraph around the list.four at offset 2 kept Worldur in
the list instead of promoting it after the list.insertFragment walk decide what to unwrap. It correctly
handles many inline and simple block cases, but it loses the distinction
between wrapper containers, sibling units, and promoted blocks.Handle structural list fragment shapes explicitly before the generic unwrapping
path in packages/slate/src/transforms-text/insert-fragment.ts.
The fix added two replacement branches:
bulleted-list, replace the parent container window with head container,
promoted middle blocks, and tail container.The tests live in packages/slate/test/clipboard-contract.ts and lock the
portable Lexical rows:
Verification:
bun test ./packages/slate/test/clipboard-contract.ts -t "partial list|copied list|paragraph fragments into a list|paragraph fragments at the end"
bun test ./packages/slate/test/clipboard-contract.ts
bun run lint:fix
bun check
The generic fragment path decides insertion by walking leaves and unwrapping blocks at the first and last fragment boundaries. That is too late for native list fragments because the unit of correctness is the structural window:
Doing this with one replace_children operation also keeps history and
selection honest.