Separate elements with IDs into their own matching pool
Elements with IDs can only match by their ID - they are semantically
distinct entities. Previously, if an element with an ID failed to match
in the ID matching phase, it could still incorrectly match in the
heuristics or tagName phases.
This change separates elements with IDs into a dedicated `idElements`
pool at build time, so they:
1. Skip the expensive `isEqualNode` check entirely
2. Only participate in ID matching
3. Get removed if no ID match is found
The idSet matching phase (for elements matched by descendant IDs) now
correctly iterates over `candidateElements` since elements with idSets
may not have IDs themselves.
This is both a correctness fix and a performance optimization.