rnxsim 0.1.366 → 0.1.367

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 (239) hide show
  1. package/README.md +14 -2
  2. package/cli/bin.ts +18 -0
  3. package/cli/bridge-flow-runner.ts +14 -151
  4. package/cli/commands/assert.ts +1 -1
  5. package/cli/commands/box/checkout-plane.ts +126 -0
  6. package/cli/commands/box/rnx-command.ts +68 -0
  7. package/cli/commands/box.ts +200 -0
  8. package/cli/commands/control.ts +12 -32
  9. package/cli/commands/do-chain.ts +412 -0
  10. package/cli/commands/flow.ts +0 -10
  11. package/cli/commands/inspect/core.ts +302 -725
  12. package/cli/commands/inspect/describe.ts +3 -76
  13. package/cli/commands/inspect/find.ts +3 -6
  14. package/cli/commands/inspect/get-layout.ts +14 -1
  15. package/cli/commands/inspect/resolve-target.ts +41 -47
  16. package/cli/commands/inspect/shared.ts +28 -123
  17. package/cli/commands/inspect/tree.ts +6 -15
  18. package/cli/commands/inspect/wait-ready.ts +2 -2
  19. package/cli/commands/inspect/wait-selector.ts +4 -8
  20. package/cli/commands/inspect.ts +23 -17
  21. package/cli/commands/install-desktop.ts +44 -7
  22. package/cli/commands/maestro-generate.ts +0 -2
  23. package/cli/commands/platform.ts +68 -0
  24. package/cli/commands/state.ts +5 -2
  25. package/cli/outbound-endpoints.ts +6 -1
  26. package/cli/parse-args.ts +13 -2
  27. package/cli/ws-bridge.ts +30 -2
  28. package/detox/index.ts +34 -17
  29. package/dist-cli/bin.js +5 -5
  30. package/dist-cli/chunks/{agent-SO6IOPRK.js → agent-EKKIAKP5.js} +2 -2
  31. package/dist-cli/chunks/{agent-wrapper-PMYCB2KO.js → agent-wrapper-MHLQUGA6.js} +2 -2
  32. package/dist-cli/chunks/{app-fonts-LULQGFY7.js → app-fonts-HN6ADRTQ.js} +2 -2
  33. package/dist-cli/chunks/{assert-WGNCD4ZU.js → assert-IMR6QCN6.js} +3 -3
  34. package/dist-cli/chunks/{auth-G7CCHH3M.js → auth-27LPMQNH.js} +2 -2
  35. package/dist-cli/chunks/{beta-N4LXL5TO.js → beta-6RXBB5AI.js} +2 -2
  36. package/dist-cli/chunks/box-DORLF3GP.js +1709 -0
  37. package/dist-cli/chunks/{camera-4JHCYBQS.js → camera-2G7JZ5DL.js} +2 -2
  38. package/dist-cli/chunks/{chunk-2UXU53MG.js → chunk-3QZJLNYY.js} +1 -1
  39. package/dist-cli/chunks/{chunk-YXXKSL6R.js → chunk-3VBVEDDA.js} +1 -1
  40. package/dist-cli/chunks/{chunk-ZJIS3CTW.js → chunk-4BWGLTKU.js} +2 -2
  41. package/dist-cli/chunks/chunk-4XSNO374.js +423 -0
  42. package/dist-cli/chunks/{chunk-RYIMSKDO.js → chunk-5DGMFUZG.js} +2 -2
  43. package/dist-cli/chunks/{chunk-M3BNCF7E.js → chunk-6DBS5EGX.js} +2 -2
  44. package/dist-cli/chunks/{chunk-ONORMKWX.js → chunk-6SO6I3D5.js} +2 -2
  45. package/dist-cli/chunks/chunk-75EFOPXE.js +32 -0
  46. package/dist-cli/chunks/{chunk-5NMCF5VF.js → chunk-A5AJFVOF.js} +1 -1
  47. package/dist-cli/chunks/{chunk-N3UC7HIX.js → chunk-AM2ODYAN.js} +1 -1
  48. package/dist-cli/chunks/{chunk-I6KIM6PN.js → chunk-AVOROGA3.js} +1 -1
  49. package/dist-cli/chunks/chunk-AXMMLO7D.js +130 -0
  50. package/dist-cli/chunks/{chunk-ORWP2E7R.js → chunk-BSEXEYNX.js} +2 -2
  51. package/dist-cli/chunks/{chunk-NQFJOCSF.js → chunk-CM6ZKZW7.js} +2 -2
  52. package/dist-cli/chunks/{chunk-ZMJ75EDG.js → chunk-DNVABCEC.js} +2 -2
  53. package/dist-cli/chunks/{chunk-2S7BKUP2.js → chunk-E33A4AIR.js} +2 -2
  54. package/dist-cli/chunks/{chunk-GXAB52K7.js → chunk-EFMOXYDD.js} +2 -2
  55. package/dist-cli/chunks/{chunk-TIAEYO6I.js → chunk-EV4P5UBP.js} +2 -2
  56. package/dist-cli/chunks/{chunk-JHXETDVC.js → chunk-G5IUIGGC.js} +1 -1
  57. package/dist-cli/chunks/chunk-GMLOCV3E.js +312 -0
  58. package/dist-cli/chunks/{chunk-ZLVRYXZ7.js → chunk-GRN2FHVW.js} +1 -1
  59. package/dist-cli/chunks/{chunk-A5FXX5PJ.js → chunk-HFFXPUEP.js} +1 -1
  60. package/dist-cli/chunks/chunk-HG6T4OOR.js +4 -0
  61. package/dist-cli/chunks/{chunk-OW5XHAA4.js → chunk-HNT2OXPZ.js} +1 -1
  62. package/dist-cli/chunks/chunk-IPEURNLI.js +24 -0
  63. package/dist-cli/chunks/chunk-IR44SFBA.js +5 -0
  64. package/dist-cli/chunks/chunk-JMSLXZFW.js +4 -0
  65. package/dist-cli/chunks/{chunk-EFR5DVNP.js → chunk-JPXQKLNH.js} +1 -1
  66. package/dist-cli/chunks/chunk-K3EC6JDE.js +4 -0
  67. package/dist-cli/chunks/{chunk-YGMEDJKJ.js → chunk-KHX65HAU.js} +2 -2
  68. package/dist-cli/chunks/{chunk-EQK5MJCU.js → chunk-LWOVKHRC.js} +2 -2
  69. package/dist-cli/chunks/{chunk-5OK3LCEJ.js → chunk-MQN5DWTI.js} +2 -2
  70. package/dist-cli/chunks/{chunk-TTASWLKM.js → chunk-N3BXQEUF.js} +1 -1
  71. package/dist-cli/chunks/{chunk-UCHW7ZHR.js → chunk-N5OSV26B.js} +2 -2
  72. package/dist-cli/chunks/chunk-PHNURIH3.js +4 -0
  73. package/dist-cli/chunks/{chunk-AFV32WZT.js → chunk-PP2KNVXP.js} +3 -3
  74. package/dist-cli/chunks/{chunk-XJU2QIRY.js → chunk-PROKNJVY.js} +2 -2
  75. package/dist-cli/chunks/chunk-QXLLY4IM.js +7 -0
  76. package/dist-cli/chunks/{chunk-HT6GFZOZ.js → chunk-RNQTOGM7.js} +1 -1
  77. package/dist-cli/chunks/{chunk-CN6XYYEA.js → chunk-RPNKKVBN.js} +27 -27
  78. package/dist-cli/chunks/{chunk-LN3RNW6X.js → chunk-RSNLI2NH.js} +2 -2
  79. package/dist-cli/chunks/{chunk-YCCCPFWT.js → chunk-RZGHV2WI.js} +2 -2
  80. package/dist-cli/chunks/chunk-SGGL3AFV.js +497 -0
  81. package/dist-cli/chunks/chunk-SH5ETJKG.js +4 -0
  82. package/dist-cli/chunks/{chunk-77ZHQJZR.js → chunk-T6J4YP56.js} +2 -2
  83. package/dist-cli/chunks/{chunk-MGDSJZ37.js → chunk-TWUE7WF5.js} +2 -2
  84. package/dist-cli/chunks/{chunk-SA6XVI55.js → chunk-U44VEQRN.js} +1 -1
  85. package/dist-cli/chunks/{chunk-IZKHOM37.js → chunk-URLVQHC3.js} +1 -1
  86. package/dist-cli/chunks/{chunk-GAJQSITE.js → chunk-VANZFTUE.js} +1 -1
  87. package/dist-cli/chunks/{chunk-EGAGZHO6.js → chunk-VB2YBH46.js} +1 -1
  88. package/dist-cli/chunks/{chunk-H23KWIKA.js → chunk-VFU2SJ7M.js} +2 -2
  89. package/dist-cli/chunks/{chunk-34IQM22N.js → chunk-VUBJ6FXU.js} +15 -15
  90. package/dist-cli/chunks/{chunk-NM2L2QV4.js → chunk-W5PATILQ.js} +2 -2
  91. package/dist-cli/chunks/chunk-WI5V4TYG.js +38 -0
  92. package/dist-cli/chunks/{chunk-6PL2CCGP.js → chunk-XTRHQAOV.js} +2 -2
  93. package/dist-cli/chunks/chunk-XWUTJLBM.js +27 -0
  94. package/dist-cli/chunks/{chunk-5DAIA77C.js → chunk-XYRUQQUR.js} +2 -2
  95. package/dist-cli/chunks/chunk-YBFB3RM7.js +4 -0
  96. package/dist-cli/chunks/chunk-YGQVQ2VG.js +4 -0
  97. package/dist-cli/chunks/{chunk-CTG7HBCA.js → chunk-YINACV32.js} +1 -1
  98. package/dist-cli/chunks/chunk-YV7ERO2V.js +5 -0
  99. package/dist-cli/chunks/{cleanup-KRIATKFH.js → cleanup-GVFHQ3KH.js} +2 -2
  100. package/dist-cli/chunks/cli-version-DPX5MA2M.js +4 -0
  101. package/dist-cli/chunks/{compat-6PTQ3QEA.js → compat-AYRTXYAM.js} +2 -2
  102. package/dist-cli/chunks/{config-ETGDUANO.js → config-K7LOOV62.js} +2 -2
  103. package/dist-cli/chunks/connect-H3CNY4IO.js +4 -0
  104. package/dist-cli/chunks/control-SMHZ2CF5.js +4 -0
  105. package/dist-cli/chunks/daemon-TEZUNI73.js +4 -0
  106. package/dist-cli/chunks/{debug-WIIB6ZKC.js → debug-ELGHG57T.js} +3 -3
  107. package/dist-cli/chunks/{desktop-TD7Q5LNB.js → desktop-4G7FK3MP.js} +3 -3
  108. package/dist-cli/chunks/{detox-7GNR33VM.js → detox-JKXGEJIZ.js} +2 -2
  109. package/dist-cli/chunks/{device-QOR3PGUF.js → device-IV3AR6KG.js} +2 -2
  110. package/dist-cli/chunks/{diagnose-RRCBLP6E.js → diagnose-3ERGF7EW.js} +2 -2
  111. package/dist-cli/chunks/{disk-cleanup-MJUGJPXT.js → disk-cleanup-ALFEVZ4B.js} +2 -2
  112. package/dist-cli/chunks/do-chain-K2B4OHM7.js +5 -0
  113. package/dist-cli/chunks/drivers-KJYXA3M3.js +4 -0
  114. package/dist-cli/chunks/env-2KF6IYZE.js +4 -0
  115. package/dist-cli/chunks/{film-D7Y64LID.js → film-UQXJFAD5.js} +3 -3
  116. package/dist-cli/chunks/flow-ASTUH56E.js +4 -0
  117. package/dist-cli/chunks/help-SNNYFELA.js +4 -0
  118. package/dist-cli/chunks/{hidden-runtime-alias-JGURVR2Z.js → hidden-runtime-alias-VJCWCFVO.js} +2 -2
  119. package/dist-cli/chunks/{home-paths-4ICFIGXO.js → home-paths-EGUZZ6J6.js} +2 -2
  120. package/dist-cli/chunks/inspect-XANAADQT.js +4 -0
  121. package/dist-cli/chunks/install-desktop-JKQA7UY6.js +4 -0
  122. package/dist-cli/chunks/{login-RU33QR2G.js → login-6KQGD7HK.js} +2 -2
  123. package/dist-cli/chunks/{logout-2IUG3Y5J.js → logout-2FCOVLBC.js} +2 -2
  124. package/dist-cli/chunks/{maestro-2MXTGG7I.js → maestro-HPSSEVJD.js} +3 -3
  125. package/dist-cli/chunks/{maestro-generate-2PYIFGLR.js → maestro-generate-KQQ6JHMR.js} +5 -5
  126. package/dist-cli/chunks/{mode-3DAPG3HF.js → mode-SH7KRZET.js} +2 -2
  127. package/dist-cli/chunks/{optional-demo-registry-BWSDJF2Z.js → optional-demo-registry-NAMCAVFX.js} +2 -2
  128. package/dist-cli/chunks/{perf-2TKPWB4N.js → perf-LWKAHXUL.js} +2 -2
  129. package/dist-cli/chunks/{permissions-5CSMHYNA.js → permissions-EZTKWTXV.js} +2 -2
  130. package/dist-cli/chunks/platform-KSOJIVN5.js +6 -0
  131. package/dist-cli/chunks/privacy-EJULWP7L.js +4 -0
  132. package/dist-cli/chunks/{record-I2HJCFP3.js → record-2IC7BAQN.js} +3 -3
  133. package/dist-cli/chunks/{report-issue-A4FZWBOC.js → report-issue-MMX6HOQO.js} +2 -2
  134. package/dist-cli/chunks/reset-PKFZA4NC.js +4 -0
  135. package/dist-cli/chunks/resolve-target-AFKU3BRE.js +4 -0
  136. package/dist-cli/chunks/runtime-XHWWPOIN.js +4 -0
  137. package/dist-cli/chunks/{screenshot-command-UFW5BFHP.js → screenshot-command-JYUXDVSA.js} +3 -3
  138. package/dist-cli/chunks/{screenshot-layers-5BIAL4YN.js → screenshot-layers-OGRY6AA3.js} +2 -2
  139. package/dist-cli/chunks/{screenshots-capture-EHYS3BV2.js → screenshots-capture-QHDN65XS.js} +2 -2
  140. package/dist-cli/chunks/{serve-O4FDM3AC.js → serve-W4BKAG4J.js} +3 -3
  141. package/dist-cli/chunks/setup-WJPMPSAC.js +4 -0
  142. package/dist-cli/chunks/sim-client-UOQGFBPO.js +4 -0
  143. package/dist-cli/chunks/{skills-ZK55SY32.js → skills-OPPXOD63.js} +2 -2
  144. package/dist-cli/chunks/state-TDAN7BOM.js +14 -0
  145. package/dist-cli/chunks/{storage-WP33ISEM.js → storage-CFB2YGLY.js} +2 -2
  146. package/dist-cli/chunks/store-4TEED4ZU.js +4 -0
  147. package/dist-cli/chunks/telemetry-S6N7CV6G.js +4 -0
  148. package/dist-cli/chunks/{timeline-DKGW5S4I.js → timeline-ASOOVP4C.js} +2 -2
  149. package/dist-cli/chunks/{upgrade-LL7G4B7G.js → upgrade-6MVJISHG.js} +2 -2
  150. package/dist-cli/chunks/upload-CBNIRFEW.js +4 -0
  151. package/dist-cli/chunks/{version-YLWR5G4F.js → version-5GS7CWRL.js} +2 -2
  152. package/dist-cli/chunks/{web-QELZ4CP6.js → web-QRRMYMV4.js} +2 -2
  153. package/dist-cli/chunks/welcome-33FXMAMB.js +5 -0
  154. package/dist-cli/chunks/{what-happened-MIOMZWM5.js → what-happened-GAZJGKXX.js} +2 -2
  155. package/dist-cli/chunks/ws-bridge-DIXUAPLQ.js +4 -0
  156. package/dist-lib/agent-daemon-client.cjs +1 -1
  157. package/dist-lib/agent-events.cjs +1 -1
  158. package/dist-lib/agent-identity.cjs +1 -1
  159. package/dist-lib/agent-sessions.cjs +1 -1
  160. package/dist-lib/attached-projects.cjs +1 -1
  161. package/dist-lib/auth/shared-session.cjs +1 -1
  162. package/dist-lib/backend-origin.cjs +1 -1
  163. package/dist-lib/beta.cjs +1 -1
  164. package/dist-lib/beta.mjs +1 -1
  165. package/dist-lib/bridge-constants.cjs +1 -1
  166. package/dist-lib/bridge-contract-input.cjs +336 -0
  167. package/dist-lib/bridge-contract-input.mjs +303 -0
  168. package/dist-lib/bridge-contract.cjs +29 -1
  169. package/dist-lib/bridge-contract.mjs +19 -0
  170. package/dist-lib/cli-constants.cjs +1 -1
  171. package/dist-lib/config.cjs +1 -1
  172. package/dist-lib/detox/index.cjs +32 -18
  173. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  174. package/dist-lib/home-paths.cjs +1 -1
  175. package/dist-lib/host/bridge-host.cjs +5 -1
  176. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  177. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  178. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  179. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  180. package/dist-lib/host/websocket-proxy.cjs +1 -1
  181. package/dist-lib/index.cjs +214 -594
  182. package/dist-lib/jump-to-source-babel.cjs +1 -1
  183. package/dist-lib/menu.cjs +1 -1
  184. package/dist-lib/menu.mjs +1 -1
  185. package/dist-lib/metro-production-bundle.cjs +1 -1
  186. package/dist-lib/metro-production-bundle.mjs +1 -1
  187. package/dist-lib/metro.cjs +1 -1
  188. package/dist-lib/profiles.cjs +1 -1
  189. package/dist-lib/public-brand.cjs +1 -1
  190. package/dist-lib/react-native-host-modules.cjs +1 -1
  191. package/dist-lib/react-native-host-modules.mjs +1 -1
  192. package/dist-lib/render-mode.cjs +1 -1
  193. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  194. package/dist-lib/sdk.cjs +273 -657
  195. package/dist-lib/sdk.mjs +271 -652
  196. package/dist-lib/skills.cjs +190 -287
  197. package/dist-lib/vite.cjs +1 -1
  198. package/package.json +9 -6
  199. package/skills/contrast/SKILL.md +3 -0
  200. package/skills/rnx-debug/SKILL.md +8 -0
  201. package/skills/rnx-test/SKILL.md +21 -0
  202. package/src/bridge-contract-input.ts +359 -0
  203. package/src/bridge-contract.ts +121 -1
  204. package/src/host/bridge-host.ts +7 -12
  205. package/src/index.ts +7 -0
  206. package/src/sim-client.ts +12 -3
  207. package/dist-cli/chunks/chunk-3X6IBUBX.js +0 -38
  208. package/dist-cli/chunks/chunk-7B37IZBN.js +0 -15
  209. package/dist-cli/chunks/chunk-DMPF2D24.js +0 -4
  210. package/dist-cli/chunks/chunk-DW37EVX5.js +0 -767
  211. package/dist-cli/chunks/chunk-DWFIH7CB.js +0 -32
  212. package/dist-cli/chunks/chunk-GUI3PDH2.js +0 -27
  213. package/dist-cli/chunks/chunk-KLF3K6ZZ.js +0 -4
  214. package/dist-cli/chunks/chunk-M5QCGLXR.js +0 -944
  215. package/dist-cli/chunks/chunk-M6SKQGAN.js +0 -5
  216. package/dist-cli/chunks/chunk-NAYLPOC5.js +0 -24
  217. package/dist-cli/chunks/chunk-VIND4SP3.js +0 -124
  218. package/dist-cli/chunks/chunk-XXNLYR64.js +0 -5
  219. package/dist-cli/chunks/chunk-ZFM24DOX.js +0 -4
  220. package/dist-cli/chunks/cli-version-SYGNHGFR.js +0 -4
  221. package/dist-cli/chunks/control-RBTNOLQM.js +0 -4
  222. package/dist-cli/chunks/daemon-N3SC5DK6.js +0 -4
  223. package/dist-cli/chunks/drivers-W6E3BZH4.js +0 -4
  224. package/dist-cli/chunks/flow-7QAE2V5Y.js +0 -4
  225. package/dist-cli/chunks/help-NLD6KWVG.js +0 -4
  226. package/dist-cli/chunks/inspect-C347BG52.js +0 -4
  227. package/dist-cli/chunks/install-desktop-YR6TWZYK.js +0 -4
  228. package/dist-cli/chunks/privacy-PWWC2BXO.js +0 -4
  229. package/dist-cli/chunks/reset-Y3PBPFYA.js +0 -4
  230. package/dist-cli/chunks/runtime-IC6NVVGA.js +0 -4
  231. package/dist-cli/chunks/setup-Y4QGSJBM.js +0 -4
  232. package/dist-cli/chunks/state-KFLAZNEY.js +0 -14
  233. package/dist-cli/chunks/store-VEE22CAY.js +0 -4
  234. package/dist-cli/chunks/telemetry-PZV4BISJ.js +0 -4
  235. package/dist-cli/chunks/upload-Y4C3YZDK.js +0 -4
  236. package/dist-cli/chunks/welcome-ZPKA5PZJ.js +0 -5
  237. package/dist-lib/mcp.cjs +0 -673
  238. package/dist-lib/mcp.mjs +0 -641
  239. package/src/mcp.ts +0 -791
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.366 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.367 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -459,6 +459,9 @@ function isDevBridgeLockfileFresh(lock, now = Date.now()) {
459
459
  }
