smix-selector-resolver 1.0.27

smix-selector-resolver — DFS pre-order + visibility filter + spatial filter (near/below/above/leftOf/rightOf/inside) + index pick (nth/first/last) + anchor-only base form short-circuit (stone, hot path).
Documentation

smix-selector-resolver

Crates.io docs.rs License

End-to-end resolver for smix-selector queries against an smix-screen::A11yNode tree. Pipeline: DFS pre-order collect → visibility filter → spatial filter (six recursive anchor keys, AND semantics) → index pick (first / last / nth). One per-call compile cache (ResolverContext) amortizes regex compilation across the entire candidate walk.

Quickstart

use smix_screen::{A11yNode, Rect};
use smix_selector::{Modifiers, Pattern, Selector};
use smix_selector_resolver::{resolve_selector, resolve_selector_all};

let tree = A11yNode {
    raw_type: "application".into(), element_type_raw: 1,
    bounds: Rect { x: 0.0, y: 0.0, w: 390.0, h: 844.0 },
    children: vec![
        A11yNode { /* "Login" button */
#            raw_type: "button".into(), element_type_raw: 1, role: None, identifier: None,
#            label: Some("Login".into()),
#            title: None, placeholder_value: None, value: None, text: None,
            bounds: Rect { x: 50.0, y: 100.0, w: 200.0, h: 40.0 },
            enabled: true, selected: false, has_focus: false, visible: true,
            children: vec![],
        },
    ],
#    role: None, identifier: None, label: None, title: None,
#    placeholder_value: None, value: None, text: None,
#    enabled: true, selected: false, has_focus: false, visible: true,
};

let selector = Selector::Text {
    text: Pattern::text("Login"),
    modifiers: Modifiers::default(),
};

let one = resolve_selector(&tree, &selector);
assert!(one.is_some());

let all = resolve_selector_all(&tree, &selector);
assert_eq!(all.len(), 1);

Performance

End-to-end resolve_selector measurements on M-series macOS:

Operation TS V8 baseline Rust release Speedup
text base, 100-node tree, label hit (DFS depth 2) 3,652 ns 1,186 ns 3.1×
text base, 100-node tree, full DFS miss 3,693 ns 1,088 ns 3.4×
text base + spatial below modifier, 4-node tree 607 ns 295 ns 2.1×
id base, 100-node tree, miss 3,234 ns 198 ns 16×
regex text base, 100-node tree, label hit 11,114 ns 10,957 ns parity (regex compile-dominant)
anchor-only + below, 4-node tree, nth=1 532 ns 277 ns 1.9×
text + nth=2, multi-match 5-X tree 468 ns 179 ns 2.6×

Numbers reproducible via cargo bench --bench resolver -p smix-selector-resolver.

The regex case sits at parity (≈11 µs) because regex compile dominates; consumer-level caches (e.g. caching the compiled selector across multiple wait_for poll iterations) push that case to ~11× as well.

Pipeline

  1. Collect: DFS pre-order over the tree, gather every node where the base form matches (text / id / label / role+name / focused / anchor accept-all).
  2. Visibility filter (from smix-screen): drop nodes with zero/negative bounds or fully-outside-viewport — unless no candidate is visible, in which case drop nothing (preserves miss reporting paths).
  3. Spatial filter: for each of near / below / above / leftOf / rightOf / inside (top-level or inside anchor.*), recursively resolve the anchor sub-selector; filter candidates by centroid-axis or geometric containment. AND semantics. Anchor null → overall None.
  4. Index pick: apply first / last / nth; later modifier overrides earlier (nth wins both).

When to reach for this

Use case Pick
Have an A11yNode tree + structured Selector → need the matched node smix-selector-resolver
Just need string match against a single node smix-selector::match_text directly
Need xpath / CSS / jQuery-style selectors use a generic query crate

Scope

  • ✅ Pure function, no async, no I/O
  • ResolverContext regex compile cache (1 compile per resolve_selector call, amortized over all candidates)
  • ✅ Anchor null short-circuits to None
  • ✅ 30 unit tests + 2 perf-gate budgets enforced in CI
  • ❌ No tree fetching (use smix-runner-client)
  • ❌ No tap/coord injection (use smix-host-coord-resolver + an injector)

License

Dual-licensed under either:

at your option.