Controls throughout the app carry `data-tour-id` attributes (nav items, pipeline card sections, estimate tabs, send-modal fields, lead-distribution controls, Gantt bars, settings tabs, the 3D builder's tools). The resolver never uses querySelector — it collects every match and picks the one that is rendered, not clipped by an ancestor, and not covered by another surface.
Also called: data-tour-id · tour anchors · highlight the right element
- 1isRendered: non-zero rect kills display:none ancestors; checkVisibility kills visibility:hidden even for fixed-position elements where offsetParent is null
- 2Ancestor clip test intersects the candidate against every overflow-clipping ancestor — scrollable ancestors do NOT reject, because scrolling can reveal them
- 3Occlusion probe samples the centre plus four inset corners with document.elementsFromPoint, skipping the Academy's own layers
- 4Waiting is DOM-driven: a MutationObserver on body plus a rAF-throttled re-resolve plus a 250ms safety interval, bounded by an 8s default timeout
Early walkthroughs would tell someone to click the highlighted control and then draw the highlight over an empty patch of screen. Usually the same button existed twice — one copy laid out for a desktop, one for a phone — and the walkthrough had found the copy that wasn't on screen. A collapsed sidebar was worse: it looks shut, but the buttons inside it still measure full size, so a step would happily point at a menu item nobody could see. Steps now check every match and pick the one that is genuinely visible and touchable, across roughly two hundred named controls on fifty-seven screens.
- Tours pointing at a hidden mobile twin of a desktop button
- Tours pointing at a nav button inside a collapsed sidebar
See it on your own jobs
Twenty minutes, your numbers, no slide deck. We’ll build one of your real buildings in front of you and send you the estimate link at the end — yours to keep either way.
or keep browsing features →