460
460
 
461
461
  // cli/ws-bridge.ts
462
+ function bridgeWsUrl(wsPort) {
463
+ return `ws://localhost:${wsPort}`;
464
+ }
462
465
  var resolvedWorld = null;
463
466
  function liveDevBridgeLockfiles() {
464
467
  return readDevBridgeLockfiles().filter((lockfile) => isDevBridgeLockfileFresh(lockfile));
@@ -556,7 +559,7 @@ function createBridge(wsPort, opts = {}) {
556
559
  let nextId = 1;
557
560
  const commandTimeoutMs = opts.commandTimeoutMs ?? 15e3;
558
561
  const pending = /* @__PURE__ */ new Map();
559
- const ws = new import_ws.WebSocket(`ws://localhost:${wsPort}`);
562
+ const ws = new import_ws.WebSocket(bridgeWsUrl(wsPort));
560
563
  const identity = getCliIdentity();
561
564
  const ready = new Promise((resolve2, reject) => {
562
565
  ws.on("open", () => {
@@ -576,7 +579,7 @@ function createBridge(wsPort, opts = {}) {
576
579
  });
577
580
  ws.on("error", (err) => {
578
581
  const cause = err.message ? `: ${err.message}` : "";
579
- reject(new Error(`could not connect to ws://localhost:${wsPort}${cause}`));
582
+ reject(new Error(`could not connect to ${bridgeWsUrl(wsPort)}${cause}`));
580
583
  });
581
584
  });
582
585
  ready.catch(() => {
@@ -1360,115 +1363,110 @@ async function tapBest(bridge, query, opts = {}) {
1360
1363
  }
1361
1364
 
1362
1365
  // cli/commands/inspect/core.ts
1363
- async function inspectTree(bridge, depth = 5, opts) {
1364
- const optsArg = opts?.format ? `, ${JSON.stringify(opts)}` : "";
1365
- const tree = await bridge.send({
1366
- type: "evaluate",
1367
- code: `(async () => await window.__sootsimTest.dumpTree(${depth}${optsArg}))()`
1366
+ async function inspectTree(bridge, depth = 5) {
1367
+ const result = await bridge.send({
1368
+ type: "query",
1369
+ query: { kind: "tree", depth }
1368
1370
  });
1369
- if (opts?.format === "boxes" && typeof tree === "string") {
1370
- try {
1371
- return { depth, tree: JSON.parse(tree) };
1372
- } catch {
1373
- return { depth, tree };
1374
- }
1375
- }
1371
+ const tree = isSemanticQueryResult(result) ? result.nodes : [];
1376
1372
  return { depth, tree };
1377
1373
  }
1378
1374
  async function inspectUrl(bridge) {
1379
1375
  const url = await bridge.send({ type: "evaluate", code: "window.location.href" });
1380
1376
  return { url: typeof url === "string" ? url : "" };
1381
1377
  }
1382
- async function inspectDescribe(bridge, dumpOpts) {
1383
- const dumpDepth = dumpOpts.maxDepth ?? 12;
1384
- const code = `(async () => {
1385
- const t = window.__sootsimTest
1386
- const mainShell = window.SootSim?.bridges?.mainShell
1387
- const kb = window.__sootsimKeyboard
1388
- if (!t) return { error: 'no test bridge' }
1389
-
1390
- let shell = null
1391
- try {
1392
- shell = typeof mainShell?.getState === 'function' ? await mainShell.getState() : null
1393
- } catch {}
1394
-
1395
- const opts = ${JSON.stringify(dumpOpts)}
1396
- const needsRecursiveDump =
1397
- opts.verbose || opts.subtreeRoot || opts.includeOccluded || opts.maxDepth != null
1398
- const globToRegex = (glob) => {
1399
- const escaped = String(glob).replace(/[.+^$(){}|[\\]\\\\]/g, '\\\\$&')
1400
- const body = escaped.replace(/\\*/g, '.*').replace(/\\?/g, '.')
1401
- return new RegExp('^' + body + '$', 'i')
1402
- }
1403
- // clipping a describe line is a DISPLAY decision. opts.fullText (set by
1404
- // \`describe --json\`) keeps the real string, so a bundle-eval stack or a
1405
- // red-box message reaches the caller whole instead of ending in an
1406
- // ellipsis nothing downstream can undo.
1407
- const clipForDisplay = (value, max) => {
1408
- const text = typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : ''
1409
- if (opts.fullText || text.length <= max) return text
1410
- return text.slice(0, max - 1).trimEnd() + '\u2026'
1378
+ function isSemanticGeometry(value) {
1379
+ return value !== null && typeof value === "object" && [
1380
+ Reflect.get(value, "x"),
1381
+ Reflect.get(value, "y"),
1382
+ Reflect.get(value, "width"),
1383
+ Reflect.get(value, "height")
1384
+ ].every(
1385
+ (coordinate) => typeof coordinate === "number" ? Number.isFinite(coordinate) : false
1386
+ );
1387
+ }
1388
+ function isSimSemanticTree(value) {
1389
+ if (!Array.isArray(value)) return false;
1390
+ return value.every((node) => {
1391
+ if (node === null || typeof node !== "object" || !Number.isInteger(Reflect.get(node, "nodeId")) || typeof Reflect.get(node, "type") !== "string" || !isSemanticGeometry(Reflect.get(node, "geometry"))) {
1392
+ return false;
1393
+ }
1394
+ for (const key of ["testID", "text", "role", "label"]) {
1395
+ const value2 = Reflect.get(node, key);
1396
+ if (value2 !== void 0 && typeof value2 !== "string") return false;
1397
+ }
1398
+ const pressable = Reflect.get(node, "pressable");
1399
+ if (pressable !== void 0 && pressable !== true) return false;
1400
+ const children = Reflect.get(node, "children");
1401
+ return children === void 0 || isSimSemanticTree(children);
1402
+ });
1403
+ }
1404
+ function isSemanticQueryResult(value) {
1405
+ const total = value !== null && typeof value === "object" ? Reflect.get(value, "total") : void 0;
1406
+ return value !== null && typeof value === "object" && typeof total === "number" && Number.isInteger(total) && total >= 0 && isSimSemanticTree(Reflect.get(value, "nodes"));
1407
+ }
1408
+ function globRegex(glob) {
1409
+ const escaped = glob.replace(/[.+^$(){}|[\]\\]/g, "\\$&");
1410
+ return new RegExp(`^${escaped.replace(/\*/g, ".*").replace(/\?/g, ".")}$`, "i");
1411
+ }
1412
+ function formatSemanticTree(nodes, opts = {}) {
1413
+ const filter = opts.filter?.toLowerCase() ?? "";
1414
+ const testIDRegex = opts.testIdLike ? globRegex(opts.testIdLike) : null;
1415
+ const onlyRegex = opts.onlyGlob ? globRegex(opts.onlyGlob) : null;
1416
+ const lines = [];
1417
+ const visit = (node, indent) => {
1418
+ const joined = [node.role, node.label, node.text, node.testID].filter((value) => typeof value === "string").join(" ");
1419
+ const matches = (!filter || joined.toLowerCase().includes(filter)) && (!testIDRegex || testIDRegex.test(node.testID ?? "")) && (!onlyRegex || onlyRegex.test(joined));
1420
+ if (matches) {
1421
+ const parts = [
1422
+ node.role ? `[${node.role}]` : node.pressable ? "[tap]" : `<${node.type}>`
1423
+ ];
1424
+ if (node.text) {
1425
+ const text = opts.fullText || node.text.length <= 50 ? node.text : `${node.text.slice(0, 49).trimEnd()}\u2026`;
1426
+ parts.push(JSON.stringify(text));
1411
1427
  }
1412
- const formatInspectableTree = async () => {
1413
- if (typeof t.listInspectable !== 'function') return null
1414
- const list = await t.listInspectable({})
1415
- if (!Array.isArray(list)) return null
1416
- const filter = typeof opts.filter === 'string' ? opts.filter.toLowerCase() : ''
1417
- const testIdRegex = opts.testIdLike ? globToRegex(opts.testIdLike) : null
1418
- const onlyRegex = opts.onlyGlob ? globToRegex(opts.onlyGlob) : null
1419
- const lines = []
1420
- let matched = 0
1421
- for (const node of list) {
1422
- const testID = node?.testID || node?.testId || ''
1423
- const role = node?.role || ''
1424
- const label = node?.accessibilityLabel || node?.label || ''
1425
- const text = node?.text || ''
1426
- if (testIdRegex && !testIdRegex.test(testID)) continue
1427
- const joined = [role, label, text, testID].filter(Boolean).join(' ')
1428
- if (onlyRegex && !onlyRegex.test(joined)) continue
1429
- if (filter && !joined.toLowerCase().includes(filter)) continue
1430
- matched++
1431
-
1432
- const type = String(node?.type || 'node').toLowerCase()
1433
- const handlers = Array.isArray(node?.handlers) ? node.handlers : []
1434
- const parts = []
1435
- if (role) parts.push('[' + role + ']')
1436
- else if (handlers.length > 0) parts.push('[tap]')
1437
- else parts.push('<' + type + '>')
1438
- const visibleText = clipForDisplay(text, 50)
1439
- if (visibleText) parts.push(JSON.stringify(visibleText))
1440
- if (testID) parts.push('#' + testID)
1441
- if (label && label !== text) {
1442
- parts.push('label=' + JSON.stringify(clipForDisplay(label, 50)))
1443
- }
1444
- if (!opts.hideXy) {
1445
- const abs = node?.absolute || {}
1446
- const layout = node?.layout || {}
1447
- const x = Math.round(Number(abs.x) || 0)
1448
- const y = Math.round(Number(abs.y) || 0)
1449
- const w = Math.round(Number(layout.width) || 0)
1450
- const h = Math.round(Number(layout.height) || 0)
1451
- parts.push('@(' + x + ',' + y + ') ' + w + 'x' + h)
1452
- }
1453
- if (handlers.length > 0) parts.push('(tap)')
1454
- lines.push(' ' + parts.join(' '))
1455
- }
1456
- return { tree: lines.join('\\n'), matched }
1428
+ if (node.testID) parts.push(`#${node.testID}`);
1429
+ if (node.label && node.label !== node.text) {
1430
+ parts.push(`label=${JSON.stringify(node.label)}`);
1457
1431
  }
1458
-
1459
- let tree = ''
1460
- const compactResult = needsRecursiveDump ? null : await formatInspectableTree()
1461
- if (compactResult) {
1462
- tree = compactResult.tree
1463
- } else {
1464
- tree = await t.dumpTree(${dumpDepth}, opts)
1432
+ if (!opts.hideXy) {
1433
+ const geometry = node.geometry;
1434
+ parts.push(
1435
+ `@(${Math.round(geometry.x)},${Math.round(geometry.y)}) ${Math.round(geometry.width)}x${Math.round(geometry.height)}`
1436
+ );
1465
1437
  }
1466
- const nodeCount = (await t.getNodeCount?.()) || 0
1467
- const keyboard = kb && typeof kb.getLayout === 'function' ? kb.getLayout() : null
1468
- return { tree, shell, nodeCount, keyboard }
1469
- })()`;
1470
- const result = await bridge.send({ type: "evaluate", code });
1471
- return result ?? {};
1438
+ if (node.pressable) parts.push("(tap)");
1439
+ lines.push(`${" ".repeat(indent + 1)}${parts.join(" ")}`);
1440
+ }
1441
+ for (const child of node.children ?? []) visit(child, indent + 1);
1442
+ };
1443
+ let roots = nodes;
1444
+ if (opts.subtreeRoot) {
1445
+ const pending = [...nodes];
1446
+ let found;
1447
+ while (pending.length > 0 && !found) {
1448
+ const candidate = pending.shift();
1449
+ if (!candidate) continue;
1450
+ if (candidate.testID === opts.subtreeRoot) found = candidate;
1451
+ else pending.push(...candidate.children ?? []);
1452
+ }
1453
+ if (!found) return `__SUBTREE_NOT_FOUND__:${opts.subtreeRoot}`;
1454
+ roots = [found];
1455
+ }
1456
+ for (const root of roots) visit(root, 0);
1457
+ return lines.join("\n");
1458
+ }
1459
+ async function inspectDescribe(bridge, dumpOpts) {
1460
+ const dumpDepth = dumpOpts.maxDepth ?? 12;
1461
+ const result = await bridge.send({
1462
+ type: "query",
1463
+ query: { kind: "tree", depth: dumpDepth }
1464
+ });
1465
+ if (!isSemanticQueryResult(result)) return {};
1466
+ return {
1467
+ tree: formatSemanticTree(result.nodes, dumpOpts),
1468
+ nodeCount: result.total
1469
+ };
1472
1470
  }
1473
1471
  function scoreLayoutTextContrast(input) {
1474
1472
  const required = input.fontSize >= 18 || input.fontSize >= 14 && (input.fontWeight ?? 400) >= 700 ? 3 : 4.5;
@@ -1683,9 +1681,6 @@ function layoutGetNativeEval(opts = {}) {
1683
1681
  type: typeof n.type === 'string' ? n.type : 'node',
1684
1682
  text,
1685
1683
  box: { x: round(abs.x), y: round(abs.y), w: round(layout.width), h: round(layout.height) },
1686
- borderRadius: 0,
1687
- padding: { t: 0, r: 0, b: 0, l: 0 },
1688
- fontSize: 0,
1689
1684
  }
1690
1685
  if (!includeStyle) return base
1691
1686
  // computedStyle has the resolved values; style is the curated subset \u2014
@@ -1755,398 +1750,80 @@ function layoutGetNativeEval(opts = {}) {
1755
1750
  })()`;
1756
1751
  }
1757
1752
  var LAYOUT_GET_NATIVE_EVAL = layoutGetNativeEval();
1758
- var INSPECT_A11Y_CODE = `(async () => {
1759
- const t = window.__sootsimTest
1760
- if (!t) return []
1761
-
1762
- const readLayout = (n) => n?.layout || null
1763
- const readAbs = (n) => n?.absolutePosition || n?.absolute || null
1764
- const readRole = (n) => n?.accessibilityRole || n?.role || null
1765
- const readLabel = (n) => n?.accessibilityLabel || n?.label || n?.text || null
1766
- const readHint = (n) => n?.accessibilityHint || n?.hint || null
1767
- const readState = (n) => n?.accessibilityState || n?.state || null
1768
- const readTestId = (n) => n?.testID || n?.testId || null
1769
- const readType = (n) => typeof n?.type === 'string' ? n.type.toLowerCase() : n?.type
1770
- const readPressable = (n) => {
1771
- if (n?.pressable) return true
1772
- const handlers = Array.isArray(n?.handlers) ? n.handlers : []
1773
- const role = readRole(n)
1774
- return handlers.length > 0 || role === 'button' || role === 'link' || role === 'tab'
1775
- }
1776
- const isTextInput = (n) => n?.isTextInput === true
1777
- const isTextNode = (n) => readType(n) === 'text'
1778
- const isVisibleTarget = (n) => {
1779
- const layout = readLayout(n)
1780
- if (!layout || layout.width <= 0 || layout.height <= 0) return false
1781
- const abs = readAbs(n)
1782
- if (!abs) return true
1783
- const device = n?.device || {}
1784
- const screenW = Number(device.width) || window.innerWidth || 0
1785
- const screenH = Number(device.height) || window.innerHeight || 0
1786
- return (
1787
- abs.x + layout.width > 0 &&
1788
- abs.y + layout.height > 0 &&
1789
- abs.x < screenW &&
1790
- abs.y < screenH
1791
- )
1792
- }
1793
- const hasAccessibleSignal = (n) => {
1794
- const role = readRole(n)
1795
- const label = readLabel(n)
1796
- const hint = readHint(n)
1797
- if (role) return true
1798
- if (hint) return true
1799
- if (isTextInput(n)) return true
1800
- if (readPressable(n)) return true
1801
- if (isTextNode(n) && n?.text) return true
1802
- if (typeof label === 'string' && label.length <= 30 && label !== n?.text) return true
1803
- return false
1804
- }
1805
- const normalize = (n) => {
1806
- const layout = readLayout(n)
1807
- const abs = readAbs(n)
1808
- const role = readRole(n) || (readPressable(n) ? 'button' : isTextInput(n) ? 'textfield' : isTextNode(n) ? 'statictext' : 'none')
1809
- return {
1810
- role,
1811
- label: readLabel(n),
1812
- hint: readHint(n),
1813
- state: readState(n),
1814
- testID: readTestId(n),
1815
- position: abs ? { x: Math.round(abs.x), y: Math.round(abs.y) } : null,
1816
- size: layout ? { w: Math.round(layout.width), h: Math.round(layout.height) } : null,
1817
- }
1818
- }
1819
-
1820
- if (typeof t.listInspectable === 'function') {
1821
- const list = await t.listInspectable({})
1822
- if (Array.isArray(list)) {
1823
- return list.filter(n => isVisibleTarget(n) && hasAccessibleSignal(n)).map(normalize)
1824
- }
1825
- }
1826
-
1827
- const all = await t.queryAll({ pruneHidden: true })
1828
- return all
1829
- .filter(n => isVisibleTarget(n) && hasAccessibleSignal(n))
1830
- .map(normalize)
1831
- })()`;
1832
1753
  async function inspectAccessibilityTree(bridge) {
1833
- const nodes = await bridge.send({ type: "evaluate", code: INSPECT_A11Y_CODE });
1834
- return Array.isArray(nodes) ? nodes : [];
1835
- }
1836
- var FIND_INSPECTABLE_TARGETS = `
1837
- const fromInspectable = async () => {
1838
- if (typeof t.listInspectable !== 'function') return null
1839
- const list = await t.listInspectable({})
1840
- if (!Array.isArray(list)) return null
1841
- return list.map((n) => {
1842
- const role = n.accessibilityRole ?? n.role
1843
- const label = n.accessibilityLabel ?? n.label
1844
- const layout = n.layout
1845
- const absolutePosition = n.absolutePosition ?? n.absolute
1846
- const screenW = Number(n.device?.width) || window.innerWidth || 0
1847
- const screenH = Number(n.device?.height) || window.innerHeight || 0
1848
- const visibleFrame = layout && absolutePosition
1849
- ? {
1850
- x: Math.max(0, absolutePosition.x),
1851
- y: Math.max(0, absolutePosition.y),
1852
- width: Math.max(
1853
- 0,
1854
- Math.min(screenW, absolutePosition.x + layout.width) -
1855
- Math.max(0, absolutePosition.x),
1856
- ),
1857
- height: Math.max(
1858
- 0,
1859
- Math.min(screenH, absolutePosition.y + layout.height) -
1860
- Math.max(0, absolutePosition.y),
1861
- ),
1862
- }
1863
- : null
1864
- const handlers = Array.isArray(n.handlers) ? n.handlers : []
1865
- const pressable =
1866
- handlers.length > 0 ||
1867
- role === 'button' ||
1868
- role === 'link' ||
1869
- role === 'tab'
1870
- return {
1871
- type: typeof n.type === 'string' ? n.type.toLowerCase() : n.type,
1872
- id: n.id ?? n.nodeId,
1873
- nodeId: n.nodeId,
1874
- testID: n.testID ?? n.testId,
1875
- text: n.text,
1876
- layout,
1877
- absolutePosition,
1878
- visibleFrame,
1879
- style: n.computedStyle ?? n.style ?? {},
1880
- childCount: n.childCount ?? 0,
1881
- pressed: n.pressed ?? false,
1882
- pressable,
1883
- isTextInput: n.isTextInput ?? false,
1884
- accessible: n.accessible ?? true,
1885
- accessibilityLabel: label,
1886
- accessibilityRole: role,
1887
- accessibilityHint: n.accessibilityHint,
1888
- accessibilityState: n.accessibilityState,
1889
- accessibilityValue: n.accessibilityValue,
1890
- }
1891
- })
1892
- }
1893
- const isVisibleTarget = (n) => {
1894
- if (!n?.layout) return false
1895
- const frame = n.visibleFrame
1896
- if (frame) return frame.width > 0 && frame.height > 0
1897
- const abs = n.absolutePosition
1898
- if (!abs) return n.layout.width > 0 && n.layout.height > 0
1899
- const screenW = window.innerWidth || 0
1900
- const screenH = window.innerHeight || 0
1901
- return (
1902
- abs.x + n.layout.width > 0 &&
1903
- abs.y + n.layout.height > 0 &&
1904
- abs.x < screenW &&
1905
- abs.y < screenH
1906
- )
1754
+ const result = await bridge.send({
1755
+ type: "query",
1756
+ query: { kind: "find", selector: { visible: true } }
1757
+ });
1758
+ if (!isSemanticQueryResult(result)) return [];
1759
+ return result.nodes.flatMap((node) => {
1760
+ if (!node.role && !node.label && !node.text && !node.pressable) return [];
1761
+ return [
1762
+ {
1763
+ role: node.role ?? (node.pressable ? "button" : node.type === "text" ? "statictext" : "none"),
1764
+ label: node.label ?? node.text ?? null,
1765
+ hint: null,
1766
+ state: null,
1767
+ testID: node.testID ?? null,
1768
+ position: {
1769
+ x: Math.round(node.geometry.x),
1770
+ y: Math.round(node.geometry.y)
1771
+ },
1772
+ size: {
1773
+ w: Math.round(node.geometry.width),
1774
+ h: Math.round(node.geometry.height)
1775
+ }
1907
1776
  }
1908
- `;
1777
+ ];
1778
+ });
1779
+ }
1909
1780
  function resolveFindMode(q) {
1910
1781
  if (q.testId) {
1911
- return {
1912
- mode: "testid",
1913
- code: `(async () => {
1914
- const t = window.__sootsimTest
1915
- if (!t) return null
1916
- return (await t.findByTestId(${JSON.stringify(q.testId)})) || (await t.findById(${JSON.stringify(q.testId)}))
1917
- })()`
1918
- };
1782
+ return { mode: "testid", selector: { testID: q.testId } };
1919
1783
  }
1920
1784
  if (q.role) {
1921
- return {
1922
- mode: "role",
1923
- code: `(async () => {
1924
- const t = window.__sootsimTest
1925
- if (!t) return []
1926
- return await t.queryAll({ hasRole: ${JSON.stringify(q.role)}, pruneHidden: true })
1927
- })()`
1928
- };
1785
+ return { mode: "role", selector: { role: q.role } };
1929
1786
  }
1930
1787
  if (q.type) {
1931
- return {
1932
- mode: "type",
1933
- code: `(async () => {
1934
- const t = window.__sootsimTest
1935
- if (!t) return []
1936
- return await t.queryAll({ type: ${JSON.stringify(q.type)}, pruneHidden: true })
1937
- })()`
1938
- };
1788
+ return { mode: "type", selector: { type: q.type } };
1939
1789
  }
1940
1790
  if (q.pressable) {
1941
- return {
1942
- mode: "pressable",
1943
- code: `(async () => {
1944
- const t = window.__sootsimTest
1945
- if (!t) return []
1946
- ${FIND_INSPECTABLE_TARGETS}
1947
- const inspectable = await fromInspectable()
1948
- if (inspectable) return inspectable.filter(n => n.pressable && isVisibleTarget(n))
1949
- const all = await t.queryAll({ pruneHidden: true })
1950
- return all.filter(n => n.pressable && isVisibleTarget(n))
1951
- })()`
1952
- };
1791
+ return { mode: "pressable", selector: { pressable: true } };
1953
1792
  }
1954
1793
  if (q.interactive) {
1955
- return {
1956
- mode: "interactive-targets",
1957
- code: `(async () => {
1958
- const t = window.__sootsimTest
1959
- if (!t) return []
1960
- ${FIND_INSPECTABLE_TARGETS}
1961
- const inspectable = await fromInspectable()
1962
- if (inspectable) {
1963
- return inspectable.filter(n => n.pressable && isVisibleTarget(n))
1964
- }
1965
- const all = await t.queryAll({ pruneHidden: true })
1966
- return all.filter(n => n.pressable && isVisibleTarget(n))
1967
- })()`
1968
- };
1794
+ return { mode: "interactive-targets", selector: { pressable: true } };
1969
1795
  }
1970
1796
  if (q.visible) {
1971
- return {
1972
- mode: "visible",
1973
- code: `(async () => {
1974
- const t = window.__sootsimTest
1975
- if (!t) return []
1976
- const all = await t.queryAll({ pruneHidden: true })
1977
- return all.filter(n => n.layout && n.layout.width > 0 && n.layout.height > 0)
1978
- })()`
1979
- };
1797
+ return { mode: "visible", selector: { visible: true } };
1980
1798
  }
1981
1799
  if (q.text) {
1982
- return {
1983
- mode: "text",
1984
- code: `(async () => {
1985
- const t = window.__sootsimTest
1986
- if (!t) return null
1987
- return await t.findByText(${JSON.stringify(q.text)})
1988
- })()`
1989
- };
1800
+ return { mode: "text", selector: { text: q.text } };
1990
1801
  }
1991
1802
  return null;
1992
1803
  }
1993
1804
  async function inspectFind(bridge, q) {
1994
1805
  const resolved = resolveFindMode(q);
1995
1806
  if (!resolved) return null;
1996
- const result = await bridge.send({ type: "evaluate", code: resolved.code });
1997
- return { mode: resolved.mode, result };
1998
- }
1999
- var WAIT_READY_PROBE = `(async () => {
2000
- const t = window.__sootsimTest
2001
- let nodes = 0
2002
- try { nodes = (await t?.getNodeCount?.()) || 0 } catch {}
2003
- let targets = 0
2004
- let liveFrameActive = false
2005
- let liveFrameChannels = 0
2006
- let liveFramePublishes = 0
2007
- let loadingText = ''
2008
- const cleanText = (value) =>
2009
- typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : ''
2010
- const loadingSignal = (value) => {
2011
- const text = cleanText(value)
2012
- if (!text) return ''
2013
- return /^(opening app\\b|looking for your app\\b|building bundle\\b|preparing rnx runtime\\b|loading native bundle\\b|loading rnx stubs\\b|loading bundle loader\\b|loading native stubs\\b|loading compat bridge\\b|loading react-native shim\\b|loading react bridge\\b|loading reconciler\\b|loading replacement modules\\b|injecting rnx modules\\b|warming sqlite engine\\b|capturing root component\\b|reloading app\\b|loading(?:[.!\u2026]*)?$|connecting(?:[.!\u2026]*)?$)/i.test(text)
2014
- ? text
2015
- : ''
2016
- }
2017
- try {
2018
- const loading = window.__sootsimConnectRNLoading
2019
- if (loading && loading.active === true) {
2020
- loadingText = [loading.title, loading.detail].map(cleanText).filter(Boolean).join(' ')
2021
- }
2022
- } catch {}
2023
- try {
2024
- if (typeof t?.listInspectable === 'function') {
2025
- const list = await t.listInspectable({})
2026
- if (Array.isArray(list)) {
2027
- const screenW = window.innerWidth || 0
2028
- const screenH = window.innerHeight || 0
2029
- const isVisible = (n) => {
2030
- const layout = n?.layout
2031
- if (!layout || layout.width <= 0 || layout.height <= 0) return false
2032
- const abs = n?.absolutePosition || n?.absolute
2033
- if (!abs) return true
2034
- const device = n?.device || {}
2035
- const w = Number(device.width) || screenW
2036
- const h = Number(device.height) || screenH
2037
- return (
2038
- abs.x + layout.width > 0 &&
2039
- abs.y + layout.height > 0 &&
2040
- abs.x < w &&
2041
- abs.y < h
2042
- )
2043
- }
2044
- const hasContentSignal = (n) => {
2045
- const role = n?.accessibilityRole || n?.role
2046
- const label = cleanText(n?.accessibilityLabel || n?.label)
2047
- const handlers = Array.isArray(n?.handlers) ? n.handlers : []
2048
- const text = cleanText(n?.text)
2049
- const testID = cleanText(n?.testID || n?.testId)
2050
- const loading = loadingSignal(text) || loadingSignal(label) || loadingSignal(testID)
2051
- if (!loadingText && loading) {
2052
- loadingText = loading
2053
- }
2054
- if (handlers.length > 0) return true
2055
- if (role) return true
2056
- if (n?.isTextInput) return true
2057
- if (text) return true
2058
- if (label) return true
2059
- if (testID && !/^splash|loading/i.test(testID)) return true
2060
- return false
2061
- }
2062
- targets = list.filter(n => isVisible(n) && hasContentSignal(n)).length
2063
- }
2064
- }
2065
- } catch {}
2066
- try {
2067
- const stats = await t?.getImageLoaderStats?.()
2068
- const presentation = stats?.liveFramePresentation
2069
- liveFrameChannels = Number(presentation?.activeChannels) || 0
2070
- liveFramePublishes = Number(presentation?.publishes) || 0
2071
- const previous = window.__sootsimWaitReadyLiveFramePublishes
2072
- liveFrameActive =
2073
- liveFrameChannels > 0 &&
2074
- typeof previous === 'number' &&
2075
- liveFramePublishes > previous
2076
- window.__sootsimWaitReadyLiveFramePublishes = liveFramePublishes
2077
- } catch {}
2078
- let externalReady = null
2079
- let externalStatus = ''
2080
- let externalError = ''
2081
- let suppressedEntryError = ''
2082
- try {
2083
- const getExternalAppState = t?.getExternalAppState
2084
- const external = typeof getExternalAppState === 'function'
2085
- ? await Promise.race([
2086
- getExternalAppState(),
2087
- new Promise((resolve) => setTimeout(
2088
- () => resolve({
2089
- state: {
2090
- ready: false,
2091
- loading: true,
2092
- status: 'waiting for tenant worker...',
2093
- },
2094
- }),
2095
- 1000,
2096
- )),
2097
- ])
2098
- : null
2099
- const state = external?.state
2100
- if (state && typeof state === 'object') {
2101
- externalReady = state.ready === true
2102
- if (typeof state.status === 'string') externalStatus = state.status
2103
- if (typeof state.error === 'string') externalError = state.error
2104
- }
2105
- if (!externalError && Array.isArray(external?.entryErrors)) {
2106
- // suppressed-and-continued entry errors (bundle-loader's patched __r
2107
- // swallows entry throws and moves on) must NOT gate readiness \u2014 the
2108
- // app frequently runs fine after one (e.g. metro's "Unknown named
2109
- // module" probing noise). they surface separately as a diagnostic
2110
- // for the timeout path.
2111
- const isReal = (item) => item && typeof item.message === 'string' && item.message.trim()
2112
- const fatal = external.entryErrors.find((item) => isReal(item) && item.suppressed !== true)
2113
- if (fatal) externalError = fatal.message
2114
- const suppressed = external.entryErrors.find((item) => isReal(item) && item.suppressed === true)
2115
- if (suppressed) suppressedEntryError = suppressed.message
2116
- }
2117
- // once the app has errored its last status ("injecting sootsim
2118
- // modules...") is stale \u2014 reporting it makes a dead app read as one that
2119
- // is still booting.
2120
- if (externalError) loadingText = ''
2121
- else if (externalReady === false && externalStatus) loadingText = externalStatus
2122
- } catch {}
2123
- let errors = 0
2124
- try { errors = window.__sootsimConsole?.count?.()?.errors ?? 0 } catch {}
2125
- return {
2126
- flag: (window).__sootsimExternalAppReady,
2127
- at: (window).__sootsimExternalAppReadyAt || 0,
2128
- nodes,
2129
- targets,
2130
- liveFrameActive,
2131
- liveFrameChannels,
2132
- liveFramePublishes,
2133
- errors,
2134
- loadingText,
2135
- externalReady,
2136
- externalStatus,
2137
- externalError,
2138
- suppressedEntryError,
2139
- }
2140
- })()`;
2141
- var READY_CONTENT_NODE_FLOOR = 100;
2142
- var READY_NODE_STABLE_MS = 750;
2143
- var WAIT_READY_PROGRESS_INTERVAL_MS = 2e3;
2144
- var READY_PROBE_POLL_MS = 150;
2145
- function readyProbeHasContent(probe) {
2146
- return probe.liveFrameActive || probe.targets > 0 || probe.nodes >= READY_CONTENT_NODE_FLOOR;
2147
- }
2148
- function readyProbeHasTargetContent(probe) {
2149
- return probe.liveFrameActive || probe.targets > 0;
1807
+ const result = await bridge.send({
1808
+ type: "query",
1809
+ query: { kind: "find", selector: resolved.selector }
1810
+ });
1811
+ if (!isSemanticQueryResult(result)) return { mode: resolved.mode, result: [] };
1812
+ const nodes = result.nodes.map(
1813
+ (node) => ({
1814
+ type: node.type,
1815
+ nodeId: node.nodeId,
1816
+ ...node.testID ? { testID: node.testID } : {},
1817
+ ...node.text ? { text: node.text } : {},
1818
+ ...node.pressable ? { pressable: true } : {},
1819
+ ...node.role ? { accessibilityRole: node.role } : {},
1820
+ ...node.label ? { accessibilityLabel: node.label } : {},
1821
+ absolutePosition: { x: node.geometry.x, y: node.geometry.y },
1822
+ layout: { width: node.geometry.width, height: node.geometry.height }
1823
+ })
1824
+ );
1825
+ const resultValue = resolved.mode === "testid" || resolved.mode === "text" ? nodes[0] ?? null : nodes;
1826
+ return { mode: resolved.mode, result: resultValue };
2150
1827
  }
2151
1828
  function waitReadyReason(status) {
2152
1829
  if (status.bridgeError) {
@@ -2157,98 +1834,46 @@ function waitReadyReason(status) {
2157
1834
  }
2158
1835
  async function inspectWaitReady(bridge, timeoutMs = 2e4, options = {}) {
2159
1836
  const start = Date.now();
2160
- const deadline = start + timeoutMs;
2161
- const progressIntervalMs = options.progressIntervalMs ?? WAIT_READY_PROGRESS_INTERVAL_MS;
2162
- let nextProgressAt = start + progressIntervalMs;
2163
- let lastNodes = -1;
2164
- let nodeStableSince = start;
2165
- let bridgeError = "";
2166
- let last = {
2167
- flag: void 0,
2168
- at: 0,
2169
- nodes: 0,
2170
- targets: 0,
2171
- liveFrameActive: false,
2172
- liveFrameChannels: 0,
2173
- liveFramePublishes: 0,
2174
- errors: 0,
2175
- loadingText: "",
2176
- externalReady: null,
2177
- externalStatus: "",
2178
- externalError: "",
2179
- suppressedEntryError: ""
2180
- };
2181
- while (Date.now() < deadline) {
2182
- try {
2183
- last = await bridge.send({ type: "evaluate", code: WAIT_READY_PROBE }) ?? last;
2184
- bridgeError = "";
2185
- } catch (error) {
2186
- bridgeError = error instanceof Error ? error.message : String(error);
2187
- if (bridgeError.startsWith("multiple sims are connected:") || bridgeError.startsWith("saved sim ")) {
2188
- throw error;
2189
- }
1837
+ let result;
1838
+ try {
1839
+ result = await bridge.send(
1840
+ {
1841
+ type: "waitFor",
1842
+ waitForOptions: { condition: { type: "ready" }, timeoutMs }
1843
+ },
1844
+ { timeoutMs: timeoutMs + 1e3 }
1845
+ );
1846
+ } catch (error) {
1847
+ const bridgeError = error instanceof Error ? error.message : String(error);
1848
+ if (bridgeError.startsWith("multiple sims are connected:") || bridgeError.startsWith("saved sim ")) {
1849
+ throw error;
2190
1850
  }
2191
- const now = Date.now();
2192
- const status2 = {
2193
- ready: false,
2194
- elapsedMs: now - start,
2195
- nodes: last.nodes,
2196
- targets: last.targets,
2197
- liveFrameActive: last.liveFrameActive,
2198
- liveFrameChannels: last.liveFrameChannels,
2199
- liveFramePublishes: last.liveFramePublishes,
2200
- flag: last.flag,
2201
- loadingText: last.loadingText,
2202
- externalReady: last.externalReady,
2203
- externalStatus: last.externalStatus,
2204
- externalError: last.externalError,
2205
- suppressedEntryError: last.suppressedEntryError,
2206
- errors: last.errors,
2207
- bridgeError
1851
+ result = {
1852
+ matched: false,
1853
+ elapsedMs: Date.now() - start,
1854
+ polls: 0,
1855
+ error: bridgeError
2208
1856
  };
2209
- if (last.nodes !== lastNodes) {
2210
- lastNodes = last.nodes;
2211
- nodeStableSince = now;
2212
- }
2213
- const hasReadySignal = last.flag === true && readyProbeHasContent(last);
2214
- const hasRenderedContentWithoutReadySignal = last.flag !== true && readyProbeHasTargetContent(last);
2215
- if ((hasReadySignal || hasRenderedContentWithoutReadySignal) && last.externalReady !== false && !last.externalError && !last.loadingText) {
2216
- if (now - nodeStableSince >= READY_NODE_STABLE_MS) {
2217
- return {
2218
- ...status2,
2219
- ready: true
2220
- };
2221
- }
2222
- }
2223
- if (last.externalError) return status2;
2224
- if (options.onProgress && progressIntervalMs > 0 && now >= nextProgressAt) {
2225
- options.onProgress(status2);
2226
- do {
2227
- nextProgressAt += progressIntervalMs;
2228
- } while (now >= nextProgressAt);
2229
- }
2230
- await new Promise((r) => setTimeout(r, READY_PROBE_POLL_MS));
2231
1857
  }
1858
+ const probe = result.probe;
2232
1859
  const status = {
2233
- ready: false,
2234
- elapsedMs: Date.now() - start,
2235
- nodes: last.nodes,
2236
- targets: last.targets,
2237
- liveFrameActive: last.liveFrameActive,
2238
- liveFrameChannels: last.liveFrameChannels,
2239
- liveFramePublishes: last.liveFramePublishes,
2240
- flag: last.flag,
2241
- loadingText: last.loadingText,
2242
- externalReady: last.externalReady,
2243
- externalStatus: last.externalStatus,
2244
- externalError: last.externalError,
2245
- suppressedEntryError: last.suppressedEntryError,
2246
- errors: last.errors,
2247
- bridgeError
1860
+ ready: result.matched,
1861
+ elapsedMs: result.elapsedMs,
1862
+ nodes: probe?.nodes ?? 0,
1863
+ targets: probe?.targets ?? 0,
1864
+ liveFrameActive: probe?.liveFrameActive ?? false,
1865
+ liveFrameChannels: probe?.liveFrameChannels ?? 0,
1866
+ liveFramePublishes: probe?.liveFramePublishes ?? 0,
1867
+ flag: probe?.flag,
1868
+ loadingText: probe?.loadingText ?? "",
1869
+ externalReady: probe?.externalReady ?? null,
1870
+ externalStatus: probe?.externalStatus ?? "",
1871
+ externalError: probe?.externalError ?? "",
1872
+ suppressedEntryError: probe?.suppressedEntryError ?? "",
1873
+ errors: probe?.errors ?? 0,
1874
+ bridgeError: result.error ?? ""
2248
1875
  };
2249
- if (options.onProgress && progressIntervalMs > 0 && Date.now() >= nextProgressAt) {
2250
- options.onProgress(status);
2251
- }
1876
+ if (!status.ready && options.onProgress) options.onProgress(status);
2252
1877
  return status;
2253
1878
  }
2254
1879
  function consoleEntriesEval(level, limit) {
@@ -2371,38 +1996,30 @@ async function getShellState(bridge, readyTimeoutMs = 0) {
2371
1996
 
2372
1997
  // cli/commands/inspect/resolve-target.ts
2373
1998
  async function resolveTargetCoords(bridge, target) {
2374
- const arg = JSON.stringify(target.value);
2375
- const findExpr = target.mode === "testid" ? `(await t.findByTestId(${arg})) || (await t.findById(${arg}))` : `await t.findByText(${arg})`;
2376
1999
  const result = await bridge.send({
2377
- type: "evaluate",
2378
- code: `(async () => {
2379
- const t = window.__sootsimTest
2380
- if (!t) return null
2381
- const n = ${findExpr}
2382
- if (!n || !n.absolutePosition || !n.layout) return null
2383
- const resolved =
2384
- typeof n.nodeId === 'number' && typeof t.resolveTapTarget === 'function'
2385
- ? await t.resolveTapTarget(n.nodeId)
2386
- : null
2387
- const cx =
2388
- typeof resolved?.cx === 'number'
2389
- ? resolved.cx
2390
- : n.absolutePosition.x + (n.layout.width || 0) / 2
2391
- const cy =
2392
- typeof resolved?.cy === 'number'
2393
- ? resolved.cy
2394
- : n.absolutePosition.y + (n.layout.height || 0) / 2
2395
- return {
2396
- x: cx,
2397
- y: cy,
2398
- id: n.id ?? null,
2399
- testID: n.testID ?? null,
2400
- text: ${JSON.stringify(target.mode === "text")} ? ${arg} : (n.text ?? n.accessibilityLabel ?? null),
2401
- type: n.type ?? null,
2402
- }
2403
- })()`
2000
+ type: "resolve",
2001
+ selector: target.mode === "testid" ? { testID: target.value } : { text: target.value }
2404
2002
  });
2405
- return result ?? null;
2003
+ if (result === null) return null;
2004
+ if (typeof result !== "object") throw new Error("resolve returned an invalid result");
2005
+ const match = Reflect.get(result, "match");
2006
+ const resolvedTarget = Reflect.get(result, "target");
2007
+ const semanticNodes = [match, resolvedTarget];
2008
+ const point = Reflect.get(result, "point");
2009
+ const x = point !== null && typeof point === "object" ? Reflect.get(point, "x") : void 0;
2010
+ const y = point !== null && typeof point === "object" ? Reflect.get(point, "y") : void 0;
2011
+ if (!isSimSemanticTree(semanticNodes) || typeof x !== "number" || !Number.isFinite(x) || typeof y !== "number" || !Number.isFinite(y)) {
2012
+ throw new Error("resolve returned an invalid result");
2013
+ }
2014
+ const semanticMatch = semanticNodes[0];
2015
+ if (!semanticMatch) throw new Error("resolve returned an invalid result");
2016
+ return {
2017
+ x,
2018
+ y,
2019
+ testID: semanticMatch.testID ?? null,
2020
+ text: semanticMatch.text ?? (target.mode === "text" ? target.value : semanticMatch.label),
2021
+ type: semanticMatch.type
2022
+ };
2406
2023
  }
2407
2024
 
2408
2025
  // src/app-url.ts
@@ -2640,9 +2257,12 @@ var SimClient = class {
2640
2257
  }
2641
2258
  async tree(depth = 5) {
2642
2259
  const { tree } = await inspectTree(this, depth);
2643
- return tree;
2260
+ return Array.isArray(tree) ? tree : [];
2261
+ }
2262
+ resolve(selector) {
2263
+ return this.send({ type: "resolve", selector });
2644
2264
  }
2645
- /** compact visible-UI dump with shell + keyboard state, as the CLI prints. */
2265
+ /** compact visible semantic tree, formatted as the CLI prints it. */
2646
2266
  describe(opts = {}) {
2647
2267
  return inspectDescribe(this, {
2648
2268
  describe: true,