smix-host-coord-resolver 0.2.0

smix-host-coord-resolver — pure pipeline: A11yNode tree + Selector → normalized (nx, ny) tap coordinate (stone). The host-side resolve step decoupled from any specific injector (HID / Apple native event / etc.) — any iOS-automation project can adopt it.
Documentation

smix-host-coord-resolver

Crates.io docs.rs License

Pure pipeline from (A11yNode tree, Selector) to a normalized (nx, ny) tap coordinate. No I/O, no async, no injector awareness — the host-side resolve step decoupled from whatever HID / Apple-native / runner-side mechanism the caller wants to drive afterward.

This crate exists because the "fetch tree → resolve selector → compute centroid → normalize into app frame" half of a typical iOS automation tap call is generic across smix, maestro, Appium, and anyone building their own automation framework. Extracting it gives you a stone-sized dependency without dragging in a specific injector or runner-client.

Quickstart

use smix_host_coord_resolver::{HostResolveError, resolve_to_norm_coord};
use smix_screen::{A11yNode, Rect};
use smix_selector::{Modifiers, Pattern, Selector};

# fn mk(label: &str, bounds: Rect) -> A11yNode {
#     A11yNode {
#         raw_type: "other".into(), role: None, identifier: None,
#         label: Some(label.into()), title: None, placeholder_value: None,
#         value: None, text: None, bounds, enabled: true, selected: false,
#         has_focus: false, visible: true, children: vec![],
#     }
# }
let mut root = mk("root", Rect { x: 0.0, y: 0.0, w: 390.0, h: 844.0 });
root.children.push(mk("Login", Rect { x: 50.0, y: 100.0, w: 200.0, h: 40.0 }));

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

let (nx, ny) = resolve_to_norm_coord(&root, &sel).unwrap();
// Hand (nx, ny) to whatever injector you have:
// my_runner.tap_at_norm_coord(nx, ny).await?;
# assert!((nx - 150.0 / 390.0).abs() < 1e-9);
# assert!((ny - 120.0 / 844.0).abs() < 1e-9);

Error variants

HostResolveError is fine-grained so callers can map each variant to their own failure type:

Variant Meaning
NotFound selector matched nothing in the tree (after visibility filter)
EmptyMatchedFrame matched node has zero-or-negative w or h
UnknownAppFrame tree.bounds has zero-or-negative w or h
CentroidOutOfFrame { nx, ny } computed normalized coord is outside (0, 1)

When to reach for this

Use case Pick
Building an iOS automation library and want host-side resolve as a stone smix-host-coord-resolver
Already use smix-selector-resolver and want the centroid step too smix-host-coord-resolver
Need a full driver with tap / fill / scroll semantics + retry use smix-driver (consumes this stone)
Doing physical-coord tap (not normalized) normalize by your viewport first, then use this

Scope

  • ✅ Pure function fn resolve_to_norm_coord(tree, selector) -> Result<(f64, f64), _>
  • ✅ Reuses smix-selector-resolver for the tree walk
  • ✅ Centroid + normalize math in (0, 1) app-frame coordinates
  • ❌ No async, no I/O, no injector
  • ❌ No retry / implicit wait — that's smix-driver's job

License

Dual-licensed under either:

at your option.