Split out of #1971 per review: #1690's scope and STOP conditions exclude selector-resolution and replay changes, but the find-index implementation produced a sound, independently valuable optimization there.
Ready on branch tier3/selector-replay-domain (commits from #1971's review cycle, adversarially reviewed):
- New
resolveSelectorChainDomain in packages/selectors/src/internal/resolve.ts: returns the matched-node set the deciding pass already collected — the winning alternative's when one resolves, else the first alternative with any match (exactly listSelectorChainMatches' selection rule, verified over a 112-combination semantic-parity fuzz vs merge-base including winner-in-later-alternative, empty trees, rectless nodes, requireRect/allowDisambiguation toggles).
resolveRecordedTarget (internal/replay.ts) drops its redundant second full-tree scan on both legs: resolved leg {iterated 2, filter 1→0}; unresolved leg {filter 1→0}.
observeTreeTraversals test helper widened to count the full array surface (flatMap/forEach/reduce/some) — the previous counter was blind to flatMap, hiding 4n uncounted scans per ambiguous candidate.
listSelectorChainMatches keeps its three other callers; published ./ast surface unchanged.
Red-first evidence and scan-count tables are in #1971's REVIEW.md/RESULT.md history.
Known adjacent defect, intentionally out of scope here: resolve-with-policy.ts:66-89 still double-scans for disambiguate/fail-closed rows (#1970) — folding it needs the first-alternative-vs-winner semantic decision documented there.
Split out of #1971 per review: #1690's scope and STOP conditions exclude selector-resolution and replay changes, but the find-index implementation produced a sound, independently valuable optimization there.
Ready on branch
tier3/selector-replay-domain(commits from #1971's review cycle, adversarially reviewed):resolveSelectorChainDomaininpackages/selectors/src/internal/resolve.ts: returns the matched-node set the deciding pass already collected — the winning alternative's when one resolves, else the first alternative with any match (exactlylistSelectorChainMatches' selection rule, verified over a 112-combination semantic-parity fuzz vs merge-base including winner-in-later-alternative, empty trees, rectless nodes, requireRect/allowDisambiguation toggles).resolveRecordedTarget(internal/replay.ts) drops its redundant second full-tree scan on both legs: resolved leg {iterated 2, filter 1→0}; unresolved leg {filter 1→0}.observeTreeTraversalstest helper widened to count the full array surface (flatMap/forEach/reduce/some) — the previous counter was blind to flatMap, hiding 4n uncounted scans per ambiguous candidate.listSelectorChainMatcheskeeps its three other callers; published ./ast surface unchanged.Red-first evidence and scan-count tables are in #1971's REVIEW.md/RESULT.md history.
Known adjacent defect, intentionally out of scope here:
resolve-with-policy.ts:66-89still double-scans for disambiguate/fail-closed rows (#1970) — folding it needs the first-alternative-vs-winner semantic decision documented there.