peachsim 0.1.637 → 0.1.639

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 (94) hide show
  1. package/README.md +8 -5
  2. package/cli/bridge-flow-runner.ts +442 -14
  3. package/cli/command-registry.ts +1 -0
  4. package/cli/commands/control.ts +42 -5
  5. package/cli/commands/inspect/core.ts +93 -24
  6. package/cli/commands/maestro.ts +2 -2
  7. package/cli/commands/serve.ts +13 -1
  8. package/cli/commands/swift.ts +326 -0
  9. package/cli/drivers/playwright-provisioning.ts +8 -16
  10. package/cli/drivers/playwright-sim-host.ts +3 -0
  11. package/cli/drivers/playwright.ts +1 -0
  12. package/cli/internal-child.ts +51 -8
  13. package/cli/main.ts +11 -0
  14. package/cli/outbound-endpoints.ts +5 -1
  15. package/dist-lib/agent-daemon-client.cjs +1 -1
  16. package/dist-lib/agent-events.cjs +1 -1
  17. package/dist-lib/agent-identity.cjs +1 -1
  18. package/dist-lib/agent-sessions.cjs +1 -1
  19. package/dist-lib/attached-projects.cjs +1 -1
  20. package/dist-lib/auth/shared-session.cjs +1 -1
  21. package/dist-lib/backend-origin.cjs +1 -1
  22. package/dist-lib/beta.cjs +1 -1
  23. package/dist-lib/beta.mjs +1 -1
  24. package/dist-lib/bridge-constants.cjs +1 -1
  25. package/dist-lib/bridge-contract-input.cjs +3 -2
  26. package/dist-lib/bridge-contract-input.mjs +3 -2
  27. package/dist-lib/bridge-contract.cjs +1 -1
  28. package/dist-lib/bridge-contract.mjs +1 -1
  29. package/dist-lib/capture-contract.cjs +1 -1
  30. package/dist-lib/capture-contract.mjs +1 -1
  31. package/dist-lib/cli-constants.cjs +1 -1
  32. package/dist-lib/cloud-contract.cjs +1 -1
  33. package/dist-lib/cloud-contract.mjs +1 -1
  34. package/dist-lib/cloud-test-args.cjs +1 -1
  35. package/dist-lib/cloud-test-args.mjs +1 -1
  36. package/dist-lib/cloud.cjs +1 -1
  37. package/dist-lib/cloud.mjs +1 -1
  38. package/dist-lib/config.cjs +1 -1
  39. package/dist-lib/detox/index.cjs +1 -1
  40. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  41. package/dist-lib/home-paths.cjs +1 -1
  42. package/dist-lib/host/bridge-host.cjs +4 -2
  43. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  44. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  45. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  46. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  47. package/dist-lib/host/websocket-proxy.cjs +1 -1
  48. package/dist-lib/index.cjs +88 -23
  49. package/dist-lib/jump-to-source-babel.cjs +1 -1
  50. package/dist-lib/jump-to-source-native.cjs +1 -1
  51. package/dist-lib/kotlin-host.cjs +1 -1
  52. package/dist-lib/menu.cjs +1 -1
  53. package/dist-lib/menu.mjs +1 -1
  54. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  55. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  56. package/dist-lib/metro-production-bundle.cjs +1 -1
  57. package/dist-lib/metro-production-bundle.mjs +1 -1
  58. package/dist-lib/metro.cjs +1 -1
  59. package/dist-lib/native-app-identity.cjs +1 -1
  60. package/dist-lib/native-app-identity.mjs +1 -1
  61. package/dist-lib/profiles.cjs +1 -1
  62. package/dist-lib/public-brand.cjs +1 -1
  63. package/dist-lib/react-native-host-modules.cjs +1 -1
  64. package/dist-lib/react-native-host-modules.mjs +1 -1
  65. package/dist-lib/render-mode.cjs +1 -1
  66. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  67. package/dist-lib/sdk.cjs +78 -20
  68. package/dist-lib/sdk.mjs +77 -20
  69. package/dist-lib/skills.cjs +441 -94
  70. package/dist-lib/swift-compile.cjs +89 -7
  71. package/dist-lib/swift-compile.mjs +89 -7
  72. package/dist-lib/swift-package.cjs +1 -1
  73. package/dist-lib/swift-package.mjs +1 -1
  74. package/dist-lib/swift-submit.cjs +89 -8
  75. package/dist-lib/swift-submit.mjs +78 -8
  76. package/dist-lib/swift.cjs +16 -33220
  77. package/dist-lib/vite-swift.cjs +510 -253
  78. package/dist-lib/vite.cjs +13 -4
  79. package/package.json +1 -1
  80. package/skills/contrast/SKILL.md +1 -0
  81. package/src/assetCatalogContract.ts +16 -0
  82. package/src/bridge-contract-input.ts +3 -2
  83. package/src/bridge-contract.ts +8 -0
  84. package/src/host/bridge-host.ts +5 -5
  85. package/src/metro-runtime-source.ts +15 -0
  86. package/src/native-seam-manifest.ts +4 -6
  87. package/src/swift-compile.ts +105 -9
  88. package/src/swift-guest.ts +51 -0
  89. package/src/swift-host.ts +900 -0
  90. package/src/swift-submit.ts +97 -6
  91. package/src/swift.ts +36 -8
  92. package/src/swiftContract.ts +52 -0
  93. package/src/vite-plugin-one.ts +4 -3
  94. package/src/vite-plugin-swift.ts +43 -755
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.637 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -4349,6 +4349,8 @@ var init_supported_native_packages = __esm({
4349
4349
  // seam
4350
4350
  "@bacons/apple-targets",
4351
4351
  // seam
4352
+ "@bam.tech/react-native-image-resizer",
4353
+ // seam
4352
4354
  "@baronha/ting",
4353
4355
  // seam
4354
4356
  "@bitdrift/react-native",
@@ -4445,6 +4447,8 @@ var init_supported_native_packages = __esm({
4445
4447
  // seam
4446
4448
  "@metamask/native-utils",
4447
4449
  // seam+coverage
4450
+ "@metamask/react-native-search-api",
4451
+ // seam+coverage
4448
4452
  "@metamask/react-native-webview",
4449
4453
  // seam
4450
4454
  "@mhpdev/react-native-haptics",
@@ -4491,6 +4495,8 @@ var init_supported_native_packages = __esm({
4491
4495
  // seam+coverage
4492
4496
  "@react-native-community/progress-view",
4493
4497
  // seam
4498
+ "@react-native-community/push-notification-ios",
4499
+ // seam
4494
4500
  "@react-native-community/slider",
4495
4501
  // coverage
4496
4502
  "@react-native-cookies/cookies",
@@ -4590,7 +4596,7 @@ var init_supported_native_packages = __esm({
4590
4596
  "appcenter-analytics",
4591
4597
  // seam
4592
4598
  "burnt",
4593
- // coverage
4599
+ // seam+coverage
4594
4600
  "customerio-reactnative",
4595
4601
  // seam
4596
4602
  "expo",
@@ -4803,6 +4809,8 @@ var init_supported_native_packages = __esm({
4803
4809
  // coverage
4804
4810
  "react-native-auto-skeleton",
4805
4811
  // seam
4812
+ "react-native-background-fetch",
4813
+ // seam+coverage
4806
4814
  "react-native-background-geolocation",
4807
4815
  // seam
4808
4816
  "react-native-background-timer",
@@ -4811,8 +4819,12 @@ var init_supported_native_packages = __esm({
4811
4819
  // seam
4812
4820
  "react-native-barcode-creator",
4813
4821
  // seam
4822
+ "react-native-biometrics",
4823
+ // seam+coverage
4814
4824
  "react-native-blasted-image",
4815
4825
  // seam
4826
+ "react-native-ble-plx",
4827
+ // seam
4816
4828
  "react-native-blob-util",
4817
4829
  // coverage
4818
4830
  "react-native-blue-crypto",
@@ -4905,6 +4917,8 @@ var init_supported_native_packages = __esm({
4905
4917
  // seam
4906
4918
  "react-native-fs",
4907
4919
  // coverage
4920
+ "react-native-geolocation-service",
4921
+ // seam
4908
4922
  "react-native-gesture-handler",
4909
4923
  // preset+semantic-proof+seam+coverage
4910
4924
  "react-native-get-device-locale",
@@ -4912,7 +4926,7 @@ var init_supported_native_packages = __esm({
4912
4926
  "react-native-get-location",
4913
4927
  // seam+coverage
4914
4928
  "react-native-get-random-values",
4915
- // coverage
4929
+ // seam+coverage
4916
4930
  "react-native-google-places",
4917
4931
  // seam
4918
4932
  "react-native-google-play-install-referrer",
@@ -4987,8 +5001,12 @@ var init_supported_native_packages = __esm({
4987
5001
  // seam+coverage
4988
5002
  "react-native-navigation",
4989
5003
  // seam
5004
+ "react-native-nfc-manager",
5005
+ // seam
4990
5006
  "react-native-nitro-buffer",
4991
5007
  // seam+coverage
5008
+ "react-native-nitro-cookies",
5009
+ // seam
4992
5010
  "react-native-nitro-device-info",
4993
5011
  // seam
4994
5012
  "react-native-nitro-fetch",
@@ -5080,7 +5098,7 @@ var init_supported_native_packages = __esm({
5080
5098
  "react-native-record-screen",
5081
5099
  // seam
5082
5100
  "react-native-restart",
5083
- // coverage
5101
+ // seam+coverage
5084
5102
  "react-native-restart-newarch",
5085
5103
  // coverage
5086
5104
  "react-native-rsa-native",
@@ -5141,6 +5159,8 @@ var init_supported_native_packages = __esm({
5141
5159
  // seam
5142
5160
  "react-native-sqlite-2",
5143
5161
  // seam
5162
+ "react-native-sqlite-storage",
5163
+ // seam+coverage
5144
5164
  "react-native-stallion",
5145
5165
  // seam
5146
5166
  "react-native-store-age-signals-native-modules",
@@ -6624,7 +6644,7 @@ var init_registry = __esm({
6624
6644
  {
6625
6645
  range: ">=0.1.0",
6626
6646
  coverage: 0.9,
6627
- note: "shell-owned iOS-style toast + alert with SF Symbol icon support; Android routes toast and alert fallback through ToastAndroid",
6647
+ note: "real upstream package \u2014 not intercepted. native side via the Burnt expo module (native-seams/burnt.ts), mirroring ios/BurntModule.swift over the shell-drawn SPIndicatorView and SPAlertView; on Android the package itself calls ToastAndroid",
6628
6648
  working: "iOS toast (title, message, preset: done/error/none/custom, duration, haptic, from, layout), iOS alert (preset: heart/done/error/none/spinner/custom, shouldDismissByTap), Android toast/title-only alert fallback via ToastAndroid.showWithGravityAndOffset, dismissAllAlerts, IconParams, all type exports",
6629
6649
  missing: "drag-to-dismiss gesture interaction on iOS toast, Android message/icon/haptic/layout customization because upstream Android Burnt ignores those fields"
6630
6650
  }
@@ -6868,10 +6888,10 @@ var init_registry = __esm({
6868
6888
  versions: [
6869
6889
  {
6870
6890
  range: ">=4.4.0",
6871
- coverage: 0.75,
6872
- note: "static BackgroundFetch API with status constants, task scheduling state, callback wiring, and finish/start/stop semantics",
6873
- working: "STATUS_* and NETWORK_TYPE_* constants, configure, scheduleTask, registerHeadlessTask, start, stop, finish, status promise/callback forms, fetch/timeout callback dispatch",
6874
- missing: "actual iOS/Android background execution, BGTaskScheduler/TSBackgroundFetch wakeups, headless JS launch while suspended"
6891
+ coverage: 0.9,
6892
+ note: "native-seam: index.js runs unchanged from the bundle; only the RNBackgroundFetch module is ours, answering as TSBackgroundFetch does on the iOS simulator: configure/start/status resolve UIBackgroundRefreshStatusAvailable (2), scheduleTask rejects because BGTaskScheduler is unavailable (or the identifier is not in BGTaskSchedulerPermittedIdentifiers), and no fetch event fires, as none fires on the simulator without Xcode",
6893
+ working: "configure, start, stop, finish, status (promise and callback forms), scheduleTask rejection with the simulator message, the real STATUS_* and NETWORK_TYPE_* constants and NativeEventEmitter wiring",
6894
+ missing: "Xcode's Simulate Background Fetch has no Peach equivalent; Android mode answers as iOS (headless tasks, JobScheduler)"
6875
6895
  }
6876
6896
  ]
6877
6897
  },
@@ -7515,9 +7535,9 @@ var init_registry = __esm({
7515
7535
  {
7516
7536
  range: ">=5.0.0",
7517
7537
  coverage: 0.8,
7518
- note: "shared system-location authorization followed by browser navigator.geolocation, with RNFusedLocation-style callbacks and permission status",
7519
- working: "default Geolocation export, PositionError constants, requestAuthorization, getCurrentPosition, watchPosition, clearWatch, stopObserving, setRNConfiguration",
7520
- missing: "native fused provider accuracy modes, Android Google Play Services resolution dialogs, significant-change monitoring, background updates"
7538
+ note: "real upstream package \u2014 not intercepted. native side via the RNFusedLocation module (native-seams/react-native-geolocation-service.ts), mirroring ios/RNFusedLocation.swift over the browser geolocation fix",
7539
+ working: "Geolocation.native.js runs unchanged: requestAuthorization statuses, getCurrentPosition with the native timeout timer and maximumAge cache, watchPosition with the 100 m default distanceFilter, CLLocation-shaped coords (-1 for unknown course, speed and vertical accuracy), and buildError codes and messages",
7540
+ missing: "desiredAccuracy levels (the browser fix has one accuracy), significant-change monitoring, background updates"
7521
7541
  }
7522
7542
  ]
7523
7543
  },
@@ -7528,9 +7548,9 @@ var init_registry = __esm({
7528
7548
  {
7529
7549
  range: ">=1.0.0",
7530
7550
  coverage: 0.85,
7531
- note: "load-safe \u2014 local notification scheduling, badge state, permission shape, and event listeners all work in-memory via the browser Notification API. real APNs delivery is intentionally absent (same constraint as every push SDK in a browser sim)",
7532
- working: "default PushNotificationIOS class, FetchResult, AuthorizationStatus, requestPermissions/checkPermissions/abandonPermissions, addEventListener/removeEventListener, presentLocalNotification, scheduleLocalNotification, addNotificationRequest, pending/delivered notification queries/removal, badge count, notification instance getters",
7533
- missing: "real APNs registration/delivery, OS notification center UI, background fetch completion callbacks, attachment download/storage, notification category action behavior"
7551
+ note: "real upstream package, not intercepted. native side via the RNCPushNotificationIOS module (native-seams/react-native-push-notification-ios.ts) following RNCPushNotificationIOS.m and RCTConvert+Notification.m over the engine notification bus: the permission prompt, delivery, banners, badge and pending requests",
7552
+ working: "requestPermissions/checkPermissions with UNNotificationSettings, the register event with the simulator token, addNotificationRequest with calendar triggers, the deprecated UILocalNotification calls, pending and delivered queries and removal, badge, categories, localNotification on a tap and notification for a dispatched push",
7553
+ missing: "repeating calendar triggers other than daily and weekly, notification attachments, the fetch completion handler of a background push"
7534
7554
  }
7535
7555
  ]
7536
7556
  },
@@ -7712,9 +7732,9 @@ var init_registry = __esm({
7712
7732
  {
7713
7733
  range: ">=13.0.0",
7714
7734
  coverage: 0.95,
7715
- note: 'native-seam: the LocalAuthentication.js wrapper + AuthenticationType/SecurityLevel enums + types run from the real bundle; only the ExpoLocalAuthentication expo module is ours, and it mirrors expo iOS LocalAuthenticationModule.swift exactly for the reference simulator (iPhone 16 Face ID device, no biometrics enrolled, no passcode): hasHardwareAsync true, isEnrolledAsync false, supportedAuthenticationTypesAsync [FACIAL_RECOGNITION], getEnrolledLevelAsync NONE, authenticateAsync resolves {success:false,error:"passcode_not_set"|"not_enrolled"} per disableDeviceFallback \u2014 the exact convertErrorCode path. cancelAuthenticate is deliberately absent so the wrapper faithfully throws UnavailabilityError on iOS (it is @platform android upstream). this IS the complete faithful contract on the reference device, same tier as react-native-exit-app / react-native-orientation-locker: a default iOS Simulator equally cannot run a real biometric prompt.',
7716
- working: "full ExpoLocalAuthentication native surface with upstream-exact iOS-simulator semantics \u2014 hasHardwareAsync, isEnrolledAsync, supportedAuthenticationTypesAsync, getEnrolledLevelAsync, authenticateAsync (disableDeviceFallback-aware error codes); real package enums/types/wrapper run from the bundle so SecurityLevel.BIOMETRIC alias + UnavailabilityError guards behave exactly as upstream; iOS/android platform divergence (cancelAuthenticate) preserved",
7717
- missing: "no real biometric prompt can succeed \u2014 but neither can the reference iOS Simulator (no enrolled biometrics, no secure enclave); a real success path would require physical biometric hardware that browsers and simulators alike do not expose"
7735
+ note: "native-seam: the published LocalAuthentication.js runs from the bundle over the ExpoLocalAuthentication module, following LocalAuthenticationModule.swift as an installed app on the iOS Simulator sees it (Face ID with no enrolled identity, a passcode set): hasHardwareAsync true, isEnrolledAsync false, supportedAuthenticationTypesAsync [FACIAL_RECOGNITION], getEnrolledLevelAsync SECRET, authenticateAsync not_enrolled with disableDeviceFallback. cancelAuthenticate is absent, as on iOS.",
7736
+ working: "hasHardwareAsync, isEnrolledAsync, supportedAuthenticationTypesAsync, getEnrolledLevelAsync, authenticateAsync biometric-only errors; UnavailabilityError for cancelAuthenticate on iOS",
7737
+ missing: "the system passcode sheet for device-fallback authentication, which settles as the sheet close button does (user_cancel)"
7718
7738
  }
7719
7739
  ]
7720
7740
  },
@@ -7796,19 +7816,19 @@ var init_registry = __esm({
7796
7816
  usage: 3,
7797
7817
  importance: 3,
7798
7818
  estimate: 1,
7799
- rationale: "the browser already provides crypto.getRandomValues, so importing the side-effect polyfill preserves the app contract"
7819
+ rationale: "the published side-effect polyfill runs unchanged over the RNGetRandomValues module"
7800
7820
  },
7801
7821
  {
7802
7822
  id: "load-safe-fallback",
7803
7823
  usage: 1,
7804
7824
  importance: 1,
7805
7825
  estimate: 1,
7806
- rationale: "the hosted runtime has secure crypto and the shim does not replace it"
7826
+ rationale: "an existing crypto.getRandomValues stays in place, as upstream leaves it"
7807
7827
  }
7808
7828
  ],
7809
7829
  capabilityCoverage: CAPABILITY_INVENTORY_PACKAGE_COVERAGE["react-native-get-random-values"],
7810
- note: "side-effect polyfill \u2014 browser already has crypto.getRandomValues, noop is correct",
7811
- working: "polyfills globalThis.crypto.getRandomValues if absent (Math.random fallback)"
7830
+ note: "real upstream package \u2014 not intercepted. native side via the RNGetRandomValues module (native-seams/react-native-get-random-values.ts), mirroring ios/RNGetRandomValues.mm",
7831
+ working: "index.js runs unchanged: it installs crypto.getRandomValues over the synchronous getRandomBase64 (SecRandomCopyBytes) when the global is missing, and leaves an existing one in place"
7812
7832
  }
7813
7833
  ]
7814
7834
  },
@@ -7962,10 +7982,10 @@ var init_registry = __esm({
7962
7982
  versions: [
7963
7983
  {
7964
7984
  range: ">=1.0.0",
7965
- coverage: 0.75,
7966
- note: "iOS Search API wrapper with in-memory Spotlight/app-history state and native event helper surface",
7967
- working: "default SearchApi instance, spotlight/app-history event listener helpers, indexSpotlightItem(s), delete by identifiers/domains, deleteAll, indexAppHistoryItem",
7968
- missing: "real CoreSpotlight indexing, NSUserActivity handoff/continueUserActivity delivery from iOS, and public index integration"
7985
+ coverage: 0.9,
7986
+ note: "native-seam: index.js runs unchanged from the bundle; only SearchApiManager is ours, resolving every index, delete and user-activity call as CoreSpotlight completes them on the iOS simulator",
7987
+ working: "indexSpotlightItem(s), deleteSpotlightItemsWithIdentifiers/InDomains, deleteAllSpotlightItems, indexAppHistoryItem, and the real event emitter wiring",
7988
+ missing: "Peach has no Spotlight search or Handoff, so spotlightSearchItemTapped and appHistorySearchItemTapped never fire"
7969
7989
  }
7970
7990
  ]
7971
7991
  },
@@ -8186,8 +8206,8 @@ var init_registry = __esm({
8186
8206
  {
8187
8207
  range: ">=0.0.1",
8188
8208
  coverage: 1,
8189
- note: "tenant-worker calls route through devMenu.reload \u2192 host window.location.reload(); host-thread callers reload directly",
8190
- working: "restart(), Restart() (both forms exported)"
8209
+ note: "real upstream package, not intercepted. native side via the RNRestart module (native-seams/react-native-restart.ts) following ios/Restart.mm: the reason outlives the reload and the reload is the one DevSettings.reload performs",
8210
+ working: "restart(), Restart() and getReason() from the published JS"
8191
8211
  }
8192
8212
  ]
8193
8213
  },
@@ -8249,10 +8269,10 @@ var init_registry = __esm({
8249
8269
  versions: [
8250
8270
  {
8251
8271
  range: ">=3.0.0",
8252
- coverage: 0.05,
8253
- note: "full class shape but every method returns unavailable/false \u2014 no WebAuthn backing",
8254
- working: "ReactNativeBiometrics class instantiates, BiometryTypes constants, all method signatures present",
8255
- missing: "isSensorAvailable always false, createKeys returns empty publicKey, biometricKeysExist always false, createSignature always fails, simplePrompt always fails, deleteKeys noop \u2014 no WebAuthn integration"
8272
+ coverage: 0.9,
8273
+ note: "real upstream package, not intercepted. native side via the ReactNativeBiometrics module (native-seams/react-native-biometrics.ts) following ios/ReactNativeBiometrics.m as the iOS Simulator answers it: Face ID with no enrolled identity, a passcode set, and a software RSA key that signs without a prompt",
8274
+ working: "isSensorAvailable (LAError -7 for biometrics, FaceID with device credentials), createKeys with an spki public key, createSignature (RSA PKCS#1 v1.5 SHA-256), biometricKeysExist, deleteKeys, simplePrompt rejecting biometric_error",
8275
+ missing: "the system passcode sheet for allowDeviceCredentials prompts, which settle as the sheet close button does"
8256
8276
  }
8257
8277
  ]
8258
8278
  },
@@ -8577,9 +8597,9 @@ var init_registry = __esm({
8577
8597
  {
8578
8598
  range: ">=8.0.0",
8579
8599
  coverage: 0.6,
8580
- note: "load-safe \u2014 local notifications and scheduling actually fire via the browser Notification API, permission flow real, channels stub. remote push (FCM/APNs) delivery is intentionally absent in browser preview",
8581
- working: "configure (calls onRegister, requests Notification permission), localNotification (fires real browser Notification), localNotificationSchedule (setTimeout-backed), requestPermissions (real Notification.requestPermission), checkPermissions, cancelLocalNotification/cancelAllLocalNotifications, setApplicationIconBadgeNumber, createChannel/channelExists/channelBlocked/getChannels stubs, Importance/Priority/RepeatType enums",
8582
- missing: "remote push registration, FCM/APNs integration, getScheduledLocalNotifications (always empty), getDeliveredNotifications (always empty), real badge read-back, subscribeToTopic/unsubscribeFromTopic (noop), repeat scheduling"
8600
+ note: "real upstream package, not intercepted. on iOS its JS runs over @react-native-community/push-notification-ios, whose RNCPushNotificationIOS module is the native side (native-seams/react-native-push-notification-ios.ts)",
8601
+ working: "configure, localNotification, localNotificationSchedule, permissions, badge, scheduled and delivered queries and cancellation, all through the published iOS path",
8602
+ missing: "the Android RNPushNotification module for Android mode"
8583
8603
  }
8584
8604
  ]
8585
8605
  },
@@ -8970,9 +8990,9 @@ var init_registry = __esm({
8970
8990
  {
8971
8991
  range: ">=3.0.0",
8972
8992
  coverage: 0.85,
8973
- note: "createResizedImage backed by browser image decoding and canvas resizing",
8974
- working: "default createResizedImage export, PNG/JPEG/WEBP format mapping, contain/cover/stretch sizing, onlyScaleDown, 90-degree rotation, outputPath/name/path response fields, blob/data URI output and size when canvas APIs are available",
8975
- missing: "native filesystem persistence, EXIF metadata preservation for keepMeta, exact RCTImageLoader asset resolution, platform-specific WEBP availability differences"
8993
+ note: "real upstream package, not intercepted. native side via the ImageResizer module (native-seams/bam-react-native-image-resizer.ts) following ios/ImageResizer.mm: the RCTImageLoader contain decode, rotation, scaling, and a JPEG or PNG written to Caches/<UUID> or Documents/<outputPath> in the app file store",
8994
+ working: "default createResizedImage export, contain/cover/stretch sizing over the loader thumbnail, onlyScaleDown, 90-degree rotation, the file written to the app container with path/uri/name/size, WEBP failing to save as on iOS",
8995
+ missing: "EXIF metadata preservation for keepMeta, the RCTLogError red box on a failure"
8976
8996
  }
8977
8997
  ]
8978
8998
  },
@@ -9112,11 +9132,11 @@ var init_registry = __esm({
9112
9132
  stubType: "native",
9113
9133
  versions: [
9114
9134
  {
9115
- range: ">=1.0.0",
9135
+ range: ">=1.2.0",
9116
9136
  coverage: 0.85,
9117
- note: "document.cookie backed; get/set/remove/clearAll fully functional in browser",
9118
- working: "get, getAll, set (with path/domain/expires/secure options), remove (with path/domain options), clearAll, Cookie interface",
9119
- missing: "httpOnly attribute (not settable via document.cookie by design), SameSite option, URL-scoped cookie reading (always reads all document.cookie)"
9137
+ note: "native-seam: the package's JS runs unchanged from the bundle; only the NitroCookies hybrid object is ours, following NitroCookies.swift over the app's one cookie jar (shared with @react-native-cookies/cookies): HTTPCookieStorage and a separate WebKit store, the Swift domain-only matching, fractional-seconds-only expiry parsing, the 400-day expiry cap, httpOnly cookies gaining a dotted domain, and the NSError descriptions Nitro hands to JS",
9138
+ working: "every sync and async method: get/getSync, set/setSync, setMany/setManySync, getCookieHeader/getCookieHeaderSync, setFromResponse/setFromResponseSync, clearByName/clearByNameSync, getAll, clearAll, flush, removeSessionCookies, with useWebKit",
9139
+ missing: "getFromResponse makes the request but returns no cookies (a browser fetch hides Set-Cookie); the jar's iteration order for getAll and the Cookie header follows insertion, where HTTPCookieStorage orders by its own rule; the jar is not the one fetch sends"
9120
9140
  }
9121
9141
  ]
9122
9142
  },
@@ -9196,22 +9216,6 @@ var init_registry = __esm({
9196
9216
  }
9197
9217
  ]
9198
9218
  },
9199
- "@welldone-software/why-did-you-render": {
9200
- category: "Device & Platform",
9201
- // pure JS (no native code), but deliberately intercepted: see the note
9202
- // below. do not "restore the real package" — it patches React internals
9203
- // and takes the bridged React down with it.
9204
- stubType: "native",
9205
- shipsNativeCode: false,
9206
- versions: [
9207
- {
9208
- range: ">=8.0.0",
9209
- coverage: 0,
9210
- note: "the reference iOS simulator performs this package's behavior. Peach currently provides only this unsupported shape: intentional noop : wdyr mutates React internals and crashes sootsim's bridged React. imports and API shape receive no behavior credit",
9211
- working: "default export callable as wdyr(React, opts) without error"
9212
- }
9213
- ]
9214
- },
9215
9219
  "react-native-pure-jwt": {
9216
9220
  category: "Auth & Payments",
9217
9221
  stubType: "native",
@@ -9891,10 +9895,10 @@ var init_registry = __esm({
9891
9895
  versions: [
9892
9896
  {
9893
9897
  range: ">=6.0.0",
9894
- coverage: 0.85,
9895
- note: "real persistent SQLite via the same lazy-loaded bedrock-sqlite WASM backend as expo-sqlite/op-sqlite/nitro-sqlite \u2014 memfs persists to the tenant-scoped IDB so a reopened database keeps its rows. faithfully mirrors upstream sqlite.js: the default callback style (openDatabase/executeSql/transaction/readTransaction/sqlBatch/close/deleteDatabase) and the enablePromise(true) toggle that swaps every method to promise variants on the same prototypes via the same config table. verified end-to-end against the on-disk bedrock build in the compat suite (callback CRUD + transaction + sqlBatch + close/reopen persistence + promise-mode).",
9896
- working: "SQLite.openDatabase (object + positional forms), deleteDatabase, enablePromise toggle, DEBUG/getAllDatabases; per-db executeSql (rows.length/item/raw, rowsAffected, insertId), transaction/readTransaction with queued tx.executeSql, sqlBatch, close (handle cached for fast reopen), default `new SQLiteFactory()` export \u2014 all backed by real bedrock SQL (joins, params, RETURNING)",
9897
- missing: "ATTACH/DETACH across databases (a second memfs open \u2014 parked until a consumer needs it); the slightly broader async/prepared-statement surface that expo-sqlite exposes natively (use expo-sqlite for that)"
9898
+ coverage: 0.9,
9899
+ note: "native-seam: sqlite.core.js runs unchanged from the bundle; only NativeModules.SQLite is ours, following platforms/ios/SQLite.m over the shared bedrock-sqlite WASM engine (tenant-scoped memfs IDB, so a reopened database keeps its rows). connections keyed by name, rowsAffected from the total_changes() delta, insertId whenever the connection has changed rows, WebSQL error codes from the primary sqlite code, BLOB columns as base64, the regexp() function, WAL journal mode, and the background batch reply after the calling turn",
9900
+ working: "open (default/Documents/Library locations), close, delete, attach, echoStringValue, executeSqlBatch/backgroundExecuteSqlBatch, executeSql/backgroundExecuteSql; everything in sqlite.core.js above them (openDatabase, transaction/readTransaction, executeSql, sqlBatch, enablePromise) is the real package",
9901
+ missing: "integral JS numbers bind as integers (iOS binds every bridged number as a double, so 5 into a TEXT column reads 5.0 there); createFromLocation copies nothing (no app-bundle www/ or sandbox asset exists); a multi-statement string errors where iOS silently runs only the first statement; regexp() bracket classes such as [[:alpha:]]; the Shared location (needs an AppGroupName) opens a temporary database as iOS does with a nil directory"
9898
9902
  }
9899
9903
  ]
9900
9904
  },
@@ -11127,9 +11131,9 @@ var init_registry = __esm({
11127
11131
  {
11128
11132
  range: ">=4.0.0",
11129
11133
  coverage: 0.85,
11130
- note: "native-seam: the pure-JS TouchID wrapper + errors.js + data/errors.js (TouchIDError classes + iOSErrors map) run from the real bundle; only NativeModules.TouchID is ours, mirroring TouchID.m on the reference iOS Simulator (Face ID device, no biometrics enrolled, no passcode): canEvaluatePolicy fails with LAErrorTouchIDNotEnrolled and passcodeFallback defaults false, so isSupported/authenticate call back with that error reason and the real wrapper rejects with the faithful NOT_ENROLLED TouchIDError \u2014 the exact value a guest gets on the reference device. same faithful tier as expo-local-authentication.",
11131
- working: "isSupported(config) and authenticate(reason, config) \u2014 real upstream wrapper + TouchIDError/TouchIDUnifiedError + iOSErrors mapping run from the bundle off a faithful native callback; reference-simulator parity (rejects with LAErrorTouchIDNotEnrolled \u2192 NOT_ENROLLED)",
11132
- missing: "no real biometric prompt can succeed \u2014 but neither can the reference iOS Simulator (no enrolled biometrics); package is deprecated upstream (expo-local-authentication is the maintained successor, also a faithful native-seam here)"
11134
+ note: "native-seam: the published TouchID wrapper and error classes run from the bundle over the TouchID module, following TouchID.m as an installed app on the iOS Simulator sees it (Face ID with no enrolled identity, a passcode set): isSupported resolves FaceID through its default passcode fallback, authenticate rejects LAErrorTouchIDNotEnrolled without one.",
11135
+ working: "isSupported(config) and authenticate(reason, config) through the passcodeFallback branches of TouchID.m, with the upstream TouchIDError mapping",
11136
+ missing: "the system passcode sheet for passcodeFallback authentication, which settles as the sheet close button does (LAErrorUserCancel)"
11133
11137
  }
11134
11138
  ]
11135
11139
  },
@@ -12785,7 +12789,7 @@ var init_registry = __esm({
12785
12789
  coverage: 0.85,
12786
12790
  note: "native-seam: the pure-JS concurrent-lock layer (setup-open.js, type-orm.js, DBListenerManager, lock-hooks, types) runs unchanged from the bundle; only the QuickSQLite NativeModule is ours \u2014 its synchronous install() populates global.__QuickSQLiteProxy with a real bedrock-sqlite-backed proxy sharing the same WASM engine as expo-sqlite/op-sqlite/nitro-sqlite/react-native-sqlite-storage, so persistence lands in the tenant-scoped memfs IDB and survives reload. faithfully implements the PowerSync concurrent-lock model: requestLock/onLockContextIsAvailable/executeInContext/releaseLock serialized per-db over the single-threaded WASM handle (read+write contexts one-at-a-time, FIFO \u2014 exactly how one SQLite connection behaves; the native lib\u2019s extra read connections are a throughput optimization, not an observable semantic). verified end-to-end against the on-disk bedrock build: CRUD, BEGIN/COMMIT, ROLLBACK, executeBatch, lock-context serialization ordering, and close/reopen persistence.",
12787
12791
  working: "install()/__QuickSQLiteProxy, open/close/delete, requestLock/releaseLock + onLockContextIsAvailable dispatch, executeInContext (real bedrock SQL incl. joins/params/RETURNING), executeBatch, refreshSchema, the upstream setup-open concurrent-connection object (execute, readLock/writeLock, readTransaction/writeTransaction, listener manager) running off the seam",
12788
- missing: "ATTACH/DETACH across databases (a second memfs open \u2014 parked until a consumer needs it, matching react-native-sqlite-storage), loadFile, true multi-connection parallelism (single-threaded WASM serializes \u2014 semantically equivalent for SQLite)"
12792
+ missing: "ATTACH/DETACH across databases (a second memfs open \u2014 parked until a consumer needs it), loadFile, true multi-connection parallelism (single-threaded WASM serializes \u2014 semantically equivalent for SQLite)"
12789
12793
  }
12790
12794
  ]
12791
12795
  },
@@ -13286,9 +13290,9 @@ var init_registry = __esm({
13286
13290
  {
13287
13291
  range: ">=2.0.0",
13288
13292
  coverage: 0.85,
13289
- note: 'load-safe noop \u2014 BleManager class, full State/LogLevel/BleErrorCode/ScanMode enum surface; manager.state() resolves "PoweredOff", connectToDevice rejects with a clear "no native adapter" error so apps with state-gated paths take the no-BLE branch. Matches react-native-ble-manager precedent.',
13290
- working: "BleManager construction, destroy(), state() (PoweredOff), onStateChange listener, startDeviceScan immediate error callback, stopDeviceScan, connectedDevices/devices/isDeviceConnected (empty/false), service+characteristic+descriptor list queries (empty), cancelTransaction, enable/disable, BleError class, every State / LogLevel / ConnectionPriority / ScanMode / ScanCallbackType / BleErrorCode constant",
13291
- missing: "no Web Bluetooth bridging in this stub \u2014 apps that absolutely need BLE should branch on state=PoweredOff; characteristic read/write/notify all reject"
13293
+ note: "real upstream package \u2014 not intercepted. native side via the BlePlx module (native-seams/react-native-ble-plx.ts), mirroring ios/BlePlx.m over MultiplatformBleAdapter 0.2.0 as the iOS simulator answers it: no Bluetooth radio, so the manager reports Unsupported",
13294
+ working: "BleManager runs unchanged: StateChangeEvent Unknown then Unsupported, state(), scans and connections failing with BluetoothUnsupported (BluetoothInUnknownState before the first update), every lookup rejecting with the adapter error JSON (InvalidIdentifiers, DeviceNotConnected, ServiceNotFound, CharacteristicNotFound, DescriptorNotFound, invalid base64), monitor errors as ReadEvent, enable/disable BluetoothStateChangeFailed",
13295
+ missing: "a real Bluetooth radio, which the iOS simulator does not have either"
13292
13296
  }
13293
13297
  ]
13294
13298
  },
@@ -13299,9 +13303,9 @@ var init_registry = __esm({
13299
13303
  {
13300
13304
  range: ">=3.0.0",
13301
13305
  coverage: 0.85,
13302
- note: 'load-safe noop \u2014 NfcManager singleton, full NfcTech / NfcEvents / NfcAdapter / NfcError / Ndef constant surface; isSupported/isEnabled=false, requestTechnology rejects with a clear "no NFC hardware" error so apps with NFC-required paths take the unsupported branch.',
13303
- working: "NfcManager.start/stop, isSupported/isEnabled (false), getLaunchTagEvent (null), setEventListener bookkeeping, registerTagEvent/unregisterTagEvent (noop), iOS invalidate*Session noops, ndefHandler/nfcAHandler/isoDepHandler structurally-correct empty responses, full NfcTech enum (Ndef, NfcA, NfcB, NfcF, NfcV, IsoDep, Mifare*, Iso15693IOS, FelicaIOS), Ndef.text/uri encode-passthrough helpers",
13304
- missing: "no WebNFC integration (WebNFC is Chrome-Android-only and requires per-scan user gesture); requestTechnology and transceive operations reject"
13306
+ note: "real upstream package, not intercepted. native side via the NfcManager module (native-seams/react-native-nfc-manager.ts) following ios/NfcManager.m as the iOS Simulator answers it: CoreNFC reports readingAvailable false and every begun session invalidates with NFCError 1",
13307
+ working: 'the published NfcManager, NfcTech handlers, NfcError and Ndef helpers; isSupported false, start rejecting "Not support in this device", registerTagEvent and requestTechnology sessions closing with NFCError:1 and the SessionClosed event, the not-connected answers of every tag command',
13308
+ missing: "tag discovery (the simulator has no NFC reader); the Android NfcManager module"
13305
13309
  }
13306
13310
  ]
13307
13311
  },
@@ -29229,6 +29233,7 @@ var init_bridge_host = __esm({
29229
29233
  // terminal 4001 code, so the client closes the abandoned window instead
29230
29234
  // of reconnecting the zombie straight back in.
29231
29235
  static SIM_IDLE_REAP_TTL_MS = 30 * 6e4;
29236
+ bindHost;
29232
29237
  preferredPort;
29233
29238
  portFallbackCount;
29234
29239
  shouldWriteLockfile;
@@ -29262,6 +29267,7 @@ var init_bridge_host = __esm({
29262
29267
  inflightScan = null;
29263
29268
  static SCAN_FRESH_MS = 2e3;
29264
29269
  constructor(opts = {}) {
29270
+ this.bindHost = opts.host ?? "127.0.0.1";
29265
29271
  this.preferredPort = opts.port ?? DEFAULT_SOOTSIM_BRIDGE_PORT;
29266
29272
  this.port = this.preferredPort;
29267
29273
  this.shouldWriteLockfile = opts.writeLockfile === true;
@@ -29338,7 +29344,7 @@ var init_bridge_host = __esm({
29338
29344
  reject(err);
29339
29345
  };
29340
29346
  server.once("error", onError);
29341
- server.listen(port, "127.0.0.1", () => {
29347
+ server.listen(port, this.bindHost, () => {
29342
29348
  if (settled) return;
29343
29349
  const address = server.address();
29344
29350
  if (!address || typeof address === "string") {
@@ -33485,6 +33491,57 @@ function layoutGetNativeEval(opts = {}) {
33485
33491
  return elements
33486
33492
  })()`;
33487
33493
  }
33494
+ function isPeachAccessibilityTree(value) {
33495
+ if (!Array.isArray(value)) return false;
33496
+ return value.every((node) => {
33497
+ if (node === null || typeof node !== "object") return false;
33498
+ if (Reflect.get(node, "platform") !== "ios" && Reflect.get(node, "platform") !== "android")
33499
+ return false;
33500
+ if (typeof Reflect.get(node, "elementId") !== "string") return false;
33501
+ if (!Number.isInteger(Reflect.get(node, "nodeId"))) return false;
33502
+ for (const key of ["type", "role"]) {
33503
+ if (typeof Reflect.get(node, key) !== "string") return false;
33504
+ }
33505
+ for (const key of ["testID", "label", "hint", "value", "placeholder"]) {
33506
+ const field = Reflect.get(node, key);
33507
+ if (field !== null && typeof field !== "string") return false;
33508
+ }
33509
+ for (const key of [
33510
+ "isAccessibilityElement",
33511
+ "enabled",
33512
+ "selected",
33513
+ "checked",
33514
+ "clickable",
33515
+ "isTextInput"
33516
+ ]) {
33517
+ if (typeof Reflect.get(node, key) !== "boolean") return false;
33518
+ }
33519
+ const hasFocus = Reflect.get(node, "hasFocus");
33520
+ if (hasFocus !== void 0 && typeof hasFocus !== "boolean") return false;
33521
+ const state = Reflect.get(node, "state");
33522
+ if (state !== null) {
33523
+ if (typeof state !== "object") return false;
33524
+ const checked = Reflect.get(state, "checked");
33525
+ if (typeof checked !== "boolean" && checked !== "mixed") return false;
33526
+ for (const key of ["disabled", "selected", "busy", "expanded"]) {
33527
+ if (typeof Reflect.get(state, key) !== "boolean") return false;
33528
+ }
33529
+ }
33530
+ const value2 = Reflect.get(node, "accessibilityValue");
33531
+ if (value2 !== null) {
33532
+ if (typeof value2 !== "object") return false;
33533
+ for (const key of ["min", "max", "now"]) {
33534
+ const field = Reflect.get(value2, key);
33535
+ if (field !== void 0 && (typeof field !== "number" || !Number.isFinite(field)))
33536
+ return false;
33537
+ }
33538
+ const text = Reflect.get(value2, "text");
33539
+ if (text !== void 0 && typeof text !== "string") return false;
33540
+ }
33541
+ if (!isSemanticGeometry(Reflect.get(node, "frame"))) return false;
33542
+ return isPeachAccessibilityTree(Reflect.get(node, "children"));
33543
+ });
33544
+ }
33488
33545
  function waitReadyReason(status) {
33489
33546
  if (status.bridgeError) {
33490
33547
  if (status.bridgeError.includes(HEADLESS_TENANT_FETCH_HUNG_MARKER) || status.bridgeError.includes(PUBLIC_NO_NETWORK_MARKER)) {
@@ -34174,17 +34231,10 @@ function resolvePlaywright() {
34174
34231
  const packageJsonPath = requireFromRoot.resolve(`${spec}/package.json`);
34175
34232
  const parsed = JSON.parse((0, import_node_fs23.readFileSync)(packageJsonPath, "utf8"));
34176
34233
  if (!isRecord(parsed) || typeof parsed.version !== "string") continue;
34177
- const bin = parsed.bin;
34178
- let binPath = null;
34179
- if (typeof bin === "string") {
34180
- binPath = bin;
34181
- } else if (isRecord(bin)) {
34182
- const named = bin.playwright ?? bin["playwright-core"];
34183
- const first = Object.values(bin).find((value) => typeof value === "string");
34184
- binPath = typeof named === "string" ? named : typeof first === "string" ? first : null;
34185
- }
34186
- if (!binPath) continue;
34187
- const cliPath = (0, import_node_path25.join)((0, import_node_path25.dirname)(packageJsonPath), binPath);
34234
+ const corePackagePath = (0, import_node_module5.createRequire)(modulePath).resolve(
34235
+ "playwright-core/package.json"
34236
+ );
34237
+ const cliPath = (0, import_node_path25.join)((0, import_node_path25.dirname)(corePackagePath), "cli.js");
34188
34238
  if (!(0, import_node_fs23.existsSync)(cliPath)) continue;
34189
34239
  return {
34190
34240
  spec,
@@ -34393,6 +34443,7 @@ async function launch2(opts) {
34393
34443
  env: {
34394
34444
  ...playwrightEnv,
34395
34445
  SOOTSIM_PW_MODULE: resolved.modulePath,
34446
+ SOOTSIM_PW_EXECUTABLE: provisioning.executablePath,
34396
34447
  SOOTSIM_PW_URL: opts.url,
34397
34448
  SOOTSIM_PW_BRIDGE_PORT: opts.bridgePort ? String(opts.bridgePort) : "",
34398
34449
  SOOTSIM_PW_HEADLESS: opts.headless ?? true ? "1" : "0",
@@ -34582,6 +34633,7 @@ var init_command_registry = __esm({
34582
34633
  "android",
34583
34634
  "remote",
34584
34635
  "preview",
34636
+ "swift",
34585
34637
  "box",
34586
34638
  "list",
34587
34639
  "use",
@@ -35172,7 +35224,7 @@ function resolveDriverHeadless(args, driverId) {
35172
35224
  function normalizeKnownTarget(target) {
35173
35225
  return target.trim();
35174
35226
  }
35175
- async function ensureBridgeForOpen(preferredPort, explicitPort = false) {
35227
+ async function ensureBridgeForOpen(preferredPort, explicitPort = false, resources) {
35176
35228
  const lock = readDaemonLockfile();
35177
35229
  assertExplicitBridgeLock(preferredPort, explicitPort, lock);
35178
35230
  const preferredWorld = worldForBridgePort(preferredPort);
@@ -35194,6 +35246,10 @@ async function ensureBridgeForOpen(preferredPort, explicitPort = false) {
35194
35246
  }
35195
35247
  await ensureRuntimeForOpen();
35196
35248
  const child = spawnSootsim(["serve", "--quiet", "--port", String(preferredPort)]);
35249
+ if (resources) {
35250
+ resources.bridge = child;
35251
+ resources.port = preferredPort;
35252
+ }
35197
35253
  let childExit = null;
35198
35254
  child.once("exit", (code, signal) => {
35199
35255
  childExit = String(code ?? signal ?? "unknown");
@@ -35214,6 +35270,10 @@ async function ensureBridgeForOpen(preferredPort, explicitPort = false) {
35214
35270
  console.error(
35215
35271
  ` Started a local bridge on port ${selectedPort}. Run \`peach daemon install\` to keep it ready between commands.`
35216
35272
  );
35273
+ if (resources) {
35274
+ resources.bridgeReady = true;
35275
+ resources.port = selectedPort;
35276
+ }
35217
35277
  return selectedPort;
35218
35278
  }
35219
35279
  if (childExit) {
@@ -35710,6 +35770,7 @@ async function waitForSimMatchOrHostExit(wsPort, commandTimeoutMs, predicate, ho
35710
35770
  const watchHost = !!hostPid && Number.isInteger(hostPid) && hostPid > 1;
35711
35771
  const hostExited = () => watchHost && hostPid !== void 0 && !isProcessAlive2(hostPid);
35712
35772
  for (let i = 0; i < attempts; i++) {
35773
+ opts.signal?.throwIfAborted();
35713
35774
  if (hostExited()) {
35714
35775
  return { kind: "host-exit" };
35715
35776
  }
@@ -36142,7 +36203,14 @@ async function runOpenCommand(args, opts = {}) {
36142
36203
  parsed.wsPort = pinned.port;
36143
36204
  }
36144
36205
  }
36145
- parsed.wsPort = await ensureBridgeForOpen(parsed.wsPort, parsed.explicitPort);
36206
+ opts.signal?.throwIfAborted();
36207
+ parsed.wsPort = await ensureBridgeForOpen(
36208
+ parsed.wsPort,
36209
+ parsed.explicitPort,
36210
+ opts.resources
36211
+ );
36212
+ if (opts.resources) opts.resources.port = parsed.wsPort;
36213
+ opts.signal?.throwIfAborted();
36146
36214
  const requestedDriverId = args.find((_, i) => args[i - 1] === "--driver") || "";
36147
36215
  if (requestedDriverId && !isDriverId(requestedDriverId)) {
36148
36216
  console.error(
@@ -36362,7 +36430,7 @@ async function runOpenCommand(args, opts = {}) {
36362
36430
  }
36363
36431
  const configuredBaseUrl = applyRNXConfigToUrl(baseUrl, runtimeConfig);
36364
36432
  const openUrl2 = await buildOpenUrl(target, configuredBaseUrl, token, buildShellUrlOpts);
36365
- const ownerPid = getStableOwnerPid();
36433
+ const ownerPid = opts.ownerPid ?? getStableOwnerPid();
36366
36434
  if (!ownerPid) {
36367
36435
  console.error(
36368
36436
  " could not resolve a stable owning process; refusing unsupervised sim host"
@@ -36375,6 +36443,7 @@ async function runOpenCommand(args, opts = {}) {
36375
36443
  rnxExit(1);
36376
36444
  }
36377
36445
  const connectWait = resolveDriverConnectWaitOptions();
36446
+ opts.signal?.throwIfAborted();
36378
36447
  const result = await driver.launch({
36379
36448
  url: openUrl2,
36380
36449
  // the shell serves the sim page but its WS bridge may have drifted off
@@ -36393,6 +36462,8 @@ async function runOpenCommand(args, opts = {}) {
36393
36462
  viewport: explicitViewport ?? (deviceFitBox ? { width: deviceFitBox.width + 120, height: deviceFitBox.height + 60 } : void 0),
36394
36463
  connectTimeoutMs: connectWait.hostTimeoutMs
36395
36464
  });
36465
+ if (opts.resources) opts.resources.driver = { ...result, driverId };
36466
+ opts.signal?.throwIfAborted();
36396
36467
  if (!result.launched) {
36397
36468
  console.error(` ${driver.name} driver: ${result.message}`);
36398
36469
  rnxExit(1);
@@ -36402,7 +36473,11 @@ async function runOpenCommand(args, opts = {}) {
36402
36473
  parsed.commandTimeoutMs,
36403
36474
  simMatchesToken,
36404
36475
  result.pid,
36405
- { attempts: connectWait.attempts, intervalMs: connectWait.intervalMs }
36476
+ {
36477
+ attempts: connectWait.attempts,
36478
+ intervalMs: connectWait.intervalMs,
36479
+ signal: opts.signal
36480
+ }
36406
36481
  );
36407
36482
  if (outcome.kind === "host-exit") {
36408
36483
  printDriverDiagnosticLogTail(result);
@@ -36424,6 +36499,7 @@ async function runOpenCommand(args, opts = {}) {
36424
36499
  rnxExit(1);
36425
36500
  }
36426
36501
  const match = outcome.sim;
36502
+ if (opts.resources) opts.resources.simId = match.id;
36427
36503
  saveCurrentSimId(match.id);
36428
36504
  signalDriverLaunchConnected(result);
36429
36505
  printOpenedSim(openUrl2, match, `${driver.name} driver`, quiet);
@@ -37809,9 +37885,228 @@ function assertSupportedSelector(selector) {
37809
37885
  if (unsupported.length > 0) {
37810
37886
  throw new Error(`selector ${unsupported.join(", ")} is not supported by peach yet`);
37811
37887
  }
37812
- for (const key of ["childOf", "below", "above", "leftOf", "rightOf"]) {
37888
+ for (const key of ["childOf", "below", "above", "leftOf", "rightOf", "containsChild"]) {
37813
37889
  assertSupportedSelector(Reflect.get(selector, key));
37814
37890
  }
37891
+ const descendants = Reflect.get(selector, "containsDescendants");
37892
+ if (descendants !== void 0) {
37893
+ if (!Array.isArray(descendants)) {
37894
+ throw new Error("selector containsDescendants must be a list of selectors");
37895
+ }
37896
+ for (const descendant of descendants) assertSupportedSelector(descendant);
37897
+ }
37898
+ }
37899
+ function needsAccessibilityProjection(selector) {
37900
+ if (typeof selector === "string") return false;
37901
+ if (ACCESSIBILITY_SELECTOR_KEYS.some((key) => selector[key] != null)) return true;
37902
+ return ["childOf", "below", "above", "leftOf", "rightOf"].some((key) => {
37903
+ const nested = Reflect.get(selector, key);
37904
+ return isSelectorRecord(nested) && needsAccessibilityProjection(nested);
37905
+ });
37906
+ }
37907
+ function accessibilityTreeNodes(tree) {
37908
+ const out = [];
37909
+ const visit = (node) => {
37910
+ out.push(node);
37911
+ for (const child of node.children) visit(child);
37912
+ };
37913
+ for (const root of tree) visit(root);
37914
+ return out;
37915
+ }
37916
+ function accessibilitySubtreeNodes(node) {
37917
+ return [node, ...node.children.flatMap(accessibilitySubtreeNodes)];
37918
+ }
37919
+ function accessibilityTextMatcher(value, kind) {
37920
+ const pattern = new RegExp(`^(?:${value})$`);
37921
+ return (candidate) => {
37922
+ const native = candidate ?? "";
37923
+ if (kind === "id")
37924
+ return pattern.test(native) || pattern.test(native.slice(native.lastIndexOf("/") + 1));
37925
+ const stripped = native.replaceAll("\n", " ");
37926
+ return native === value || stripped === value || pattern.test(native) || pattern.test(stripped);
37927
+ };
37928
+ }
37929
+ function accessibilityBounds(node) {
37930
+ const left = Math.trunc(node.frame.x);
37931
+ const top = Math.trunc(node.frame.y);
37932
+ return {
37933
+ x: left,
37934
+ y: top,
37935
+ width: Math.trunc(node.frame.x + node.frame.width) - left,
37936
+ height: Math.trunc(node.frame.y + node.frame.height) - top
37937
+ };
37938
+ }
37939
+ function accessibilityBasicFilters(selector) {
37940
+ const filters = [];
37941
+ if (selector.text != null) {
37942
+ const matches = accessibilityTextMatcher(selector.text, "text");
37943
+ filters.push(
37944
+ (node) => matches(node.label) || matches(node.value) || matches(node.placeholder)
37945
+ );
37946
+ }
37947
+ if (selector.id != null) {
37948
+ const matches = accessibilityTextMatcher(selector.id, "id");
37949
+ filters.push((node) => matches(node.testID));
37950
+ }
37951
+ if (selector.width != null || selector.height != null) {
37952
+ const tolerance = selector.tolerance ?? 0;
37953
+ filters.push((node) => {
37954
+ const bounds = accessibilityBounds(node);
37955
+ return (selector.width == null || Math.abs(bounds.width - selector.width) <= tolerance) && (selector.height == null || Math.abs(bounds.height - selector.height) <= tolerance);
37956
+ });
37957
+ }
37958
+ for (const trait of selector.traits ? selector.traits.split(" ") : []) {
37959
+ const filter = ACCESSIBILITY_TRAIT_FILTERS[trait.replaceAll("-", "_").toUpperCase()];
37960
+ if (!filter) throw new Error(`unknown selector trait: ${trait}`);
37961
+ filters.push(filter);
37962
+ }
37963
+ if (selector.enabled != null) {
37964
+ filters.push((node) => node.enabled === selector.enabled);
37965
+ }
37966
+ if (selector.selected != null) {
37967
+ filters.push((node) => node.selected === selector.selected);
37968
+ }
37969
+ if (selector.checked != null) {
37970
+ filters.push((node) => node.checked === selector.checked);
37971
+ }
37972
+ if (selector.focused != null) {
37973
+ filters.push((node) => (node.hasFocus ?? false) === selector.focused);
37974
+ }
37975
+ return filters;
37976
+ }
37977
+ function deepestMatchingAccessibilityNodes(nodes, matches) {
37978
+ const out = [];
37979
+ const included = new Set(nodes);
37980
+ const nested = new Set(
37981
+ nodes.flatMap((node) => node.children.filter((child) => included.has(child)))
37982
+ );
37983
+ for (const node of nodes.filter((node2) => !nested.has(node2))) {
37984
+ const children = deepestMatchingAccessibilityNodes(node.children, matches);
37985
+ if (children.length > 0) out.push(...children);
37986
+ else if (matches(node)) out.push(node);
37987
+ }
37988
+ return distinctAccessibilityNodes(out);
37989
+ }
37990
+ function distinctAccessibilityNodes(nodes) {
37991
+ const buckets = /* @__PURE__ */ new Map();
37992
+ return nodes.filter((node) => {
37993
+ const key = JSON.stringify([
37994
+ node.testID ?? "",
37995
+ node.label ?? "",
37996
+ node.placeholder ?? "",
37997
+ node.value ?? "",
37998
+ node.enabled,
37999
+ node.selected,
38000
+ node.checked,
38001
+ node.clickable,
38002
+ node.hasFocus ?? false,
38003
+ accessibilityBounds(node),
38004
+ node.children.length
38005
+ ]);
38006
+ const bucket = buckets.get(key);
38007
+ if (bucket?.some((other) => sameNativeAccessibilityShape(node, other))) return false;
38008
+ if (bucket) bucket.push(node);
38009
+ else buckets.set(key, [node]);
38010
+ return true;
38011
+ });
38012
+ }
38013
+ function isSelectorRecord(value) {
38014
+ return typeof value === "object" && value !== null && !Array.isArray(value);
38015
+ }
38016
+ function accessibilityAnchorSelector(value) {
38017
+ if (typeof value === "string") return { text: value };
38018
+ return isSelectorRecord(value) ? value : {};
38019
+ }
38020
+ function sameNativeAccessibilityShape(a, b) {
38021
+ 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(accessibilityBounds(a)) !== JSON.stringify(accessibilityBounds(b)) || a.children.length !== b.children.length) {
38022
+ return false;
38023
+ }
38024
+ for (const [index, child] of a.children.entries()) {
38025
+ const other = b.children[index];
38026
+ if (!other || !sameNativeAccessibilityShape(child, other)) return false;
38027
+ }
38028
+ return true;
38029
+ }
38030
+ function accessibilitySubtreeMatches(all, node, selector) {
38031
+ if (filterAccessibilityNodes(all, [node], selector).length > 0) return true;
38032
+ return node.children.some((child) => accessibilitySubtreeMatches(all, child, selector));
38033
+ }
38034
+ function filterAccessibilityNodes(all, nodes, selector) {
38035
+ if (selector.childOf == null) {
38036
+ return filterAccessibilityNodesIn(all, nodes, selector);
38037
+ }
38038
+ const container = filterAccessibilityNodes(
38039
+ all,
38040
+ all,
38041
+ accessibilityAnchorSelector(selector.childOf)
38042
+ )[0];
38043
+ if (!container) return [];
38044
+ return filterAccessibilityNodesIn(all, accessibilitySubtreeNodes(container), selector);
38045
+ }
38046
+ function filterAccessibilityNodesIn(all, nodes, selector) {
38047
+ const basic = accessibilityBasicFilters(selector);
38048
+ let candidates = basic.length > 0 ? deepestMatchingAccessibilityNodes(
38049
+ nodes,
38050
+ (node) => basic.every((filter) => filter(node))
38051
+ ) : [...nodes];
38052
+ const relativeGeometry = {
38053
+ below: (node, other) => accessibilityBounds(node).y > accessibilityBounds(other).y,
38054
+ above: (node, other) => accessibilityBounds(node).y < accessibilityBounds(other).y,
38055
+ leftOf: (node, other) => accessibilityBounds(node).x < accessibilityBounds(other).x,
38056
+ rightOf: (node, other) => accessibilityBounds(node).x > accessibilityBounds(other).x
38057
+ };
38058
+ for (const key of ["below", "above", "leftOf", "rightOf"]) {
38059
+ const anchor = selector[key];
38060
+ if (anchor == null) continue;
38061
+ const anchors = filterAccessibilityNodes(
38062
+ all,
38063
+ nodes,
38064
+ accessibilityAnchorSelector(anchor)
38065
+ );
38066
+ candidates = candidates.filter(
38067
+ (node) => anchors.some((other) => relativeGeometry[key](node, other))
38068
+ );
38069
+ }
38070
+ if (selector.containsChild != null) {
38071
+ const child = filterAccessibilityNodes(
38072
+ all,
38073
+ all,
38074
+ accessibilityAnchorSelector(selector.containsChild)
38075
+ )[0];
38076
+ candidates = child ? candidates.filter(
38077
+ (node) => node.children.some(
38078
+ (candidate) => sameNativeAccessibilityShape(candidate, child)
38079
+ )
38080
+ ) : [];
38081
+ }
38082
+ if (selector.containsDescendants != null) {
38083
+ const descendants = selector.containsDescendants.map(accessibilityAnchorSelector);
38084
+ candidates = candidates.filter(
38085
+ (node) => descendants.every(
38086
+ (descendant) => node.children.some(
38087
+ (child) => accessibilitySubtreeMatches(all, child, descendant)
38088
+ )
38089
+ )
38090
+ );
38091
+ }
38092
+ candidates = distinctAccessibilityNodes(candidates);
38093
+ if (selector.index != null) {
38094
+ const index = Math.trunc(Number(selector.index));
38095
+ const sorted = [...candidates].sort((a, b) => {
38096
+ const aBounds = accessibilityBounds(a);
38097
+ const bBounds = accessibilityBounds(b);
38098
+ return aBounds.y - bBounds.y || aBounds.x - bBounds.x;
38099
+ });
38100
+ const resolved = index >= 0 ? index : sorted.length + index;
38101
+ if (resolved < 0 || resolved >= sorted.length) return [];
38102
+ const picked = sorted[resolved];
38103
+ return picked ? [picked] : [];
38104
+ }
38105
+ return [...candidates].sort((a, b) => Number(b.clickable) - Number(a.clickable));
38106
+ }
38107
+ function resolveAccessibilitySelector(tree, selector) {
38108
+ const all = accessibilityTreeNodes(tree);
38109
+ return filterAccessibilityNodes(all, all, selector)[0] ?? null;
37815
38110
  }
37816
38111
  function isObjectRecord(value) {
37817
38112
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -37920,7 +38215,7 @@ function parseFlowSteps(content) {
37920
38215
  const parsed = import_yaml2.default.parse(candidate);
37921
38216
  return Array.isArray(parsed) ? parsed : [];
37922
38217
  }
37923
- var fs20, path20, import_devices4, import_yaml2, import_proof_frame, DEFAULT_TIMEOUT, ASSERTION_LOOKUP_MS, FLOW_TIMEOUT_SCALE, scaledTimeout, MAESTRO_SELECTOR_KEYS, SELECTOR_FILTER_KEYS, UNSUPPORTED_SELECTOR_KEYS, MAESTRO_PERMISSION_KINDS, MAESTRO_PERMISSION_STATUS, SootSimBridgeFlowRunner;
38218
+ var fs20, path20, import_devices4, import_yaml2, import_proof_frame, DEFAULT_TIMEOUT, ASSERTION_LOOKUP_MS, FLOW_TIMEOUT_SCALE, scaledTimeout, MAESTRO_SELECTOR_KEYS, SELECTOR_FILTER_KEYS, UNSUPPORTED_SELECTOR_KEYS, ACCESSIBILITY_SELECTOR_KEYS, ACCESSIBILITY_TRAIT_FILTERS, MAESTRO_PERMISSION_KINDS, MAESTRO_PERMISSION_STATUS, SootSimBridgeFlowRunner;
37924
38219
  var init_bridge_flow_runner = __esm({
37925
38220
  "cli/bridge-flow-runner.ts"() {
37926
38221
  "use strict";
@@ -37987,12 +38282,21 @@ var init_bridge_flow_runner = __esm({
37987
38282
  "leftOf",
37988
38283
  "rightOf"
37989
38284
  ];
37990
- UNSUPPORTED_SELECTOR_KEYS = [
38285
+ UNSUPPORTED_SELECTOR_KEYS = ["css"];
38286
+ ACCESSIBILITY_SELECTOR_KEYS = [
37991
38287
  "containsChild",
37992
38288
  "containsDescendants",
37993
- "focused",
37994
- "css"
38289
+ "focused"
37995
38290
  ];
38291
+ ACCESSIBILITY_TRAIT_FILTERS = {
38292
+ // IOSDriver sets attributes["text"] on every node, including empty text.
38293
+ TEXT: () => true,
38294
+ SQUARE: (node) => {
38295
+ const bounds = accessibilityBounds(node);
38296
+ return Math.abs(1 - bounds.width / bounds.height) < 0.03;
38297
+ },
38298
+ LONG_TEXT: (node) => (node.value?.length ?? 0) > 200
38299
+ };
37996
38300
  MAESTRO_PERMISSION_KINDS = {
37997
38301
  all: PERMISSION_KINDS,
37998
38302
  bluetooth: ["bluetooth"],
@@ -38275,6 +38579,9 @@ var init_bridge_flow_runner = __esm({
38275
38579
  }
38276
38580
  async findElement(opts) {
38277
38581
  assertSupportedSelector(opts);
38582
+ if (needsAccessibilityProjection(opts)) {
38583
+ return this.findElementAccessibilityTree(opts);
38584
+ }
38278
38585
  if (opts.id != null || opts.index != null || opts.text != null && /[.*+?^$()[\]{}|\\]/.test(opts.text) || SELECTOR_FILTER_KEYS.some((key) => opts[key] != null)) {
38279
38586
  return this.findElementScoped(opts);
38280
38587
  }
@@ -38307,6 +38614,46 @@ var init_bridge_flow_runner = __esm({
38307
38614
  }
38308
38615
  })()`);
38309
38616
  }
38617
+ // structural filters (containsChild / containsDescendants / focused) read the
38618
+ // engine's XCTest accessibility projection — one bridge evaluate, then the
38619
+ // same Orchestra.buildFilter semantics the render resolver implements, over
38620
+ // PeachAccessibilityNode instead of the visible semantic tree.
38621
+ async findElementAccessibilityTree(opts) {
38622
+ const tree = await this.evaluate(`(async () => {
38623
+ const test = window.__sootsimTest
38624
+ if (!test || typeof test.captureAccessibilityTree !== 'function') throw new Error('engine accessibility snapshot is unavailable')
38625
+ return await test.captureAccessibilityTree()
38626
+ })()`);
38627
+ if (!isPeachAccessibilityTree(tree))
38628
+ throw new Error("engine returned an invalid accessibility snapshot");
38629
+ if (tree.length === 0) return null;
38630
+ if (tree.some((node2) => node2.platform !== "ios"))
38631
+ throw new Error(
38632
+ "containsChild, containsDescendants and focused require the iOS accessibility hierarchy"
38633
+ );
38634
+ const node = resolveAccessibilitySelector(tree, opts);
38635
+ if (!node) return null;
38636
+ const frame = node.frame;
38637
+ return {
38638
+ nodeId: Number.isFinite(node.nodeId) ? node.nodeId : null,
38639
+ id: node.testID,
38640
+ testID: node.testID,
38641
+ text: node.label,
38642
+ accessibilityLabel: node.label,
38643
+ absolutePosition: { x: frame.x, y: frame.y },
38644
+ layout: { width: frame.width, height: frame.height },
38645
+ // the engine applies Maestro's integer-frame viewport filter to the
38646
+ // native hierarchy before this lookup; preserve the native bounds.
38647
+ visibleFrame: {
38648
+ x: frame.x,
38649
+ y: frame.y,
38650
+ width: frame.width,
38651
+ height: frame.height,
38652
+ area: frame.width * frame.height
38653
+ },
38654
+ isTextInput: node.isTextInput
38655
+ };
38656
+ }
38310
38657
  // maestro's selector resolver (Orchestra.buildFilter over Filters.kt):
38311
38658
  // id and text patterns, size, traits, enabled/selected, the relative
38312
38659
  // below/above/leftOf/rightOf filters, `index:` (nth match) and `childOf:`
@@ -38669,7 +39016,7 @@ var init_bridge_flow_runner = __esm({
38669
39016
  break;
38670
39017
  }
38671
39018
  }
38672
- const narrowed = opts.index != null || SELECTOR_FILTER_KEYS.some((key) => opts[key] != null);
39019
+ const narrowed = opts.index != null || SELECTOR_FILTER_KEYS.some((key) => opts[key] != null) || ACCESSIBILITY_SELECTOR_KEYS.some((key) => opts[key] != null);
38673
39020
  const direct = element || !narrowed ? await this.activatePressTarget(opts) : null;
38674
39021
  if (direct?.ok) {
38675
39022
  await this.settleAfterInput();