peachsim 0.1.1620 → 0.1.1625

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 (138) hide show
  1. package/README.md +35 -5
  2. package/cli/app-fonts.ts +2 -1
  3. package/cli/bridge-flow-runner.ts +184 -113
  4. package/cli/cloud-client.ts +68 -57
  5. package/cli/commands/box/search-command.ts +3 -2
  6. package/cli/commands/box.ts +10 -29
  7. package/cli/commands/control.ts +108 -43
  8. package/cli/commands/inspect/settling.ts +57 -20
  9. package/cli/commands/maestro-compare.ts +11 -32
  10. package/cli/commands/platform.ts +71 -36
  11. package/cli/commands/upload.ts +5 -1
  12. package/cli/dev-build-freshness.ts +37 -2
  13. package/cli/drivers/playwright-sim-host.ts +6 -5
  14. package/cli/maestro-accessibility.ts +53 -0
  15. package/cli/maestro-compare.ts +24 -0
  16. package/cli/outbound-endpoints.ts +22 -5
  17. package/cli/parent-pid.ts +0 -9
  18. package/cli/remote-simulators.ts +112 -0
  19. package/cli/remoteSimulatorContract.ts +21 -0
  20. package/detox/expectations.ts +5 -1
  21. package/detox/index.ts +133 -98
  22. package/detox/motion-change.cjs +37 -0
  23. package/detox/motion-change.d.cts +16 -0
  24. package/detox/navigation-lifecycle.ts +22 -4
  25. package/dist-lib/agent-daemon-client.cjs +3 -2
  26. package/dist-lib/agent-events.cjs +1 -1
  27. package/dist-lib/attached-projects.cjs +1 -1
  28. package/dist-lib/auth/shared-session.cjs +3 -2
  29. package/dist-lib/backend-origin.cjs +1 -1
  30. package/dist-lib/beta.cjs +1 -1
  31. package/dist-lib/beta.mjs +1 -1
  32. package/dist-lib/bridge-constants.cjs +1 -1
  33. package/dist-lib/bridge-contract-input.cjs +1 -1
  34. package/dist-lib/bridge-contract-input.mjs +1 -1
  35. package/dist-lib/bridge-contract.cjs +1 -1
  36. package/dist-lib/bridge-contract.mjs +1 -1
  37. package/dist-lib/bundle-channel.cjs +1520 -934
  38. package/dist-lib/capture-contract.cjs +1 -1
  39. package/dist-lib/capture-contract.mjs +1 -1
  40. package/dist-lib/cloud-contract.cjs +1 -1
  41. package/dist-lib/cloud-contract.mjs +1 -1
  42. package/dist-lib/cloud-test-args.cjs +1 -1
  43. package/dist-lib/cloud-test-args.mjs +1 -1
  44. package/dist-lib/cloud.cjs +3 -2
  45. package/dist-lib/cloud.mjs +3 -2
  46. package/dist-lib/config.cjs +5 -4
  47. package/dist-lib/config.mjs +5 -4
  48. package/dist-lib/detox/index.cjs +1021 -364
  49. package/dist-lib/dev-bundle-resolution.cjs +51 -23
  50. package/dist-lib/expo-swift-bundle.cjs +1343 -801
  51. package/dist-lib/expo-swift-metadata.cjs +5 -4
  52. package/dist-lib/expo-swift-metadata.mjs +5 -4
  53. package/dist-lib/home-paths.cjs +1 -1
  54. package/dist-lib/host/bridge-host.cjs +2468 -1341
  55. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  56. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  57. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  58. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  59. package/dist-lib/host/websocket-proxy.cjs +1 -1
  60. package/dist-lib/index.cjs +1262 -300
  61. package/dist-lib/jump-to-source-babel.cjs +1 -1
  62. package/dist-lib/jump-to-source-native.cjs +1 -1
  63. package/dist-lib/kotlin-host.cjs +1 -1
  64. package/dist-lib/menu.cjs +3 -2
  65. package/dist-lib/menu.mjs +3 -2
  66. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  67. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  68. package/dist-lib/metro-production-bundle.cjs +5 -4
  69. package/dist-lib/metro-production-bundle.mjs +5 -4
  70. package/dist-lib/metro.cjs +799 -398
  71. package/dist-lib/native-app-identity-project.cjs +728 -22
  72. package/dist-lib/native-app-identity-project.mjs +727 -22
  73. package/dist-lib/native-app-identity.cjs +224 -9
  74. package/dist-lib/native-app-identity.mjs +222 -8
  75. package/dist-lib/native-firebase-metadata.cjs +5 -4
  76. package/dist-lib/native-firebase-metadata.mjs +5 -4
  77. package/dist-lib/plist.cjs +1 -1
  78. package/dist-lib/plist.mjs +1 -1
  79. package/dist-lib/profiles.cjs +1 -1
  80. package/dist-lib/public-brand.cjs +3 -2
  81. package/dist-lib/react-native-host-modules.cjs +4 -1
  82. package/dist-lib/react-native-host-modules.mjs +4 -1
  83. package/dist-lib/scripts/dev-server-scanner.cjs +94 -28
  84. package/dist-lib/sdk.cjs +89 -24
  85. package/dist-lib/sdk.mjs +89 -24
  86. package/dist-lib/skills.cjs +4391 -2350
  87. package/dist-lib/swift-compile.cjs +328 -142
  88. package/dist-lib/swift-compile.mjs +342 -147
  89. package/dist-lib/swift-package.cjs +1 -1
  90. package/dist-lib/swift-package.mjs +1 -1
  91. package/dist-lib/swift-submit.cjs +1 -1
  92. package/dist-lib/swift-submit.mjs +1 -1
  93. package/dist-lib/swift-toolchain.cjs +1 -1
  94. package/dist-lib/swift.cjs +1 -1
  95. package/dist-lib/vite-swift.cjs +888 -559
  96. package/dist-lib/vite.cjs +68 -14
  97. package/dist-lib/worklet-executable.cjs +25 -2
  98. package/dist-lib/worklet-executable.mjs +25 -2
  99. package/package.json +2 -2
  100. package/scripts/dev-server-scanner.ts +33 -13
  101. package/src/admitted-mac-build.ts +34 -8
  102. package/src/android-app-identity.ts +146 -0
  103. package/src/android-autolinking.ts +75 -0
  104. package/src/android-string-value.ts +36 -0
  105. package/src/android-strings.ts +5 -36
  106. package/src/androidAutolinkingContract.ts +40 -0
  107. package/src/bundleGlobalFooter.ts +26 -7
  108. package/src/codex-client.ts +3 -2
  109. package/src/dev-bundle-resolution.ts +58 -25
  110. package/src/expo-kotlin-host.ts +6 -36
  111. package/src/expo-swift-bundle.ts +17 -12
  112. package/src/expo-swift-host.ts +477 -173
  113. package/src/expoSwiftContract.ts +6 -0
  114. package/src/host/bridge-host.ts +7 -55
  115. package/src/host/staged-host-files.ts +29 -2
  116. package/src/installed-package-versions.ts +54 -14
  117. package/src/launchMetadataContract.ts +24 -0
  118. package/src/metro-plugin.ts +37 -42
  119. package/src/native-app-identity-project.ts +116 -3
  120. package/src/native-app-identity.ts +86 -11
  121. package/src/nitro-compile.ts +2 -5
  122. package/src/nitro-source.ts +147 -63
  123. package/src/process-group.ts +90 -60
  124. package/src/process-output.ts +0 -9
  125. package/src/public-brand.ts +1 -0
  126. package/src/react-native-host-modules.ts +3 -0
  127. package/src/sim-client.ts +6 -4
  128. package/src/swift-compile.ts +176 -28
  129. package/src/swift-distribution.lock.json +37 -11
  130. package/src/swift-distribution.ts +61 -9
  131. package/src/swift-host.ts +58 -27
  132. package/src/swift-objc-standin.ts +13 -0
  133. package/src/swift-standin-emit.ts +1 -1
  134. package/src/swift-standins.ts +17 -5
  135. package/src/vite-plugin.ts +8 -19
  136. package/src/worklet-executable.ts +30 -3
  137. package/src/worklets-babel.ts +35 -14
  138. package/src/xcode-project.ts +10 -0
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1625 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1216,6 +1216,22 @@ var require_motion_change = __commonJS({
1216
1216
  definition: MOTION_CHANGE_DEFINITION2
1217
1217
  });
1218
1218
  }
1219
+ var DENSE_SAMPLE_MAX_GAP_MS = 1e3 / 60 + 1;
1220
+ var DENSE_SAMPLE_MINIMUM_MOVING_PAIRS = 6;
1221
+ function evaluateMotionTransitions(transitions) {
1222
+ const measured = transitions.every(
1223
+ (transition) => transition.dimensionsMatch && transition.changePercent !== null && transition.changePixels !== null && transition.totalPixels !== null
1224
+ );
1225
+ const moving = transitions.filter((transition) => transition.passedMinimum).length;
1226
+ const dense = transitions.length > DENSE_SAMPLE_MINIMUM_MOVING_PAIRS && transitions.every(
1227
+ (transition) => transition.toOffsetMs - transition.fromOffsetMs <= DENSE_SAMPLE_MAX_GAP_MS
1228
+ );
1229
+ return {
1230
+ dense,
1231
+ moving,
1232
+ passed: transitions.length > 0 && measured && (dense ? moving >= DENSE_SAMPLE_MINIMUM_MOVING_PAIRS : moving === transitions.length)
1233
+ };
1234
+ }
1219
1235
  var DISPLAY_LINK_INTERVAL_MS = 1e3 / 60;
1220
1236
  var DISCRETE_FRAME_TIMING_TOLERANCE_MS = 1e3 / 60 + 1e3 / 600;
