docs/solutions/ui-bugs/2026-05-02-slate-iframe-mounted-dom-nodes-need-path-metadata-fallback.md
Clicking mounted Slate content inside the iframe example crashed or logged false DOM coverage warnings because the DOM bridge only trusted hot weak-map state. In the iframe/custom renderer path, the paragraph and text spans were real Slate DOM, but lifecycle timing could leave bridge maps behind the mounted DOM.
dev-browser reproduced the crash on
http://localhost:3100/examples/iframe by clicking the first iframe
paragraph.Cannot resolve a Slate node from DOM node.omitted editable child without a DOM coverage boundary for ordinary paragraph children.data-slate-node="element" and
data-slate-node="text" without data-slate-path or runtime metadata.useSlateNodeRef to populate weak maps left no fallback when
the event path reached a mounted node whose mapping was missing.toDOMNode fallback was incomplete. toDOMPoint can already
know the model path while node-to-path maps are still catching up, so it needs
a direct mounted-DOM-by-path fallback.data-slate-path from foreign DOM would have hidden real
bugs and opened the bridge to unsafe outside-editor nodes.Make the mounted DOM contract explicit and guarded:
data-slate-path and data-slate-runtime-id directly on Slate element
and text DOM, including custom renderElement / renderText attributes;DOMEditor.toSlateNode fall back from a missing weak-map entry to
data-slate-path only when the DOM node is inside the current editor and the
path still exists;DOMEditor.toDOMNode and toDOMPoint recover mounted DOM from the
current Slate path plus matching data-slate-runtime-id;Representative bridge shape:
const fallbackPath =
domEl && DOMEditor.hasDOMNode(editor, domEl)
? parseSlateDOMPath(domEl.getAttribute('data-slate-path'))
: null
if (fallbackPath && Editor.hasPath(editor, fallbackPath)) {
const [fallbackNode] = editor.read((state) => state.nodes.get(fallbackPath))
const key = DOMEditor.findKey(editor, fallbackNode)
keyToElement.set(key, domEl)
ELEMENT_TO_NODE.set(domEl, fallbackNode)
NODE_TO_ELEMENT.set(fallbackNode, domEl)
return fallbackNode
}
For Slate-to-DOM point export, do not force recovery through stale node maps when the caller already resolved the model path:
const el =
DOMEditor.toDOMNode(editor, text, { suppressThrow: true }) ??
findMountedDOMNodeByPath(editor, resolvedPoint.path)
if (!el) {
throw new Error(`Cannot resolve a DOM node from Slate node: ${text}`)
}
Weak maps are the fast path, but mounted Slate DOM needs a stable recovery lane when an iframe, custom renderer, or lifecycle gap leaves that fast path empty. The fallback is safe because it is scoped to the editor root, matched to the runtime id, and validated against the live model path before resolving the node.
The dev-safety checker should catch genuinely omitted editable children, not race normal iframe children. A macrotask is enough for the mounted bridge to catch up while still surfacing real omissions in the same browser turn.
toDOMPoint fallback tests should cover stale node path maps separately from
stale DOM weak maps.