execbro 2.3.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (596) hide show
  1. package/README.md +2 -0
  2. package/build/__tests__/helpers/fake-cdp-server.d.ts +0 -12
  3. package/build/__tests__/helpers/fake-cdp-server.d.ts.map +1 -1
  4. package/build/__tests__/helpers/fake-cdp-server.js +0 -22
  5. package/build/__tests__/helpers/fake-cdp-server.js.map +1 -1
  6. package/build/__tests__/helpers/tap-test-helpers.d.ts +0 -23
  7. package/build/__tests__/helpers/tap-test-helpers.d.ts.map +1 -1
  8. package/build/__tests__/helpers/tap-test-helpers.js +2 -29
  9. package/build/__tests__/helpers/tap-test-helpers.js.map +1 -1
  10. package/build/__tests__/integration/auto-reconnect.test.js +4 -14
  11. package/build/__tests__/integration/auto-reconnect.test.js.map +1 -1
  12. package/build/__tests__/integration/connection-health.test.js +1 -10
  13. package/build/__tests__/integration/connection-health.test.js.map +1 -1
  14. package/build/__tests__/integration/execute-in-app.test.js +1 -11
  15. package/build/__tests__/integration/execute-in-app.test.js.map +1 -1
  16. package/build/__tests__/integration/native-logs.test.js +0 -5
  17. package/build/__tests__/integration/native-logs.test.js.map +1 -1
  18. package/build/__tests__/integration/per-call-timeout.test.js +1 -5
  19. package/build/__tests__/integration/per-call-timeout.test.js.map +1 -1
  20. package/build/__tests__/integration/socket-close-midflight.test.js +2 -12
  21. package/build/__tests__/integration/socket-close-midflight.test.js.map +1 -1
  22. package/build/__tests__/integration/stale-target-probe.test.js +2 -17
  23. package/build/__tests__/integration/stale-target-probe.test.js.map +1 -1
  24. package/build/__tests__/integration/tap-strategies.test.js +2 -36
  25. package/build/__tests__/integration/tap-strategies.test.js.map +1 -1
  26. package/build/__tests__/integration/text-input.test.js +0 -39
  27. package/build/__tests__/integration/text-input.test.js.map +1 -1
  28. package/build/__tests__/integration/tools.test.js +1 -7
  29. package/build/__tests__/integration/tools.test.js.map +1 -1
  30. package/build/__tests__/setup.js +0 -2
  31. package/build/__tests__/setup.js.map +1 -1
  32. package/build/__tests__/unit/androidEmulatorAvds.test.js +0 -10
  33. package/build/__tests__/unit/androidEmulatorAvds.test.js.map +1 -1
  34. package/build/__tests__/unit/androidPinch.test.d.ts +2 -0
  35. package/build/__tests__/unit/androidPinch.test.d.ts.map +1 -0
  36. package/build/__tests__/unit/androidPinch.test.js +119 -0
  37. package/build/__tests__/unit/androidPinch.test.js.map +1 -0
  38. package/build/__tests__/unit/appContext.test.js +0 -2
  39. package/build/__tests__/unit/appContext.test.js.map +1 -1
  40. package/build/__tests__/unit/appRequest.test.js +0 -3
  41. package/build/__tests__/unit/appRequest.test.js.map +1 -1
  42. package/build/__tests__/unit/buildToken.test.js +0 -6
  43. package/build/__tests__/unit/buildToken.test.js.map +1 -1
  44. package/build/__tests__/unit/burst.test.js +0 -1
  45. package/build/__tests__/unit/burst.test.js.map +1 -1
  46. package/build/__tests__/unit/clearLogsNative.test.js +0 -8
  47. package/build/__tests__/unit/clearLogsNative.test.js.map +1 -1
  48. package/build/__tests__/unit/componentFormatNaming.test.js +0 -13
  49. package/build/__tests__/unit/componentFormatNaming.test.js.map +1 -1
  50. package/build/__tests__/unit/componentSource.test.js +0 -4
  51. package/build/__tests__/unit/componentSource.test.js.map +1 -1
  52. package/build/__tests__/unit/componentSourceResolve.test.js +0 -1
  53. package/build/__tests__/unit/componentSourceResolve.test.js.map +1 -1
  54. package/build/__tests__/unit/contextWiring.test.js +0 -3
  55. package/build/__tests__/unit/contextWiring.test.js.map +1 -1
  56. package/build/__tests__/unit/coordinateMiss.test.js +0 -16
  57. package/build/__tests__/unit/coordinateMiss.test.js.map +1 -1
  58. package/build/__tests__/unit/deviceDiscovery.test.js +0 -4
  59. package/build/__tests__/unit/deviceDiscovery.test.js.map +1 -1
  60. package/build/__tests__/unit/deviceResolution.test.js +0 -2
  61. package/build/__tests__/unit/deviceResolution.test.js.map +1 -1
  62. package/build/__tests__/unit/deviceResolver.test.js +0 -1
  63. package/build/__tests__/unit/deviceResolver.test.js.map +1 -1
  64. package/build/__tests__/unit/deviceResolverStaleRetry.test.js +0 -8
  65. package/build/__tests__/unit/deviceResolverStaleRetry.test.js.map +1 -1
  66. package/build/__tests__/unit/emulatorBridge.test.d.ts +2 -0
  67. package/build/__tests__/unit/emulatorBridge.test.d.ts.map +1 -0
  68. package/build/__tests__/unit/emulatorBridge.test.js +76 -0
  69. package/build/__tests__/unit/emulatorBridge.test.js.map +1 -0
  70. package/build/__tests__/unit/emulatorGrpc.test.d.ts +2 -0
  71. package/build/__tests__/unit/emulatorGrpc.test.d.ts.map +1 -0
  72. package/build/__tests__/unit/emulatorGrpc.test.js +33 -0
  73. package/build/__tests__/unit/emulatorGrpc.test.js.map +1 -0
  74. package/build/__tests__/unit/epochRender.test.js +0 -3
  75. package/build/__tests__/unit/epochRender.test.js.map +1 -1
  76. package/build/__tests__/unit/errorCategorization.test.js +0 -18
  77. package/build/__tests__/unit/errorCategorization.test.js.map +1 -1
  78. package/build/__tests__/unit/exec.test.js +0 -1
  79. package/build/__tests__/unit/exec.test.js.map +1 -1
  80. package/build/__tests__/unit/executor.test.js +0 -15
  81. package/build/__tests__/unit/executor.test.js.map +1 -1
  82. package/build/__tests__/unit/findElementTreeReuse.test.js +0 -12
  83. package/build/__tests__/unit/findElementTreeReuse.test.js.map +1 -1
  84. package/build/__tests__/unit/fingerprint.test.js +0 -4
  85. package/build/__tests__/unit/fingerprint.test.js.map +1 -1
  86. package/build/__tests__/unit/focusedInputTools.test.js +0 -4
  87. package/build/__tests__/unit/focusedInputTools.test.js.map +1 -1
  88. package/build/__tests__/unit/getLogsEmptyContract.test.js +0 -30
  89. package/build/__tests__/unit/getLogsEmptyContract.test.js.map +1 -1
  90. package/build/__tests__/unit/getLogsNativeSource.test.js +0 -22
  91. package/build/__tests__/unit/getLogsNativeSource.test.js.map +1 -1
  92. package/build/__tests__/unit/graphqlOperation.test.d.ts +2 -0
  93. package/build/__tests__/unit/graphqlOperation.test.d.ts.map +1 -0
  94. package/build/__tests__/unit/graphqlOperation.test.js +107 -0
  95. package/build/__tests__/unit/graphqlOperation.test.js.map +1 -0
  96. package/build/__tests__/unit/inputTarget.test.js +0 -16
  97. package/build/__tests__/unit/inputTarget.test.js.map +1 -1
  98. package/build/__tests__/unit/inputTextTool.test.js +0 -3
  99. package/build/__tests__/unit/inputTextTool.test.js.map +1 -1
  100. package/build/__tests__/unit/jsLogEvents.test.js +0 -3
  101. package/build/__tests__/unit/jsLogEvents.test.js.map +1 -1
  102. package/build/__tests__/unit/keyboardMetrics.test.js +0 -2
  103. package/build/__tests__/unit/keyboardMetrics.test.js.map +1 -1
  104. package/build/__tests__/unit/keyboardRaise.test.js +0 -2
  105. package/build/__tests__/unit/keyboardRaise.test.js.map +1 -1
  106. package/build/__tests__/unit/licenseGrace.test.js +1 -4
  107. package/build/__tests__/unit/licenseGrace.test.js.map +1 -1
  108. package/build/__tests__/unit/logEventFormat.test.js +0 -27
  109. package/build/__tests__/unit/logEventFormat.test.js.map +1 -1
  110. package/build/__tests__/unit/logEvents.test.js +1 -5
  111. package/build/__tests__/unit/logEvents.test.js.map +1 -1
  112. package/build/__tests__/unit/logGrouping.test.js +0 -2
  113. package/build/__tests__/unit/logGrouping.test.js.map +1 -1
  114. package/build/__tests__/unit/logOwnership.test.js +0 -11
  115. package/build/__tests__/unit/logOwnership.test.js.map +1 -1
  116. package/build/__tests__/unit/logRelevance.test.js +0 -6
  117. package/build/__tests__/unit/logRelevance.test.js.map +1 -1
  118. package/build/__tests__/unit/logSourceAndroid.test.js +0 -4
  119. package/build/__tests__/unit/logSourceAndroid.test.js.map +1 -1
  120. package/build/__tests__/unit/logSourceIos.test.js +0 -5
  121. package/build/__tests__/unit/logSourceIos.test.js.map +1 -1
  122. package/build/__tests__/unit/logs.test.js +0 -1
  123. package/build/__tests__/unit/logs.test.js.map +1 -1
  124. package/build/__tests__/unit/measureComponentTools.test.js +0 -1
  125. package/build/__tests__/unit/measureComponentTools.test.js.map +1 -1
  126. package/build/__tests__/unit/metro.test.js +0 -2
  127. package/build/__tests__/unit/metro.test.js.map +1 -1
  128. package/build/__tests__/unit/nativeInputValue.test.js +0 -4
  129. package/build/__tests__/unit/nativeInputValue.test.js.map +1 -1
  130. package/build/__tests__/unit/nativeLogs.test.js +1 -23
  131. package/build/__tests__/unit/nativeLogs.test.js.map +1 -1
  132. package/build/__tests__/unit/navigateVerify.test.js +0 -6
  133. package/build/__tests__/unit/navigateVerify.test.js.map +1 -1
  134. package/build/__tests__/unit/navigation.test.js +0 -5
  135. package/build/__tests__/unit/navigation.test.js.map +1 -1
  136. package/build/__tests__/unit/networkInterceptor.test.js +0 -2
  137. package/build/__tests__/unit/networkInterceptor.test.js.map +1 -1
  138. package/build/__tests__/unit/overlayGuard.test.d.ts +2 -0
  139. package/build/__tests__/unit/overlayGuard.test.d.ts.map +1 -0
  140. package/build/__tests__/unit/overlayGuard.test.js +123 -0
  141. package/build/__tests__/unit/overlayGuard.test.js.map +1 -0
  142. package/build/__tests__/unit/pendingExecutions.test.js +0 -6
  143. package/build/__tests__/unit/pendingExecutions.test.js.map +1 -1
  144. package/build/__tests__/unit/pinchGeometry.test.d.ts +2 -0
  145. package/build/__tests__/unit/pinchGeometry.test.d.ts.map +1 -0
  146. package/build/__tests__/unit/pinchGeometry.test.js +139 -0
  147. package/build/__tests__/unit/pinchGeometry.test.js.map +1 -0
  148. package/build/__tests__/unit/pinchTool.test.d.ts +2 -0
  149. package/build/__tests__/unit/pinchTool.test.d.ts.map +1 -0
  150. package/build/__tests__/unit/pinchTool.test.js +39 -0
  151. package/build/__tests__/unit/pinchTool.test.js.map +1 -0
  152. package/build/__tests__/unit/probe.test.js +0 -1
  153. package/build/__tests__/unit/probe.test.js.map +1 -1
  154. package/build/__tests__/unit/projectMemory.test.js +6 -7
  155. package/build/__tests__/unit/projectMemory.test.js.map +1 -1
  156. package/build/__tests__/unit/projectStore.test.js +2 -2
  157. package/build/__tests__/unit/projectStore.test.js.map +1 -1
  158. package/build/__tests__/unit/reloadAppAutoConnect.test.js +0 -9
  159. package/build/__tests__/unit/reloadAppAutoConnect.test.js.map +1 -1
  160. package/build/__tests__/unit/rnGlobalsBootstrap.test.js +3 -3
  161. package/build/__tests__/unit/rnGlobalsBootstrap.test.js.map +1 -1
  162. package/build/__tests__/unit/routeHistory.test.d.ts +2 -0
  163. package/build/__tests__/unit/routeHistory.test.d.ts.map +1 -0
  164. package/build/__tests__/unit/routeHistory.test.js +136 -0
  165. package/build/__tests__/unit/routeHistory.test.js.map +1 -0
  166. package/build/__tests__/unit/screenSpace.test.js +30 -13
  167. package/build/__tests__/unit/screenSpace.test.js.map +1 -1
  168. package/build/__tests__/unit/screenState.test.js +3 -6
  169. package/build/__tests__/unit/screenState.test.js.map +1 -1
  170. package/build/__tests__/unit/screenStateInputValue.test.js +0 -7
  171. package/build/__tests__/unit/screenStateInputValue.test.js.map +1 -1
  172. package/build/__tests__/unit/screenStateKeyboard.test.js +0 -1
  173. package/build/__tests__/unit/screenStateKeyboard.test.js.map +1 -1
  174. package/build/__tests__/unit/screenshot-diff.test.js +0 -6
  175. package/build/__tests__/unit/screenshot-diff.test.js.map +1 -1
  176. package/build/__tests__/unit/sdkMirrorPoller.test.js +0 -8
  177. package/build/__tests__/unit/sdkMirrorPoller.test.js.map +1 -1
  178. package/build/__tests__/unit/settleAndDiff.test.js +0 -11
  179. package/build/__tests__/unit/settleAndDiff.test.js.map +1 -1
  180. package/build/__tests__/unit/signedVerdict.test.js +0 -5
  181. package/build/__tests__/unit/signedVerdict.test.js.map +1 -1
  182. package/build/__tests__/unit/swipeDirection.test.js +6 -9
  183. package/build/__tests__/unit/swipeDirection.test.js.map +1 -1
  184. package/build/__tests__/unit/symbolicate.test.js +0 -1
  185. package/build/__tests__/unit/symbolicate.test.js.map +1 -1
  186. package/build/__tests__/unit/tap.test.js +2 -40
  187. package/build/__tests__/unit/tap.test.js.map +1 -1
  188. package/build/__tests__/unit/textEntry.test.js +0 -27
  189. package/build/__tests__/unit/textEntry.test.js.map +1 -1
  190. package/build/__tests__/unit/toolDescriptions.test.js +0 -40
  191. package/build/__tests__/unit/toolDescriptions.test.js.map +1 -1
  192. package/build/__tests__/unit/toolsJson.test.js +0 -10
  193. package/build/__tests__/unit/toolsJson.test.js.map +1 -1
  194. package/build/__tests__/unit/transportClassify.test.js +0 -20
  195. package/build/__tests__/unit/transportClassify.test.js.map +1 -1
  196. package/build/__tests__/unit/truncate.test.js +0 -5
  197. package/build/__tests__/unit/truncate.test.js.map +1 -1
  198. package/build/__tests__/unit/usageCache.test.js +0 -3
  199. package/build/__tests__/unit/usageCache.test.js.map +1 -1
  200. package/build/__tests__/unit/usageGate.test.js +1 -1
  201. package/build/__tests__/unit/usageGate.test.js.map +1 -1
  202. package/build/__tests__/unit/visibility.test.js +0 -8
  203. package/build/__tests__/unit/visibility.test.js.map +1 -1
  204. package/build/__tests__/unit/waitForMeasurements.test.js +2 -7
  205. package/build/__tests__/unit/waitForMeasurements.test.js.map +1 -1
  206. package/build/bin.js +0 -18
  207. package/build/bin.js.map +1 -1
  208. package/build/core/android.d.ts +0 -120
  209. package/build/core/android.d.ts.map +1 -1
  210. package/build/core/android.js +4 -259
  211. package/build/core/android.js.map +1 -1
  212. package/build/core/androidPinch.d.ts +21 -0
  213. package/build/core/androidPinch.d.ts.map +1 -0
  214. package/build/core/androidPinch.js +72 -0
  215. package/build/core/androidPinch.js.map +1 -0
  216. package/build/core/androidSystemBars.d.ts +7 -0
  217. package/build/core/androidSystemBars.d.ts.map +1 -0
  218. package/build/core/androidSystemBars.js +21 -0
  219. package/build/core/androidSystemBars.js.map +1 -0
  220. package/build/core/appContext.d.ts +0 -50
  221. package/build/core/appContext.d.ts.map +1 -1
  222. package/build/core/appContext.js +0 -50
  223. package/build/core/appContext.js.map +1 -1
  224. package/build/core/appDetection.d.ts +0 -16
  225. package/build/core/appDetection.d.ts.map +1 -1
  226. package/build/core/appDetection.js +0 -36
  227. package/build/core/appDetection.js.map +1 -1
  228. package/build/core/appRequest.d.ts +0 -12
  229. package/build/core/appRequest.d.ts.map +1 -1
  230. package/build/core/appRequest.js +0 -15
  231. package/build/core/appRequest.js.map +1 -1
  232. package/build/core/bufferConfig.d.ts +0 -2
  233. package/build/core/bufferConfig.d.ts.map +1 -1
  234. package/build/core/bufferConfig.js +0 -2
  235. package/build/core/bufferConfig.js.map +1 -1
  236. package/build/core/bundle.js +0 -40
  237. package/build/core/bundle.js.map +1 -1
  238. package/build/core/componentSearch.d.ts +0 -19
  239. package/build/core/componentSearch.d.ts.map +1 -1
  240. package/build/core/componentSearch.js +1 -22
  241. package/build/core/componentSearch.js.map +1 -1
  242. package/build/core/componentSource.d.ts +0 -21
  243. package/build/core/componentSource.d.ts.map +1 -1
  244. package/build/core/componentSource.js +0 -27
  245. package/build/core/componentSource.js.map +1 -1
  246. package/build/core/componentTree.js +0 -12
  247. package/build/core/componentTree.js.map +1 -1
  248. package/build/core/config.d.ts +0 -8
  249. package/build/core/config.d.ts.map +1 -1
  250. package/build/core/config.js +0 -10
  251. package/build/core/config.js.map +1 -1
  252. package/build/core/connection.d.ts +0 -76
  253. package/build/core/connection.d.ts.map +1 -1
  254. package/build/core/connection.js +15 -340
  255. package/build/core/connection.js.map +1 -1
  256. package/build/core/connectionState.d.ts +0 -78
  257. package/build/core/connectionState.d.ts.map +1 -1
  258. package/build/core/connectionState.js +4 -92
  259. package/build/core/connectionState.js.map +1 -1
  260. package/build/core/debugGlobals.js +0 -68
  261. package/build/core/debugGlobals.js.map +1 -1
  262. package/build/core/deviceDiscovery.d.ts +0 -5
  263. package/build/core/deviceDiscovery.d.ts.map +1 -1
  264. package/build/core/deviceDiscovery.js +0 -12
  265. package/build/core/deviceDiscovery.js.map +1 -1
  266. package/build/core/deviceResolver.d.ts +0 -10
  267. package/build/core/deviceResolver.d.ts.map +1 -1
  268. package/build/core/deviceResolver.js +0 -54
  269. package/build/core/deviceResolver.js.map +1 -1
  270. package/build/core/emulatorBridge.d.ts +17 -0
  271. package/build/core/emulatorBridge.d.ts.map +1 -0
  272. package/build/core/emulatorBridge.js +78 -0
  273. package/build/core/emulatorBridge.js.map +1 -0
  274. package/build/core/emulatorGrpc.d.ts +14 -0
  275. package/build/core/emulatorGrpc.d.ts.map +1 -0
  276. package/build/core/emulatorGrpc.js +93 -0
  277. package/build/core/emulatorGrpc.js.map +1 -0
  278. package/build/core/epochRender.d.ts +0 -12
  279. package/build/core/epochRender.d.ts.map +1 -1
  280. package/build/core/epochRender.js +0 -12
  281. package/build/core/epochRender.js.map +1 -1
  282. package/build/core/errorScreenParser.d.ts +0 -21
  283. package/build/core/errorScreenParser.d.ts.map +1 -1
  284. package/build/core/errorScreenParser.js +0 -50
  285. package/build/core/errorScreenParser.js.map +1 -1
  286. package/build/core/errors.d.ts +0 -15
  287. package/build/core/errors.d.ts.map +1 -1
  288. package/build/core/errors.js +0 -15
  289. package/build/core/errors.js.map +1 -1
  290. package/build/core/escapeNonAscii.d.ts +0 -9
  291. package/build/core/escapeNonAscii.d.ts.map +1 -1
  292. package/build/core/escapeNonAscii.js +1 -43
  293. package/build/core/escapeNonAscii.js.map +1 -1
  294. package/build/core/exec.d.ts +0 -12
  295. package/build/core/exec.d.ts.map +1 -1
  296. package/build/core/exec.js +2 -14
  297. package/build/core/exec.js.map +1 -1
  298. package/build/core/executor.js +0 -3
  299. package/build/core/executor.js.map +1 -1
  300. package/build/core/fastRefreshRecorder.js +0 -4
  301. package/build/core/fastRefreshRecorder.js.map +1 -1
  302. package/build/core/fastRefreshTools.js +0 -3
  303. package/build/core/fastRefreshTools.js.map +1 -1
  304. package/build/core/feedback.js +0 -4
  305. package/build/core/feedback.js.map +1 -1
  306. package/build/core/graphqlOperation.d.ts +13 -0
  307. package/build/core/graphqlOperation.d.ts.map +1 -0
  308. package/build/core/graphqlOperation.js +80 -0
  309. package/build/core/graphqlOperation.js.map +1 -0
  310. package/build/core/guides.d.ts +0 -10
  311. package/build/core/guides.d.ts.map +1 -1
  312. package/build/core/guides.js +18 -12
  313. package/build/core/guides.js.map +1 -1
  314. package/build/core/iconSemantics.d.ts +0 -15
  315. package/build/core/iconSemantics.d.ts.map +1 -1
  316. package/build/core/iconSemantics.js +0 -31
  317. package/build/core/iconSemantics.js.map +1 -1
  318. package/build/core/index.d.ts +2 -0
  319. package/build/core/index.d.ts.map +1 -1
  320. package/build/core/index.js +5 -47
  321. package/build/core/index.js.map +1 -1
  322. package/build/core/injected/visibility.d.ts +0 -30
  323. package/build/core/injected/visibility.d.ts.map +1 -1
  324. package/build/core/injected/visibility.js +0 -33
  325. package/build/core/injected/visibility.js.map +1 -1
  326. package/build/core/injectedFilters.d.ts +0 -28
  327. package/build/core/injectedFilters.d.ts.map +1 -1
  328. package/build/core/injectedFilters.js +0 -28
  329. package/build/core/injectedFilters.js.map +1 -1
  330. package/build/core/inputTarget.d.ts +1 -34
  331. package/build/core/inputTarget.d.ts.map +1 -1
  332. package/build/core/inputTarget.js +0 -26
  333. package/build/core/inputTarget.js.map +1 -1
  334. package/build/core/inputTargetTools.d.ts +0 -7
  335. package/build/core/inputTargetTools.d.ts.map +1 -1
  336. package/build/core/inputTargetTools.js +0 -7
  337. package/build/core/inputTargetTools.js.map +1 -1
  338. package/build/core/inspector.d.ts +0 -33
  339. package/build/core/inspector.d.ts.map +1 -1
  340. package/build/core/inspector.js +27 -40
  341. package/build/core/inspector.js.map +1 -1
  342. package/build/core/ios.d.ts +11 -122
  343. package/build/core/ios.d.ts.map +1 -1
  344. package/build/core/ios.js +32 -299
  345. package/build/core/ios.js.map +1 -1
  346. package/build/core/jsExecute.d.ts +0 -100
  347. package/build/core/jsExecute.d.ts.map +1 -1
  348. package/build/core/jsExecute.js +0 -261
  349. package/build/core/jsExecute.js.map +1 -1
  350. package/build/core/jsLogEvents.d.ts +0 -8
  351. package/build/core/jsLogEvents.d.ts.map +1 -1
  352. package/build/core/jsLogEvents.js +0 -8
  353. package/build/core/jsLogEvents.js.map +1 -1
  354. package/build/core/keyboardMetrics.js +0 -8
  355. package/build/core/keyboardMetrics.js.map +1 -1
  356. package/build/core/keyboardRaise.d.ts +0 -8
  357. package/build/core/keyboardRaise.d.ts.map +1 -1
  358. package/build/core/keyboardRaise.js +0 -23
  359. package/build/core/keyboardRaise.js.map +1 -1
  360. package/build/core/license.d.ts +0 -4
  361. package/build/core/license.d.ts.map +1 -1
  362. package/build/core/license.js +2 -55
  363. package/build/core/license.js.map +1 -1
  364. package/build/core/logDiagnosis.d.ts +0 -34
  365. package/build/core/logDiagnosis.d.ts.map +1 -1
  366. package/build/core/logDiagnosis.js +0 -15
  367. package/build/core/logDiagnosis.js.map +1 -1
  368. package/build/core/logEventFormat.d.ts +0 -1
  369. package/build/core/logEventFormat.d.ts.map +1 -1
  370. package/build/core/logEventFormat.js +0 -30
  371. package/build/core/logEventFormat.js.map +1 -1
  372. package/build/core/logEvents.d.ts +0 -54
  373. package/build/core/logEvents.d.ts.map +1 -1
  374. package/build/core/logEvents.js +0 -37
  375. package/build/core/logEvents.js.map +1 -1
  376. package/build/core/logGrouping.d.ts +0 -9
  377. package/build/core/logGrouping.d.ts.map +1 -1
  378. package/build/core/logGrouping.js +0 -17
  379. package/build/core/logGrouping.js.map +1 -1
  380. package/build/core/logOwnership.d.ts +0 -17
  381. package/build/core/logOwnership.d.ts.map +1 -1
  382. package/build/core/logOwnership.js +0 -34
  383. package/build/core/logOwnership.js.map +1 -1
  384. package/build/core/logRelevance.d.ts +0 -8
  385. package/build/core/logRelevance.d.ts.map +1 -1
  386. package/build/core/logRelevance.js +0 -15
  387. package/build/core/logRelevance.js.map +1 -1
  388. package/build/core/logSourceAndroid.d.ts +0 -8
  389. package/build/core/logSourceAndroid.d.ts.map +1 -1
  390. package/build/core/logSourceAndroid.js +0 -11
  391. package/build/core/logSourceAndroid.js.map +1 -1
  392. package/build/core/logSourceIos.d.ts +0 -8
  393. package/build/core/logSourceIos.d.ts.map +1 -1
  394. package/build/core/logSourceIos.js +1 -24
  395. package/build/core/logSourceIos.js.map +1 -1
  396. package/build/core/logbox.d.ts +0 -30
  397. package/build/core/logbox.d.ts.map +1 -1
  398. package/build/core/logbox.js +0 -43
  399. package/build/core/logbox.js.map +1 -1
  400. package/build/core/logs.d.ts +0 -6
  401. package/build/core/logs.d.ts.map +1 -1
  402. package/build/core/logs.js +0 -19
  403. package/build/core/logs.js.map +1 -1
  404. package/build/core/metro.d.ts +0 -14
  405. package/build/core/metro.d.ts.map +1 -1
  406. package/build/core/metro.js +1 -32
  407. package/build/core/metro.js.map +1 -1
  408. package/build/core/moduleRegistry.d.ts +0 -17
  409. package/build/core/moduleRegistry.d.ts.map +1 -1
  410. package/build/core/moduleRegistry.js +0 -17
  411. package/build/core/moduleRegistry.js.map +1 -1
  412. package/build/core/nativeInputValue.d.ts +0 -29
  413. package/build/core/nativeInputValue.d.ts.map +1 -1
  414. package/build/core/nativeInputValue.js +0 -18
  415. package/build/core/nativeInputValue.js.map +1 -1
  416. package/build/core/nativeLogs.d.ts +0 -47
  417. package/build/core/nativeLogs.d.ts.map +1 -1
  418. package/build/core/nativeLogs.js +0 -82
  419. package/build/core/nativeLogs.js.map +1 -1
  420. package/build/core/nativeOnlyHints.js +0 -4
  421. package/build/core/nativeOnlyHints.js.map +1 -1
  422. package/build/core/navigate.d.ts +0 -15
  423. package/build/core/navigate.d.ts.map +1 -1
  424. package/build/core/navigate.js +0 -22
  425. package/build/core/navigate.js.map +1 -1
  426. package/build/core/navigation.d.ts +0 -60
  427. package/build/core/navigation.d.ts.map +1 -1
  428. package/build/core/navigation.js +0 -60
  429. package/build/core/navigation.js.map +1 -1
  430. package/build/core/network.d.ts +0 -5
  431. package/build/core/network.d.ts.map +1 -1
  432. package/build/core/network.js +10 -31
  433. package/build/core/network.js.map +1 -1
  434. package/build/core/networkInterceptor.d.ts +0 -19
  435. package/build/core/networkInterceptor.d.ts.map +1 -1
  436. package/build/core/networkInterceptor.js +3 -27
  437. package/build/core/networkInterceptor.js.map +1 -1
  438. package/build/core/ocr.d.ts +0 -31
  439. package/build/core/ocr.d.ts.map +1 -1
  440. package/build/core/ocr.js +1 -71
  441. package/build/core/ocr.js.map +1 -1
  442. package/build/core/paths.js +0 -1
  443. package/build/core/paths.js.map +1 -1
  444. package/build/core/pinchGeometry.d.ts +28 -0
  445. package/build/core/pinchGeometry.d.ts.map +1 -0
  446. package/build/core/pinchGeometry.js +97 -0
  447. package/build/core/pinchGeometry.js.map +1 -0
  448. package/build/core/pinchThresholds.d.ts +14 -0
  449. package/build/core/pinchThresholds.d.ts.map +1 -0
  450. package/build/core/pinchThresholds.js +9 -0
  451. package/build/core/pinchThresholds.js.map +1 -0
  452. package/build/core/posthog.js +0 -7
  453. package/build/core/posthog.js.map +1 -1
  454. package/build/core/pressables.d.ts +0 -25
  455. package/build/core/pressables.d.ts.map +1 -1
  456. package/build/core/pressables.js +0 -37
  457. package/build/core/pressables.js.map +1 -1
  458. package/build/core/probe.d.ts +0 -9
  459. package/build/core/probe.d.ts.map +1 -1
  460. package/build/core/probe.js +0 -9
  461. package/build/core/probe.js.map +1 -1
  462. package/build/core/projectMemory.d.ts +0 -2
  463. package/build/core/projectMemory.d.ts.map +1 -1
  464. package/build/core/projectMemory.js +2 -7
  465. package/build/core/projectMemory.js.map +1 -1
  466. package/build/core/projectStore.d.ts +0 -18
  467. package/build/core/projectStore.d.ts.map +1 -1
  468. package/build/core/projectStore.js +0 -19
  469. package/build/core/projectStore.js.map +1 -1
  470. package/build/core/promiseHandles.d.ts +0 -13
  471. package/build/core/promiseHandles.d.ts.map +1 -1
  472. package/build/core/promiseHandles.js +0 -25
  473. package/build/core/promiseHandles.js.map +1 -1
  474. package/build/core/register.js +0 -44
  475. package/build/core/register.js.map +1 -1
  476. package/build/core/rnGlobalsBootstrap.d.ts +0 -13
  477. package/build/core/rnGlobalsBootstrap.d.ts.map +1 -1
  478. package/build/core/rnGlobalsBootstrap.js +0 -22
  479. package/build/core/rnGlobalsBootstrap.js.map +1 -1
  480. package/build/core/routeHistory.d.ts +24 -0
  481. package/build/core/routeHistory.d.ts.map +1 -0
  482. package/build/core/routeHistory.js +186 -0
  483. package/build/core/routeHistory.js.map +1 -0
  484. package/build/core/screenLayout.d.ts +0 -11
  485. package/build/core/screenLayout.d.ts.map +1 -1
  486. package/build/core/screenLayout.js +24 -61
  487. package/build/core/screenLayout.js.map +1 -1
  488. package/build/core/screenSpace.d.ts +32 -44
  489. package/build/core/screenSpace.d.ts.map +1 -1
  490. package/build/core/screenSpace.js +80 -46
  491. package/build/core/screenSpace.js.map +1 -1
  492. package/build/core/screenSpaceDevice.d.ts +0 -8
  493. package/build/core/screenSpaceDevice.d.ts.map +1 -1
  494. package/build/core/screenSpaceDevice.js +31 -14
  495. package/build/core/screenSpaceDevice.js.map +1 -1
  496. package/build/core/screenState.d.ts +2 -74
  497. package/build/core/screenState.d.ts.map +1 -1
  498. package/build/core/screenState.js +204 -145
  499. package/build/core/screenState.js.map +1 -1
  500. package/build/core/sdkBridge.js +0 -8
  501. package/build/core/sdkBridge.js.map +1 -1
  502. package/build/core/sdkMirrorPoller.d.ts +0 -18
  503. package/build/core/sdkMirrorPoller.d.ts.map +1 -1
  504. package/build/core/sdkMirrorPoller.js +0 -47
  505. package/build/core/sdkMirrorPoller.js.map +1 -1
  506. package/build/core/selectionBuffer.d.ts +0 -6
  507. package/build/core/selectionBuffer.d.ts.map +1 -1
  508. package/build/core/selectionBuffer.js +0 -5
  509. package/build/core/selectionBuffer.js.map +1 -1
  510. package/build/core/signedVerdict.js +0 -6
  511. package/build/core/signedVerdict.js.map +1 -1
  512. package/build/core/state.d.ts +0 -31
  513. package/build/core/state.d.ts.map +1 -1
  514. package/build/core/state.js +0 -57
  515. package/build/core/state.js.map +1 -1
  516. package/build/core/symbolicate.js +0 -11
  517. package/build/core/symbolicate.js.map +1 -1
  518. package/build/core/telemetry.d.ts +0 -47
  519. package/build/core/telemetry.d.ts.map +1 -1
  520. package/build/core/telemetry.js +1 -173
  521. package/build/core/telemetry.js.map +1 -1
  522. package/build/core/textEntry.d.ts +0 -34
  523. package/build/core/textEntry.d.ts.map +1 -1
  524. package/build/core/textEntry.js +0 -73
  525. package/build/core/textEntry.js.map +1 -1
  526. package/build/core/toolHelpers.d.ts +0 -12
  527. package/build/core/toolHelpers.d.ts.map +1 -1
  528. package/build/core/toolHelpers.js +2 -47
  529. package/build/core/toolHelpers.js.map +1 -1
  530. package/build/core/truncate.d.ts +0 -17
  531. package/build/core/truncate.d.ts.map +1 -1
  532. package/build/core/truncate.js +0 -32
  533. package/build/core/truncate.js.map +1 -1
  534. package/build/core/types.d.ts +0 -6
  535. package/build/core/types.d.ts.map +1 -1
  536. package/build/core/usageCache.js +0 -12
  537. package/build/core/usageCache.js.map +1 -1
  538. package/build/index.js +0 -18
  539. package/build/index.js.map +1 -1
  540. package/build/pro/coordinateMiss.d.ts +0 -30
  541. package/build/pro/coordinateMiss.d.ts.map +1 -1
  542. package/build/pro/coordinateMiss.js +0 -17
  543. package/build/pro/coordinateMiss.js.map +1 -1
  544. package/build/pro/overlayGuard.d.ts +27 -0
  545. package/build/pro/overlayGuard.d.ts.map +1 -0
  546. package/build/pro/overlayGuard.js +103 -0
  547. package/build/pro/overlayGuard.js.map +1 -0
  548. package/build/pro/screenshot-diff.js +4 -6
  549. package/build/pro/screenshot-diff.js.map +1 -1
  550. package/build/pro/tap.d.ts +0 -55
  551. package/build/pro/tap.d.ts.map +1 -1
  552. package/build/pro/tap.js +72 -316
  553. package/build/pro/tap.js.map +1 -1
  554. package/build/pro/usageGate.js +0 -9
  555. package/build/pro/usageGate.js.map +1 -1
  556. package/build/pro/usageNotifications.js +1 -13
  557. package/build/pro/usageNotifications.js.map +1 -1
  558. package/build/pro/verifyAction.d.ts +0 -13
  559. package/build/pro/verifyAction.d.ts.map +1 -1
  560. package/build/pro/verifyAction.js +0 -45
  561. package/build/pro/verifyAction.js.map +1 -1
  562. package/build/tools/_deviceArg.d.ts +0 -35
  563. package/build/tools/_deviceArg.d.ts.map +1 -1
  564. package/build/tools/_deviceArg.js +0 -35
  565. package/build/tools/_deviceArg.js.map +1 -1
  566. package/build/tools/accountTools.js +2 -2
  567. package/build/tools/accountTools.js.map +1 -1
  568. package/build/tools/bundleTools.js +0 -16
  569. package/build/tools/bundleTools.js.map +1 -1
  570. package/build/tools/componentTools.d.ts.map +1 -1
  571. package/build/tools/componentTools.js +47 -53
  572. package/build/tools/componentTools.js.map +1 -1
  573. package/build/tools/connectionTools.js +1 -59
  574. package/build/tools/connectionTools.js.map +1 -1
  575. package/build/tools/deviceTools.js +0 -11
  576. package/build/tools/deviceTools.js.map +1 -1
  577. package/build/tools/executionTools.js +1 -18
  578. package/build/tools/executionTools.js.map +1 -1
  579. package/build/tools/interactionTools.d.ts +0 -10
  580. package/build/tools/interactionTools.d.ts.map +1 -1
  581. package/build/tools/interactionTools.js +190 -59
  582. package/build/tools/interactionTools.js.map +1 -1
  583. package/build/tools/logTools.d.ts +0 -13
  584. package/build/tools/logTools.d.ts.map +1 -1
  585. package/build/tools/logTools.js +2 -126
  586. package/build/tools/logTools.js.map +1 -1
  587. package/build/tools/metaTools.js +0 -10
  588. package/build/tools/metaTools.js.map +1 -1
  589. package/build/tools/networkTools.js +4 -43
  590. package/build/tools/networkTools.js.map +1 -1
  591. package/build/tools/requestTools.js +0 -5
  592. package/build/tools/requestTools.js.map +1 -1
  593. package/build/tools/screenshotTools.js +5 -111
  594. package/build/tools/screenshotTools.js.map +1 -1
  595. package/package.json +2 -2
  596. package/tools.json +16 -1
