docs/solutions/logic-errors/2026-05-07-slate-v2-editor-query-reverse-must-reverse-emitted-matches.md
state.nodes.match({ reverse: true }) was using reverse raw traversal, so nested
matches could come back in a different structural order than callers expect.
For the public editor query contract, reverse means the exact inverse of the
forward matched entries.
["0", "0.1", "0.3", "1"].["1", "0", "0.3", "0.1"].["1", "0.3", "0.1", "0"].Node.nodes(..., { reverse: true }) directly through editor-query
filtering changed the order that parents and children reached match,
mode, pass, and universal logic.Node.nodes would have widened the change into structural
iterator behavior even though the bug was in the public editor query result
contract.editor/nodes.ts traversal restored.Traverse the normalized forward range through the existing editor-query filter
pipeline, buffer emitted matches when reverse is requested, and reverse that
emitted list at the end.
const nodeEntries = Node.nodes(editor, {
from,
to,
pass,
})
const matches: NodeEntry<T>[] = []
const shouldBuffer = reverse || universal
for (const [node, path] of nodeEntries) {
const emit = mode === 'lowest' ? hit : ([node, path] as NodeEntry<T>)
if (emit) {
if (shouldBuffer) {
matches.push(emit)
} else {
yield emit
}
}
}
if (shouldBuffer) {
yield* reverse ? matches.reverse() : matches
}
Add a public query-contract test that asserts reverse output is exactly the inverse of the forward output:
const forward = paths()
assert.deepEqual(forward, ['0', '0.1', '0.3', '1'])
assert.deepEqual(paths({ reverse: true }), [...forward].reverse())
The editor query helper owns public match, mode, pass, voids, and
universal behavior. Forward traversal keeps those behaviors on one proven
path. Reversing only the emitted matches changes caller-visible order without
forking the filter engine or changing lower-level raw traversal semantics.
The buffer is also bounded to emitted matches, not every visited node. That is the right cost for exact reverse query output.
state.nodes.match(...) path
before changing raw iterators.forward.reverse() when the contract is about
caller-visible match order.#5080: public reverse editor query order.#5684: related/repro-first traversal pressure, not claimed by this fix.#5028: adjacent traversal API pressure, not claimed by this fix.