EditorControls.selectPhrase (frontend/src/editor/editor.tsx) collects the paragraph's TextNodes and searches each one independently:
for (const tier of MATCH_TIERS) {
for (const node of textNodes) {
const hit = firstMatch(tier, node.getTextContent(), phrase);
…
}
}
Lexical splits a paragraph into several TextNodes wherever formatting changes, so a phrase crossing one of those boundaries — the **bold** word — exists in no single node and is found at no tier. The writer sees the partner "look for" a phrase that is plainly in front of them and fail. The highlight tool reports a miss, and applyEditOp's reveal silently skips the selection.
This is independent of the fold ladder: it was equally true of the previous toLowerCase().indexOf() implementation.
Fix
Match once against the paragraph's full text, then map the resulting source offsets back to (node, offset) for the selection endpoints — the same map-back discipline the folds already use for dropped and merged characters. Roughly:
- Concatenate the node texts, recording each node's start offset.
- Run the tier ladder on the concatenation.
- Walk the offsets back to the node containing
start and the node containing end, with the remainder as the in-node offset.
$createRangeSelection already supports anchor and focus in different nodes, so the selection itself needs no change.
Test with a paragraph built as three text nodes (plain / bold / plain) and a needle spanning all three.
Found while making text matching typography-tolerant (#587). Dissolved for free if #589 happens, since matching would move to the document's plain text and hosts would resolve ranges instead.
EditorControls.selectPhrase(frontend/src/editor/editor.tsx) collects the paragraph'sTextNodes and searches each one independently:Lexical splits a paragraph into several
TextNodes wherever formatting changes, so a phrase crossing one of those boundaries —the **bold** word— exists in no single node and is found at no tier. The writer sees the partner "look for" a phrase that is plainly in front of them and fail. Thehighlighttool reports a miss, andapplyEditOp's reveal silently skips the selection.This is independent of the fold ladder: it was equally true of the previous
toLowerCase().indexOf()implementation.Fix
Match once against the paragraph's full text, then map the resulting source offsets back to (node, offset) for the selection endpoints — the same map-back discipline the folds already use for dropped and merged characters. Roughly:
startand the node containingend, with the remainder as the in-node offset.$createRangeSelectionalready supports anchor and focus in different nodes, so the selection itself needs no change.Test with a paragraph built as three text nodes (plain / bold / plain) and a needle spanning all three.
Found while making text matching typography-tolerant (#587). Dissolved for free if #589 happens, since matching would move to the document's plain text and hosts would resolve ranges instead.