peachsim 0.1.637 → 0.1.638

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 (79) hide show
  1. package/README.md +8 -5
  2. package/cli/bridge-flow-runner.ts +442 -14
  3. package/cli/commands/inspect/core.ts +93 -24
  4. package/cli/commands/maestro.ts +2 -2
  5. package/cli/commands/serve.ts +13 -1
  6. package/cli/drivers/playwright-provisioning.ts +8 -16
  7. package/cli/drivers/playwright-sim-host.ts +3 -0
  8. package/cli/drivers/playwright.ts +1 -0
  9. package/cli/internal-child.ts +51 -8
  10. package/dist-lib/agent-daemon-client.cjs +1 -1
  11. package/dist-lib/agent-events.cjs +1 -1
  12. package/dist-lib/agent-identity.cjs +1 -1
  13. package/dist-lib/agent-sessions.cjs +1 -1
  14. package/dist-lib/attached-projects.cjs +1 -1
  15. package/dist-lib/auth/shared-session.cjs +1 -1
  16. package/dist-lib/backend-origin.cjs +1 -1
  17. package/dist-lib/beta.cjs +1 -1
  18. package/dist-lib/beta.mjs +1 -1
  19. package/dist-lib/bridge-constants.cjs +1 -1
  20. package/dist-lib/bridge-contract-input.cjs +3 -2
  21. package/dist-lib/bridge-contract-input.mjs +3 -2
  22. package/dist-lib/bridge-contract.cjs +1 -1
  23. package/dist-lib/bridge-contract.mjs +1 -1
  24. package/dist-lib/capture-contract.cjs +1 -1
  25. package/dist-lib/capture-contract.mjs +1 -1
  26. package/dist-lib/cli-constants.cjs +1 -1
  27. package/dist-lib/cloud-contract.cjs +1 -1
  28. package/dist-lib/cloud-contract.mjs +1 -1
  29. package/dist-lib/cloud-test-args.cjs +1 -1
  30. package/dist-lib/cloud-test-args.mjs +1 -1
  31. package/dist-lib/cloud.cjs +1 -1
  32. package/dist-lib/cloud.mjs +1 -1
  33. package/dist-lib/config.cjs +1 -1
  34. package/dist-lib/detox/index.cjs +1 -1
  35. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  36. package/dist-lib/home-paths.cjs +1 -1
  37. package/dist-lib/host/bridge-host.cjs +4 -2
  38. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  39. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  40. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  41. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  42. package/dist-lib/host/websocket-proxy.cjs +1 -1
  43. package/dist-lib/index.cjs +76 -20
  44. package/dist-lib/jump-to-source-babel.cjs +1 -1
  45. package/dist-lib/jump-to-source-native.cjs +1 -1
  46. package/dist-lib/kotlin-host.cjs +1 -1
  47. package/dist-lib/menu.cjs +1 -1
  48. package/dist-lib/menu.mjs +1 -1
  49. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  50. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  51. package/dist-lib/metro-production-bundle.cjs +1 -1
  52. package/dist-lib/metro-production-bundle.mjs +1 -1
  53. package/dist-lib/metro.cjs +1 -1
  54. package/dist-lib/native-app-identity.cjs +1 -1
  55. package/dist-lib/native-app-identity.mjs +1 -1
  56. package/dist-lib/profiles.cjs +1 -1
  57. package/dist-lib/public-brand.cjs +1 -1
  58. package/dist-lib/react-native-host-modules.cjs +1 -1
  59. package/dist-lib/react-native-host-modules.mjs +1 -1
  60. package/dist-lib/render-mode.cjs +1 -1
  61. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  62. package/dist-lib/sdk.cjs +78 -20
  63. package/dist-lib/sdk.mjs +77 -20
  64. package/dist-lib/skills.cjs +394 -62
  65. package/dist-lib/swift-compile.cjs +89 -7
  66. package/dist-lib/swift-compile.mjs +89 -7
  67. package/dist-lib/swift-package.cjs +1 -1
  68. package/dist-lib/swift-package.mjs +1 -1
  69. package/dist-lib/swift-submit.cjs +1 -1
  70. package/dist-lib/swift-submit.mjs +1 -1
  71. package/dist-lib/swift.cjs +122 -34
  72. package/dist-lib/vite-swift.cjs +89 -7
  73. package/dist-lib/vite.cjs +1 -1
  74. package/package.json +1 -1
  75. package/src/bridge-contract-input.ts +3 -2
  76. package/src/bridge-contract.ts +8 -0
  77. package/src/host/bridge-host.ts +5 -5
  78. package/src/native-seam-manifest.ts +4 -6
  79. package/src/swift-compile.ts +105 -9
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.637 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.638 | (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",
@@ -4491,6 +4493,8 @@ var init_supported_native_packages = __esm({
4491
4493
  // seam+coverage
4492
4494
  "@react-native-community/progress-view",
4493
4495
  // seam
4496
+ "@react-native-community/push-notification-ios",
4497
+ // seam
4494
4498
  "@react-native-community/slider",
4495
4499
  // coverage
4496
4500
  "@react-native-cookies/cookies",
@@ -4590,7 +4594,7 @@ var init_supported_native_packages = __esm({
4590
4594
  "appcenter-analytics",
4591
4595
  // seam
4592
4596
  "burnt",
4593
- // coverage
4597
+ // seam+coverage
4594
4598
  "customerio-reactnative",
4595
4599
  // seam
4596
4600
  "expo",
@@ -4811,8 +4815,12 @@ var init_supported_native_packages = __esm({
4811
4815
  // seam
4812
4816
  "react-native-barcode-creator",
4813
4817
  // seam
4818
+ "react-native-biometrics",
4819
+ // seam+coverage
4814
4820
  "react-native-blasted-image",
4815
4821
  // seam
4822
+ "react-native-ble-plx",
4823
+ // seam
4816
4824
  "react-native-blob-util",
4817
4825
  // coverage
4818
4826
  "react-native-blue-crypto",
@@ -4905,6 +4913,8 @@ var init_supported_native_packages = __esm({
4905
4913
  // seam
4906
4914
  "react-native-fs",
4907
4915
  // coverage
4916
+ "react-native-geolocation-service",
4917
+ // seam
4908
4918
  "react-native-gesture-handler",
4909
4919
  // preset+semantic-proof+seam+coverage
4910
4920
  "react-native-get-device-locale",
@@ -4912,7 +4922,7 @@ var init_supported_native_packages = __esm({
4912
4922
  "react-native-get-location",
4913
4923
  // seam+coverage
4914
4924
  "react-native-get-random-values",
4915
- // coverage
4925
+ // seam+coverage
4916
4926
  "react-native-google-places",
4917
4927
  // seam
4918
4928
  "react-native-google-play-install-referrer",
@@ -4987,6 +4997,8 @@ var init_supported_native_packages = __esm({
4987
4997
  // seam+coverage
4988
4998
  "react-native-navigation",
4989
4999
  // seam
5000
+ "react-native-nfc-manager",
5001
+ // seam
4990
5002
  "react-native-nitro-buffer",
4991
5003
  // seam+coverage
4992
5004
  "react-native-nitro-device-info",
@@ -5080,7 +5092,7 @@ var init_supported_native_packages = __esm({
5080
5092
  "react-native-record-screen",
5081
5093
  // seam
5082
5094
  "react-native-restart",
5083
- // coverage
5095
+ // seam+coverage
5084
5096
  "react-native-restart-newarch",
5085
5097
  // coverage
5086
5098
  "react-native-rsa-native",
@@ -5141,6 +5153,8 @@ var init_supported_native_packages = __esm({
5141
5153
  // seam
5142
5154
  "react-native-sqlite-2",
5143
5155
  // seam
5156
+ "react-native-sqlite-storage",
5157
+ // seam+coverage
5144
5158
  "react-native-stallion",
5145
5159
  // seam
5146
5160
  "react-native-store-age-signals-native-modules",
@@ -6624,7 +6638,7 @@ var init_registry = __esm({
6624
6638
  {
6625
6639
  range: ">=0.1.0",
6626
6640
  coverage: 0.9,
6627
- note: "shell-owned iOS-style toast + alert with SF Symbol icon support; Android routes toast and alert fallback through ToastAndroid",
6641
+ 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
6642
  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
6643
  missing: "drag-to-dismiss gesture interaction on iOS toast, Android message/icon/haptic/layout customization because upstream Android Burnt ignores those fields"
6630
6644
  }
@@ -7515,9 +7529,9 @@ var init_registry = __esm({
7515
7529
  {
7516
7530
  range: ">=5.0.0",
7517
7531
  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"
7532
+ 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",
7533
+ 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",
7534
+ missing: "desiredAccuracy levels (the browser fix has one accuracy), significant-change monitoring, background updates"
7521
7535
  }
7522
7536
  ]
7523
7537
  },
@@ -7528,9 +7542,9 @@ var init_registry = __esm({
7528
7542
  {
7529
7543
  range: ">=1.0.0",
7530
7544
  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"
7545
+ 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",
7546
+ 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",
7547
+ missing: "repeating calendar triggers other than daily and weekly, notification attachments, the fetch completion handler of a background push"
7534
7548
  }
7535
7549
  ]
7536
7550
  },
@@ -7712,9 +7726,9 @@ var init_registry = __esm({
7712
7726
  {
7713
7727
  range: ">=13.0.0",
7714
7728
  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"
7729
+ 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.",
7730
+ working: "hasHardwareAsync, isEnrolledAsync, supportedAuthenticationTypesAsync, getEnrolledLevelAsync, authenticateAsync biometric-only errors; UnavailabilityError for cancelAuthenticate on iOS",
7731
+ missing: "the system passcode sheet for device-fallback authentication, which settles as the sheet close button does (user_cancel)"
7718
7732
  }
7719
7733
  ]
7720
7734
  },
@@ -7796,19 +7810,19 @@ var init_registry = __esm({
7796
7810
  usage: 3,
7797
7811
  importance: 3,
7798
7812
  estimate: 1,
7799
- rationale: "the browser already provides crypto.getRandomValues, so importing the side-effect polyfill preserves the app contract"
7813
+ rationale: "the published side-effect polyfill runs unchanged over the RNGetRandomValues module"
7800
7814
  },
7801
7815
  {
7802
7816
  id: "load-safe-fallback",
7803
7817
  usage: 1,
7804
7818
  importance: 1,
7805
7819
  estimate: 1,
7806
- rationale: "the hosted runtime has secure crypto and the shim does not replace it"
7820
+ rationale: "an existing crypto.getRandomValues stays in place, as upstream leaves it"
7807
7821
  }
7808
7822
  ],
7809
7823
  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)"
7824
+ 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",
7825
+ 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
7826
  }
7813
7827
  ]
7814
7828
  },
@@ -8186,8 +8200,8 @@ var init_registry = __esm({
8186
8200
  {
8187
8201
  range: ">=0.0.1",
8188
8202
  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)"
8203
+ 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",
8204
+ working: "restart(), Restart() and getReason() from the published JS"
8191
8205
  }
8192
8206
  ]
8193
8207
  },
@@ -8249,10 +8263,10 @@ var init_registry = __esm({
8249
8263
  versions: [
8250
8264
  {
8251
8265
  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"
8266
+ coverage: 0.9,
8267
+ 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",
8268
+ 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",
8269
+ missing: "the system passcode sheet for allowDeviceCredentials prompts, which settle as the sheet close button does"
8256
8270
  }
8257
8271
  ]
8258
8272
  },
@@ -8577,9 +8591,9 @@ var init_registry = __esm({
8577
8591
  {
8578
8592
  range: ">=8.0.0",
8579
8593
  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"
8594
+ 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)",
8595
+ working: "configure, localNotification, localNotificationSchedule, permissions, badge, scheduled and delivered queries and cancellation, all through the published iOS path",
8596
+ missing: "the Android RNPushNotification module for Android mode"
8583
8597
  }
8584
8598
  ]
8585
8599
  },
@@ -8970,9 +8984,9 @@ var init_registry = __esm({
8970
8984
  {
8971
8985
  range: ">=3.0.0",
8972
8986
  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"
8987
+ 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",
8988
+ 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",
8989
+ missing: "EXIF metadata preservation for keepMeta, the RCTLogError red box on a failure"
8976
8990
  }
8977
8991
  ]
8978
8992
  },
@@ -9891,10 +9905,10 @@ var init_registry = __esm({
9891
9905
  versions: [
9892
9906
  {
9893
9907
  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)"
9908
+ coverage: 0.9,
9909
+ 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",
9910
+ 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",
9911
+ 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
9912
  }
9899
9913
  ]
9900
9914
  },
@@ -11127,9 +11141,9 @@ var init_registry = __esm({
11127
11141
  {
11128
11142
  range: ">=4.0.0",
11129
11143
  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)"
11144
+ 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.",
11145
+ working: "isSupported(config) and authenticate(reason, config) through the passcodeFallback branches of TouchID.m, with the upstream TouchIDError mapping",
11146
+ missing: "the system passcode sheet for passcodeFallback authentication, which settles as the sheet close button does (LAErrorUserCancel)"
11133
11147
  }
11134
11148
  ]
11135
11149
  },
@@ -12785,7 +12799,7 @@ var init_registry = __esm({
12785
12799
  coverage: 0.85,
12786
12800
  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
12801
  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)"
12802
+ 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
12803
  }
12790
12804
  ]
12791
12805
  },
@@ -13286,9 +13300,9 @@ var init_registry = __esm({
13286
13300
  {
13287
13301
  range: ">=2.0.0",
13288
13302
  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"
13303
+ 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",
13304
+ 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",
13305
+ missing: "a real Bluetooth radio, which the iOS simulator does not have either"
13292
13306
  }
13293
13307
  ]
13294
13308
  },
@@ -13299,9 +13313,9 @@ var init_registry = __esm({
13299
13313
  {
13300
13314
  range: ">=3.0.0",
13301
13315
  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"
13316
+ 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",
13317
+ 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',
13318
+ missing: "tag discovery (the simulator has no NFC reader); the Android NfcManager module"
13305
13319
  }
13306
13320
  ]
13307
13321
  },
@@ -29229,6 +29243,7 @@ var init_bridge_host = __esm({
29229
29243
  // terminal 4001 code, so the client closes the abandoned window instead
29230
29244
  // of reconnecting the zombie straight back in.
29231
29245
  static SIM_IDLE_REAP_TTL_MS = 30 * 6e4;
29246
+ bindHost;
29232
29247
  preferredPort;
29233
29248
  portFallbackCount;
29234
29249
  shouldWriteLockfile;
@@ -29262,6 +29277,7 @@ var init_bridge_host = __esm({
29262
29277
  inflightScan = null;
29263
29278
  static SCAN_FRESH_MS = 2e3;
29264
29279
  constructor(opts = {}) {
29280
+ this.bindHost = opts.host ?? "127.0.0.1";
29265
29281
  this.preferredPort = opts.port ?? DEFAULT_SOOTSIM_BRIDGE_PORT;
29266
29282
  this.port = this.preferredPort;
29267
29283
  this.shouldWriteLockfile = opts.writeLockfile === true;
@@ -29338,7 +29354,7 @@ var init_bridge_host = __esm({
29338
29354
  reject(err);
29339
29355
  };
29340
29356
  server.once("error", onError);
29341
- server.listen(port, "127.0.0.1", () => {
29357
+ server.listen(port, this.bindHost, () => {
29342
29358
  if (settled) return;
29343
29359
  const address = server.address();
29344
29360
  if (!address || typeof address === "string") {
@@ -33485,6 +33501,57 @@ function layoutGetNativeEval(opts = {}) {
33485
33501
  return elements
33486
33502
  })()`;
33487
33503
  }
33504
+ function isPeachAccessibilityTree(value) {
33505
+ if (!Array.isArray(value)) return false;
33506
+ return value.every((node) => {
33507
+ if (node === null || typeof node !== "object") return false;
33508
+ if (Reflect.get(node, "platform") !== "ios" && Reflect.get(node, "platform") !== "android")
33509
+ return false;
33510
+ if (typeof Reflect.get(node, "elementId") !== "string") return false;
33511
+ if (!Number.isInteger(Reflect.get(node, "nodeId"))) return false;
33512
+ for (const key of ["type", "role"]) {
33513
+ if (typeof Reflect.get(node, key) !== "string") return false;
33514
+ }
33515
+ for (const key of ["testID", "label", "hint", "value", "placeholder"]) {
33516
+ const field = Reflect.get(node, key);
33517
+ if (field !== null && typeof field !== "string") return false;
33518
+ }
33519
+ for (const key of [
33520
+ "isAccessibilityElement",
33521
+ "enabled",
33522
+ "selected",
33523
+ "checked",
33524
+ "clickable",
33525
+ "isTextInput"
33526
+ ]) {
33527
+ if (typeof Reflect.get(node, key) !== "boolean") return false;
33528
+ }
33529
+ const hasFocus = Reflect.get(node, "hasFocus");
33530
+ if (hasFocus !== void 0 && typeof hasFocus !== "boolean") return false;
33531
+ const state = Reflect.get(node, "state");
33532
+ if (state !== null) {
33533
+ if (typeof state !== "object") return false;
33534
+ const checked = Reflect.get(state, "checked");
33535
+ if (typeof checked !== "boolean" && checked !== "mixed") return false;
33536
+ for (const key of ["disabled", "selected", "busy", "expanded"]) {
33537
+ if (typeof Reflect.get(state, key) !== "boolean") return false;
33538
+ }
33539
+ }
33540
+ const value2 = Reflect.get(node, "accessibilityValue");
33541
+ if (value2 !== null) {
33542
+ if (typeof value2 !== "object") return false;
33543
+ for (const key of ["min", "max", "now"]) {
33544
+ const field = Reflect.get(value2, key);
33545
+ if (field !== void 0 && (typeof field !== "number" || !Number.isFinite(field)))
33546
+ return false;
33547
+ }
33548
+ const text = Reflect.get(value2, "text");
33549
+ if (text !== void 0 && typeof text !== "string") return false;
33550
+ }
33551
+ if (!isSemanticGeometry(Reflect.get(node, "frame"))) return false;
33552
+ return isPeachAccessibilityTree(Reflect.get(node, "children"));
33553
+ });
33554
+ }
33488
33555
  function waitReadyReason(status) {
33489
33556
  if (status.bridgeError) {
33490
33557
  if (status.bridgeError.includes(HEADLESS_TENANT_FETCH_HUNG_MARKER) || status.bridgeError.includes(PUBLIC_NO_NETWORK_MARKER)) {
@@ -34174,17 +34241,10 @@ function resolvePlaywright() {
34174
34241
  const packageJsonPath = requireFromRoot.resolve(`${spec}/package.json`);
34175
34242
  const parsed = JSON.parse((0, import_node_fs23.readFileSync)(packageJsonPath, "utf8"));
34176
34243
  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);
34244
+ const corePackagePath = (0, import_node_module5.createRequire)(modulePath).resolve(
34245
+ "playwright-core/package.json"
34246
+ );
34247
+ const cliPath = (0, import_node_path25.join)((0, import_node_path25.dirname)(corePackagePath), "cli.js");
34188
34248
  if (!(0, import_node_fs23.existsSync)(cliPath)) continue;
34189
34249
  return {
34190
34250
  spec,
@@ -34393,6 +34453,7 @@ async function launch2(opts) {
34393
34453
  env: {
34394
34454
  ...playwrightEnv,
34395
34455
  SOOTSIM_PW_MODULE: resolved.modulePath,
34456
+ SOOTSIM_PW_EXECUTABLE: provisioning.executablePath,
34396
34457
  SOOTSIM_PW_URL: opts.url,
34397
34458
  SOOTSIM_PW_BRIDGE_PORT: opts.bridgePort ? String(opts.bridgePort) : "",
34398
34459
  SOOTSIM_PW_HEADLESS: opts.headless ?? true ? "1" : "0",
@@ -37809,9 +37870,228 @@ function assertSupportedSelector(selector) {
37809
37870
  if (unsupported.length > 0) {
37810
37871
  throw new Error(`selector ${unsupported.join(", ")} is not supported by peach yet`);
37811
37872
  }
37812
- for (const key of ["childOf", "below", "above", "leftOf", "rightOf"]) {
37873
+ for (const key of ["childOf", "below", "above", "leftOf", "rightOf", "containsChild"]) {
37813
37874
  assertSupportedSelector(Reflect.get(selector, key));
37814
37875
  }
37876
+ const descendants = Reflect.get(selector, "containsDescendants");
37877
+ if (descendants !== void 0) {
37878
+ if (!Array.isArray(descendants)) {
37879
+ throw new Error("selector containsDescendants must be a list of selectors");
37880
+ }
37881
+ for (const descendant of descendants) assertSupportedSelector(descendant);
37882
+ }
37883
+ }
37884
+ function needsAccessibilityProjection(selector) {
37885
+ if (typeof selector === "string") return false;
37886
+ if (ACCESSIBILITY_SELECTOR_KEYS.some((key) => selector[key] != null)) return true;
37887
+ return ["childOf", "below", "above", "leftOf", "rightOf"].some((key) => {
37888
+ const nested = Reflect.get(selector, key);
37889
+ return isSelectorRecord(nested) && needsAccessibilityProjection(nested);
37890
+ });
37891
+ }
37892
+ function accessibilityTreeNodes(tree) {
37893
+ const out = [];
37894
+ const visit = (node) => {
37895
+ out.push(node);
37896
+ for (const child of node.children) visit(child);
37897
+ };
37898
+ for (const root of tree) visit(root);
37899
+ return out;
37900
+ }
37901
+ function accessibilitySubtreeNodes(node) {
37902
+ return [node, ...node.children.flatMap(accessibilitySubtreeNodes)];
37903
+ }
37904
+ function accessibilityTextMatcher(value, kind) {
37905
+ const pattern = new RegExp(`^(?:${value})$`);
37906
+ return (candidate) => {
37907
+ const native = candidate ?? "";
37908
+ if (kind === "id")
37909
+ return pattern.test(native) || pattern.test(native.slice(native.lastIndexOf("/") + 1));
37910
+ const stripped = native.replaceAll("\n", " ");
37911
+ return native === value || stripped === value || pattern.test(native) || pattern.test(stripped);
37912
+ };
37913
+ }
37914
+ function accessibilityBounds(node) {
37915
+ const left = Math.trunc(node.frame.x);
37916
+ const top = Math.trunc(node.frame.y);
37917
+ return {
37918
+ x: left,
37919
+ y: top,
37920
+ width: Math.trunc(node.frame.x + node.frame.width) - left,
37921
+ height: Math.trunc(node.frame.y + node.frame.height) - top
37922
+ };
37923
+ }
37924
+ function accessibilityBasicFilters(selector) {
37925
+ const filters = [];
37926
+ if (selector.text != null) {
37927
+ const matches = accessibilityTextMatcher(selector.text, "text");
37928
+ filters.push(
37929
+ (node) => matches(node.label) || matches(node.value) || matches(node.placeholder)
37930
+ );
37931
+ }
37932
+ if (selector.id != null) {
37933
+ const matches = accessibilityTextMatcher(selector.id, "id");
37934
+ filters.push((node) => matches(node.testID));
37935
+ }
37936
+ if (selector.width != null || selector.height != null) {
37937
+ const tolerance = selector.tolerance ?? 0;
37938
+ filters.push((node) => {
37939
+ const bounds = accessibilityBounds(node);
37940
+ return (selector.width == null || Math.abs(bounds.width - selector.width) <= tolerance) && (selector.height == null || Math.abs(bounds.height - selector.height) <= tolerance);
37941
+ });
37942
+ }
37943
+ for (const trait of selector.traits ? selector.traits.split(" ") : []) {
37944
+ const filter = ACCESSIBILITY_TRAIT_FILTERS[trait.replaceAll("-", "_").toUpperCase()];
37945
+ if (!filter) throw new Error(`unknown selector trait: ${trait}`);
37946
+ filters.push(filter);
37947
+ }
37948
+ if (selector.enabled != null) {
37949
+ filters.push((node) => node.enabled === selector.enabled);
37950
+ }
37951
+ if (selector.selected != null) {
37952
+ filters.push((node) => node.selected === selector.selected);
37953
+ }
37954
+ if (selector.checked != null) {
37955
+ filters.push((node) => node.checked === selector.checked);
37956
+ }
37957
+ if (selector.focused != null) {
37958
+ filters.push((node) => (node.hasFocus ?? false) === selector.focused);
37959
+ }
37960
+ return filters;
37961
+ }
37962
+ function deepestMatchingAccessibilityNodes(nodes, matches) {
37963
+ const out = [];
37964
+ const included = new Set(nodes);
37965
+ const nested = new Set(
37966
+ nodes.flatMap((node) => node.children.filter((child) => included.has(child)))
37967
+ );
37968
+ for (const node of nodes.filter((node2) => !nested.has(node2))) {
37969
+ const children = deepestMatchingAccessibilityNodes(node.children, matches);
37970
+ if (children.length > 0) out.push(...children);
37971
+ else if (matches(node)) out.push(node);
37972
+ }
37973
+ return distinctAccessibilityNodes(out);
37974
+ }
37975
+ function distinctAccessibilityNodes(nodes) {
37976
+ const buckets = /* @__PURE__ */ new Map();
37977
+ return nodes.filter((node) => {
37978
+ const key = JSON.stringify([
37979
+ node.testID ?? "",
37980
+ node.label ?? "",
37981
+ node.placeholder ?? "",
37982
+ node.value ?? "",
37983
+ node.enabled,
37984
+ node.selected,
37985
+ node.checked,
37986
+ node.clickable,
37987
+ node.hasFocus ?? false,
37988
+ accessibilityBounds(node),
37989
+ node.children.length
37990
+ ]);
37991
+ const bucket = buckets.get(key);
37992
+ if (bucket?.some((other) => sameNativeAccessibilityShape(node, other))) return false;
37993
+ if (bucket) bucket.push(node);
37994
+ else buckets.set(key, [node]);
37995
+ return true;
37996
+ });
37997
+ }
37998
+ function isSelectorRecord(value) {
37999
+ return typeof value === "object" && value !== null && !Array.isArray(value);
38000
+ }
38001
+ function accessibilityAnchorSelector(value) {
38002
+ if (typeof value === "string") return { text: value };
38003
+ return isSelectorRecord(value) ? value : {};
38004
+ }
38005
+ function sameNativeAccessibilityShape(a, b) {
38006
+ 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) {
38007
+ return false;
38008
+ }
38009
+ for (const [index, child] of a.children.entries()) {
38010
+ const other = b.children[index];
38011
+ if (!other || !sameNativeAccessibilityShape(child, other)) return false;
38012
+ }
38013
+ return true;
38014
+ }
38015
+ function accessibilitySubtreeMatches(all, node, selector) {
38016
+ if (filterAccessibilityNodes(all, [node], selector).length > 0) return true;
38017
+ return node.children.some((child) => accessibilitySubtreeMatches(all, child, selector));
38018
+ }
38019
+ function filterAccessibilityNodes(all, nodes, selector) {
38020
+ if (selector.childOf == null) {
38021
+ return filterAccessibilityNodesIn(all, nodes, selector);
38022
+ }
38023
+ const container = filterAccessibilityNodes(
38024
+ all,
38025
+ all,
38026
+ accessibilityAnchorSelector(selector.childOf)
38027
+ )[0];
38028
+ if (!container) return [];
38029
+ return filterAccessibilityNodesIn(all, accessibilitySubtreeNodes(container), selector);
38030
+ }
38031
+ function filterAccessibilityNodesIn(all, nodes, selector) {
38032
+ const basic = accessibilityBasicFilters(selector);
38033
+ let candidates = basic.length > 0 ? deepestMatchingAccessibilityNodes(
38034
+ nodes,
38035
+ (node) => basic.every((filter) => filter(node))
38036
+ ) : [...nodes];
38037
+ const relativeGeometry = {
38038
+ below: (node, other) => accessibilityBounds(node).y > accessibilityBounds(other).y,
38039
+ above: (node, other) => accessibilityBounds(node).y < accessibilityBounds(other).y,
38040
+ leftOf: (node, other) => accessibilityBounds(node).x < accessibilityBounds(other).x,
38041
+ rightOf: (node, other) => accessibilityBounds(node).x > accessibilityBounds(other).x
38042
+ };
38043
+ for (const key of ["below", "above", "leftOf", "rightOf"]) {
38044
+ const anchor = selector[key];
38045
+ if (anchor == null) continue;
38046
+ const anchors = filterAccessibilityNodes(
38047
+ all,
38048
+ nodes,
38049
+ accessibilityAnchorSelector(anchor)
38050
+ );
38051
+ candidates = candidates.filter(
38052
+ (node) => anchors.some((other) => relativeGeometry[key](node, other))
38053
+ );
38054
+ }
38055
+ if (selector.containsChild != null) {
38056
+ const child = filterAccessibilityNodes(
38057
+ all,
38058
+ all,
38059
+ accessibilityAnchorSelector(selector.containsChild)
38060
+ )[0];
38061
+ candidates = child ? candidates.filter(
38062
+ (node) => node.children.some(
38063
+ (candidate) => sameNativeAccessibilityShape(candidate, child)
38064
+ )
38065
+ ) : [];
38066
+ }
38067
+ if (selector.containsDescendants != null) {
38068
+ const descendants = selector.containsDescendants.map(accessibilityAnchorSelector);
38069
+ candidates = candidates.filter(
38070
+ (node) => descendants.every(
38071
+ (descendant) => node.children.some(
38072
+ (child) => accessibilitySubtreeMatches(all, child, descendant)
38073
+ )
38074
+ )
38075
+ );
38076
+ }
38077
+ candidates = distinctAccessibilityNodes(candidates);
38078
+ if (selector.index != null) {
38079
+ const index = Math.trunc(Number(selector.index));
38080
+ const sorted = [...candidates].sort((a, b) => {
38081
+ const aBounds = accessibilityBounds(a);
38082
+ const bBounds = accessibilityBounds(b);
38083
+ return aBounds.y - bBounds.y || aBounds.x - bBounds.x;
38084
+ });
38085
+ const resolved = index >= 0 ? index : sorted.length + index;
38086
+ if (resolved < 0 || resolved >= sorted.length) return [];
38087
+ const picked = sorted[resolved];
38088
+ return picked ? [picked] : [];
38089
+ }
38090
+ return [...candidates].sort((a, b) => Number(b.clickable) - Number(a.clickable));
38091
+ }
38092
+ function resolveAccessibilitySelector(tree, selector) {
38093
+ const all = accessibilityTreeNodes(tree);
38094
+ return filterAccessibilityNodes(all, all, selector)[0] ?? null;
37815
38095
  }
37816
38096
  function isObjectRecord(value) {
37817
38097
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -37920,7 +38200,7 @@ function parseFlowSteps(content) {
37920
38200
  const parsed = import_yaml2.default.parse(candidate);
37921
38201
  return Array.isArray(parsed) ? parsed : [];
37922
38202
  }
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;
38203
+ 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
38204
  var init_bridge_flow_runner = __esm({
37925
38205
  "cli/bridge-flow-runner.ts"() {
37926
38206
  "use strict";
@@ -37987,12 +38267,21 @@ var init_bridge_flow_runner = __esm({
37987
38267
  "leftOf",
37988
38268
  "rightOf"
37989
38269
  ];
37990
- UNSUPPORTED_SELECTOR_KEYS = [
38270
+ UNSUPPORTED_SELECTOR_KEYS = ["css"];
38271
+ ACCESSIBILITY_SELECTOR_KEYS = [
37991
38272
  "containsChild",
37992
38273
  "containsDescendants",
37993
- "focused",
37994
- "css"
38274
+ "focused"
37995
38275
  ];
38276
+ ACCESSIBILITY_TRAIT_FILTERS = {
38277
+ // IOSDriver sets attributes["text"] on every node, including empty text.
38278
+ TEXT: () => true,
38279
+ SQUARE: (node) => {
38280
+ const bounds = accessibilityBounds(node);
38281
+ return Math.abs(1 - bounds.width / bounds.height) < 0.03;
38282
+ },
38283
+ LONG_TEXT: (node) => (node.value?.length ?? 0) > 200
38284
+ };
37996
38285
  MAESTRO_PERMISSION_KINDS = {
37997
38286
  all: PERMISSION_KINDS,
37998
38287
  bluetooth: ["bluetooth"],
@@ -38275,6 +38564,9 @@ var init_bridge_flow_runner = __esm({
38275
38564
  }
38276
38565
  async findElement(opts) {
38277
38566
  assertSupportedSelector(opts);
38567
+ if (needsAccessibilityProjection(opts)) {
38568
+ return this.findElementAccessibilityTree(opts);
38569
+ }
38278
38570
  if (opts.id != null || opts.index != null || opts.text != null && /[.*+?^$()[\]{}|\\]/.test(opts.text) || SELECTOR_FILTER_KEYS.some((key) => opts[key] != null)) {
38279
38571
  return this.findElementScoped(opts);
38280
38572
  }
@@ -38307,6 +38599,46 @@ var init_bridge_flow_runner = __esm({
38307
38599
  }
38308
38600
  })()`);
38309
38601
  }
38602
+ // structural filters (containsChild / containsDescendants / focused) read the
38603
+ // engine's XCTest accessibility projection — one bridge evaluate, then the
38604
+ // same Orchestra.buildFilter semantics the render resolver implements, over
38605
+ // PeachAccessibilityNode instead of the visible semantic tree.
38606
+ async findElementAccessibilityTree(opts) {
38607
+ const tree = await this.evaluate(`(async () => {
38608
+ const test = window.__sootsimTest
38609
+ if (!test || typeof test.captureAccessibilityTree !== 'function') throw new Error('engine accessibility snapshot is unavailable')
38610
+ return await test.captureAccessibilityTree()
38611
+ })()`);
38612
+ if (!isPeachAccessibilityTree(tree))
38613
+ throw new Error("engine returned an invalid accessibility snapshot");
38614
+ if (tree.length === 0) return null;
38615
+ if (tree.some((node2) => node2.platform !== "ios"))
38616
+ throw new Error(
38617
+ "containsChild, containsDescendants and focused require the iOS accessibility hierarchy"
38618
+ );
38619
+ const node = resolveAccessibilitySelector(tree, opts);
38620
+ if (!node) return null;
38621
+ const frame = node.frame;
38622
+ return {
38623
+ nodeId: Number.isFinite(node.nodeId) ? node.nodeId : null,
38624
+ id: node.testID,
38625
+ testID: node.testID,
38626
+ text: node.label,
38627
+ accessibilityLabel: node.label,
38628
+ absolutePosition: { x: frame.x, y: frame.y },
38629
+ layout: { width: frame.width, height: frame.height },
38630
+ // the engine applies Maestro's integer-frame viewport filter to the
38631
+ // native hierarchy before this lookup; preserve the native bounds.
38632
+ visibleFrame: {
38633
+ x: frame.x,
38634
+ y: frame.y,
38635
+ width: frame.width,
38636
+ height: frame.height,
38637
+ area: frame.width * frame.height
38638
+ },
38639
+ isTextInput: node.isTextInput
38640
+ };
38641
+ }
38310
38642
  // maestro's selector resolver (Orchestra.buildFilter over Filters.kt):
38311
38643
  // id and text patterns, size, traits, enabled/selected, the relative
38312
38644
  // below/above/leftOf/rightOf filters, `index:` (nth match) and `childOf:`
@@ -38669,7 +39001,7 @@ var init_bridge_flow_runner = __esm({
38669
39001
  break;
38670
39002
  }
38671
39003
  }
38672
- const narrowed = opts.index != null || SELECTOR_FILTER_KEYS.some((key) => opts[key] != null);
39004
+ const narrowed = opts.index != null || SELECTOR_FILTER_KEYS.some((key) => opts[key] != null) || ACCESSIBILITY_SELECTOR_KEYS.some((key) => opts[key] != null);
38673
39005
  const direct = element || !narrowed ? await this.activatePressTarget(opts) : null;
38674
39006
  if (direct?.ok) {
38675
39007
  await this.settleAfterInput();