`--do "tap 'Save'"` finds whatever currently carries that label -- the text,
or the padded control around it, preferring the clickable one -- resolves its
box from the accessibility tree at the moment of the gesture, and presses its
centre. It is the name the control already has for assistive technology, so
there is nothing extra to keep in step with it.
`tap X Y` still works and is now the exception. A coordinate is a position
measured once by hand: anything that moves the control makes the tap land on
whatever now sits there, and the script then reports a number that was never
measured, which reads exactly like a result rather than like a failure.
ai-app's two benchmark scripts pressed a header button at `tap 723 205` and
that button has now moved; Iris asked on 2026-09-03 that the fix be in the
tool rather than a habit each script remembers.
Two things make the failure loud, which is the whole point. A label that is
not on screen ends the recording with `# error` and a non-zero exit, and
`record` now prints the error lines rather than the head of a trace that can
be thousands of frames long. And the sampling loop waits for the action
thread before exiting -- a script whose last step outlasted the recording
used to have its outcome discarded, including that error.
The label is looked for over three seconds rather than once: the
accessibility connection has no window at all for the first frames after it
is made, and a control revealed by the previous step arrives a frame or two
later. The wait is bounded and the failure is still loud.
Exercised against a running emulator: a tap by label, a tap that finds
nothing, and a failure that lands after the recording's own duration.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ui-trace found adb on PATH, and this machine's ambient PATH puts the SDK's
own platform-tools ahead of ~/.local/bin -- so unless a project's
android-env.sh had been sourced into that same shell, ui-trace drove the
device through the unwrapped adb and failed with "more than one device" as
soon as a second emulator was up. The wrapper installed beside it is the one
that knows which emulator this checkout means, so it is now the first
candidate rather than a coincidence of PATH order.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ui-trace, the adb wrapper and its device recorder were loose files under
~/.local, and each Android checkout carried its own copy of the same
emulator boot sequence. This puts them together, with an install script that
symlinks them back so editing the repo is editing what runs.
Two things are new rather than moved. `emu` is the emulator lifecycle --
name, serial, list, up, down -- keyed on the AVD named after the enclosing
checkout, which is the rule that lets several sessions work here at once;
and `adb` now fills in `-s` from that same rule, because with two emulators
attached a bare `adb shell pm list packages` comes back empty rather than
failing, which reads as the app being uninstalled rather than the question
being ambiguous.
`emu up` refuses when the machine has no room. On 2026-08-30 an emulator
started with 2.8 GB available invoked the OOM killer, and what it took was
not the emulator that had just started: it walked the user slice and killed
pipewire, dbus-broker and another session's emulator first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>