package/build/pro/tap.js CHANGED
@@ -14,9 +14,8 @@ export function buildVerificationExplanation(v) {
14
14
  const action = v.action ?? "tap";
15
15
  const Action = action[0].toUpperCase() + action.slice(1);
16
16
  const target = action === "swipe" ? "scroll surface" : "element";
17
- // Burst path with typed verdict
18
17
  if (v.kind === "settled_elsewhere") {
19
- return `${Action} caused a visible UI change (${pct(v.changeRate)} pixel diff). The screen updated as expected.`;
18
+ return `${Action} caused a visible UI change (${pct(v.changeRate)} pixel diff). Something on screen responded; a pixel diff cannot identify which element, so this is not confirmation that the intended target handled it.`;
20
19
  }
21
20
  if (v.kind === "snap_back") {
22
21
  if (action === "swipe") {
@@ -31,9 +30,8 @@ export function buildVerificationExplanation(v) {
31
30
  return (`No visual change detected — neither persistent nor transient across burst frames. ` +
32
31
  `The ${target} may not respond visually or the ${action} may have missed its target.`);
33
32
  }
34
- // Legacy non-burst path
35
33
  if (v.meaningful) {
36
- return `${Action} caused a visible UI change (${pct(v.changeRate)} pixel diff). The screen updated as expected.`;
34
+ return `${Action} caused a visible UI change (${pct(v.changeRate)} pixel diff). Something on screen responded; a pixel diff cannot identify which element, so this is not confirmation that the intended target handled it.`;
37
35
  }
38
36
  return (`No visual change detected between before and after screenshots. ` +
39
37
  `The ${target} may not respond visually or the ${action} may have missed.`);
@@ -67,9 +65,6 @@ export async function analyzeBurstFrames(frames, options) {
67
65
  const persistentDiff = await compareScreenshots(frames[0], frames[frames.length - 1], options);
68
66
  const persistentChangeRate = persistentDiff.changeRate;
69
67
  const transientChangeDetected = !persistentDiff.changed && framesWithChange.length > 0;
70
- // Snap-back (transient motion that reverts) is NOT a successful gesture —
71
- // the content moved during the drag but returned to its starting position.
72
- // Callers diagnosing scroll failures need this to read as "didn't work".
73
68
  const meaningful = persistentDiff.changed;
74
69
  let kind;
75
70
  if (persistentDiff.changed) {
@@ -91,7 +86,6 @@ export async function analyzeBurstFrames(frames, options) {
91
86
  kind
92
87
  };
93
88
  }
94
- // --- Helpers ---
95
89
  export function buildQuery(options) {
96
90
  const query = {};
97
91
  if (options.text !== undefined)
@@ -106,11 +100,6 @@ export function buildQuery(options) {
106
100
  query.y = options.y;
107
101
  return query;
108
102
  }
109
- /**
110
- * Check if text contains characters that break Hermes Runtime.evaluate.
111
- * Standard accented Latin characters (Polish, Vietnamese, French, German, etc.)
112
- * and Cyrillic work fine in Hermes. Only emoji and special Unicode ranges cause issues.
113
- */
114
103
  export function hasProblematicUnicode(text) {
115
104
  const emojiPattern = /[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{FE00}-\u{FE0F}]|[\u{1F900}-\u{1F9FF}]|[\u{200D}]|[\u{20E3}]|[\u{E0020}-\u{E007F}]/u;
116
105
  return emojiPattern.test(text);
@@ -157,13 +146,6 @@ export function reconstructLinesFromWords(words) {
157
146
  return { text, bbox: { x0, y0, x1, y1 }, tapCenter: { x: tapX, y: tapY }, words: sortedWords };
158
147
  });
159
148
  }
160
- /**
161
- * Narrow the tap point to just the words that produced the substring match.
162
- * Without this, a query for "Remotely" against a reconstructed line "Yes Remotely No"
163
- * would tap at the line's centroid (middle of "Remotely" in this case, but easily wrong
164
- * for asymmetric layouts). Tracks character offsets through the joined text so we know
165
- * which constituent words contributed to the matched range.
166
- */
167
149
  function refineTapCenterToMatchingWords(line, normalizedNeedle) {
168
150
  const normalizedFull = normalizeForMatch(line.text);
169
151
  const startChar = normalizedFull.indexOf(normalizedNeedle);
@@ -176,11 +158,10 @@ function refineTapCenterToMatchingWords(line, normalizedNeedle) {
176
158
  const wNorm = normalizeForMatch(w.text);
177
159
  const wordStart = offset;
178
160
  const wordEnd = offset + wNorm.length;
179
- // Word overlaps the matched range
180
161
  if (wordEnd > startChar && wordStart < endChar) {
181
162
  covered.push(w);
182
163
  }
183
- offset = wordEnd + 1; // +1 for the joining space
164
+ offset = wordEnd + 1;
184
165
  }
185
166
  if (covered.length === 0)
186
167
  return line.tapCenter;
@@ -206,10 +187,6 @@ export function findOcrMatch(ocrResult, query) {
206
187
  const substringWord = words.find((w) => normalizeForMatch(w.text).includes(needle));
207
188
  if (substringWord)
208
189
  return { text: substringWord.text, tapCenter: substringWord.tapCenter };
209
- // Fall through to phrases reconstructed from word detections. Tap point is
210
- // refined to only the words covering the matched substring, so a query for
211
- // "Continue with Google" inside "[icon] Continue with Google" lands on the
212
- // text run instead of the line centroid (and won't drift onto the icon).
213
190
  const reconstructed = reconstructLinesFromWords(words);
214
191
  const exactRecon = reconstructed.find((l) => normalizeForMatch(l.text) === needle);
215
192
  if (exactRecon) {
@@ -226,7 +203,6 @@ export function getAvailableStrategies(query, strategy) {
226
203
  return ["coordinate"];
227
204
  }
228
205
  if (strategy !== "auto") {
229
- // Always fallback to OCR for text queries — explicit strategy may miss visible text
230
206
  if (query.text && strategy !== "ocr" && strategy !== "coordinate") {
231
207
  return [strategy, "ocr"];
232
208
  }
@@ -249,16 +225,6 @@ export function getAvailableStrategies(query, strategy) {
249
225
  }
250
226
  return ["fiber", "accessibility", "ocr"];
251
227
  }
252
- /**
253
- * Convert screenshot image coordinates to platform-native tap coordinates.
254
- *
255
- * For iOS: screenshot pixels → device pixels (undo downscale) → points (÷ DPR)
256
- * For Android: screenshot pixels → device pixels (undo downscale)
257
- *
258
- * IMPORTANT: Only use this for EXTERNAL coordinates from screenshots.
259
- * Internal strategies (OCR, accessibility, fiber) produce tap-ready coordinates
260
- * and call iosTap/androidTap directly — they must NOT go through this function.
261
- */
262
228
  export function convertScreenshotToTapCoords(pixelX, pixelY, platform, devicePixelRatio, scaleFactor = 1) {
263
229
  const deviceX = pixelX * scaleFactor;
264
230
  const deviceY = pixelY * scaleFactor;
@@ -270,16 +236,7 @@ export function convertScreenshotToTapCoords(pixelX, pixelY, platform, devicePix
270
236
  y: Math.round(deviceY / devicePixelRatio)
271
237
  };
272
238
  }
273
- /** @deprecated Use convertScreenshotToTapCoords instead */
274
239
  export const convertPixelsToPoints = convertScreenshotToTapCoords;
275
- /**
276
- * Turn a swipe direction (+ optional pixel distance) into screenshot-pixel
277
- * start/end coordinates, centered on the screen. Content-scroll semantics:
278
- * "up" = finger travels bottom→top, revealing content below.
279
- * Distance defaults to 33% of the relevant axis and endpoints clamp to the
280
- * 10%–90% margin so the gesture never runs off-screen. Exact travel length is
281
- * preserved; for odd distances the band may sit ≤1px off the axis midpoint.
282
- */
283
240
  export function computeSwipeFromDirection(direction, distance, width, height) {
284
241
  const vertical = direction === "up" || direction === "down";
285
242
  const axis = vertical ? height : width;
@@ -288,11 +245,10 @@ export function computeSwipeFromDirection(direction, distance, width, height) {
288
245
  const cy = Math.round(height / 2);
289
246
  const lo = Math.round(0.1 * axis);
290
247
  const hi = Math.round(0.9 * axis);
291
- // Center a band of length d on the axis midpoint, then clamp to [lo, hi].
292
248
  const mid = Math.round(axis / 2);
293
249
  const half = Math.floor(d / 2);
294
- let far = mid - half; // smaller-coordinate end
295
- let near = far + d; // exact length d
250
+ let far = mid - half;
251
+ let near = far + d;
296
252
  if (near > hi) {
297
253
  near = hi;
298
254
  far = near - d;
@@ -301,29 +257,23 @@ export function computeSwipeFromDirection(direction, distance, width, height) {
301
257
  far = lo;
302
258
  near = far + d;
303
259
  }
304
- // distance exceeds the available span — collapse to the full margin
305
260
  if (near > hi)
306
261
  near = hi;
307
262
  if (far < lo)
308
263
  far = lo;
309
264
  switch (direction) {
310
- case "up": // finger bottom→top: start at larger Y, end at smaller Y
265
+ case "up":
311
266
  return { startX: cx, startY: near, endX: cx, endY: far };
312
- case "down": // finger top→bottom
267
+ case "down":
313
268
  return { startX: cx, startY: far, endX: cx, endY: near };
314
- case "left": // finger right→left
269
+ case "left":
315
270
  return { startX: near, startY: cy, endX: far, endY: cy };
316
- case "right": // finger left→right
271
+ case "right":
317
272
  return { startX: far, startY: cy, endX: near, endY: cy };
318
273
  }
319
274
  }
320
275
  export function formatTapSuccess(data) {
321
276
  const { screenshot, verification, ...rest } = data;
322
- // I3 (2026-05-16): when the tap landed on a TextInput-like element and the diff
323
- // reports no visual change, that does NOT mean the focus failed. iOS simulators with
324
- // the hardware keyboard connected (Cmd+K) suppress the on-screen keyboard, so the
325
- // input is focused but the screen looks identical. Surface this so the agent doesn't
326
- // assume the tap missed and retry the wrong thing.
327
277
  const isTextInputComponent = (() => {
328
278
  const name = data.component ?? data.pressed ?? "";
329
279
  return /textfield|textinput|edittext|searchfield/i.test(name);
@@ -370,16 +320,6 @@ function buildErrorMessage(query) {
370
320
  parts.push(`component="${query.component}"`);
371
321
  return `No element found matching ${parts.join(", ")}`;
372
322
  }
373
- // I2 (2026-05-16): RN component composition routinely produces N matches at the same
374
- // coordinates for the same testID/text (e.g. ThemedButton → TouchableOpacity → TouchableOpacity).
375
- // The C1 ambiguity guard refuses all such taps and demands `index=`. That's correct in the
376
- // spatial-ambiguity case (two distinct buttons share a label) but wrong in the
377
- // wrapper-ambiguity case — there's only one logical button, just stacked components.
378
- //
379
- // Collapse matches that share the same (testID, text) and overlap geometrically
380
- // (centers within TOLERANCE_PX). Keeps the first occurrence — fiber/a11y walks parent-first,
381
- // so the outermost wrapper survives (its onPress almost always proxies to inner handlers).
382
- // Spatial ambiguity (different x/y) is preserved and continues to surface the C1 refusal.
383
323
  function collapseGeometricallyEquivalentMatches(matches, getCenter, getKey) {
384
324
  if (matches.length <= 1)
385
325
  return matches;
@@ -412,7 +352,6 @@ function ocrSimilarity(a, b) {
412
352
  return 0;
413
353
  if (a === b)
414
354
  return 1;
415
- // Dice coefficient over character bigrams (case/space-insensitive)
416
355
  const bigrams = (s) => {
417
356
  const m = new Map();
418
357
  if (s.length < 2) {
@@ -451,9 +390,6 @@ export function findClosestOcrText(ocrResult, query) {
451
390
  candidates.push(...ocrResult.words.map(w => ({ text: w.text })));
452
391
  if (ocrResult?.lines?.length)
453
392
  candidates.push(...ocrResult.lines.map(l => ({ text: l.text })));
454
- // Include reconstructed phrases so the diagnostic surfaces "Continue with Google@1.00"
455
- // instead of "Continue@0.54" when the engine fragmented a multi-word phrase into
456
- // separate word detections.
457
393
  if (ocrResult?.words?.length) {
458
394
  candidates.push(...reconstructLinesFromWords(ocrResult.words).map(l => ({ text: l.text })));
459
395
  }
@@ -470,7 +406,6 @@ export function findClosestOcrText(ocrResult, query) {
470
406
  }
471
407
  return best;
472
408
  }
473
- // --- Strategy Functions ---
474
409
  async function tryFiberStrategy(query, index, maxTraversalDepth, sink, device) {
475
410
  if (sink) {
476
411
  sink.fiber.ran = true;
@@ -478,10 +413,8 @@ async function tryFiberStrategy(query, index, maxTraversalDepth, sink, device) {
478
413
  }
479
414
  const startedAt = Date.now();
480
415
  try {
481
- // Retry with increasing depth if the initial traversal finds nothing
482
416
  const baseDepth = maxTraversalDepth ?? 15;
483
417
  const depthAttempts = [baseDepth];
484
- // Only add deeper retries if user didn't explicitly set a high depth
485
418
  if (baseDepth <= 15) {
486
419
  depthAttempts.push(30, 45);
487
420
  }
@@ -540,16 +473,8 @@ async function tryFiberAtDepth(query, index, maxTraversalDepth, device) {
540
473
  }
541
474
  return strategyResult;
542
475
  }
543
- // Fiber finds the element by text/testID/component, then measures its
544
- // host component's screen position for a native tap. This ensures the tap
545
- // goes through React's event pipeline, executing any onPress wrappers
546
- // (analytics, debouncing, state tracking) inside the component.
547
476
  if (parsed.needsNativeTap) {
548
- // Ambiguity guard: if multiple elements match and the caller didn't
549
- // specify an explicit index, refuse to tap and surface the full list
550
- // so the agent can pick the right one.
551
477
  if ((parsed.totalMatches ?? 1) > 1 && index === undefined) {
552
- // I2: collapse geometrically-identical wrapper matches before refusing.
553
478
  const collapsed = collapseGeometricallyEquivalentMatches(parsed.allMatches ?? [], (m) => ({ x: m.x, y: m.y }), (m) => `${m.testID ?? ""}::${m.text ?? ""}`);
554
479
  if (collapsed.length > 1) {
555
480
  return {
@@ -559,9 +484,6 @@ async function tryFiberAtDepth(query, index, maxTraversalDepth, device) {
559
484
  ambiguous: true
560
485
  };
561
486
  }
562
- // Collapsed to one logical element — fall through and tap. The JS-side
563
- // search already picked allMatches[0] (the outermost wrapper) for
564
- // parsed.pressed/parsed.nativeTapTarget.
565
487
  }
566
488
  const elementType = parsed.isInput ? "input element" : "pressable element";
567
489
  if (parsed.nativeTapTarget && parsed.nativeTapTarget.x && parsed.nativeTapTarget.y) {
@@ -584,7 +506,6 @@ async function tryFiberAtDepth(query, index, maxTraversalDepth, device) {
584
506
  reason: `Found ${parsed.pressed} (${elementType}) but could not measure coordinates — falling through to next strategy`
585
507
  };
586
508
  }
587
- // All elements now use needsNativeTap — this shouldn't be reached
588
509
  return {
589
510
  success: false,
590
511
  reason: "Unexpected: element did not request native tap"
@@ -611,28 +532,20 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
611
532
  };
612
533
  }
613
534
  if (platform === "ios") {
614
- // One accessibility dump serves every pass below: `axe describe-ui`
615
- // costs ~210ms and the screen cannot change between predicates, so
616
- // re-fetching per pass was pure overhead (2-3 dumps per tap).
617
535
  const { iosGetUITree } = await import("../core/ios.js");
618
536
  const tree = await iosGetUITree(udid);
619
- // iOS: testID maps to accessibilityIdentifier — search by identifier first,
620
- // then fall back to labelContains for text-based searches
621
537
  let result;
622
538
  if (hasTestID && !hasText) {
623
- // Try exact identifier match first (testID → accessibilityIdentifier)
624
539
  result = await iosFindElement({
625
540
  identifier: query.testID,
626
541
  index
627
542
  }, udid, tree);
628
- // Fall back to identifierContains if exact match fails
629
543
  if (!result.success || !result.allMatches || result.allMatches.length === 0) {
630
544
  result = await iosFindElement({
631
545
  identifierContains: query.testID,
632
546
  index
633
547
  }, udid, tree);
634
548
  }
635
- // Last resort: try labelContains in case testID is reflected in label
636
549
  if (!result.success || !result.allMatches || result.allMatches.length === 0) {
637
550
  result = await iosFindElement({
638
551
  labelContains: query.testID,
@@ -642,8 +555,6 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
642
555
  }
643
556
  else {
644
557
  const searchText = query.text;
645
- // Exact label match first to avoid matching dialog titles like "Clear All"
646
- // when the query is a single word that happens to be a substring ("Clear").
647
558
  result = await iosFindElement({
648
559
  label: searchText,
649
560
  index
@@ -670,12 +581,6 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
670
581
  reason: result.error ?? "No iOS accessibility match"
671
582
  };
672
583
  }
673
- // Ambiguity guard: when the predicate matches multiple a11y elements
674
- // (e.g. text="My Events" hits both the header title AND the bottom tab),
675
- // refuse to tap without an explicit index — picking allMatches[0] silently
676
- // landed on non-interactive header elements in production telemetry.
677
- // Mirrors the fiber-strategy guard further up the file.
678
- // I2: collapse geometrically-identical wrapper matches before refusing.
679
584
  const iosEffectiveMatches = result.allMatches.length > 1
680
585
  ? collapseGeometricallyEquivalentMatches(result.allMatches, (m) => ({ x: m.center?.x, y: m.center?.y }), (m) => `${m.identifier ?? ""}::${m.label ?? ""}`)
681
586
  : result.allMatches;
@@ -712,7 +617,6 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
712
617
  };
713
618
  }
714
619
  else {
715
- // Android: testID maps to resource-id, text maps to text content
716
620
  const searchOptions = { index };
717
621
  if (hasTestID && !hasText) {
718
622
  searchOptions.resourceId = query.testID;
@@ -720,13 +624,9 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
720
624
  else if (hasText) {
721
625
  searchOptions.textContains = query.text;
722
626
  }
723
- // Same single-dump reuse as iOS above — a uiautomator hierarchy dump
724
- // is the most expensive step of an Android accessibility tap.
725
627
  const { androidGetUITree } = await import("../core/android.js");
726
628
  const androidTree = await androidGetUITree(undefined, signal);
727
629
  let result = await androidFindElement(searchOptions, undefined, signal, androidTree);
728
- // If testID search via resourceId failed, try contentDescContains
729
- // (older RN versions map testID to content-description)
730
630
  if (hasTestID && !hasText && (!result.success || !result.allMatches || result.allMatches.length === 0)) {
731
631
  result = await androidFindElement({
732
632
  contentDescContains: query.testID,
@@ -748,8 +648,6 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
748
648
  reason: result.error ?? "No Android accessibility match"
749
649
  };
750
650
  }
751
- // Ambiguity guard — see iOS branch above for rationale.
752
- // I2: collapse geometrically-identical wrapper matches before refusing.
753
651
  const androidEffectiveMatches = result.allMatches.length > 1
754
652
  ? collapseGeometricallyEquivalentMatches(result.allMatches, (m) => ({ x: m.center?.x, y: m.center?.y }), (m) => `${m.resourceId ?? ""}::${m.text ?? m.contentDesc ?? ""}`)
755
653
  : result.allMatches;
@@ -797,11 +695,6 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
797
695
  sink.accessibility.durationMs = Date.now() - startedAt;
798
696
  }
799
697
  }
800
- /**
801
- * Run OCR sense (capture + recognize + match) and tap the match. Also records
802
- * `sink.ocr.bestCandidate` (matched text + tap coords) into the evidence sink,
803
- * which is serialized into the R2 failure bundle for diagnostics.
804
- */
805
698
  async function tryOcrStrategy(query, platform, udid, sink, signal) {
806
699
  if (sink)
807
700
  sink.ocr.ran = true;
@@ -855,10 +748,6 @@ async function tryOcrStrategy(query, platform, udid, sink, signal) {
855
748
  sink.ocr.closestMatch = findClosestOcrText(ocrResult, searchText);
856
749
  }
857
750
  const match = findOcrMatch(ocrResult, searchText);
858
- // Record the best candidate (matched text + tap coords) into the
859
- // evidence sink. This is serialized into the R2 failure bundle and is
860
- // useful when diagnosing OCR taps (e.g. OCR found the match at these
861
- // coords but the tap didn't register a visual change).
862
751
  if (sink && match) {
863
752
  const closest = sink.ocr.closestMatch;
864
753
  sink.ocr.bestCandidate = {
@@ -875,7 +764,6 @@ async function tryOcrStrategy(query, platform, udid, sink, signal) {
875
764
  };
876
765
  }
877
766
  if (platform === "ios") {
878
- // tapCenter is in image-pixel space (downscaled) — convert to points
879
767
  const { getDevicePixelRatio } = await import("../core/ios.js");
880
768
  const dpr = await getDevicePixelRatio(udid);
881
769
  const tapResult = await iosTap(Math.round((match.tapCenter.x * scaleFactor) / dpr), Math.round((match.tapCenter.y * scaleFactor) / dpr), { udid });
@@ -887,7 +775,6 @@ async function tryOcrStrategy(query, platform, udid, sink, signal) {
887
775
  }
888
776
  }
889
777
  else {
890
- // Android: image-pixel → device-pixel (undo downscale), ADB accepts pixels
891
778
  await androidTap(Math.round(match.tapCenter.x * scaleFactor), Math.round(match.tapCenter.y * scaleFactor));
892
779
  }
893
780
  return {
@@ -917,8 +804,6 @@ async function tryCoordinateStrategy(pixelX, pixelY, platform, lastScreenshot, u
917
804
  if (platform === "ios") {
918
805
  const scaleFactor = lastScreenshot?.scaleFactor ?? 1;
919
806
  const { getDevicePixelRatio } = await import("../core/ios.js");
920
- // Reuse the caller's frame dimensions when it has them, so the first
921
- // coordinate tap of a session doesn't pay for an extra screenshot.
922
807
  const dprHint = lastScreenshot && lastScreenshot.originalWidth > 0 && lastScreenshot.originalHeight > 0
923
808
  ? { width: lastScreenshot.originalWidth, height: lastScreenshot.originalHeight }
924
809
  : undefined;
@@ -957,18 +842,7 @@ async function tryCoordinateStrategy(pixelX, pixelY, platform, lastScreenshot, u
957
842
  }
958
843
  const TAP_TIMEOUT_MS = 25000;
959
844
  const MIN_STRATEGY_BUDGET_MS = 500;
960
- // Per-strategy budget. OCR cap on Android is bumped via maxStrategyMs() because
961
- // the ADB screencap+pull leg has ~2s variance on real devices; iOS stays at 5s
962
- // where xcrun simctl screenshot is consistent.
963
- // Heavy strategies (fiber on deep trees with multi-depth retries, axe accessibility
964
- // dumps on dense iOS screens) need more headroom — previous caps produced spurious
965
- // timeouts that the agent read as "element missing" when the strategy simply didn't
966
- // get to finish. The overall TAP_TIMEOUT_MS budget still bounds the worst case.
967
- // Coordinate looks "light" (one tap subprocess) but on iOS it pairs an axe/idb
968
- // invocation with a CDP getDevicePixelRatio + best-effort fiber inspection;
969
- // the 3000ms cap surfaced as spurious "coordinate timed out" failures while
970
- // 20+ seconds of the overall budget were still unused. Align with the other
971
- // strategies — the global TAP_TIMEOUT_MS still bounds the worst case.
845
+ const OVERLAY_GUARD_BUDGET_MS = 3000;
972
846
  const MAX_STRATEGY_MS = {
973
847
  fiber: 8000,
974
848
  accessibility: 6000,
@@ -980,9 +854,6 @@ function maxStrategyMs(strategy, platform) {
980
854
  return 9000;
981
855
  return MAX_STRATEGY_MS[strategy] ?? 5000;
982
856
  }
983
- // Matches only the outer withTimeout wrapper message for a tap strategy.
984
- // Nested sub-operation errors inside a strategy (e.g. "CDP getProperties timed out after 150ms")
985
- // must NOT be classified as a tap-level timeout.
986
857
  const STRATEGY_TIMEOUT_RE = /^(fiber|accessibility|ocr|coordinate) timed out after \d+ms$/;
987
858
  export function isTapTimeout(attempted) {
988
859
  return attempted.some((a) => a.outcome === "timeout" || a.outcome === "skipped" || STRATEGY_TIMEOUT_RE.test(a.reason) || a.reason.startsWith("Skipped —"));
@@ -999,12 +870,6 @@ function withTimeout(promise, ms, label) {
999
870
  });
1000
871
  });
1001
872
  }
1002
- /**
1003
- * Like withTimeout, but creates an AbortController whose signal is handed to
1004
- * the inner factory. On timeout the signal aborts so subprocess work
1005
- * (uiautomator dump, OCR fetch, screencap) can be killed instead of running
1006
- * past the strategy cap and bleeding into total tap duration.
1007
- */
1008
873
  function withCancelableTimeout(make, ms, label) {
1009
874
  const ctrl = new AbortController();
1010
875
  const timer = setTimeout(() => ctrl.abort(), ms);
@@ -1015,15 +880,6 @@ function withCancelableTimeout(make, ms, label) {
1015
880
  throw err;
1016
881
  });
1017
882
  }
1018
- /**
1019
- * Compute marker coordinates in screenshot-pixel space (what the returned PNG uses).
1020
- * Unit rules by strategy, assuming screenshotScale = downscale factor:
1021
- * coordinate : input is already screenshot pixels → pass through
1022
- * ocr : match.tapCenter is image-pixel (= screenshot-pixel) → pass through
1023
- * iOS points : point * DPR / screenshotScale
1024
- * android dp : dp * densityScale / screenshotScale
1025
- * android devicePx : devicePx / screenshotScale
1026
- */
1027
883
  function computeMarkerPx(args) {
1028
884
  const { strategy, input, convertedTo, platform, screenshotScale, devicePixelRatio, androidDensityScale } = args;
1029
885
  const scale = screenshotScale || 1;
@@ -1036,36 +892,27 @@ function computeMarkerPx(args) {
1036
892
  return { x: Math.round(convertedTo.x), y: Math.round(convertedTo.y) };
1037
893
  }
1038
894
  if (platform === "ios") {
1039
- // fiber/accessibility on iOS return points
1040
895
  const dpr = devicePixelRatio || 3;
1041
896
  return {
1042
897
  x: Math.round((convertedTo.x * dpr) / scale),
1043
898
  y: Math.round((convertedTo.y * dpr) / scale)
1044
899
  };
1045
900
  }
1046
- // Android
1047
901
  if (strategy === "fiber+native" && androidDensityScale) {
1048
- // Fabric fiber returns dp; fiber+native path scales to device pixels before tap
1049
902
  return {
1050
903
  x: Math.round((convertedTo.x * androidDensityScale) / scale),
1051
904
  y: Math.round((convertedTo.y * androidDensityScale) / scale)
1052
905
  };
1053
906
  }
1054
- // accessibility android: convertedTo is device pixels
1055
907
  return {
1056
908
  x: Math.round(convertedTo.x / scale),
1057
909
  y: Math.round(convertedTo.y / scale)
1058
910
  };
1059
911
  }
1060
- // D2 (Step 6): in-memory ring buffer of recent coord-strategy artifact keys
1061
- // `${round(x/20)}:${round(y/20)}`. Entries expire after 60s. The first
1062
- // failed coord tap at a grid cell uploads its artifact; retries within the
1063
- // window get suppressed.
1064
912
  const recentCoordArtifacts = new Map();
1065
913
  const COORD_DEDUP_WINDOW_MS = 60_000;
1066
914
  const COORD_DEDUP_BUCKET = 20;
1067
915
  function shouldDedupArtifact(ctx) {
1068
- // Only applies to coordinate-strategy artifacts where we have x,y coords.
1069
916
  const winningStrat = ctx.attempted.find(a => a.reason === "success")?.strategy
1070
917
  ?? ctx.attempted[ctx.attempted.length - 1]?.strategy;
1071
918
  if (winningStrat !== "coordinate")
@@ -1076,7 +923,6 @@ function shouldDedupArtifact(ctx) {
1076
923
  return false;
1077
924
  const key = `${Math.round(x / COORD_DEDUP_BUCKET)}:${Math.round(y / COORD_DEDUP_BUCKET)}`;
1078
925
  const now = Date.now();
1079
- // Sweep stale entries opportunistically — keeps the map from growing.
1080
926
  for (const [k, ts] of recentCoordArtifacts) {
1081
927
  if (now - ts > COORD_DEDUP_WINDOW_MS)
1082
928
  recentCoordArtifacts.delete(k);
@@ -1087,47 +933,29 @@ function shouldDedupArtifact(ctx) {
1087
933
  recentCoordArtifacts.set(key, now);
1088
934
  return false;
1089
935
  }
1090
- /** Test-only: drop the dedup state so a fresh tap always uploads. */
1091
936
  export function resetCoordArtifactDedup() {
1092
937
  recentCoordArtifacts.clear();
1093
938
  }
1094
- /**
1095
- * Turn an unmeaningful coordinate tap into an explanation.
1096
- *
1097
- * The tricky part is coordinate spaces. The caller works in screenshot pixels;
1098
- * the tap was executed in points (iOS) or device pixels (Android); the screen
1099
- * state reports points/dp. We map the tapped point INTO screen-state space to
1100
- * do the hit test, and map the answer BACK into the caller's screenshot pixels
1101
- * so any suggested coordinates can be passed straight to another tap().
1102
- */
1103
939
  async function diagnoseCoordinateMiss(args) {
1104
940
  try {
1105
941
  const { getScreenState } = await import("../core/screenState.js");
1106
942
  const { explainCoordinateMiss } = await import("./coordinateMiss.js");
943
+ const { screenStateToScreenSpace, screenStateToDeliveredPx } = await import("../core/screenSpace.js");
944
+ const { resolveScreenSpaceMetrics } = await import("../core/screenSpaceDevice.js");
945
+ const { getConnectedAppByDevice, getFirstConnectedApp } = await import("../core/index.js");
1107
946
  const ss = await getScreenState({ device: args.deviceName });
1108
947
  if (!ss.success || !ss.screenState)
1109
948
  return undefined;
1110
- // Android taps are executed in device pixels; screen state speaks dp.
1111
- let pointInState = args.point;
1112
- if (args.pointUnit === "pixels") {
1113
- const { androidGetDensity } = await import("../core/android.js");
1114
- const density = await androidGetDensity();
1115
- const densityScale = (density.density || 420) / 160;
1116
- pointInState = { x: args.point.x / densityScale, y: args.point.y / densityScale };
1117
- }
1118
- // Ratio between screen-state space and the caller's screenshot pixels,
1119
- // derived from the conversion this very tap already performed. Uses the
1120
- // larger axis so a near-zero coordinate can't amplify rounding error.
1121
- const useY = Math.abs(pointInState.y) > Math.abs(pointInState.x);
1122
- const numerator = useY ? args.inputPoint.y : args.inputPoint.x;
1123
- const denominator = useY ? pointInState.y : pointInState.x;
1124
- const factor = denominator !== 0 && Number.isFinite(numerator / denominator)
1125
- ? numerator / denominator
1126
- : 1;
1127
- return explainCoordinateMiss(pointInState, ss.screenState, (v) => Math.round(v * factor)) ?? undefined;
949
+ const app = (args.deviceName ? getConnectedAppByDevice(args.deviceName) : null) ?? getFirstConnectedApp();
950
+ const metrics = await resolveScreenSpaceMetrics({
951
+ platform: app?.platform ?? "ios",
952
+ udid: app?.simulatorUdid,
953
+ deviceId: app?.adbSerial
954
+ });
955
+ const normalised = screenStateToDeliveredPx(screenStateToScreenSpace(ss.screenState, metrics), metrics);
956
+ return explainCoordinateMiss(args.inputPoint, normalised) ?? undefined;
1128
957
  }
1129
958
  catch {
1130
- // Diagnosis is best-effort — never let it turn a successful tap into an error.
1131
959
  return undefined;
1132
960
  }
1133
961
  }
@@ -1135,26 +963,15 @@ async function captureTapArtifact(ctx) {
1135
963
  try {
1136
964
  const { getServerVersion, categorizeError } = await import("../core/telemetry.js");
1137
965
  const strategyChain = ctx.attempted.map(a => `${a.strategy}:${a.reason.slice(0, 40)}`).join("|");
1138
- // Resolve error category up-front so we can short-circuit driver-missing failures.
1139
- // These aren't tap-tool bugs — they're host setup problems (no idb/axe/adb on PATH).
1140
- // Skipping the artifact upload saves R2 storage and stops these from polluting the
1141
- // failure dashboard, which is the signal we use to triage genuine tap regressions.
1142
966
  const errorCategory = ctx.errorCategory
1143
967
  ?? categorizeError(ctx.errorMessage ?? "", strategyChain);
1144
968
  if (errorCategory === "driver_missing") {
1145
969
  return undefined;
1146
970
  }
1147
- // D1 (Step 6): no-metro unmeaningful taps have no diagnostic signal
1148
- // beyond a screenshot diff that's already below threshold by definition.
1149
- // ~50% of recent failure-artifact rows were these — they crowd out real
1150
- // failures in the dashboard. Telemetry row still flows; just no R2 bundle.
1151
971
  const noMetro = ctx.evidence ? !ctx.evidence.fiber.metroConnected : connectedApps.size === 0;
1152
972
  if (ctx.outcome === "unmeaningful" && noMetro) {
1153
973
  return undefined;
1154
974
  }
1155
- // D2 (Step 6): retry deduplication — coord-strategy taps at the same
1156
- // grid cell within 60s upload the same near-identical artifact. The
1157
- // first capture is enough; subsequent ones are noise.
1158
975
  if (shouldDedupArtifact(ctx)) {
1159
976
  return undefined;
1160
977
  }
@@ -1250,7 +1067,6 @@ function attachArtifactSignals(result, signals) {
1250
1067
  result.accessibilityMatchCount = signals.accessibilityMatchCount;
1251
1068
  if (signals.appRoute)
1252
1069
  result.appRoute = signals.appRoute;
1253
- // Append agent-facing hints to error message
1254
1070
  if (result.error) {
1255
1071
  if (signals.ocrClosestMatch) {
1256
1072
  result.error = `${result.error}\nClosest OCR match: ${signals.ocrClosestMatch}`;
@@ -1267,7 +1083,6 @@ function attachArtifactSignals(result, signals) {
1267
1083
  }
1268
1084
  return result;
1269
1085
  }
1270
- // --- Orchestrator ---
1271
1086
  export async function tap(options) {
1272
1087
  const query = buildQuery(options);
1273
1088
  const strategy = options.strategy || "auto";
@@ -1275,7 +1090,6 @@ export async function tap(options) {
1275
1090
  const maxTraversalDepth = options.maxTraversalDepth;
1276
1091
  const deadline = Date.now() + TAP_TIMEOUT_MS;
1277
1092
  const remainingMs = () => Math.max(0, deadline - Date.now());
1278
- // Validate inputs
1279
1093
  const hasSearchParam = query.text || query.testID || query.component;
1280
1094
  const hasCoordinates = query.x !== undefined || query.y !== undefined;
1281
1095
  if (!hasSearchParam && !hasCoordinates) {
@@ -1292,7 +1106,6 @@ export async function tap(options) {
1292
1106
  error: "Both x and y coordinates must be provided"
1293
1107
  };
1294
1108
  }
1295
- // Native mode: bypass React Native connection, tap directly via ADB/simctl
1296
1109
  if (options.native && hasCoordinates) {
1297
1110
  const nativeResolved = await resolveDeviceTarget(options.device);
1298
1111
  if (!nativeResolved.ok) {
@@ -1305,7 +1118,6 @@ export async function tap(options) {
1305
1118
  const platform = nativeResolved.target.platform;
1306
1119
  const nativeUdid = nativeResolved.target.iosUdid;
1307
1120
  const nativeShouldScreenshot = options.screenshot !== false;
1308
- // Decoupled (I5, 2026-05-16): verify runs even when image bytes aren't returned.
1309
1121
  const nativeShouldVerify = options.verify !== false;
1310
1122
  let nativeBeforeBuffer = null;
1311
1123
  let nativeScreenshotMeta;
@@ -1320,7 +1132,6 @@ export async function tap(options) {
1320
1132
  };
1321
1133
  }
1322
1134
  }
1323
- // If no screenshot was taken for verification, take one just for scaleFactor
1324
1135
  if (!nativeScreenshotMeta) {
1325
1136
  const ref = await captureScreenshot(platform, nativeUdid);
1326
1137
  if (ref) {
@@ -1329,7 +1140,6 @@ export async function tap(options) {
1329
1140
  originalHeight: ref.height,
1330
1141
  scaleFactor: ref.scaleFactor
1331
1142
  };
1332
- // Also use it for verification if buffer is needed
1333
1143
  if (!nativeBeforeBuffer) {
1334
1144
  nativeBeforeBuffer = ref.buffer;
1335
1145
  }
@@ -1398,10 +1208,6 @@ export async function tap(options) {
1398
1208
  suggestion: `Take a screenshot (${platform === "ios" ? "ios_screenshot" : "android_screenshot"}) to verify coordinates.`
1399
1209
  });
1400
1210
  }
1401
- // Resolve device target via the unified resolver. Replaces the prior chain
1402
- // of (udid → connectedApp → findSimulatorByName) lookups and the standalone
1403
- // "no platform → probe OS for devices" branch. Returns a structured error
1404
- // when ambiguous; surface that to the caller so they can disambiguate.
1405
1211
  const resolved = await resolveDeviceTarget(options.device);
1406
1212
  if (!resolved.ok) {
1407
1213
  return {
@@ -1413,10 +1219,6 @@ export async function tap(options) {
1413
1219
  const deviceNote = resolved.note;
1414
1220
  const platform = resolved.target.platform;
1415
1221
  let targetUdid = resolved.target.iosUdid;
1416
- // Pick the connected app to bias strategy selection. Prefer the registry
1417
- // entry whose identifier matches the resolved target; fall back to a
1418
- // device-name match (handy when registry entries lack identifiers yet);
1419
- // last resort is the first connected app on the matching platform.
1420
1222
  const allApps = Array.from(connectedApps.values());
1421
1223
  let hasMetro = allApps.length > 0;
1422
1224
  let app;
@@ -1431,16 +1233,11 @@ export async function tap(options) {
1431
1233
  app = getConnectedAppByDevice(options.device) ?? undefined;
1432
1234
  }
1433
1235
  catch {
1434
- // getConnectedAppByDevice throws on ambiguous matches; the resolver
1435
- // would have already returned that error, so reaching here means
1436
- // the resolved target's identifier just doesn't appear in the
1437
- // registry yet — fall through to the platform-first pick below.
1438
1236
  }
1439
1237
  }
1440
1238
  if (!app) {
1441
1239
  app = allApps.find((a) => a.platform === platform) ?? allApps[0];
1442
1240
  }
1443
- // Try to auto-connect to Metro (for fiber strategy), but don't fail if it doesn't work.
1444
1241
  if (!hasMetro) {
1445
1242
  try {
1446
1243
  await withTimeout((async () => {
@@ -1463,26 +1260,17 @@ export async function tap(options) {
1463
1260
  }
1464
1261
  }
1465
1262
  catch {
1466
- // Auto-connect failed — Metro-dependent strategies will be skipped
1467
1263
  }
1468
1264
  }
1469
- // The response's `device` label must reflect the RESOLVED target, not the
1470
- // registry-app fallback. When the registry lookup couldn't find a matching
1471
- // app (e.g. simulatorUdid hadn't been backfilled yet), `app` lands on the
1472
- // first iOS entry — which on a multi-sim setup is the WRONG device. The
1473
- // resolver already knows the right deviceName; trust it. Bug #5 (2026-05-20).
1474
1265
  const deviceName = resolved.target.deviceName || app?.deviceInfo?.deviceName;
1475
- // Determine strategies
1476
1266
  const strategies = getAvailableStrategies(query, strategy);
1477
1267
  const attempted = [];
1478
1268
  const evidence = makeEmptyEvidenceSink();
1479
- // Early UI driver check for iOS — fail fast instead of falling through every strategy
1480
1269
  const UI_DRIVER_REQUIRED_STRATEGIES = ["accessibility", "ocr", "coordinate"];
1481
1270
  let uiDriverMissing = false;
1482
1271
  if (platform === "ios") {
1483
1272
  uiDriverMissing = !(await isUiDriverAvailable());
1484
1273
  }
1485
- // Filter strategies by available capabilities
1486
1274
  const filteredStrategies = strategies.filter((strat) => {
1487
1275
  if (strat === "fiber" && !hasMetro) {
1488
1276
  attempted.push({
@@ -1516,18 +1304,56 @@ export async function tap(options) {
1516
1304
  error: errorMessage
1517
1305
  };
1518
1306
  }
1519
- // Determine screenshot and verification behavior.
1520
- // Decoupled (I5, 2026-05-16): verify can run without returning image bytes —
1521
- // capture cost is paid either way; bandwidth cost is what `screenshot` toggles.
1307
+ let shadowWarning;
1308
+ if (hasMetro) {
1309
+ const guardBudget = Math.min(remainingMs(), OVERLAY_GUARD_BUDGET_MS);
1310
+ if (guardBudget >= MIN_STRATEGY_BUDGET_MS) {
1311
+ let blockedBy = null;
1312
+ try {
1313
+ const { checkOverlayBlocking } = await import("./overlayGuard.js");
1314
+ blockedBy = await withTimeout(checkOverlayBlocking({
1315
+ query,
1316
+ platform,
1317
+ udid: targetUdid,
1318
+ deviceId: resolved.target.androidSerial,
1319
+ deviceName
1320
+ }), guardBudget, "overlay-guard");
1321
+ }
1322
+ catch {
1323
+ blockedBy = null;
1324
+ }
1325
+ if (blockedBy?.kind === "blocked") {
1326
+ attempted.push({
1327
+ strategy: "overlay-guard",
1328
+ reason: `refused — target covered by ${blockedBy.overlay}`,
1329
+ outcome: "skipped"
1330
+ });
1331
+ return {
1332
+ success: false,
1333
+ query,
1334
+ attempted,
1335
+ device: deviceName,
1336
+ platform,
1337
+ error: `Refused: ${blockedBy.element} is covered by ${blockedBy.overlay}. ` +
1338
+ `A tap here would be delivered to the overlay, not to the target, ` +
1339
+ `so it was not dispatched.`,
1340
+ suggestion: `Dismiss or close the overlay first, then retry. ` +
1341
+ `get_screen_state lists the overlay's own controls, and everything behind it under "🚫 Blocked by overlay". ` +
1342
+ `To tap the overlay itself, use coordinates from its own group.`
1343
+ };
1344
+ }
1345
+ if (blockedBy?.kind === "shadowed") {
1346
+ shadowWarning =
1347
+ `This coordinate is occupied by ${blockedBy.hit}, part of ${blockedBy.overlay} — ` +
1348
+ `that is what received the tap. ${blockedBy.covered} sits underneath it and ` +
1349
+ `cannot be tapped until the overlay closes.`;
1350
+ }
1351
+ }
1352
+ }
1522
1353
  const shouldScreenshot = options.screenshot !== false;
1523
1354
  const canVerify = options.verify !== false;
1524
- // Capture "before" screenshot. Always attempted (independent of canVerify)
1525
- // so failure artifacts always carry a before.png — the diagnostic value of
1526
- // the before frame doesn't depend on whether we run the post-tap diff.
1527
1355
  let beforeBuffer = null;
1528
1356
  let beforeScaleFactor;
1529
- // Pixel dimensions of the frame we just captured. getDevicePixelRatio would
1530
- // otherwise shell out for a screenshot of its own to learn exactly this.
1531
1357
  let beforeDims;
1532
1358
  {
1533
1359
  const before = await captureScreenshot(platform, targetUdid);
@@ -1537,16 +1363,6 @@ export async function tap(options) {
1537
1363
  beforeDims = { width: before.width, height: before.height };
1538
1364
  }
1539
1365
  }
1540
- // OCR runs lazily when the loop reaches it (see the `case "ocr"` branch).
1541
- // A concurrent pre-warm probe used to fire here to shave ~5s off OCR-win
1542
- // rows, but it dispatched a paid Google Vision request at t=0 on every
1543
- // text-predicate tap — and since cloud OCR (~200ms) finishes well before
1544
- // the higher-priority strategy that usually wins, the post-win abort came
1545
- // too late to cancel the billed request. OCR wins only ~1.9% of taps, so
1546
- // the pre-warm paid for cloud Vision on ~42% of eligible taps to help a
1547
- // tiny minority. Removed 2026-06-02; the timeout-recovery path below still
1548
- // salvages the perfect-match-past-cap case.
1549
- // Execute strategies in order with per-strategy caps and overall budget
1550
1366
  for (const strat of filteredStrategies) {
1551
1367
  const remaining = remainingMs();
1552
1368
  if (remaining < MIN_STRATEGY_BUDGET_MS) {
@@ -1563,8 +1379,6 @@ export async function tap(options) {
1563
1379
  try {
1564
1380
  switch (strat) {
1565
1381
  case "fiber":
1566
- // Fiber is JS-only against a CDP target — no subprocess to cancel,
1567
- // so the cheaper non-cancellable wrapper is fine.
1568
1382
  result = await withTimeout(tryFiberStrategy(query, index, maxTraversalDepth, evidence, deviceName), budget, `fiber`);
1569
1383
  break;
1570
1384
  case "accessibility":
@@ -1574,9 +1388,6 @@ export async function tap(options) {
1574
1388
  result = await withCancelableTimeout((signal) => tryOcrStrategy(query, platform, targetUdid, evidence, signal), budget, `ocr`);
1575
1389
  break;
1576
1390
  case "coordinate":
1577
- // Prefer `beforeScaleFactor` (captured against `targetUdid` this turn)
1578
- // over `app.lastScreenshot.scaleFactor` (stale and may belong to a
1579
- // different device on a multi-sim setup). Bug #5 (2026-05-20).
1580
1391
  result = await withTimeout(tryCoordinateStrategy(query.x, query.y, platform, beforeScaleFactor != null
1581
1392
  ? {
1582
1393
  originalWidth: beforeDims?.width ?? 0,
@@ -1591,11 +1402,6 @@ export async function tap(options) {
1591
1402
  }
1592
1403
  catch (err) {
1593
1404
  const reason = err instanceof Error ? err.message : String(err);
1594
- // Classify the throw: a strategy-level timeout means "result UNKNOWN"
1595
- // (the strategy didn't finish), distinct from "ran clean and found
1596
- // nothing". Agents must not infer absence from timeouts. The
1597
- // `outcome` field carries that distinction; `reason` stays the raw
1598
- // message so existing log-matchers / classifiers keep working.
1599
1405
  const outcome = STRATEGY_TIMEOUT_RE.test(reason) ? "timeout" : "error";
1600
1406
  attempted.push({ strategy: strat, reason, outcome });
1601
1407
  continue;
@@ -1630,11 +1436,6 @@ export async function tap(options) {
1630
1436
  beforeScaleFactor,
1631
1437
  markerPx: strategyMarker
1632
1438
  }));
1633
- // Burst always produces a screenshot for diff visualization. Drop the
1634
- // bytes when the caller didn't ask for them AND the tap was meaningful
1635
- // — keeping the screenshot on unmeaningful taps saves the agent a
1636
- // round-trip when it needs to diagnose. Frames remain in imageBuffer
1637
- // either way (accessible via get_images(groupId=verification.burstGroupId)).
1638
1439
  if (!shouldScreenshot && verification?.meaningful !== false)
1639
1440
  screenshot = undefined;
1640
1441
  }
@@ -1648,8 +1449,6 @@ export async function tap(options) {
1648
1449
  beforeScaleFactor,
1649
1450
  markerPx: strategyMarker
1650
1451
  }));
1651
- // Mirror the burst-path gate: drop the screenshot only when the
1652
- // caller didn't ask for it AND the tap was meaningful.
1653
1452
  if (!shouldScreenshot && verification?.meaningful !== false)
1654
1453
  screenshot = undefined;
1655
1454
  }
@@ -1674,18 +1473,12 @@ export async function tap(options) {
1674
1473
  screenshot,
1675
1474
  verification
1676
1475
  });
1677
- // Capture an artifact for "successful but unmeaningful" taps so we can
1678
- // diagnose taps that landed wrong or hit non-responsive elements.
1679
- // Uses the same `meaningful` flag the agent sees so dashboard outcomes
1680
- // stay consistent with what the caller observed.
1476
+ if (shadowWarning) {
1477
+ successResult.warning = successResult.warning
1478
+ ? `${successResult.warning}\n${shadowWarning}`
1479
+ : shadowWarning;
1480
+ }
1681
1481
  if (verification && !verification.skipped && verification.meaningful === false) {
1682
- // A coordinate tap that changed nothing is the one case where the
1683
- // agent has no idea what went wrong — it estimated x/y from a
1684
- // screenshot and got back "nothing happened". Screen state knows
1685
- // what sits at that point and what an overlay blocks; spend the
1686
- // ~200ms here (already the slow path) to turn a dead end into a
1687
- // next step. Text/testID/component predicates don't need this:
1688
- // their failure modes are already named in `attempted`.
1689
1482
  if (strat === "coordinate" && result.convertedTo) {
1690
1483
  const diagnosis = await diagnoseCoordinateMiss({
1691
1484
  point: { x: result.convertedTo.x, y: result.convertedTo.y },
@@ -1721,10 +1514,6 @@ export async function tap(options) {
1721
1514
  }
1722
1515
  return successResult;
1723
1516
  }
1724
- // Classify the strategy's non-success result. The reason string carries
1725
- // the diagnostic from the strategy; outcome lets agents branch on category
1726
- // without parsing prose. "invisible" / "ambiguous" are pinned by substring
1727
- // matches the fiber strategy emits via pressables.ts.
1728
1517
  const reasonStr = result.reason || "";
1729
1518
  let attemptOutcome = "not-found";
1730
1519
  if (result.ambiguous)
@@ -1734,36 +1523,26 @@ export async function tap(options) {
1734
1523
  else if (STRATEGY_TIMEOUT_RE.test(reasonStr))
1735
1524
  attemptOutcome = "timeout";
1736
1525
  attempted.push({ strategy: strat, reason: reasonStr, outcome: attemptOutcome });
1737
- // If fiber found an element with measured coordinates, do a native tap directly
1738
1526
  if (strat === "fiber" && result.convertedTo && result.pressed) {
1739
1527
  try {
1740
1528
  const coords = result.convertedTo;
1741
1529
  if (platform === "ios") {
1742
- // react-native-screens modal/sheet presentations cause measureInWindow to
1743
- // return y relative to the screen's content origin, not the window. If the
1744
- // measured y falls inside the safe-area band, shift it down by the inset.
1745
1530
  const safeAreaTop = await getIOSSafeAreaTop(targetUdid);
1746
1531
  const tapY = (safeAreaTop > 0 && coords.y < safeAreaTop) ? coords.y + safeAreaTop : coords.y;
1747
1532
  await iosTap(coords.x, tapY, { udid: targetUdid });
1748
1533
  coords.y = tapY;
1749
1534
  }
1750
1535
  else {
1751
- // Fabric returns dp — androidTap expects pixels
1752
- // Convert dp to pixels using device density
1753
1536
  const { androidGetDensity } = await import("../core/android.js");
1754
1537
  const densityResult = await androidGetDensity();
1755
1538
  const densityScale = (densityResult.density || 420) / 160;
1756
1539
  const pxX = Math.round(coords.x * densityScale);
1757
1540
  const pxY = Math.round(coords.y * densityScale);
1758
1541
  await androidTap(pxX, pxY);
1759
- // Report the actual tap coords (pixels) the caller can pass straight
1760
- // to coordinate tools / verification — not the raw dp from fiber.
1761
- // Matches every other Android coord report in the tool (OB1, 2026-05-20).
1762
1542
  coords.x = pxX;
1763
1543
  coords.y = pxY;
1764
1544
  coords.unit = "pixels";
1765
1545
  }
1766
- // fiber+native uses native tap — always verify
1767
1546
  let screenshot;
1768
1547
  let verification;
1769
1548
  let fnDpr;
@@ -1840,12 +1619,8 @@ export async function tap(options) {
1840
1619
  });
1841
1620
  }
1842
1621
  catch {
1843
- // Native tap at fiber coordinates failed — continue to next strategy
1844
1622
  }
1845
1623
  }
1846
- // Ambiguous fiber result — multiple elements matched, no index given.
1847
- // Return immediately with the full list so the agent can decide.
1848
- // Do NOT fall through to other strategies (they can't resolve ambiguity).
1849
1624
  if (result.matches && result.ambiguous) {
1850
1625
  const { screenshot: matchScreenshot } = shouldScreenshot
1851
1626
  ? await verifyAndCapture({ platform, shouldVerify: false, shouldScreenshot: true, beforeBuffer: null, udid: targetUdid })
@@ -1869,15 +1644,8 @@ export async function tap(options) {
1869
1644
  });
1870
1645
  }
1871
1646
  }
1872
- // All strategies failed — check if the tap budget was the cause.
1873
- // Only outer withTimeout wrapper messages or Skipped entries count; nested
1874
- // sub-op errors that happen to contain "timed out" do not imply the tap
1875
- // itself ran out of time.
1876
1647
  const hitTimeout = isTapTimeout(attempted);
1877
1648
  const elapsed = TAP_TIMEOUT_MS - remainingMs();
1878
- // If fiber located the element but visibility filtered it out, prepend a
1879
- // scroll-or-dismiss hint so the agent doesn't waste a turn assuming the
1880
- // testID/component is missing.
1881
1649
  const fiberSawInvisible = attempted.some(a => a.strategy === "fiber" && a.outcome === "invisible");
1882
1650
  const allTimedOut = attempted.length > 0 && attempted.every(a => a.outcome === "timeout" || a.outcome === "skipped");
1883
1651
  let suggestion = buildSuggestion(query, strategies, platform);
@@ -1885,8 +1653,6 @@ export async function tap(options) {
1885
1653
  suggestion = `Element exists in the React tree but is not on screen. Try scrolling it into view with swipe(), dismiss any overlay covering it, or wait for layout to settle. ` + suggestion;
1886
1654
  }
1887
1655
  else if (allTimedOut) {
1888
- // No strategy got to a definitive answer — don't let the agent conclude
1889
- // the element is missing. Steer toward retry or a different strategy.
1890
1656
  suggestion = `All strategies timed out — the element's presence is UNKNOWN. Retry the tap (transient slowness is common on dense screens), or try a different strategy explicitly (e.g. strategy='fiber' if accessibility timed out). ` + suggestion;
1891
1657
  }
1892
1658
  const { screenshot: failScreenshot } = shouldScreenshot
@@ -1899,8 +1665,6 @@ export async function tap(options) {
1899
1665
  scaleFactor: failScreenshot.scaleFactor
1900
1666
  };
1901
1667
  }
1902
- // Pick the right error framing: a definitive "not found" is different from
1903
- // "all strategies timed out — we don't know whether it exists".
1904
1668
  let errorOverride;
1905
1669
  if (hitTimeout) {
1906
1670
  errorOverride = `Tap timed out after ${elapsed}ms (budget ${TAP_TIMEOUT_MS}ms)`;
@@ -1935,10 +1699,6 @@ export async function tap(options) {
1935
1699
  });
1936
1700
  return attachArtifactSignals(failureResult, failSignals);
1937
1701
  }
1938
- // Matches testIDs typical of virtualized list items: trailing `-<digits>` (e.g. `store-item-0`,
1939
- // `route-item-6`) or containing a UUID v4 fragment (e.g. `group-card-e4566bc6-9164-4fd0-...`).
1940
- // Used by buildSuggestion() to tell the agent to scroll first when a list-item testID
1941
- // can't be found in the visible fiber tree.
1942
1702
  const VIRTUALIZED_TESTID_RE = /(-\d+$)|([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
1943
1703
  function looksLikeVirtualizedListItem(testID) {
1944
1704
  return VIRTUALIZED_TESTID_RE.test(testID);
@@ -1960,10 +1720,6 @@ function buildSuggestion(query, triedStrategies, platform) {
1960
1720
  if (query.testID && !triedStrategies.includes("ocr")) {
1961
1721
  suggestions.push("testID not found in fiber/accessibility tree — verify the element is on the current screen with a screenshot");
1962
1722
  }
1963
- // List-item testIDs (suffix `-N` or containing a UUID) are typically rendered inside a
1964
- // virtualized list — items beyond the viewport are unmounted, so the fiber tree genuinely
1965
- // doesn't contain them. Adding the hint as a separate suggestion (rather than rewriting
1966
- // the error) keeps the existing message stable for tooling that parses it.
1967
1723
  if (query.testID && looksLikeVirtualizedListItem(query.testID)) {
1968
1724
  suggestions.push("testID looks like a virtualized list item — scroll it on-screen first " +
1969
1725
  `(swipe()${platform === "ios" ? " or ios_button arrow keys" : ""}) before tapping`);