From the 2026-08-06 architecture review (claims verified by independent adversarial passes; checked against ADR 0011/0015 + the Selector Capture Reliability Contract).
Problem
Five native consumers of "resolve a selector against the screen" each hand-assemble their own subset of the capture → match → disambiguate → occlusion → off-screen pipeline. The shared core (packages/selectors) owns parse/match/disambiguation only; everything around it is per-caller:
| caller |
ambiguity policy |
occlusion |
off-screen |
poll budget |
click/press/fill (resolution.ts:267-351) |
disambiguate (disambiguateAmbiguous:true) |
yes |
yes |
none (one capture + one immediate interactiveOnly=false re-capture) |
is non-exists / get attrs (selector-read.ts:316-321,465-470) |
fail closed (requireUnique:true, disambiguateAmbiguous:false) |
none |
none |
none |
exists / find read actions (selector-read.ts:294-314,437-443) |
first match, ambiguity-tolerant |
none |
none |
none |
wait (selector-wait.ts:213-278) |
no disambiguation concept (matchedNodes[0]) |
none |
none |
300ms poll / 10s default |
mutating find (daemon/handlers/find.ts) |
own ranking (preferOnscreenMatches/rankInteractiveMatches/interactiveMatchScore) |
own check |
own scoring |
none |
That's at least 4 distinct ambiguity policies declared as inline literals at call sites — no central policy table exists. Occlusion (isSnapshotNodeInteractionBlocked) is consulted by 2 of 5. Mutating find chains two engines per action: its own ranking, then re-dispatch by @ref through resolution.ts's ref path (find.ts:391-402). And resolveSelectorInteractionTarget is module-private, so resolution semantics are only testable through the full-runtime harness with hand-built snapshot fixtures.
Proposed deepening
One native resolution module whose interface takes the target plus a declared policy (ambiguity mode, occlusion on/off, poll budget) as data. Each caller's current semantics are preserved exactly — the win is that the whole policy matrix becomes visible, diffable, and testable through one interface, and a future policy change is a table edit instead of a 4-file hunt.
The ADR pass rated this a restatement of ADR 0011's own thesis: "each guarantee has exactly one home that all TS paths import."
Scope guards
- The Maestro engine stays fully separate (ADR 0015) — its own matcher, visibility, and poll loops are compatibility-policy-owned and must not be unified into this.
is keeps failing closed on ambiguity; no silent fallbacks (ADR 0011 + Selector Capture Reliability Contract). This refactor changes zero semantics.
- Whether
click/tap/is ever get an implicit lookup budget is a deliberately open product decision (2026-07-31) — out of scope here; this deepening only makes it a one-row policy change if ever taken.
- Direct iOS selector fast-path gating rules (simple one-term selectors, never during pending stabilization) unchanged.
Acceptance criteria
From the 2026-08-06 architecture review (claims verified by independent adversarial passes; checked against ADR 0011/0015 + the Selector Capture Reliability Contract).
Problem
Five native consumers of "resolve a selector against the screen" each hand-assemble their own subset of the capture → match → disambiguate → occlusion → off-screen pipeline. The shared core (
packages/selectors) owns parse/match/disambiguation only; everything around it is per-caller:click/press/fill(resolution.ts:267-351)disambiguateAmbiguous:true)interactiveOnly=falsere-capture)isnon-exists /get attrs(selector-read.ts:316-321,465-470)requireUnique:true, disambiguateAmbiguous:false)exists/findread actions (selector-read.ts:294-314,437-443)wait(selector-wait.ts:213-278)matchedNodes[0])find(daemon/handlers/find.ts)preferOnscreenMatches/rankInteractiveMatches/interactiveMatchScore)That's at least 4 distinct ambiguity policies declared as inline literals at call sites — no central policy table exists. Occlusion (
isSnapshotNodeInteractionBlocked) is consulted by 2 of 5. Mutatingfindchains two engines per action: its own ranking, then re-dispatch by@refthroughresolution.ts's ref path (find.ts:391-402). AndresolveSelectorInteractionTargetis module-private, so resolution semantics are only testable through the full-runtime harness with hand-built snapshot fixtures.Proposed deepening
One native resolution module whose interface takes the target plus a declared policy (ambiguity mode, occlusion on/off, poll budget) as data. Each caller's current semantics are preserved exactly — the win is that the whole policy matrix becomes visible, diffable, and testable through one interface, and a future policy change is a table edit instead of a 4-file hunt.
The ADR pass rated this a restatement of ADR 0011's own thesis: "each guarantee has exactly one home that all TS paths import."
Scope guards
iskeeps failing closed on ambiguity; no silent fallbacks (ADR 0011 + Selector Capture Reliability Contract). This refactor changes zero semantics.click/tap/isever get an implicit lookup budget is a deliberately open product decision (2026-07-31) — out of scope here; this deepening only makes it a one-row policy change if ever taken.Acceptance criteria
findno longer chains two resolution enginesdisambiguateAmbiguous/requireUniqueliterals at call sites outside the policy declaration