1221
1237
  function parseDiscreteFrameCadence(value) {
@@ -1371,6 +1387,7 @@ var require_motion_change = __commonJS({
1371
1387
  MOTION_CHANGE_DEFINITION: MOTION_CHANGE_DEFINITION2,
1372
1388
  measureMotionChange: measureMotionChange2,
1373
1389
  measureProofMotionChange,
1390
+ evaluateMotionTransitions,
1374
1391
  DISCRETE_FRAME_TIMING_TOLERANCE_MS,
1375
1392
  parseDiscreteFrameCadence,
1376
1393
  classifyDiscreteFrame: classifyDiscreteFrame2,
@@ -1398,23 +1415,23 @@ function previousRemoteBuildDelivery(destination) {
1398
1415
  if (!label) return void 0;
1399
1416
  const local = newestCompletedDelivery(root, label, (0, import_node_path2.basename)(destination));
1400
1417
  if (local) return local;
1401
- const commonDir = (0, import_node_child_process2.spawnSync)(
1418
+ const commonDir = (0, import_node_child_process3.spawnSync)(
1402
1419
  "git",
1403
1420
  ["rev-parse", "--path-format=absolute", "--git-common-dir"],
1404
1421
  { cwd: (0, import_node_path2.dirname)(root), encoding: "utf8" }
1405
1422
  );
1406
1423
  if (commonDir.status !== 0) return void 0;
1407
1424
  const primary = (0, import_node_path2.join)((0, import_node_path2.dirname)(commonDir.stdout.trim()), ".remote-deliveries");
1408
- if (primary === root || !(0, import_node_fs2.existsSync)(primary)) return void 0;
1425
+ if (primary === root || !(0, import_node_fs3.existsSync)(primary)) return void 0;
1409
1426
  return newestCompletedDelivery(primary, label);
1410
1427
  }
1411
1428
  function deliveryGroup(name2) {
1412
1429
  return /^([a-z0-9-]+)-[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.exec(name2)?.[1] ?? (/^\d+-\d+$/.test(name2) ? "default" : void 0);
1413
1430
  }
1414
1431
  function newestCompletedDelivery(root, label, exclude) {
1415
- return (0, import_node_fs2.readdirSync)(root, { withFileTypes: true }).filter(
1432
+ return (0, import_node_fs3.readdirSync)(root, { withFileTypes: true }).filter(
1416
1433
  (entry) => entry.isDirectory() && entry.name !== exclude && deliveryGroup(entry.name) === label
1417
- ).map((entry) => (0, import_node_path2.join)(root, entry.name)).filter((path3) => (0, import_node_fs2.existsSync)((0, import_node_path2.join)(path3, ".remote-transfer-complete"))).map((path3) => ({ path: path3, modified: (0, import_node_fs2.statSync)(path3).mtimeMs })).sort((left, right) => right.modified - left.modified)[0]?.path;
1434
+ ).map((entry) => (0, import_node_path2.join)(root, entry.name)).filter((path3) => (0, import_node_fs3.existsSync)((0, import_node_path2.join)(path3, ".remote-transfer-complete"))).map((path3) => ({ path: path3, modified: (0, import_node_fs3.statSync)(path3).mtimeMs })).sort((left, right) => right.modified - left.modified)[0]?.path;
1418
1435
  }
1419
1436
  function prunePreviousRemoteBuildDeliveries(destination, keepCount = 1) {
1420
1437
  const root = (0, import_node_path2.dirname)((0, import_node_path2.resolve)(destination));
@@ -1422,15 +1439,15 @@ function prunePreviousRemoteBuildDeliveries(destination, keepCount = 1) {
1422
1439
  const label = deliveryGroup((0, import_node_path2.basename)(destination));
1423
1440
  if (!label) return;
1424
1441
  const destinationPath = (0, import_node_path2.resolve)(destination);
1425
- const entries = (0, import_node_fs2.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && deliveryGroup(entry.name) === label).map((entry) => (0, import_node_path2.join)(root, entry.name));
1426
- const completed = entries.filter((path3) => (0, import_node_fs2.existsSync)((0, import_node_path2.join)(path3, ".remote-transfer-complete"))).map((path3) => ({ path: path3, modified: (0, import_node_fs2.statSync)(path3).mtimeMs })).sort((left, right) => right.modified - left.modified);
1442
+ const entries = (0, import_node_fs3.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && deliveryGroup(entry.name) === label).map((entry) => (0, import_node_path2.join)(root, entry.name));
1443
+ const completed = entries.filter((path3) => (0, import_node_fs3.existsSync)((0, import_node_path2.join)(path3, ".remote-transfer-complete"))).map((path3) => ({ path: path3, modified: (0, import_node_fs3.statSync)(path3).mtimeMs })).sort((left, right) => right.modified - left.modified);
1427
1444
  const toKeep = /* @__PURE__ */ new Set([destinationPath]);
1428
1445
  for (let index = 0; index < keepCount && index < completed.length; index++) {
1429
1446
  toKeep.add(completed[index].path);
1430
1447
  }
1431
1448
  for (const entry of completed) {
1432
1449
  if (!toKeep.has(entry.path)) {
1433
- (0, import_node_fs2.rmSync)(entry.path, { recursive: true, force: true });
1450
+ (0, import_node_fs3.rmSync)(entry.path, { recursive: true, force: true });
1434
1451
  }
1435
1452
  }
1436
1453
  }
@@ -1443,13 +1460,13 @@ function remoteBuildReportPath(args, leg, relativeReport) {
1443
1460
  }
1444
1461
  return (0, import_node_path2.join)((0, import_node_path2.resolve)(directory), relativeReport);
1445
1462
  }
1446
- var import_node_child_process2, import_node_crypto2, import_node_fs2, import_node_path2;
1463
+ var import_node_child_process3, import_node_crypto2, import_node_fs3, import_node_path2;
1447
1464
  var init_remote_build_delivery = __esm({
1448
1465
  "../../scripts/lib/remote-build-delivery.ts"() {
1449
1466
  "use strict";
1450
- import_node_child_process2 = require("node:child_process");
1467
+ import_node_child_process3 = require("node:child_process");
1451
1468
  import_node_crypto2 = require("node:crypto");
1452
- import_node_fs2 = require("node:fs");
1469
+ import_node_fs3 = require("node:fs");
1453
1470
  import_node_path2 = require("node:path");
1454
1471
  }
1455
1472
  });
@@ -1567,10 +1584,10 @@ var require_native_frame_receipt = __commonJS({
1567
1584
  decodeNativeFramebuffer,
1568
1585
  writeNativeFramebufferPng
1569
1586
  };
1570
- async function buildNativeFrameConsumer({ root, cacheRoot }) {
1587
+ async function buildNativeFrameConsumer({ root, cacheRoot, waitForAdmission = false }) {
1571
1588
  const fs3 = require("node:fs");
1572
1589
  const path3 = require("node:path");
1573
- const { spawn } = require("node:child_process");
1590
+ const { spawn: spawn2 } = require("node:child_process");
1574
1591
  const source = path3.join(root, "scripts/debug/native-ios-frame-consumer.m");
1575
1592
  const sourceSha256 = createHash("sha256").update(fs3.readFileSync(source)).digest("hex");
1576
1593
  const cache = path3.join(cacheRoot, "frame-consumer", sourceSha256);
@@ -1579,13 +1596,14 @@ var require_native_frame_receipt = __commonJS({
1579
1596
  const { remoteBuildDelivery: remoteBuildDelivery2 } = await Promise.resolve().then(() => (init_remote_build_delivery(), remote_build_delivery_exports));
1580
1597
  const delivery = remoteBuildDelivery2(root, "native-frame-consumer");
1581
1598
  await new Promise((resolve3, reject) => {
1582
- const child = spawn(
1599
+ const child = spawn2(
1583
1600
  "bun",
1584
1601
  [
1585
1602
  "remote",
1586
1603
  "--delivery-dir",
1587
1604
  delivery,
1588
1605
  "--mac",
1606
+ ...waitForAdmission ? ["--wait"] : [],
1589
1607
  "bash",
1590
1608
  "-c",
1591
1609
  'actual=$(shasum -a 256 scripts/debug/native-ios-frame-consumer.m); test "${actual%% *}" = "$1" && mkdir -p .remote-out && clang -fobjc-arc -framework Foundation -framework IOSurface -lcompression scripts/debug/native-ios-frame-consumer.m -o .remote-out/native-frame-consumer && printf "%s\\n" "$1" > .remote-out/native-frame-consumer.source-sha256',
@@ -1685,7 +1703,7 @@ var require_native_frame_receipt = __commonJS({
1685
1703
  async function encodeNativeFramebufferMovie(rawDir, videoPath) {
1686
1704
  const fs3 = require("node:fs");
1687
1705
  const path3 = require("node:path");
1688
- const { spawn } = require("node:child_process");
1706
+ const { spawn: spawn2 } = require("node:child_process");
1689
1707
  const { Readable } = require("node:stream");
1690
1708
  const { pipeline } = require("node:stream/promises");
1691
1709
  const rows = fs3.readFileSync(path3.join(rawDir, "frames.jsonl"), "utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
@@ -1725,7 +1743,7 @@ var require_native_frame_receipt = __commonJS({
1725
1743
  let activeProcess;
1726
1744
  let deadline;
1727
1745
  try {
1728
- encoder = spawn(
1746
+ encoder = spawn2(
1729
1747
  "ffmpeg",
1730
1748
  [
1731
1749
  "-v",
@@ -1801,7 +1819,7 @@ var require_native_frame_receipt = __commonJS({
1801
1819
  completed
1802
1820
  ]);
1803
1821
  const media = await new Promise((resolve3, reject) => {
1804
- const probe = spawn(
1822
+ const probe = spawn2(
1805
1823
  "ffprobe",
1806
1824
  [
1807
1825
  "-v",
@@ -1887,7 +1905,7 @@ var require_attempt_events = __commonJS({
1887
1905
  }
1888
1906
  module2.exports = { attemptEventsPath, writeAttemptEvent: writeAttemptEvent2 };
1889
1907
  var crypto2 = require("node:crypto");
1890
- var { spawnSync: spawnSync2 } = require("node:child_process");
1908
+ var { spawnSync: spawnSync3 } = require("node:child_process");
1891
1909
  var {
1892
1910
  parseDiscreteFrameCadence,
1893
1911
  classifyDiscreteFrame: classifyDiscreteFrame2,
@@ -2011,7 +2029,7 @@ var require_attempt_events = __commonJS({
2011
2029
  return context;
2012
2030
  }
2013
2031
  function decodeDiscreteNativeFrames(recording, cadence, probeRect, rootRect) {
2014
- const metadata = spawnSync2(
2032
+ const metadata = spawnSync3(
2015
2033
  "ffprobe",
2016
2034
  [
2017
2035
  "-v",
@@ -2040,7 +2058,7 @@ var require_attempt_events = __commonJS({
2040
2058
  const width = Math.round(probeRect.width * sx), height = Math.round(probeRect.height * sy);
2041
2059
  if (x < 0 || y < 0 || width <= 0 || height <= 0 || x + width > stream.width || y + height > stream.height)
2042
2060
  throw new Error("cadence recording probe escapes its original frame");
2043
- const decoded = spawnSync2(
2061
+ const decoded = spawnSync3(
2044
2062
  "ffmpeg",
2045
2063
  [
2046
2064
  "-v",
@@ -2581,7 +2599,7 @@ __export(detox_exports, {
2581
2599
  module.exports = __toCommonJS(detox_exports);
2582
2600
  var import_crypto = require("crypto");
2583
2601
  var fs2 = __toESM(require("fs"), 1);
2584
- var import_node_child_process3 = require("node:child_process");
2602
+ var import_node_child_process4 = require("node:child_process");
2585
2603
  var import_node_module = require("node:module");
2586
2604
  var import_node_util = require("node:util");
2587
2605
  var path2 = __toESM(require("path"), 1);
@@ -2589,11 +2607,60 @@ var import_playwright_core = require("playwright-core");
2589
2607
 
2590
2608
  // ../../scripts/lib/reap-browser.ts
2591
2609
  var childProcess = __toESM(require("node:child_process"), 1);
2592
- var import_node_child_process = require("node:child_process");
2610
+ var import_node_child_process2 = require("node:child_process");
2593
2611
  var fs = __toESM(require("node:fs"), 1);
2594
2612
  var os = __toESM(require("node:os"), 1);
2595
2613
  var path = __toESM(require("node:path"), 1);
2596
2614
 
2615
+ // src/process-group.ts
2616
+ var import_node_child_process = require("node:child_process");
2617
+ var import_node_fs = require("node:fs");
2618
+ function listProcessChildren(parents, timeoutMs = 3e3) {
2619
+ if (parents.some((pid) => !Number.isInteger(pid) || pid <= 1))
2620
+ throw new Error("process inspection requires valid parent pids");
2621
+ if (parents.length === 0) return [];
2622
+ const deadline = Date.now() + timeoutMs;
2623
+ let children;
2624
+ if (process.platform === "linux") {
2625
+ const lists = [];
2626
+ for (const parent of parents) {
2627
+ try {
2628
+ const tasks = `/proc/${parent}/task`;
2629
+ for (const thread of (0, import_node_fs.readdirSync)(tasks)) {
2630
+ if (Date.now() >= deadline)
2631
+ throw new Error("process descendant inspection exceeded its deadline");
2632
+ try {
2633
+ lists.push((0, import_node_fs.readFileSync)(`${tasks}/${thread}/children`, "utf8"));
2634
+ } catch (error) {
2635
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
2636
+ throw error;
2637
+ }
2638
+ }
2639
+ } catch (error) {
2640
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
2641
+ throw error;
2642
+ }
2643
+ }
2644
+ children = lists.join(" ");
2645
+ } else {
2646
+ const result = (0, import_node_child_process.spawnSync)("pgrep", ["-P", parents.join(",")], {
2647
+ encoding: "utf8",
2648
+ stdio: ["ignore", "pipe", "pipe"],
2649
+ timeout: Math.max(1, timeoutMs),
2650
+ killSignal: "SIGKILL"
2651
+ });
2652
+ if (result.error || result.status !== 0 && result.status !== 1) {
2653
+ throw new Error(
2654
+ `could not inspect descendants of processes ${parents.join(",")}: ${result.error?.message || result.stderr.trim() || `pgrep exited ${result.status}`}`
2655
+ );
2656
+ }
2657
+ children = result.stdout;
2658
+ }
2659
+ return [...new Set(children.trim().split(/\s+/).map(Number))].filter(
2660
+ (pid) => Number.isInteger(pid) && pid > 1 && !parents.includes(pid)
2661
+ );
2662
+ }
2663
+
2597
2664
  // ../../scripts/lib/chromium-admission.ts
2598
2665
  function readChromiumQueue(deps, verified, own) {
2599
2666
  const { fs: fs3, os: os2, path: path3, childProcess: childProcess2 } = deps;
@@ -2616,12 +2683,16 @@ function readChromiumQueue(deps, verified, own) {
2616
2683
  if (known !== void 0) return known;
2617
2684
  let same = pid === process.pid;
2618
2685
  if (!same) {
2686
+ let started = "";
2619
2687
  try {
2620
- const started = childProcess2.execFileSync("ps", ["-p", String(pid), "-o", "lstart="], { encoding: "utf8" }).trim();
2621
- same = Math.abs(Date.parse(started) / 1e3 - Number(match[2])) <= 2;
2622
- } catch {
2623
- same = false;
2688
+ started = childProcess2.execFileSync("ps", ["-p", String(pid), "-o", "lstart="], { encoding: "utf8" }).trim();
2689
+ } catch (err) {
2690
+ const gone = err instanceof Error && "status" in err && err.status === 1;
2691
+ if (!gone) return true;
2624
2692
  }
2693
+ const startedAt = Date.parse(started);
2694
+ if (started && Number.isNaN(startedAt)) return true;
2695
+ same = Math.abs(startedAt / 1e3 - Number(match[2])) <= 2;
2625
2696
  }
2626
2697
  verified.set(entry, same);
2627
2698
  return same;
@@ -2741,13 +2812,13 @@ function currentRuntime() {
2741
2812
  return { platform: process.platform, env: process.env };
2742
2813
  }
2743
2814
  function processCommand(pid) {
2744
- return (0, import_node_child_process.execFileSync)("/bin/ps", ["-p", String(pid), "-o", "command="], {
2815
+ return (0, import_node_child_process2.execFileSync)("/bin/ps", ["-p", String(pid), "-o", "command="], {
2745
2816
  encoding: "utf8"
2746
2817
  }).trim();
2747
2818
  }
2748
2819
  function processIdentity(pid) {
2749
2820
  try {
2750
- const identity = (0, import_node_child_process.execFileSync)(
2821
+ const identity = (0, import_node_child_process2.execFileSync)(
2751
2822
  "/bin/ps",
2752
2823
  ["-p", String(pid), "-o", "lstart=", "-o", "command="],
2753
2824
  { encoding: "utf8" }
@@ -2884,10 +2955,10 @@ function rootChromePid(b) {
2884
2955
  function killTree(pid) {
2885
2956
  let kids = [];
2886
2957
  try {
2887
- kids = (0, import_node_child_process.execSync)(`pgrep -P ${pid} || true`).toString().split("\n").filter(Boolean);
2958
+ kids = listProcessChildren([pid]);
2888
2959
  } catch {
2889
2960
  }
2890
- for (const k of kids) killTree(Number(k));
2961
+ for (const k of kids) killTree(k);
2891
2962
  try {
2892
2963
  process.kill(pid, "SIGKILL");
2893
2964
  } catch {
@@ -5867,6 +5938,28 @@ function isAppBootTargetUrl(initUrl) {
5867
5938
  }
5868
5939
  }
5869
5940
 
5941
+ // cli/maestro-accessibility.ts
5942
+ function accessibilityBounds(node) {
5943
+ const left = Math.trunc(node.frame.x);
5944
+ const top = Math.trunc(node.frame.y);
5945
+ return {
5946
+ x: left,
5947
+ y: top,
5948
+ width: Math.trunc(node.frame.x + node.frame.width) - left,
5949
+ height: Math.trunc(node.frame.y + node.frame.height) - top
5950
+ };
5951
+ }
5952
+ function sameNativeAccessibilityShape(a, b, bounds) {
5953
+ if ((a.testID ?? "") !== (b.testID ?? "") || (a.label ?? "") !== (b.label ?? "") || (a.placeholder ?? "") !== (b.placeholder ?? "") || (a.value ?? "") !== (b.value ?? "") || a.enabled !== b.enabled || a.selected !== b.selected || a.checked !== b.checked || a.clickable !== b.clickable || (a.hasFocus ?? false) !== (b.hasFocus ?? false) || JSON.stringify(bounds(a)) !== JSON.stringify(bounds(b)) || a.children.length !== b.children.length) {
5954
+ return false;
5955
+ }
5956
+ for (const [index, child] of a.children.entries()) {
5957
+ const other = b.children[index];
5958
+ if (!other || !sameNativeAccessibilityShape(child, other, bounds)) return false;
5959
+ }
5960
+ return true;
5961
+ }
5962
+
5870
5963
  // cli/commands/inspect/core.ts
5871
5964
  function layoutBoundFontFamily(style) {
5872
5965
  const raw = typeof style === "object" && style !== null ? style.fontFamily : void 0;
@@ -6225,6 +6318,7 @@ async function waitForSootsimIdle({
6225
6318
  stablePolls = 3,
6226
6319
  strict = false,
6227
6320
  paintStill = false,
6321
+ maestro = false,
6228
6322
  motionOnly = false,
6229
6323
  transitionStartWindowMs = 80,
6230
6324
  transitionTimeoutMs = 4e3
@@ -6256,12 +6350,15 @@ async function waitForSootsimIdle({
6256
6350
  const pollMs = ${Math.max(1, Math.round(pollMs))}
6257
6351
  const strict = ${strict ? "true" : "false"}
6258
6352
  const paintStill = ${paintStill ? "true" : "false"}
6353
+ const maestro = ${maestro ? "true" : "false"}
6354
+ const accessibilityBounds = ${accessibilityBounds.toString()}
6355
+ const sameNativeAccessibilityShape = ${sameNativeAccessibilityShape.toString()}
6259
6356
  const motionOnly = ${motionOnly ? "true" : "false"}
6260
6357
  // full mounted-geometry observation needs at least two hierarchy reads
6261
6358
  // plus the stillness window, which a sub-second budget cannot afford:
6262
6359
  // tight budgets (auto-wait) settle on worker stillness plus root
6263
6360
  // membership, generous ones confirm the geometry too.
6264
- const fullGeometry = ${!motionOnly && maxMs >= 1e3 ? "true" : "false"}
6361
+ const fullGeometry = ${!maestro && !motionOnly && maxMs >= 1e3 ? "true" : "false"}
6265
6362
  const requiredStillMs = paintStill ? 0 : ${Math.max(1, Math.round(stablePolls)) * Math.max(1, Math.round(pollMs))}
6266
6363
  const minimumStablePolls = paintStill ? 1 : ${Math.min(2, Math.max(1, Math.round(stablePolls)))}
6267
6364
  const composite = paintStill ? window.SootSim?.bridges?.liveComposite : null
@@ -6383,6 +6480,9 @@ async function waitForSootsimIdle({
6383
6480
  // strict also waits out shell motion: a system alert, sheet or menu
6384
6481
  // animates in the shell worker while the app's own stats read still,
6385
6482
  // so its presentation is complete only once the shell's list empties.
6483
+ const accessibilityRead = maestro
6484
+ ? answer(() => window.__sootsimTest.captureAccessibilityTree()).catch(() => null)
6485
+ : null
6386
6486
  const motionRead = strict
6387
6487
  ? answer(() => window.__sootsimTest?.listAnimations?.()).catch(() => null)
6388
6488
  : null
@@ -6392,9 +6492,12 @@ async function waitForSootsimIdle({
6392
6492
  // the compositor rejects a stats request it is too busy to answer.
6393
6493
  const compositorRead = answer(() => window.__sootsimCompositor.getStats()).catch(() => BUSY)
6394
6494
  const countsRead = motionOnly ? null : answer(() => window.__sootsimTest?.getRequestCounts?.()).catch(() => null)
6395
- // mounted-root membership: the live node count moves while the tree
6396
- // mounts, unmounts or recycles, in either render mode.
6397
- const nodeCountRead = answer(() => window.__sootsimTest?.getNodeCount?.()).catch(() => null)
6495
+ // mounted-root membership: the count of nodes that can reach the
6496
+ // window moves while the screen mounts, unmounts or recycles, in
6497
+ // either render mode. a screen a pushed one covers or an inactive
6498
+ // tab stays mounted out of the window, so what it mounts is not
6499
+ // screen motion.
6500
+ const nodeCountRead = answer(() => window.__sootsimTest?.getWindowNodeCount?.()).catch(() => null)
6398
6501
  // the mounted hierarchy in render-worker mode comes from the test
6399
6502
  // bridge: window.__sootsimRoot is an empty placeholder there, so a
6400
6503
  // walk over it hashes every screen to the same empty string while
@@ -6424,7 +6527,7 @@ async function waitForSootsimIdle({
6424
6527
  const motion = await motionRead
6425
6528
  if (motion === BUSY) workerBusy = true
6426
6529
  else if (Array.isArray(motion)) {
6427
- const blocking = motion.filter((anim) => anim.realm === 'shell' && anim.priority !== 'low')
6530
+ const blocking = motion.filter((anim) => anim.realm === 'shell' && anim.priority !== 'low' && anim.visible !== false)
6428
6531
  shellAnimating = blocking.length > 0
6429
6532
  // name what holds the settle so a timeout points at its owner.
6430
6533
  shellBlockers = blocking
@@ -6456,6 +6559,8 @@ async function waitForSootsimIdle({
6456
6559
  stats.hasActiveAnims === true ||
6457
6560
  stats.hasActiveBlockingNativeAnimations === true ||
6458
6561
  (!paintStill && stats.hasPendingBlockingAnimationFrames === true)
6562
+ // the runtime reports only layout work that can reach the window:
6563
+ // a covered screen or inactive tab committing does not hold it.
6459
6564
  layoutDirty =
6460
6565
  stats.layoutDirty === true && stats.renderRequested === true
6461
6566
  // a freshly-pushed screen showing a skeleton is layout-stable
@@ -6488,6 +6593,11 @@ async function waitForSootsimIdle({
6488
6593
  const total = await nodeCountRead
6489
6594
  if (total === BUSY) workerBusy = true
6490
6595
  nodeCount = typeof total === 'number' ? total : null
6596
+ const accessibility = accessibilityRead ? await accessibilityRead : null
6597
+ const nativeHierarchy = Array.isArray(accessibility) && accessibility.length > 0
6598
+ ? accessibility
6599
+ : null
6600
+ if (accessibility === BUSY) workerBusy = true
6491
6601
  let hierarchy = hierarchyRead ? await hierarchyRead : null
6492
6602
  if (hierarchy === BUSY) {
6493
6603
  workerBusy = true
@@ -6568,7 +6678,7 @@ async function waitForSootsimIdle({
6568
6678
  pixelFrameToken = token
6569
6679
  }
6570
6680
  }
6571
- return { layout: nodes.join(';'), screenPixels, animating, shellAnimating, shellBlockers, layoutDirty, pendingFetches, workerBusy, webViewStarting, webViewBlocker, renderStatsAvailable, requestTotal, requestInFlight, appCommits, nodeCount }
6681
+ return { layout: nodes.join(';'), nativeHierarchy, screenPixels, animating, shellAnimating, shellBlockers, layoutDirty, pendingFetches, workerBusy, webViewStarting, webViewBlocker, renderStatsAvailable, requestTotal, requestInFlight, appCommits, nodeCount }
6572
6682
  }
6573
6683
 
6574
6684
  // how long generic background network is allowed to keep us waiting AFTER
@@ -6582,6 +6692,7 @@ async function waitForSootsimIdle({
6582
6692
  // so the generic request counters are a courtesy, not a gate: honor them
6583
6693
  // only within this grace once the screen is visually still.
6584
6694
  const networkQuietGraceMs = 1200
6695
+ let lastNativeHierarchy = null
6585
6696
  let lastLayout = ''
6586
6697
  let lastScreenPixels = null
6587
6698
  let screenChangedPolls = 0
@@ -6609,6 +6720,9 @@ async function waitForSootsimIdle({
6609
6720
  }
6610
6721
  return true
6611
6722
  }
6723
+ const hierarchyUnchanged = (current, previous) =>
6724
+ current !== null && previous !== null && current.length === previous.length &&
6725
+ current.every((node, index) => sameNativeAccessibilityShape(node, previous[index], accessibilityBounds))
6612
6726
  const IDLE_HELD = { idleHeld: true }
6613
6727
  while (true) {
6614
6728
  if (clockNow() >= deadline) {
@@ -6654,7 +6768,7 @@ async function waitForSootsimIdle({
6654
6768
  // the loop head drains a transition still in flight before it ends.
6655
6769
  if (snapshot.workerBusy && clockNow() >= deadline && report) continue
6656
6770
  polls++
6657
- report = { snapshot, layout: lastLayout, screenPixels: lastScreenPixels, requestTotal: lastRequestTotal, appCommits: lastAppCommits, nodeCount: lastNodeCount }
6771
+ report = { snapshot, nativeHierarchy: lastNativeHierarchy, layout: lastLayout, screenPixels: lastScreenPixels, requestTotal: lastRequestTotal, appCommits: lastAppCommits, nodeCount: lastNodeCount }
6658
6772
  if (snapshot.layoutDirty) dirtyPolls++
6659
6773
  if (snapshot.animating || snapshot.shellAnimating) animatingPolls++
6660
6774
  if (snapshot.webViewStarting) webViewStartingPolls++
@@ -6675,7 +6789,15 @@ async function waitForSootsimIdle({
6675
6789
  // strictOk and adds shell motion plus the blocking-stats floor. a
6676
6790
  // null count or commit reading means the runtime does not report
6677
6791
  // it, which reads as stable the way a missing stats block does.
6678
- const visuallyStill =
6792
+ // native screen checks observe output, not animation ownership. after
6793
+ // its 3 s pixel phase, iOS accepts two equal native TreeNodes even
6794
+ // while paint-only playback continues. overlap hierarchy reads so
6795
+ // neither the gesture nor screenshot deadline grows.
6796
+ const maestroStill = snapshot.screenPixels !== null && !snapshot.workerBusy && !snapshot.webViewStarting && (
6797
+ !screenChanged || (clockNow() - start >= 3000 &&
6798
+ hierarchyUnchanged(snapshot.nativeHierarchy, lastNativeHierarchy))
6799
+ )
6800
+ const visuallyStill = maestro ? maestroStill :
6679
6801
  strictOk &&
6680
6802
  !snapshot.animating &&
6681
6803
  !snapshot.layoutDirty &&
@@ -6703,7 +6825,7 @@ async function waitForSootsimIdle({
6703
6825
  // grace (continuous background traffic must not block forever).
6704
6826
  if (
6705
6827
  stillProven() &&
6706
- (motionOnly || networkQuiet || clockNow() - visuallyStillSince >= networkQuietGraceMs)
6828
+ (maestro || motionOnly || networkQuiet || clockNow() - visuallyStillSince >= networkQuietGraceMs)
6707
6829
  ) {
6708
6830
  if (transitionWait) {
6709
6831
  await transitionWait
@@ -6715,6 +6837,7 @@ async function waitForSootsimIdle({
6715
6837
  stillPolls = []
6716
6838
  visuallyStillSince = 0
6717
6839
  }
6840
+ lastNativeHierarchy = snapshot.nativeHierarchy
6718
6841
  lastLayout = snapshot.layout
6719
6842
  lastScreenPixels = snapshot.screenPixels
6720
6843
  lastRequestTotal = snapshot.requestTotal
@@ -6737,20 +6860,21 @@ async function waitForSootsimIdle({
6737
6860
  }
6738
6861
  const last = report.snapshot
6739
6862
  const blockers = []
6740
- if (strict && !last.renderStatsAvailable) blockers.push('render stats unavailable')
6741
- if (last.animating) blockers.push('animations still running')
6742
- if (last.shellAnimating) blockers.push('shell animations still running (' + last.shellBlockers + ')')
6743
- if (last.layoutDirty) blockers.push('layout still dirty')
6744
- if (last.appCommits !== null && report.appCommits !== null && last.appCommits !== report.appCommits) blockers.push('layout commits still landing')
6745
- if (last.nodeCount !== null && report.nodeCount !== null && last.nodeCount !== report.nodeCount) blockers.push('node count still changing')
6746
- if (!motionOnly && last.pendingFetches > 0) blockers.push(last.pendingFetches + ' image fetches pending')
6863
+ if (!maestro && strict && !last.renderStatsAvailable) blockers.push('render stats unavailable')
6864
+ if (!maestro && last.animating) blockers.push('animations still running')
6865
+ if (!maestro && last.shellAnimating) blockers.push('shell animations still running (' + last.shellBlockers + ')')
6866
+ if (!maestro && last.layoutDirty) blockers.push('layout still dirty')
6867
+ if (!maestro && last.appCommits !== null && report.appCommits !== null && last.appCommits !== report.appCommits) blockers.push('layout commits still landing')
6868
+ if (!maestro && last.nodeCount !== null && report.nodeCount !== null && last.nodeCount !== report.nodeCount) blockers.push('node count still changing')
6869
+ if (!maestro && !motionOnly && last.pendingFetches > 0) blockers.push(last.pendingFetches + ' image fetches pending')
6747
6870
  if (last.workerBusy) blockers.push('a render worker too busy to report')
6748
6871
  if (last.webViewStarting) blockers.push(last.webViewBlocker || 'webView still starting')
6749
- if (last.layout !== report.layout) blockers.push('layout still changing')
6872
+ if (!maestro && last.layout !== report.layout) blockers.push('layout still changing')
6873
+ if (maestro && !hierarchyUnchanged(last.nativeHierarchy, report.nativeHierarchy)) blockers.push(last.nativeHierarchy === null ? 'native accessibility hierarchy unavailable' : 'native accessibility hierarchy still changing')
6750
6874
  if (paintStill && !last.screenPixels) blockers.push('screen pixels unavailable')
6751
6875
  else if (paintStill && !screenUnchanged(last.screenPixels, report.screenPixels)) blockers.push('screen pixels still changing')
6752
- if (!motionOnly && last.requestInFlight > 0) blockers.push(last.requestInFlight + ' requests in flight')
6753
- else if (!motionOnly && last.requestTotal !== report.requestTotal) blockers.push('new requests issued')
6876
+ if (!maestro && !motionOnly && last.requestInFlight > 0) blockers.push(last.requestInFlight + ' requests in flight')
6877
+ else if (!maestro && !motionOnly && last.requestTotal !== report.requestTotal) blockers.push('new requests issued')
6754
6878
  if (polls < 2) blockers.push('sim too busy to poll twice')
6755
6879
  // the last read is still: motion ended too near the deadline for the
6756
6880
  // stillness window to hold before it.
@@ -6850,6 +6974,470 @@ var nativeRuntimeLocation = readBrowserLocation();
6850
6974
  // ../peach-engine/src/metro-fingerprint-registry-client.ts
6851
6975
  var import_metro_fingerprint_registry = require("peachsim/metro-fingerprint-registry");
6852
6976
 
6977
+ // ../peach-compat/src/wrapper-real-packages.ts
6978
+ var SOOTSIM_COMPAT_WRAPPER_REAL_PACKAGES = {
6979
+ expoUiConfirmationDialog: {
6980
+ specifier: "@peach-internal/expo-ui-confirmation-dialog",
6981
+ packageName: "@expo/ui/src/swift-ui/ConfirmationDialog/index.tsx"
6982
+ },
6983
+ expoUiShareLink: {
6984
+ specifier: "@peach-internal/expo-ui-share-link",
6985
+ packageName: "@expo/ui/src/swift-ui/ShareLink/index.tsx"
6986
+ },
6987
+ expoUiNamespace: {
6988
+ specifier: "@peach-internal/expo-ui-namespace",
6989
+ packageName: "@expo/ui/src/swift-ui/Namespace.tsx"
6990
+ },
6991
+ expoUiHost: {
6992
+ specifier: "@peach-internal/expo-ui-host",
6993
+ packageName: "@expo/ui/src/swift-ui/Host/index.tsx"
6994
+ },
6995
+ expoUiBottomSheet: {
6996
+ specifier: "@peach-internal/expo-ui-bottom-sheet",
6997
+ packageName: "@expo/ui/src/swift-ui/BottomSheet/index.tsx"
6998
+ },
6999
+ expoUiTabView: {
7000
+ specifier: "@peach-internal/expo-ui-tab-view",
7001
+ packageName: "@expo/ui/src/swift-ui/TabView/index.tsx"
7002
+ },
7003
+ expoUiForm: {
7004
+ specifier: "@peach-internal/expo-ui-form",
7005
+ packageName: "@expo/ui/src/swift-ui/Form/index.tsx"
7006
+ },
7007
+ expoUiSection: {
7008
+ specifier: "@peach-internal/expo-ui-section",
7009
+ packageName: "@expo/ui/src/swift-ui/Section/index.tsx"
7010
+ },
7011
+ expoUiAccessoryWidgetBackground: {
7012
+ specifier: "@peach-internal/expo-ui-accessory-widget-background",
7013
+ packageName: "@expo/ui/src/swift-ui/AccessoryWidgetBackground/index.tsx"
7014
+ },
7015
+ expoUiGlassEffectContainer: {
7016
+ specifier: "@peach-internal/expo-ui-glass-effect-container",
7017
+ packageName: "@expo/ui/src/swift-ui/GlassEffectContainer/index.tsx"
7018
+ },
7019
+ expoUiContentUnavailableView: {
7020
+ specifier: "@peach-internal/expo-ui-content-unavailable-view",
7021
+ packageName: "@expo/ui/src/swift-ui/ContentUnavailableView/index.tsx"
7022
+ },
7023
+ expoUiColorPicker: {
7024
+ specifier: "@peach-internal/expo-ui-color-picker",
7025
+ packageName: "@expo/ui/src/swift-ui/ColorPicker/index.tsx"
7026
+ },
7027
+ expoUiProgressView: {
7028
+ specifier: "@peach-internal/expo-ui-progress-view",
7029
+ packageName: "@expo/ui/src/swift-ui/ProgressView/index.tsx"
7030
+ },
7031
+ expoUiToggle: {
7032
+ specifier: "@peach-internal/expo-ui-toggle",
7033
+ packageName: "@expo/ui/src/swift-ui/Toggle/index.tsx"
7034
+ },
7035
+ expoUiPicker: {
7036
+ specifier: "@peach-internal/expo-ui-picker",
7037
+ packageName: "@expo/ui/src/swift-ui/Picker/index.tsx"
7038
+ },
7039
+ expoUiDivider: {
7040
+ specifier: "@peach-internal/expo-ui-divider",
7041
+ packageName: "@expo/ui/src/swift-ui/Divider/index.tsx"
7042
+ },
7043
+ expoUiText: {
7044
+ specifier: "@peach-internal/expo-ui-text",
7045
+ packageName: "@expo/ui/src/swift-ui/Text/index.tsx"
7046
+ },
7047
+ expoUiMenu: {
7048
+ specifier: "@peach-internal/expo-ui-menu",
7049
+ packageName: "@expo/ui/src/swift-ui/Menu/index.tsx"
7050
+ },
7051
+ expoUiImage: {
7052
+ specifier: "@peach-internal/expo-ui-image",
7053
+ packageName: "@expo/ui/src/swift-ui/Image/index.tsx"
7054
+ },
7055
+ expoUiAlert: {
7056
+ specifier: "@peach-internal/expo-ui-alert",
7057
+ packageName: "@expo/ui/src/swift-ui/Alert/index.tsx"
7058
+ },
7059
+ expoUiPopover: {
7060
+ specifier: "@peach-internal/expo-ui-popover",
7061
+ packageName: "@expo/ui/src/swift-ui/Popover/index.tsx"
7062
+ },
7063
+ expoUiRNHostView: {
7064
+ specifier: "@peach-internal/expo-ui-rnhost-view",
7065
+ packageName: "@expo/ui/src/swift-ui/RNHostView.tsx"
7066
+ },
7067
+ expoUiOverlay: {
7068
+ specifier: "@peach-internal/expo-ui-overlay",
7069
+ packageName: "@expo/ui/src/swift-ui/Overlay/index.tsx"
7070
+ },
7071
+ expoUiBackground: {
7072
+ specifier: "@peach-internal/expo-ui-background",
7073
+ packageName: "@expo/ui/src/swift-ui/Background/index.tsx"
7074
+ },
7075
+ expoUiMask: {
7076
+ specifier: "@peach-internal/expo-ui-mask",
7077
+ packageName: "@expo/ui/src/swift-ui/Mask/index.tsx"
7078
+ },
7079
+ expoUiShapes: {
7080
+ specifier: "@peach-internal/expo-ui-shapes",
7081
+ packageName: "@expo/ui/src/swift-ui/Shapes/index.tsx"
7082
+ },
7083
+ expoUiControlGroup: {
7084
+ specifier: "@peach-internal/expo-ui-control-group",
7085
+ packageName: "@expo/ui/src/swift-ui/ControlGroup/index.tsx"
7086
+ },
7087
+ expoUiScrollView: {
7088
+ specifier: "@peach-internal/expo-ui-scroll-view",
7089
+ packageName: "@expo/ui/src/swift-ui/ScrollView/index.tsx"
7090
+ },
7091
+ expoUiGrid: {
7092
+ specifier: "@peach-internal/expo-ui-grid",
7093
+ packageName: "@expo/ui/src/swift-ui/Grid/index.tsx"
7094
+ },
7095
+ expoUiGroup: {
7096
+ specifier: "@peach-internal/expo-ui-group",
7097
+ packageName: "@expo/ui/src/swift-ui/Group/index.tsx"
7098
+ },
7099
+ expoUiSpacer: {
7100
+ specifier: "@peach-internal/expo-ui-spacer",
7101
+ packageName: "@expo/ui/src/swift-ui/Spacer/index.tsx"
7102
+ },
7103
+ expoUiLazyHStack: {
7104
+ specifier: "@peach-internal/expo-ui-lazy-hstack",
7105
+ packageName: "@expo/ui/src/swift-ui/LazyHStack/index.tsx"
7106
+ },
7107
+ expoUiLazyVStack: {
7108
+ specifier: "@peach-internal/expo-ui-lazy-vstack",
7109
+ packageName: "@expo/ui/src/swift-ui/LazyVStack/index.tsx"
7110
+ },
7111
+ expoUiHStack: {
7112
+ specifier: "@peach-internal/expo-ui-hstack",
7113
+ packageName: "@expo/ui/src/swift-ui/HStack/index.tsx"
7114
+ },
7115
+ expoUiVStack: {
7116
+ specifier: "@peach-internal/expo-ui-vstack",
7117
+ packageName: "@expo/ui/src/swift-ui/VStack/index.tsx"
7118
+ },
7119
+ expoUiZStack: {
7120
+ specifier: "@peach-internal/expo-ui-zstack",
7121
+ packageName: "@expo/ui/src/swift-ui/ZStack/index.tsx"
7122
+ },
7123
+ expoUiButton: {
7124
+ specifier: "@peach-internal/expo-ui-button",
7125
+ packageName: "@expo/ui/src/swift-ui/Button/index.tsx"
7126
+ },
7127
+ expoUiDatePicker: {
7128
+ specifier: "@peach-internal/expo-ui-date-picker",
7129
+ packageName: "@expo/ui/src/swift-ui/DatePicker/index.tsx"
7130
+ },
7131
+ expoUiSlider: {
7132
+ specifier: "@peach-internal/expo-ui-slider",
7133
+ packageName: "@expo/ui/src/swift-ui/Slider/index.tsx"
7134
+ },
7135
+ expoUiStepper: {
7136
+ specifier: "@peach-internal/expo-ui-stepper",
7137
+ packageName: "@expo/ui/src/swift-ui/Stepper/index.tsx"
7138
+ },
7139
+ expoUiLink: {
7140
+ specifier: "@peach-internal/expo-ui-link",
7141
+ packageName: "@expo/ui/src/swift-ui/Link/index.tsx"
7142
+ },
7143
+ expoUiAnimationConstants: {
7144
+ specifier: "@peach-internal/expo-ui-animation-constants",
7145
+ packageName: "@expo/ui/build/swift-ui/modifiers/animation/constants.js"
7146
+ },
7147
+ expoUiWithAnimation: {
7148
+ specifier: "@peach-internal/expo-ui-with-animation",
7149
+ packageName: "@expo/ui/build/swift-ui/withAnimation.js"
7150
+ },
7151
+ expoUiNativeState: {
7152
+ specifier: "@peach-internal/expo-ui-native-state",
7153
+ packageName: "@expo/ui/build/State/index.js"
7154
+ },
7155
+ expoUiSyncToggle: {
7156
+ specifier: "@peach-internal/expo-ui-sync-toggle",
7157
+ packageName: "@expo/ui/build/swift-ui/SyncToggle/index.js"
7158
+ },
7159
+ expoUiTextField: {
7160
+ specifier: "@peach-internal/expo-ui-text-field",
7161
+ packageName: "@expo/ui/src/swift-ui/TextField/index.tsx"
7162
+ },
7163
+ expoUiSecureField: {
7164
+ specifier: "@peach-internal/expo-ui-secure-field",
7165
+ packageName: "@expo/ui/src/swift-ui/SecureField/index.tsx"
7166
+ },
7167
+ expoUiArrangementView: {
7168
+ specifier: "@peach-internal/expo-ui-arrangement-view",
7169
+ packageName: "@expo/ui/src/swift-ui/ArrangementView/index.tsx"
7170
+ },
7171
+ expoUiLazyHStackForEach: {
7172
+ specifier: "@peach-internal/expo-ui-lazy-hstack-for-each",
7173
+ packageName: "@expo/ui/src/swift-ui/LazyHStack/index.tsx"
7174
+ },
7175
+ expoUiLazyVStackForEach: {
7176
+ specifier: "@peach-internal/expo-ui-lazy-vstack-for-each",
7177
+ packageName: "@expo/ui/src/swift-ui/LazyVStack/index.tsx"
7178
+ },
7179
+ expoUiNavigationDestination: {
7180
+ specifier: "@peach-internal/expo-ui-navigation-destination",
7181
+ packageName: "@expo/ui/src/swift-ui/NavigationDestination/index.tsx"
7182
+ },
7183
+ expoUiNavigationLink: {
7184
+ specifier: "@peach-internal/expo-ui-navigation-link",
7185
+ packageName: "@expo/ui/src/swift-ui/NavigationLink/index.tsx"
7186
+ },
7187
+ expoUiNavigationStack: {
7188
+ specifier: "@peach-internal/expo-ui-navigation-stack",
7189
+ packageName: "@expo/ui/src/swift-ui/NavigationStack/index.tsx"
7190
+ },
7191
+ expoUiNavigationSplitView: {
7192
+ specifier: "@peach-internal/expo-ui-navigation-split-view",
7193
+ packageName: "@expo/ui/src/swift-ui/NavigationSplitView/index.tsx"
7194
+ },
7195
+ expoUiToolbar: {
7196
+ specifier: "@peach-internal/expo-ui-toolbar",
7197
+ packageName: "@expo/ui/src/swift-ui/Toolbar/index.tsx"
7198
+ },
7199
+ expoUiList: {
7200
+ specifier: "@peach-internal/expo-ui-list",
7201
+ packageName: "@expo/ui/src/swift-ui/List/index.tsx"
7202
+ },
7203
+ expoUiDisclosureGroup: {
7204
+ specifier: "@peach-internal/expo-ui-disclosure-group",
7205
+ packageName: "@expo/ui/src/swift-ui/DisclosureGroup/index.tsx"
7206
+ },
7207
+ expoUiGauge: {
7208
+ specifier: "@peach-internal/expo-ui-gauge",
7209
+ packageName: "@expo/ui/src/swift-ui/Gauge/index.tsx"
7210
+ },
7211
+ expoUiLabeledContent: {
7212
+ specifier: "@peach-internal/expo-ui-labeled-content",
7213
+ packageName: "@expo/ui/src/swift-ui/LabeledContent/index.tsx"
7214
+ },
7215
+ expoUiLabel: {
7216
+ specifier: "@peach-internal/expo-ui-label",
7217
+ packageName: "@expo/ui/src/swift-ui/Label/index.tsx"
7218
+ },
7219
+ expoCodedError: {
7220
+ specifier: "@peach-internal/expo-coded-error",
7221
+ packageName: "expo-modules-core/build/errors/CodedError.js"
7222
+ },
7223
+ expoUnavailabilityError: {
7224
+ specifier: "@peach-internal/expo-unavailability-error",
7225
+ packageName: "expo-modules-core/build/errors/UnavailabilityError.js"
7226
+ },
7227
+ expoPlatform: {
7228
+ specifier: "@peach-internal/expo-platform",
7229
+ packageName: "expo-modules-core/build/Platform.js"
7230
+ },
7231
+ expoSharedObjectHook: {
7232
+ specifier: "@peach-internal/expo-shared-object-hook",
7233
+ packageName: "expo-modules-core/build/hooks/useReleasingSharedObject.js"
7234
+ },
7235
+ expoSharedObjectLifecycleHook: {
7236
+ specifier: "@peach-internal/expo-shared-object-lifecycle-hook",
7237
+ packageName: "expo-modules-core/build/hooks/useReleasingSharedObjectWithLifecycle.js"
7238
+ },
7239
+ expoApplication: {
7240
+ specifier: "@sootsim-internal/expo-application-real",
7241
+ packageName: "expo-application"
7242
+ },
7243
+ expoBackgroundFetch: {
7244
+ specifier: "@sootsim-internal/expo-background-fetch-real",
7245
+ packageName: "expo-background-fetch"
7246
+ },
7247
+ expoBackgroundTask: {
7248
+ specifier: "@sootsim-internal/expo-background-task-real",
7249
+ packageName: "expo-background-task"
7250
+ },
7251
+ expoSensors: {
7252
+ specifier: "@sootsim-internal/expo-sensors-real",
7253
+ packageName: "expo-sensors"
7254
+ },
7255
+ expoBattery: {
7256
+ specifier: "@sootsim-internal/expo-battery-real",
7257
+ packageName: "expo-battery"
7258
+ },
7259
+ expoBrightness: {
7260
+ specifier: "@sootsim-internal/expo-brightness-real",
7261
+ packageName: "expo-brightness"
7262
+ },
7263
+ expoConstants: {
7264
+ specifier: "@sootsim-internal/expo-constants-real",
7265
+ packageName: "expo-constants"
7266
+ },
7267
+ expoCalendar: {
7268
+ specifier: "@sootsim-internal/expo-calendar-real",
7269
+ packageName: "expo-calendar"
7270
+ },
7271
+ expoCalendarLegacy: {
7272
+ specifier: "@sootsim-internal/expo-calendar-legacy-real",
7273
+ // the package's own `./legacy` export, not a hardcoded `src/legacy/...`
7274
+ // path — this follows wherever upstream points the subpath, in every
7275
+ // version that has one. versions without it never import the legacy
7276
+ // stub, so the alias just goes unused there.
7277
+ packageName: "expo-calendar/legacy"
7278
+ },
7279
+ expoCellular: {
7280
+ specifier: "@sootsim-internal/expo-cellular-real",
7281
+ packageName: "expo-cellular"
7282
+ },
7283
+ expoContactsContacts: {
7284
+ specifier: "@sootsim-internal/expo-contacts-contacts",
7285
+ // sdk 57 split Contacts.ts into src/index.ts + legacy/ + next/
7286
+ packageName: "expo-contacts/src/index.ts"
7287
+ },
7288
+ expoContactsContactAccessButton: {
7289
+ specifier: "@sootsim-internal/expo-contacts-contact-access-button",
7290
+ packageName: "expo-contacts/src/ContactAccessButton.tsx"
7291
+ },
7292
+ expoContactsLegacy: {
7293
+ specifier: "@sootsim-internal/expo-contacts-legacy-real",
7294
+ // same as expoCalendarLegacy: the public `./legacy` export, not a src path.
7295
+ packageName: "expo-contacts/legacy"
7296
+ },
7297
+ expoInsights: {
7298
+ specifier: "@sootsim-internal/expo-insights-real",
7299
+ packageName: "expo-insights"
7300
+ },
7301
+ expoIntentLauncher: {
7302
+ specifier: "@sootsim-internal/expo-intent-launcher-real",
7303
+ packageName: "expo-intent-launcher"
7304
+ },
7305
+ expoMailComposer: {
7306
+ specifier: "@sootsim-internal/expo-mail-composer-real",
7307
+ packageName: "expo-mail-composer"
7308
+ },
7309
+ expoNetwork: {
7310
+ specifier: "@sootsim-internal/expo-network-real",
7311
+ packageName: "expo-network"
7312
+ },
7313
+ expoPrint: {
7314
+ specifier: "@sootsim-internal/expo-print-real",
7315
+ packageName: "expo-print"
7316
+ },
7317
+ expoScreenCapture: {
7318
+ specifier: "@sootsim-internal/expo-screen-capture-real",
7319
+ packageName: "expo-screen-capture"
7320
+ },
7321
+ expoScreenOrientation: {
7322
+ specifier: "@sootsim-internal/expo-screen-orientation-real",
7323
+ packageName: "expo-screen-orientation"
7324
+ },
7325
+ expoQuickActions: {
7326
+ specifier: "@sootsim-internal/expo-quick-actions-real",
7327
+ packageName: "expo-quick-actions/build/index.js"
7328
+ },
7329
+ expoSecureStore: {
7330
+ specifier: "@sootsim-internal/expo-secure-store-real",
7331
+ packageName: "expo-secure-store"
7332
+ },
7333
+ expoSplashScreen: {
7334
+ specifier: "@sootsim-internal/expo-splash-screen-real",
7335
+ packageName: "expo-splash-screen"
7336
+ },
7337
+ expoSqlite: {
7338
+ specifier: "@sootsim-internal/expo-sqlite-real",
7339
+ packageName: "expo-sqlite"
7340
+ },
7341
+ expoLinearGradient: {
7342
+ specifier: "@sootsim-internal/expo-linear-gradient-real",
7343
+ packageName: "expo-linear-gradient"
7344
+ },
7345
+ expoBlur: {
7346
+ specifier: "@sootsim-internal/expo-blur-real",
7347
+ packageName: "expo-blur"
7348
+ },
7349
+ expoSymbols: {
7350
+ specifier: "@sootsim-internal/expo-symbols-real",
7351
+ packageName: "expo-symbols"
7352
+ },
7353
+ communityCheckbox: {
7354
+ specifier: "@sootsim-internal/react-native-community-checkbox-real",
7355
+ packageName: "@react-native-community/checkbox/dist/js/CheckBox.ios.js"
7356
+ },
7357
+ reactNativeMaskedView: {
7358
+ specifier: "@sootsim-internal/masked-view-real",
7359
+ packageName: "@react-native-masked-view/masked-view"
7360
+ },
7361
+ expoSharing: {
7362
+ specifier: "@sootsim-internal/expo-sharing-real",
7363
+ packageName: "expo-sharing"
7364
+ },
7365
+ expoSms: {
7366
+ specifier: "@sootsim-internal/expo-sms-real",
7367
+ packageName: "expo-sms"
7368
+ },
7369
+ expoSpeech: {
7370
+ specifier: "@sootsim-internal/expo-speech-real",
7371
+ packageName: "expo-speech"
7372
+ },
7373
+ expoStoreReview: {
7374
+ specifier: "@sootsim-internal/expo-store-review-real",
7375
+ packageName: "expo-store-review"
7376
+ },
7377
+ expoTaskManager: {
7378
+ specifier: "@sootsim-internal/expo-task-manager-real",
7379
+ packageName: "expo-task-manager"
7380
+ },
7381
+ expoTrackingTransparency: {
7382
+ specifier: "@sootsim-internal/expo-tracking-transparency-real",
7383
+ packageName: "expo-tracking-transparency"
7384
+ },
7385
+ expoVideoThumbnails: {
7386
+ specifier: "@sootsim-internal/expo-video-thumbnails-real",
7387
+ packageName: "expo-video-thumbnails"
7388
+ },
7389
+ googleSignin: {
7390
+ specifier: "@sootsim-internal/react-native-google-signin-real",
7391
+ packageName: "@react-native-google-signin/google-signin"
7392
+ },
7393
+ reactNativeHapticFeedback: {
7394
+ specifier: "@sootsim-internal/react-native-haptic-feedback-real",
7395
+ packageName: "react-native-haptic-feedback"
7396
+ },
7397
+ reactNativeSegmentedControl: {
7398
+ specifier: "@sootsim-internal/react-native-segmented-control-real",
7399
+ packageName: "@react-native-segmented-control/segmented-control/js/SegmentedControl.ios.js"
7400
+ },
7401
+ reactNativeUitextview: {
7402
+ specifier: "@sootsim-internal/react-native-uitextview-real",
7403
+ packageName: "react-native-uitextview"
7404
+ },
7405
+ reactNativeWorkletsBundleMode: {
7406
+ specifier: "@sootsim-internal/react-native-worklets-bundle-mode-real",
7407
+ packageName: "react-native-worklets/lib/module/debug/bundleMode.native.js"
7408
+ },
7409
+ reactNativeWorkletsFunction: {
7410
+ specifier: "@peach-internal/react-native-worklets-function-real",
7411
+ packageName: "react-native-worklets/lib/module/workletFunction.js"
7412
+ },
7413
+ reactNativeWorkletsRunLoop: {
7414
+ specifier: "@sootsim-internal/react-native-worklets-run-loop-real",
7415
+ packageName: "react-native-worklets/lib/module/runLoop/workletRuntime/index.js"
7416
+ }
7417
+ };
7418
+ var PEACH_REACT_NATIVE_UPSTREAM_MODULES = {
7419
+ reactNativeBatchedBridge: {
7420
+ specifier: "@peach-internal/react-native/BatchedBridge/BatchedBridge",
7421
+ packageName: "react-native/Libraries/BatchedBridge/BatchedBridge.js"
7422
+ },
7423
+ reactNativeStyleAttributes: {
7424
+ specifier: "@peach-internal/react-native/Components/View/ReactNativeStyleAttributes",
7425
+ packageName: "react-native/Libraries/Components/View/ReactNativeStyleAttributes.js"
7426
+ },
7427
+ reactNativeProcessColor: {
7428
+ specifier: "@peach-internal/react-native/StyleSheet/processColor",
7429
+ packageName: "react-native/Libraries/StyleSheet/processColor.js"
7430
+ },
7431
+ reactNativeFontVariationSettings: {
7432
+ specifier: "@peach-internal/react-native-font-variation-settings",
7433
+ packageName: "react-native/Libraries/StyleSheet/processFontVariationSettings.js"
7434
+ }
7435
+ };
7436
+ var PEACH_UPSTREAM_MODULES = {
7437
+ ...SOOTSIM_COMPAT_WRAPPER_REAL_PACKAGES,
7438
+ ...PEACH_REACT_NATIVE_UPSTREAM_MODULES
7439
+ };
7440
+
6853
7441
  // ../peach-engine/src/metro-fingerprint.ts
6854
7442
  var import_metro_production_bundle2 = require("peachsim/metro-production-bundle");
6855
7443
  var import_react_native_host_modules = require("peachsim/react-native-host-modules");
@@ -6910,7 +7498,8 @@ var peachPublicBrand = Object.freeze({
6910
7498
  packageName: "peachsim",
6911
7499
  commandName: "peach",
6912
7500
  domain: "peachsim.com",
6913
- origin: "https://peachsim.com"
7501
+ origin: "https://peachsim.com",
7502
+ boxOrigin: "https://rnx-box.contrast.dev"
6914
7503
  });
6915
7504
  var rnxPublicBrand2 = peachPublicBrand;
6916
7505
 
@@ -9252,10 +9841,6 @@ function buildComm() {
9252
9841
  return () => null;
9253
9842
  }
9254
9843
  function build() {
9255
- const envPpid = Number(process.env.PPID);
9256
- if (Number.isFinite(envPpid) && envPpid > 1 && envPpid !== process.ppid) {
9257
- return () => envPpid;
9258
- }
9259
9844
  if (process.platform === "linux") return buildLinux();
9260
9845
  if (process.platform === "darwin") {
9261
9846
  const ffi = tryBuildDarwinFfi();
@@ -9418,24 +10003,24 @@ function tryBuildDarwinPgidFfi() {
9418
10003
 
9419
10004
  // cli/secure-file.ts
9420
10005
  var import_node_crypto = require("node:crypto");
9421
- var import_node_fs = require("node:fs");
10006
+ var import_node_fs2 = require("node:fs");
9422
10007
  var import_node_path = require("node:path");
9423
10008
  function writeSecureJsonAtomic(filepath, value) {
9424
- (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(filepath), { recursive: true });
10009
+ (0, import_node_fs2.mkdirSync)((0, import_node_path.dirname)(filepath), { recursive: true });
9425
10010
  const temporary = `${filepath}.tmp-${process.pid}-${(0, import_node_crypto.randomUUID)()}`;
9426
10011
  let descriptor = null;
9427
10012
  try {
9428
- descriptor = (0, import_node_fs.openSync)(temporary, "wx", 384);
9429
- (0, import_node_fs.fchmodSync)(descriptor, 384);
9430
- (0, import_node_fs.writeFileSync)(descriptor, `${JSON.stringify(value, null, 2)}
10013
+ descriptor = (0, import_node_fs2.openSync)(temporary, "wx", 384);
10014
+ (0, import_node_fs2.fchmodSync)(descriptor, 384);
10015
+ (0, import_node_fs2.writeFileSync)(descriptor, `${JSON.stringify(value, null, 2)}
9431
10016
  `);
9432
- (0, import_node_fs.fsyncSync)(descriptor);
9433
- (0, import_node_fs.closeSync)(descriptor);
10017
+ (0, import_node_fs2.fsyncSync)(descriptor);
10018
+ (0, import_node_fs2.closeSync)(descriptor);
9434
10019
  descriptor = null;
9435
- (0, import_node_fs.renameSync)(temporary, filepath);
10020
+ (0, import_node_fs2.renameSync)(temporary, filepath);
9436
10021
  } catch (error) {
9437
- if (descriptor !== null) (0, import_node_fs.closeSync)(descriptor);
9438
- (0, import_node_fs.rmSync)(temporary, { force: true });
10022
+ if (descriptor !== null) (0, import_node_fs2.closeSync)(descriptor);
10023
+ (0, import_node_fs2.rmSync)(temporary, { force: true });
9439
10024
  throw error;
9440
10025
  }
9441
10026
  }
@@ -10848,6 +11433,9 @@ var BASE_FONT_URLS = [SF_PRO_FONT_URL];
10848
11433
  var BOOT_FONT_URLS = [...BASE_FONT_URLS];
10849
11434
  var PLATFORM_FONT_FAMILIES = [DEFAULT_FONT_FAMILY, EMOJI_FONT_FAMILY];
10850
11435
  var _registeredFamilies = new Set(PLATFORM_FONT_FAMILIES);
11436
+ var _registeredFamiliesByFold = new Map(
11437
+ PLATFORM_FONT_FAMILIES.map((family) => [family.toLowerCase(), family])
11438
+ );
10851
11439
 
10852
11440
  // ../peach-engine/src/engine/scroll-momentum-math.ts
10853
11441
  var IOS_STATUS_BAR_SCROLL_TO_TOP_RESPONSE_SECONDS = 0.6;
@@ -11323,72 +11911,269 @@ async function dispatchTwoFingerGesture(page, startA, startB, endA, endB, option
11323
11911
  await page.waitForTimeout(250);
11324
11912
  }
11325
11913
 
11326
- // detox/expectations.ts
11327
- var DEFAULT_VISIBILITY_THRESHOLD = 0.75;
11328
- async function scrollMatcherElement(page, findNode, matcher, pixels, direction, startPositionX, startPositionY) {
11329
- const node = await findNode(matcher);
11330
- if (!node) {
11331
- throw new Error(`scroll container not found: ${JSON.stringify(matcher)}`);
11332
- }
11333
- const before = scrollOffsetOf(node);
11334
- await dragScrollNode(page, node, pixels, direction, startPositionX, startPositionY);
11335
- const after = scrollOffsetOf(await findNode(matcher));
11336
- return Math.abs(after.x - before.x) + Math.abs(after.y - before.y);
11337
- }
11338
- function scrollOffsetOf(node) {
11339
- return { x: node?.scrollOffsetX ?? 0, y: node?.scrollOffsetY ?? 0 };
11340
- }
11341
- function visibleFraction(node) {
11342
- const totalArea = (node?.layout?.width ?? 0) * (node?.layout?.height ?? 0);
11343
- const frame = node?.visibleFrame;
11344
- if (totalArea <= 0 || typeof frame?.area !== "number") return 0;
11345
- return frame.area / totalArea;
11346
- }
11347
- function isNodeVisible(node) {
11348
- return visibleFraction(node) >= DEFAULT_VISIBILITY_THRESHOLD;
11914
+ // detox/navigation-lifecycle.ts
11915
+ var import_attempt_events = __toESM(require_attempt_events(), 1);
11916
+ var DIAGNOSTIC_LIMIT = 40;
11917
+ var pageDiagnostics = /* @__PURE__ */ new WeakMap();
11918
+ var activeNavigations = /* @__PURE__ */ new WeakMap();
11919
+ var lastNavigations = /* @__PURE__ */ new WeakMap();
11920
+ var nextNavigationIdentity = 0;
11921
+ var persistedDiagnostics = /* @__PURE__ */ new WeakSet();
11922
+ function persistDiagnostic(diagnostic) {
11923
+ if (persistedDiagnostics.has(diagnostic)) return;
11924
+ persistedDiagnostics.add(diagnostic);
11925
+ (0, import_attempt_events.writeAttemptEvent)(diagnostic);
11349
11926
  }
11350
- function focusIsWithin(focused, nodeId) {
11351
- return focused.nodeId === nodeId || focused.ancestorNodeIds.includes(nodeId);
11927
+ function recordPageDiagnostic(page, diagnostic, persist = false) {
11928
+ const entries = pageDiagnostics.get(page);
11929
+ if (!entries) return;
11930
+ entries.push(diagnostic);
11931
+ if (entries.length > DIAGNOSTIC_LIMIT) entries.shift();
11932
+ if (persist) persistDiagnostic(diagnostic);
11352
11933
  }
11353
- async function isTextInputNodeFocused(node, page) {
11354
- const focused = await page.evaluate(async () => window.__sootsimTest.getFocusedNode());
11355
- return focused !== null && focusIsWithin(focused, node.nodeId);
11934
+ function recordGuestError(page, diagnostic) {
11935
+ recordPageDiagnostic(page, diagnostic);
11936
+ for (const entry of pageDiagnostics.get(page) ?? []) persistDiagnostic(entry);
11356
11937
  }
11357
- async function isNodeFocused(node, page) {
11358
- if (node.isTextInput) return isTextInputNodeFocused(node, page);
11359
- return page.evaluate(
11360
- (nodeId) => document.activeElement?.getAttribute("data-sootsim-id") === String(nodeId),
11361
- node.nodeId
11362
- );
11938
+ function isTopLevelDocumentRequest(page, request) {
11939
+ if (!request.isNavigationRequest() || request.resourceType() !== "document") {
11940
+ return false;
11941
+ }
11942
+ try {
11943
+ return request.frame() === page.mainFrame();
11944
+ } catch {
11945
+ return false;
11946
+ }
11363
11947
  }
11364
- function describeNodeVisibility(node) {
11365
- if (!node) return "not found";
11366
- const absX = node.absolutePosition?.x ?? node.layout?.x ?? 0;
11367
- const absY = node.absolutePosition?.y ?? node.layout?.y ?? 0;
11368
- return JSON.stringify({
11369
- layout: node.layout,
11370
- absolutePosition: node.absolutePosition ?? null,
11371
- frame: {
11372
- x: absX,
11373
- y: absY,
11374
- right: absX + (node.layout?.width ?? 0),
11375
- bottom: absY + (node.layout?.height ?? 0)
11948
+ function recordNavigationMilestoneFor(page, active, milestone, details = {}) {
11949
+ recordPageDiagnostic(
11950
+ page,
11951
+ {
11952
+ ...active,
11953
+ ...details,
11954
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
11955
+ milestone,
11956
+ target: process.env.CONFORMANCE_PROOF_TARGET || "sootsim",
11957
+ type: "rnx-navigation",
11958
+ url: details.url ?? active.navigationUrl
11376
11959
  },
11377
- visibleFrame: node.visibleFrame ?? null,
11378
- visibleFraction: visibleFraction(node),
11379
- threshold: DEFAULT_VISIBILITY_THRESHOLD,
11380
- style: node.style ?? null
11381
- });
11960
+ true
11961
+ );
11382
11962
  }
11383
- function createExpect(findNode, getPage2) {
11384
- return function sootsimExpect2(el) {
11385
- return new SootSimExpectation(el._matcher, findNode, getPage2, false);
11386
- };
11963
+ function recordNavigationMilestone(page, milestone, details = {}) {
11964
+ const active = activeNavigations.get(page);
11965
+ if (active) recordNavigationMilestoneFor(page, active, milestone, details);
11387
11966
  }
11388
- var SootSimExpectation = class _SootSimExpectation {
11389
- matcher;
11390
- findNode;
11391
- getPage;
11967
+ function attachPageDiagnostics(page) {
11968
+ if (pageDiagnostics.has(page)) return;
11969
+ pageDiagnostics.set(page, []);
11970
+ page.on("console", (message) => {
11971
+ const diagnostic = {
11972
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
11973
+ level: message.type(),
11974
+ message: message.text(),
11975
+ type: "page-console"
11976
+ };
11977
+ if (message.type() === "error") recordGuestError(page, diagnostic);
11978
+ else recordPageDiagnostic(page, diagnostic);
11979
+ });
11980
+ page.on("pageerror", (error) => {
11981
+ recordGuestError(page, {
11982
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
11983
+ message: error.message,
11984
+ type: "page-error"
11985
+ });
11986
+ });
11987
+ page.on("request", (request) => {
11988
+ if (!isTopLevelDocumentRequest(page, request)) return;
11989
+ recordNavigationMilestone(page, "document-request", {
11990
+ method: request.method(),
11991
+ url: request.url()
11992
+ });
11993
+ });
11994
+ page.on("response", (response) => {
11995
+ const request = response.request();
11996
+ if (!isTopLevelDocumentRequest(page, request)) return;
11997
+ recordNavigationMilestone(page, "document-response", {
11998
+ headersReceived: true,
11999
+ status: response.status(),
12000
+ url: request.url()
12001
+ });
12002
+ });
12003
+ page.on("requestfinished", (request) => {
12004
+ if (!isTopLevelDocumentRequest(page, request)) return;
12005
+ recordNavigationMilestone(page, "document-request-finished", { url: request.url() });
12006
+ });
12007
+ page.on("requestfailed", (request) => {
12008
+ const failure = request.failure();
12009
+ const errorText = failure?.errorText ?? "";
12010
+ if (isTopLevelDocumentRequest(page, request)) {
12011
+ recordNavigationMilestone(page, "document-request-failed", {
12012
+ errorText,
12013
+ url: request.url()
12014
+ });
12015
+ return;
12016
+ }
12017
+ recordPageDiagnostic(page, {
12018
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
12019
+ errorText,
12020
+ method: request.method(),
12021
+ type: "subresource-request-failed",
12022
+ url: request.url()
12023
+ });
12024
+ });
12025
+ page.on("domcontentloaded", () => {
12026
+ recordNavigationMilestone(page, "domcontentloaded");
12027
+ });
12028
+ page.on("load", () => {
12029
+ recordNavigationMilestone(page, "load");
12030
+ });
12031
+ }
12032
+ async function readSootsimPageState(page) {
12033
+ try {
12034
+ return await page.evaluate(() => {
12035
+ const bootMarks = Reflect.get(window, "__sootsimBootMarks");
12036
+ return {
12037
+ bodyText: document.body?.innerText?.slice(0, 500) ?? "",
12038
+ bootStage: document.documentElement.dataset.sootsimBootStage ?? null,
12039
+ bootMarks: Array.isArray(bootMarks) ? bootMarks.slice(-16) : [],
12040
+ capturedAtEpochMs: Date.now(),
12041
+ bridges: {
12042
+ sootsim: Boolean(window.SootSim),
12043
+ test: Boolean(window.__sootsimTest)
12044
+ },
12045
+ globals: Object.keys(window).filter((key) => key.startsWith("__sootsim") || key === "SootSim").sort(),
12046
+ readyState: document.readyState,
12047
+ title: document.title,
12048
+ url: window.location.href
12049
+ };
12050
+ });
12051
+ } catch (error) {
12052
+ return {
12053
+ evaluateError: error instanceof Error ? error.message : String(error)
12054
+ };
12055
+ }
12056
+ }
12057
+ async function describeSootsimBridgeFailure(page, cause, summary = "timed out waiting for sootsim test bridge", pageState) {
12058
+ const recordBridgeFailure = pageState === void 0;
12059
+ const state = pageState ?? await readSootsimPageState(page);
12060
+ const navigation = lastNavigations.get(page);
12061
+ if (recordBridgeFailure && navigation) {
12062
+ recordNavigationMilestoneFor(page, navigation, "error", {
12063
+ errorText: cause instanceof Error ? cause.message : String(cause),
12064
+ failurePhase: "bridge",
12065
+ pageState: state
12066
+ });
12067
+ }
12068
+ return new Error(
12069
+ [
12070
+ summary,
12071
+ `cause: ${cause instanceof Error ? cause.message : String(cause)}`,
12072
+ `page: ${JSON.stringify(state)}`,
12073
+ `recent page diagnostics:
12074
+ ${JSON.stringify(getSootsimNavigationDiagnostics(page))}`
12075
+ ].join("\n")
12076
+ );
12077
+ }
12078
+ async function navigateSootsimPage(page, url, identity) {
12079
+ attachPageDiagnostics(page);
12080
+ const navigation = {
12081
+ ...identity,
12082
+ navigationUrl: url,
12083
+ navigationId: ++nextNavigationIdentity
12084
+ };
12085
+ activeNavigations.set(page, navigation);
12086
+ lastNavigations.set(page, navigation);
12087
+ recordNavigationMilestone(page, "start");
12088
+ try {
12089
+ return await page.goto(url, { waitUntil: "load", timeout: 3e4 });
12090
+ } catch (error) {
12091
+ const pageState = await readSootsimPageState(page);
12092
+ recordNavigationMilestone(page, "error", {
12093
+ errorText: error instanceof Error ? error.message : String(error),
12094
+ failurePhase: "navigation",
12095
+ pageState
12096
+ });
12097
+ throw await describeSootsimBridgeFailure(
12098
+ page,
12099
+ error,
12100
+ "sootsim navigation failed",
12101
+ pageState
12102
+ );
12103
+ } finally {
12104
+ activeNavigations.delete(page);
12105
+ }
12106
+ }
12107
+ function getSootsimNavigationDiagnostics(page) {
12108
+ return [...pageDiagnostics.get(page) ?? []];
12109
+ }
12110
+
12111
+ // detox/expectations.ts
12112
+ var DEFAULT_VISIBILITY_THRESHOLD = 0.75;
12113
+ async function scrollMatcherElement(page, findNode, matcher, pixels, direction, startPositionX, startPositionY) {
12114
+ const node = await findNode(matcher);
12115
+ if (!node) {
12116
+ throw new Error(`scroll container not found: ${JSON.stringify(matcher)}`);
12117
+ }
12118
+ const before = scrollOffsetOf(node);
12119
+ await dragScrollNode(page, node, pixels, direction, startPositionX, startPositionY);
12120
+ const after = scrollOffsetOf(await findNode(matcher));
12121
+ return Math.abs(after.x - before.x) + Math.abs(after.y - before.y);
12122
+ }
12123
+ function scrollOffsetOf(node) {
12124
+ return { x: node?.scrollOffsetX ?? 0, y: node?.scrollOffsetY ?? 0 };
12125
+ }
12126
+ function visibleFraction(node) {
12127
+ const totalArea = (node?.layout?.width ?? 0) * (node?.layout?.height ?? 0);
12128
+ const frame = node?.visibleFrame;
12129
+ if (totalArea <= 0 || typeof frame?.area !== "number") return 0;
12130
+ return frame.area / totalArea;
12131
+ }
12132
+ function isNodeVisible(node) {
12133
+ return visibleFraction(node) >= DEFAULT_VISIBILITY_THRESHOLD;
12134
+ }
12135
+ function focusIsWithin(focused, nodeId) {
12136
+ return focused.nodeId === nodeId || focused.ancestorNodeIds.includes(nodeId);
12137
+ }
12138
+ async function isTextInputNodeFocused(node, page) {
12139
+ const focused = await page.evaluate(async () => window.__sootsimTest.getFocusedNode());
12140
+ return focused !== null && focusIsWithin(focused, node.nodeId);
12141
+ }
12142
+ async function isNodeFocused(node, page) {
12143
+ if (node.isTextInput) return isTextInputNodeFocused(node, page);
12144
+ return page.evaluate(
12145
+ (nodeId) => document.activeElement?.getAttribute("data-sootsim-id") === String(nodeId),
12146
+ node.nodeId
12147
+ );
12148
+ }
12149
+ function describeNodeVisibility(node) {
12150
+ if (!node) return "not found";
12151
+ const absX = node.absolutePosition?.x ?? node.layout?.x ?? 0;
12152
+ const absY = node.absolutePosition?.y ?? node.layout?.y ?? 0;
12153
+ return JSON.stringify({
12154
+ layout: node.layout,
12155
+ absolutePosition: node.absolutePosition ?? null,
12156
+ frame: {
12157
+ x: absX,
12158
+ y: absY,
12159
+ right: absX + (node.layout?.width ?? 0),
12160
+ bottom: absY + (node.layout?.height ?? 0)
12161
+ },
12162
+ visibleFrame: node.visibleFrame ?? null,
12163
+ visibleFraction: visibleFraction(node),
12164
+ threshold: DEFAULT_VISIBILITY_THRESHOLD,
12165
+ style: node.style ?? null
12166
+ });
12167
+ }
12168
+ function createExpect(findNode, getPage2) {
12169
+ return function sootsimExpect2(el) {
12170
+ return new SootSimExpectation(el._matcher, findNode, getPage2, false);
12171
+ };
12172
+ }
12173
+ var SootSimExpectation = class _SootSimExpectation {
12174
+ matcher;
12175
+ findNode;
12176
+ getPage;
11392
12177
  negated;
11393
12178
  constructor(matcher, findNode, getPage2, negated) {
11394
12179
  this.matcher = matcher;
@@ -11604,7 +12389,11 @@ var SootSimWaitForChain = class {
11604
12389
  const node = await this.findNode(this.matcher);
11605
12390
  const exists = node !== null && node !== void 0;
11606
12391
  if (this._negated ? exists : !exists) {
11607
- throw new Error(`waitFor toExist failed: ${JSON.stringify(this.matcher)}`);
12392
+ throw new Error(
12393
+ `waitFor toExist failed: ${JSON.stringify(this.matcher)}
12394
+ recent page diagnostics:
12395
+ ${JSON.stringify(getSootsimNavigationDiagnostics(this.getPage()))}`
12396
+ );
11608
12397
  }
11609
12398
  };
11610
12399
  return new SootSimWaitForAction(this.assertionFn, this.findNode, this.getPage);
@@ -11790,193 +12579,6 @@ var by = {
11790
12579
 
11791
12580
  // detox/index.ts
11792
12581
  var import_motion_change = __toESM(require_motion_change(), 1);
11793
-
11794
- // detox/navigation-lifecycle.ts
11795
- var import_attempt_events = __toESM(require_attempt_events(), 1);
11796
- var DIAGNOSTIC_LIMIT = 40;
11797
- var pageDiagnostics = /* @__PURE__ */ new WeakMap();
11798
- var activeNavigations = /* @__PURE__ */ new WeakMap();
11799
- var lastNavigations = /* @__PURE__ */ new WeakMap();
11800
- var nextNavigationIdentity = 0;
11801
- function recordPageDiagnostic(page, diagnostic, persist = false) {
11802
- const entries = pageDiagnostics.get(page);
11803
- if (!entries) return;
11804
- entries.push(diagnostic);
11805
- if (entries.length > DIAGNOSTIC_LIMIT) entries.shift();
11806
- if (persist) (0, import_attempt_events.writeAttemptEvent)(diagnostic);
11807
- }
11808
- function isTopLevelDocumentRequest(page, request) {
11809
- if (!request.isNavigationRequest() || request.resourceType() !== "document") {
11810
- return false;
11811
- }
11812
- try {
11813
- return request.frame() === page.mainFrame();
11814
- } catch {
11815
- return false;
11816
- }
11817
- }
11818
- function recordNavigationMilestoneFor(page, active, milestone, details = {}) {
11819
- recordPageDiagnostic(
11820
- page,
11821
- {
11822
- ...active,
11823
- ...details,
11824
- capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
11825
- milestone,
11826
- target: process.env.CONFORMANCE_PROOF_TARGET || "sootsim",
11827
- type: "rnx-navigation",
11828
- url: details.url ?? active.navigationUrl
11829
- },
11830
- true
11831
- );
11832
- }
11833
- function recordNavigationMilestone(page, milestone, details = {}) {
11834
- const active = activeNavigations.get(page);
11835
- if (active) recordNavigationMilestoneFor(page, active, milestone, details);
11836
- }
11837
- function attachPageDiagnostics(page) {
11838
- if (pageDiagnostics.has(page)) return;
11839
- pageDiagnostics.set(page, []);
11840
- page.on("console", (message) => {
11841
- recordPageDiagnostic(page, {
11842
- capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
11843
- level: message.type(),
11844
- message: message.text(),
11845
- type: "page-console"
11846
- });
11847
- });
11848
- page.on("pageerror", (error) => {
11849
- recordPageDiagnostic(page, {
11850
- capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
11851
- message: error.message,
11852
- type: "page-error"
11853
- });
11854
- });
11855
- page.on("request", (request) => {
11856
- if (!isTopLevelDocumentRequest(page, request)) return;
11857
- recordNavigationMilestone(page, "document-request", {
11858
- method: request.method(),
11859
- url: request.url()
11860
- });
11861
- });
11862
- page.on("response", (response) => {
11863
- const request = response.request();
11864
- if (!isTopLevelDocumentRequest(page, request)) return;
11865
- recordNavigationMilestone(page, "document-response", {
11866
- headersReceived: true,
11867
- status: response.status(),
11868
- url: request.url()
11869
- });
11870
- });
11871
- page.on("requestfinished", (request) => {
11872
- if (!isTopLevelDocumentRequest(page, request)) return;
11873
- recordNavigationMilestone(page, "document-request-finished", { url: request.url() });
11874
- });
11875
- page.on("requestfailed", (request) => {
11876
- const failure = request.failure();
11877
- const errorText = failure?.errorText ?? "";
11878
- if (isTopLevelDocumentRequest(page, request)) {
11879
- recordNavigationMilestone(page, "document-request-failed", {
11880
- errorText,
11881
- url: request.url()
11882
- });
11883
- return;
11884
- }
11885
- recordPageDiagnostic(page, {
11886
- capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
11887
- errorText,
11888
- method: request.method(),
11889
- type: "subresource-request-failed",
11890
- url: request.url()
11891
- });
11892
- });
11893
- page.on("domcontentloaded", () => {
11894
- recordNavigationMilestone(page, "domcontentloaded");
11895
- });
11896
- page.on("load", () => {
11897
- recordNavigationMilestone(page, "load");
11898
- });
11899
- }
11900
- async function readSootsimPageState(page) {
11901
- try {
11902
- return await page.evaluate(() => {
11903
- const bootMarks = Reflect.get(window, "__sootsimBootMarks");
11904
- return {
11905
- bodyText: document.body?.innerText?.slice(0, 500) ?? "",
11906
- bootStage: document.documentElement.dataset.sootsimBootStage ?? null,
11907
- bootMarks: Array.isArray(bootMarks) ? bootMarks.slice(-16) : [],
11908
- capturedAtEpochMs: Date.now(),
11909
- bridges: {
11910
- sootsim: Boolean(window.SootSim),
11911
- test: Boolean(window.__sootsimTest)
11912
- },
11913
- globals: Object.keys(window).filter((key) => key.startsWith("__sootsim") || key === "SootSim").sort(),
11914
- readyState: document.readyState,
11915
- title: document.title,
11916
- url: window.location.href
11917
- };
11918
- });
11919
- } catch (error) {
11920
- return {
11921
- evaluateError: error instanceof Error ? error.message : String(error)
11922
- };
11923
- }
11924
- }
11925
- async function describeSootsimBridgeFailure(page, cause, summary = "timed out waiting for sootsim test bridge", pageState) {
11926
- const recordBridgeFailure = pageState === void 0;
11927
- const state = pageState ?? await readSootsimPageState(page);
11928
- const navigation = lastNavigations.get(page);
11929
- if (recordBridgeFailure && navigation) {
11930
- recordNavigationMilestoneFor(page, navigation, "error", {
11931
- errorText: cause instanceof Error ? cause.message : String(cause),
11932
- failurePhase: "bridge",
11933
- pageState: state
11934
- });
11935
- }
11936
- return new Error(
11937
- [
11938
- summary,
11939
- `cause: ${cause instanceof Error ? cause.message : String(cause)}`,
11940
- `page: ${JSON.stringify(state)}`,
11941
- `recent page diagnostics:
11942
- ${JSON.stringify(getSootsimNavigationDiagnostics(page))}`
11943
- ].join("\n")
11944
- );
11945
- }
11946
- async function navigateSootsimPage(page, url, identity) {
11947
- attachPageDiagnostics(page);
11948
- const navigation = {
11949
- ...identity,
11950
- navigationUrl: url,
11951
- navigationId: ++nextNavigationIdentity
11952
- };
11953
- activeNavigations.set(page, navigation);
11954
- lastNavigations.set(page, navigation);
11955
- recordNavigationMilestone(page, "start");
11956
- try {
11957
- return await page.goto(url, { waitUntil: "load", timeout: 3e4 });
11958
- } catch (error) {
11959
- const pageState = await readSootsimPageState(page);
11960
- recordNavigationMilestone(page, "error", {
11961
- errorText: error instanceof Error ? error.message : String(error),
11962
- failurePhase: "navigation",
11963
- pageState
11964
- });
11965
- throw await describeSootsimBridgeFailure(
11966
- page,
11967
- error,
11968
- "sootsim navigation failed",
11969
- pageState
11970
- );
11971
- } finally {
11972
- activeNavigations.delete(page);
11973
- }
11974
- }
11975
- function getSootsimNavigationDiagnostics(page) {
11976
- return [...pageDiagnostics.get(page) ?? []];
11977
- }
11978
-
11979
- // detox/index.ts
11980
12582
  var import_proof_frame = __toESM(require_proof_frame(), 1);
11981
12583
  var BASE_URL = process.env.PEACH_URL || "http://localhost:5173";
11982
12584
  var SCREENSHOT_DIR = process.env.PEACH_SCREENSHOT_DIR || path2.join(process.cwd(), "test", "detox-driver", "screenshots", String(process.pid));
@@ -12174,6 +12776,8 @@ async function waitForSootsimWorkletsIdle(page, timeout = 3e3) {
12174
12776
  const deadline = start + timeout2;
12175
12777
  const requiredStableMs = 120;
12176
12778
  let lastPending = null;
12779
+ let completedReads = 0;
12780
+ let lastReply = null;
12177
12781
  while (Date.now() < deadline) {
12178
12782
  let timer;
12179
12783
  const stats = await Promise.race([
@@ -12189,6 +12793,8 @@ async function waitForSootsimWorkletsIdle(page, timeout = 3e3) {
12189
12793
  });
12190
12794
  if (stats === void 0) break;
12191
12795
  if (stats === null) return { settled: true, elapsed: Date.now() - start };
12796
+ completedReads++;
12797
+ lastReply = JSON.stringify({ elapsedMs: Date.now() - start, stats });
12192
12798
  lastPending = stats.pendingRunWorklets;
12193
12799
  if (lastPending === 0 && stats.runWorkletsIdleMs !== null && stats.runWorkletsIdleMs >= requiredStableMs) {
12194
12800
  return {
@@ -12199,30 +12805,69 @@ async function waitForSootsimWorkletsIdle(page, timeout = 3e3) {
12199
12805
  }
12200
12806
  await sleep(25);
12201
12807
  }
12202
- let shellAnswered = null;
12808
+ const waited = Date.now() - start;
12809
+ const observation = { completedReads, lastReply };
12810
+ let realms = null;
12203
12811
  if (lastPending === null) {
12204
- const shell = window.SootSim?.bridges?.shellHost;
12205
- shellAnswered = shell ? await Promise.race([
12206
- shell.callTestBridge("getPeerStats", false).then(
12207
- () => true,
12208
- () => false
12209
- ),
12210
- sleep(1e3).then(() => false)
12211
- ]) : null;
12812
+ const probe = async (bridge) => {
12813
+ if (!bridge || typeof bridge !== "object" || !("callTestBridge" in bridge) || typeof bridge.callTestBridge !== "function") {
12814
+ return { status: "missing-bridge" };
12815
+ }
12816
+ const callTestBridge = bridge.callTestBridge.bind(bridge);
12817
+ const asked = Date.now();
12818
+ let timer;
12819
+ try {
12820
+ return await Promise.race([
12821
+ Promise.resolve().then(() => callTestBridge("getPeerStats", false)).then(
12822
+ (stats) => ({
12823
+ status: "answered",
12824
+ elapsedMs: Date.now() - asked,
12825
+ stats
12826
+ }),
12827
+ (error) => ({
12828
+ status: "rejected",
12829
+ elapsedMs: Date.now() - asked,
12830
+ error: error instanceof Error ? error.message : String(error)
12831
+ })
12832
+ ),
12833
+ new Promise((resolve3) => {
12834
+ timer = setTimeout(
12835
+ () => resolve3({ status: "no-reply", elapsedMs: Date.now() - asked }),
12836
+ 1e3
12837
+ );
12838
+ })
12839
+ ]);
12840
+ } finally {
12841
+ if (timer !== void 0) clearTimeout(timer);
12842
+ }
12843
+ };
12844
+ const timerAsked = Date.now();
12845
+ const [tenant, shell, pageTimerLateMs] = await Promise.all([
12846
+ probe(window.__sootsimRenderHost),
12847
+ probe(window.SootSim?.bridges?.shellHost),
12848
+ sleep(0).then(() => Date.now() - timerAsked)
12849
+ ]);
12850
+ realms = JSON.stringify({
12851
+ phase: "after-deadline",
12852
+ tenant,
12853
+ shell,
12854
+ pageTimerLateMs
12855
+ });
12212
12856
  }
12213
12857
  return {
12214
12858
  settled: false,
12215
- elapsed: Date.now() - start,
12859
+ elapsed: waited,
12216
12860
  pending: lastPending,
12217
- shellAnswered
12861
+ realms,
12862
+ observation
12218
12863
  };
12219
12864
  },
12220
12865
  { timeout }
12221
12866
  );
12222
12867
  if (!result?.settled) {
12223
- const pending = result?.pending ?? `no peer stats reply; shell alone ${result?.shellAnswered == null ? "has no bridge" : result.shellAnswered ? "answered within 1000ms" : "silent for 1000ms"}`;
12868
+ const pending = result?.pending ?? `no peer stats reply; ${result?.realms}`;
12224
12869
  throw new Error(
12225
- `sootsim worklet runtime did not settle after ${result?.elapsed ?? timeout}ms (pending=${pending})`
12870
+ `sootsim worklet runtime did not settle after ${result?.elapsed ?? timeout}ms (pending=${pending}; observation=${JSON.stringify(result?.observation)})`
12226
12871
  );
12227
12872
  }
12228
12873
  }
@@ -12461,41 +13106,49 @@ async function nodeOffsetToPage(page, nodeInfo, dx, dy) {
12461
13106
  const center = getNodeCenter(nodeInfo);
12462
13107
  return sootsimToPage(page, center.x + dx, center.y + dy);
12463
13108
  }
12464
- async function getDefaultTapPoint(page, nodeInfo) {
12465
- const resolvedTarget = await page.evaluate(
12466
- (nodeId) => window.__sootsimTest?.resolveTapTarget?.(nodeId) ?? null,
12467
- nodeInfo.nodeId
12468
- );
12469
- const center = resolvedTarget && typeof resolvedTarget.cx === "number" && typeof resolvedTarget.cy === "number" ? { x: resolvedTarget.cx, y: resolvedTarget.cy } : getNodeCenter(nodeInfo);
12470
- const frame = nodeInfo.visibleFrame ?? {
12471
- x: nodeInfo.absolutePosition?.x ?? nodeInfo.layout?.x ?? 0,
12472
- y: nodeInfo.absolutePosition?.y ?? nodeInfo.layout?.y ?? 0,
12473
- width: nodeInfo.layout?.width ?? 0,
12474
- height: nodeInfo.layout?.height ?? 0
12475
- };
12476
- const viewport = await readSootsimInteractiveViewport(page);
12477
- const fullFrameTolerance = 0.5;
12478
- const canTapCenter = typeof nodeInfo.absolutePosition?.x === "number" && typeof nodeInfo.absolutePosition?.y === "number" && typeof nodeInfo.layout?.width === "number" && typeof nodeInfo.layout?.height === "number" && Math.abs(frame.x - nodeInfo.absolutePosition.x) <= fullFrameTolerance && Math.abs(frame.y - nodeInfo.absolutePosition.y) <= fullFrameTolerance && frame.width >= nodeInfo.layout.width - fullFrameTolerance && frame.height >= nodeInfo.layout.height - fullFrameTolerance && center.x >= 0 && center.y >= 0 && center.x < viewport.width && center.y < viewport.height;
12479
- if (canTapCenter) return center;
12480
- const left = Math.max(0, frame.x);
12481
- const top = Math.max(0, frame.y);
12482
- const right = Math.min(viewport.width, frame.x + frame.width);
12483
- const bottom = Math.min(viewport.height, frame.y + frame.height);
12484
- if (right <= left || bottom <= top) {
12485
- return center;
12486
- }
12487
- return {
12488
- x: left + (right - left) / 2,
12489
- y: top + (bottom - top) / 2
12490
- };
12491
- }
12492
- async function dispatchTapAtSootsimPoint(page, x, y, token) {
13109
+ async function dispatchTapAtSootsimPoint(page, point, token) {
12493
13110
  await page.evaluate(
12494
- async ({ x: x2, y: y2, token: token2 }) => {
13111
+ async ({ point: point2, token: token2 }) => {
13112
+ let x;
13113
+ let y;
13114
+ if ("nodeId" in point2) {
13115
+ const resolved = await window.__sootsimTest.resolveTapTarget(point2.nodeId);
13116
+ if (!resolved) throw new Error(`element disappeared before tap: ${point2.nodeId}`);
13117
+ const node = resolved.match;
13118
+ if (point2.offset) {
13119
+ x = node.absolutePosition.x + (point2.offset.x ?? node.layout.width / 2);
13120
+ y = node.absolutePosition.y + (point2.offset.y ?? node.layout.height / 2);
13121
+ } else {
13122
+ x = resolved.cx;
13123
+ y = resolved.cy;
13124
+ const frame = node.visibleFrame ?? {
13125
+ x: node.absolutePosition.x,
13126
+ y: node.absolutePosition.y,
13127
+ width: node.layout.width,
13128
+ height: node.layout.height
13129
+ };
13130
+ const { viewport } = point2;
13131
+ const tolerance = 0.5;
13132
+ const canTapCenter = Math.abs(frame.x - node.absolutePosition.x) <= tolerance && Math.abs(frame.y - node.absolutePosition.y) <= tolerance && frame.width >= node.layout.width - tolerance && frame.height >= node.layout.height - tolerance && x >= 0 && y >= 0 && x < viewport.width && y < viewport.height;
13133
+ if (!canTapCenter) {
13134
+ const left = Math.max(0, frame.x);
13135
+ const top = Math.max(0, frame.y);
13136
+ const right = Math.min(viewport.width, frame.x + frame.width);
13137
+ const bottom = Math.min(viewport.height, frame.y + frame.height);
13138
+ if (right > left && bottom > top) {
13139
+ x = left + (right - left) / 2;
13140
+ y = top + (bottom - top) / 2;
13141
+ }
13142
+ }
13143
+ }
13144
+ } else {
13145
+ ;
13146
+ ({ x, y } = point2);
13147
+ }
12495
13148
  const interactTap = window.SootSim?.bridges?.interact?.tap;
12496
13149
  if (typeof interactTap === "function") {
12497
13150
  if (token2) window.__sootsimAnchorTimedInput?.(token2);
12498
- const result = await interactTap(x2, y2);
13151
+ const result = await interactTap(x, y);
12499
13152
  if (result && (typeof result !== "object" || result.hit !== false)) {
12500
13153
  return;
12501
13154
  }
@@ -12505,9 +13158,9 @@ async function dispatchTapAtSootsimPoint(page, x, y, token) {
12505
13158
  throw new Error("sootsim tap bridge is not installed");
12506
13159
  }
12507
13160
  if (token2) window.__sootsimAnchorTimedInput?.(token2);
12508
- await tap(x2, y2);
13161
+ await tap(x, y);
12509
13162
  },
12510
- { x, y, token }
13163
+ { point, token }
12511
13164
  );
12512
13165
  }
12513
13166
  async function focusElementForTextEntry(el) {
@@ -12696,11 +13349,13 @@ function createSootSimElement(matcher) {
12696
13349
  `Cannot perform action due to constraint failure: element not visible (${describeNodeVisibility(node)}): ${JSON.stringify(matcher)}`
12697
13350
  );
12698
13351
  }
12699
- const defaultPoint = point ? null : await getDefaultTapPoint(page, node);
12700
- const targetSootSimX = typeof point?.x === "number" ? node.absolutePosition.x + point.x : defaultPoint?.x ?? node.absolutePosition.x + node.layout.width / 2;
12701
- const targetSootSimY = typeof point?.y === "number" ? node.absolutePosition.y + point.y : defaultPoint?.y ?? node.absolutePosition.y + node.layout.height / 2;
13352
+ const viewport = await readSootsimInteractiveViewport(page);
12702
13353
  const captureToken = page.__sootsimCaptureToken;
12703
- await dispatchTapAtSootsimPoint(page, targetSootSimX, targetSootSimY, captureToken);
13354
+ await dispatchTapAtSootsimPoint(
13355
+ page,
13356
+ { nodeId: node.nodeId, viewport, offset: point },
13357
+ captureToken
13358
+ );
12704
13359
  if (_synchronizationEnabled) {
12705
13360
  await waitForSootsimIdle2(page);
12706
13361
  }
@@ -13014,8 +13669,10 @@ function createSootSimElement(matcher) {
13014
13669
  const captureToken = page.__sootsimCaptureToken;
13015
13670
  await dispatchTapAtSootsimPoint(
13016
13671
  page,
13017
- tapTarget.target.absolutePosition.x + tapTarget.target.layout.width / 2,
13018
- tapTarget.target.absolutePosition.y + tapTarget.target.layout.height / 2,
13672
+ {
13673
+ x: tapTarget.target.absolutePosition.x + tapTarget.target.layout.width / 2,
13674
+ y: tapTarget.target.absolutePosition.y + tapTarget.target.layout.height / 2
13675
+ },
13019
13676
  captureToken
13020
13677
  );
13021
13678
  },
@@ -13249,7 +13906,7 @@ var device = {
13249
13906
  "cli.js"
13250
13907
  );
13251
13908
  try {
13252
- await (0, import_node_util.promisify)(import_node_child_process3.execFile)(
13909
+ await (0, import_node_util.promisify)(import_node_child_process4.execFile)(
13253
13910
  process.execPath,
13254
13911
  [cliPath, "install", "chromium"],
13255
13912
  {