Composable / Filtered Candidate Locators
anchor_locator resolves a single anchor→target relation and grid_locator
addresses grid cells; neither supports composable refinement of a candidate set —
.within(panel).filter(has_text="Delete").nth(2) — the Selenium-4 / Playwright
chained-and-filtered locator idiom. Today refining means re-querying a backend. This is
a pure post-filter over boxes from any source (template match, OCR, the a11y tree,
Fuse & Order On-Screen Element Boxes).
A Candidates wraps a list of {x, y, width, height, …} boxes; every method
returns a new Candidates so chains are side-effect-free and fully unit-testable.
Pure-stdlib, imports no PySide6.
Headless API
from je_auto_control import from_boxes
target = (from_boxes(boxes) # boxes from any locator
.within((0, 0, 1920, 120)) # only the toolbar
.filter(has_text="Delete") # only delete buttons
.sort_reading() # left-to-right
.nth(1)) # the second one
if target.center():
click(*target.center())
within(region) keeps boxes whose centre is inside the rectangle; filter keeps
boxes matching every supplied criterion (has_text substring, near (x, y,
max_dist) proximity, min_area / max_area, or an arbitrary predicate);
sort_reading orders them; nth / first / last select; resolve()
returns the surviving list and center() the first box’s centre. Chains never mutate
the original set.
Executor command
AC_locate_chain applies a JSON list of ops to a boxes array in order —
{op:"within",region:[…]}, {op:"filter",has_text:…}, {op:"reading"},
{op:"nth",index:…}, {op:"first"}, {op:"last"} — returning
{count, boxes, center}. It is exposed as the MCP tool ac_locate_chain and as a
Script Builder command under Image.