v0.0.5 — iOS 26 tap reliability
Warning
Experimental. Ennio is at an early experimental stage. APIs, package
names, internals, and behavior may change without notice between releases.
iOS only. Expect rough edges; do not rely on it for production-critical test
suites yet. The 0.0.x line is a public preview, not a stability commitment.
Third preview. Headline: iOS 26 tap reliability. Three platform-level
races that previously dropped taps during sheet presents, keyboard-up
states, and RNS stack pushes are now closed end-to-end. Example suite
40/40. Habits sign-up-and-delete passes from cold launch to backend
deletion.
What's new
Tap reliability fixes
| Issue | Fix | Where |
|---|---|---|
| 50 ms held tap claimed by iOS 26 sheet's pan recogniser → tap routed to sheet drag instead of the inner UITextField | hidTap durations ≤ 50 ms clamp to instant down/up | writer.ts |
| Missed focus tap silently dropped subsequent HID keystrokes | typeText routes through pasteFromClipboard when bound to a testID — landing is keyed by testID, not the focused responder |
writer.ts |
RNS first-touch race: first tap on a freshly mounted screen swallowed by RNSScreenStackView |
assertVisible / assertNotVisible / inputText auto re-fire the last tap once when the expected post-tap state never arrives |
maestro-runner.ts |
Point taps doubled the sheet's surface offset (tapAt was screen-relative but the runner added the surface origin again) |
revert surfaceOffset from tapAt — point coordinates stay screen-relative |
writer.ts |
| Daemon vs. CLI parity: HID gRPC sometimes rejected fractional coordinates the CLI rounded | round float x / y to ints in the persistent HID daemon |
hid-daemon.py |
findLabelMatch picked the outer aggregator Pressable whose AX label happened to contain "Email" (RN auto-aggregates child Text into a parent's accessibilityLabel) |
rank candidates by label specificity — UITextField.placeholder exact > accessibilityLabel exact > CONTAINS — so a text tap on "Email" lands on the leaf RCTUITextField |
EnnioRuntimeHelper.mm |
viewIsHittableAtCenter only walked from hit-test result up toward the target; missed the RN shape where hit-test surfaces the wrapper and the target is one hop down |
bidirectional walk (target ancestor AND descendant accepted) | EnnioRuntimeHelper.mm |
getViewWindowFrameByLabel was returning screen-converted coords; sheets live in the same window so the second convertRect:toWindow:nil was a regression |
drop the screen conversion — return window-relative coords | EnnioRuntimeHelper.mm |
Podspec
React-FabricComponents was split out of React-Fabric in RN 0.78+; it owns
the TextInput shadow-tree headers. The podspec now adds the public/private
search paths and an explicit dependency so
buildReactNativeFromSource: true builds resolve them.
Cross-version verified
- Example suite, iOS 26: 40 / 40 PASS.
- Habits app
signup-delete.yaml, iOS 26: cold launch → sign up via email → today tab → settings tab → delete account → back to marketing screen.
Install
bun add @reactiive/ennio react-native-nitro-modules
bun add -d @reactiive/ennio-expo-pluginSame setup as v0.0.4 — add the plugin to app.json, npx expo prebuild --clean,
npx expo run:ios, write a Maestro YAML.
Limitations
Unchanged from v0.0.4 — iOS only, Bridgeless / Fabric only, Metro must be
running.
Full Changelog: v0.0.4...v0.0.5