docs/solutions/logic-errors/2026-04-26-slate-v2-selectable-voids-should-be-atomic-navigation-points.md
In /examples/embeds, placing the caret at the end of the first paragraph and pressing ArrowRight skipped the embed void block and landed in the following paragraph.
That is wrong for a selectable block void. The void is not text content to traverse by default, but it is still a selectable document position.
The traversal layer filtered out every path inside a void when voids: false.
That made sense for hiding void internals, but it also removed the void's atomic selection point. Editor.after, Editor.before, and editor.move() then saw no position between the previous paragraph and the following paragraph.
Keep the contract in core Editor.positions:
voids: true enters the actual childrenDo not special-case the embeds example or React keydown. The public traversal contract is the owner.
Add a core query-contract test before browser work:
Editor.positions(editor, { at: [] }) returns one point for a selectable voidEditor.positions(editor, { at: [], voids: true }) returns the actual internal child offsetsEditor.after, Editor.before, and editor.move() visit the void before the following blockThen add browser proof for /examples/embeds:
[0,0]@177[1,0]@0[2,0]@0The same rule applies to inline voids during unit: 'character' movement.
/examples/mentions exposed the sharper failure: ArrowRight from before a
mention skipped the mention and consumed the first character after it, while
ArrowLeft from after the mention consumed the previous character before it.
The fix stayed in core traversal:
The regression test should cover both sides of at least two inline voids:
[1,0]@end ArrowRight -> [1,1,0]@0[1,1,0]@0 ArrowRight -> [1,2]@0[1,2]@0 ArrowLeft -> [1,1,0]@0[1,1,0]@0 ArrowLeft -> [1,0]@end[1,2]@end ArrowRight -> [1,3,0]@0[1,3,0]@0 ArrowRight -> [1,4]@0[1,4]@0 ArrowLeft -> [1,3,0]@0[1,3,0]@0 ArrowLeft -> [1,2]@endDo not collapse the two stops back into one. Skipping from the preceding text straight to the following text makes the mention impossible to select with plain arrow-key navigation.
Atomic selection is not enough if model-owned Backspace and Enter still fall through to generic text transforms.
Two browser rows locked the block-void command contract in /examples/images:
Keep this in the model-owned mutation path, not in the image example. The React command layer owns the browser intent because it sees the selected void and can repair the model selection after the structural operation.
When keyboard navigation skips a void, test Editor.positions first. If core traversal does not expose the void atomically, React keydown fixes are papering over the wrong layer.
For inline void regressions, also test Editor.after and Editor.before with
unit: 'character'; default unit: 'offset' can pass while human ArrowLeft and
ArrowRight still overshoot.
For destructive block-void regressions, replay both user paths: Backspace from the adjacent empty block and Enter from a clicked selected void. The first protects deletion ordering; the second protects click-selected and arrow-selected void parity.