docs/solutions/logic-errors/2026-05-04-inline-void-clipboard-export-must-not-assume-block-void-spacer-dom.md
The DOM clipboard exporter reused a block-void assumption for every void
selection. When the selection started inside an inline void, it tried to attach
data-slate-fragment to [data-slate-spacer], but inline void shells do not
render that spacer.
TypeError: null is not an object (evaluating 'attach.setAttribute').data-slate-spacer;
inline voids place hidden zero-width text around visible content instead.editor.dom.clipboard.writeSelection; the crash lived in DOM transport.character field.Keep the Slate fragment model-backed, but make DOM attachment tolerant of inline void DOM:
if (startVoid) {
attach =
contents.querySelector('[data-slate-spacer]') ??
contents.querySelector(
'[data-slate-node="element"], [data-slate-node="text"], [data-slate-string], [data-slate-zero-width]'
) ??
attach
}
let attachElement: Element
if (isDOMElement(attach)) {
attachElement = attach
} else {
const span = contents.ownerDocument.createElement('span')
if (attach) {
span.appendChild(attach)
}
contents.appendChild(span)
attachElement = span
}
attachElement.setAttribute('data-slate-fragment', encoded)
The regression should select the inline void through its empty text child and prove all four behaviors:
data-slate-spacerapplication/x-slate-fragment preserves the inline void nodetext/plain does not leak FEFF or neighboring textThe fragment payload is the editor truth. The DOM node carrying
data-slate-fragment is only transport metadata for browser clipboard HTML.
Block voids and inline voids have different DOM shapes, so the exporter should find a valid cloned element or create a tiny wrapper. It should not require one specific void shell.
data-slate-spacer is incomplete.