peachsim 0.1.627 → 0.1.628

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 (92) hide show
  1. package/LICENSE +1 -1
  2. package/cli/bin.ts +6 -2
  3. package/cli/bridge-flow-runner.ts +79 -72
  4. package/cli/commands/cleanup.ts +6 -6
  5. package/cli/commands/control.ts +4 -5
  6. package/cli/commands/detox.ts +25 -11
  7. package/cli/commands/flow.ts +3 -2
  8. package/cli/commands/inspect.ts +22 -37
  9. package/cli/commands/mode.ts +11 -7
  10. package/cli/commands/report-issue.ts +1 -1
  11. package/cli/commands/setup.ts +16 -11
  12. package/cli/commands/show-touches-mode.ts +33 -0
  13. package/cli/commands/skills.ts +16 -0
  14. package/cli/commands/test.ts +8 -1
  15. package/cli/commands/welcome.ts +3 -3
  16. package/cli/drivers/playwright-sim-host.ts +6 -5
  17. package/cli/help-core.ts +9 -4
  18. package/cli/internal-child.ts +14 -2
  19. package/cli/levenshtein.ts +27 -0
  20. package/cli/main.ts +28 -18
  21. package/cli/ws-bridge.ts +3 -2
  22. package/detox/index.ts +234 -7
  23. package/dist-lib/agent-daemon-client.cjs +1 -1
  24. package/dist-lib/agent-events.cjs +1 -1
  25. package/dist-lib/agent-identity.cjs +1 -1
  26. package/dist-lib/agent-sessions.cjs +1 -1
  27. package/dist-lib/attached-projects.cjs +1 -1
  28. package/dist-lib/auth/shared-session.cjs +1 -1
  29. package/dist-lib/backend-origin.cjs +1 -1
  30. package/dist-lib/beta.cjs +1 -1
  31. package/dist-lib/beta.mjs +1 -1
  32. package/dist-lib/bridge-constants.cjs +1 -1
  33. package/dist-lib/bridge-contract-input.cjs +1 -1
  34. package/dist-lib/bridge-contract-input.mjs +1 -1
  35. package/dist-lib/bridge-contract.cjs +1 -1
  36. package/dist-lib/bridge-contract.mjs +1 -1
  37. package/dist-lib/capture-contract.cjs +1 -1
  38. package/dist-lib/capture-contract.mjs +1 -1
  39. package/dist-lib/cli-constants.cjs +1 -1
  40. package/dist-lib/cloud-contract.cjs +1 -1
  41. package/dist-lib/cloud-contract.mjs +1 -1
  42. package/dist-lib/cloud-test-args.cjs +1 -1
  43. package/dist-lib/cloud-test-args.mjs +1 -1
  44. package/dist-lib/cloud.cjs +1 -1
  45. package/dist-lib/cloud.mjs +1 -1
  46. package/dist-lib/config.cjs +1 -1
  47. package/dist-lib/detox/index.cjs +170 -13
  48. package/dist-lib/dev-bundle-resolution.cjs +2 -2
  49. package/dist-lib/home-paths.cjs +1 -1
  50. package/dist-lib/host/bridge-host.cjs +42 -37
  51. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  52. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  53. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  54. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  55. package/dist-lib/host/websocket-proxy.cjs +1 -1
  56. package/dist-lib/index.cjs +44 -5
  57. package/dist-lib/jump-to-source-babel.cjs +1 -1
  58. package/dist-lib/jump-to-source-native.cjs +1 -1
  59. package/dist-lib/menu.cjs +7 -1
  60. package/dist-lib/menu.mjs +7 -1
  61. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  62. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  63. package/dist-lib/metro-production-bundle.cjs +1 -1
  64. package/dist-lib/metro-production-bundle.mjs +1 -1
  65. package/dist-lib/metro.cjs +43 -4
  66. package/dist-lib/profiles.cjs +1 -1
  67. package/dist-lib/public-brand.cjs +1 -1
  68. package/dist-lib/react-native-host-modules.cjs +1 -1
  69. package/dist-lib/react-native-host-modules.mjs +1 -1
  70. package/dist-lib/render-mode.cjs +1 -1
  71. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  72. package/dist-lib/sdk.cjs +1 -1
  73. package/dist-lib/sdk.mjs +1 -1
  74. package/dist-lib/skills.cjs +140 -149
  75. package/dist-lib/swift-compile.cjs +1 -1
  76. package/dist-lib/swift-compile.mjs +1 -1
  77. package/dist-lib/swift-package.cjs +1 -1
  78. package/dist-lib/swift-package.mjs +1 -1
  79. package/dist-lib/swift-submit.cjs +1 -1
  80. package/dist-lib/swift-submit.mjs +1 -1
  81. package/dist-lib/swift.cjs +7234 -7069
  82. package/dist-lib/vite-kotlin.cjs +1 -1
  83. package/dist-lib/vite-swift.cjs +1 -1
  84. package/dist-lib/vite.cjs +43 -4
  85. package/package.json +1 -1
  86. package/skills/contrast/SKILL.md +1 -1
  87. package/src/dev-bundle-resolution.ts +1 -1
  88. package/src/host/bridge-host.ts +2 -34
  89. package/src/menu.ts +7 -0
  90. package/src/native-seam-manifest.ts +4 -16
  91. package/src/runtime-assets.ts +59 -2
  92. package/src/vite-plugin.ts +723 -25
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.627 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (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;
@@ -4707,7 +4707,7 @@ var init_supported_native_packages = __esm({
4707
4707
  "expo-sensors",
4708
4708
  // semantic-proof
4709
4709
  "expo-sharing",
4710
- // semantic-proof
4710
+ // semantic-proof+coverage
4711
4711
  "expo-sms",
4712
4712
  // semantic-proof
4713
4713
  "expo-speech",
@@ -5210,6 +5210,8 @@ var init_supported_native_packages = __esm({
5210
5210
  // preset+visual-proof+seam
5211
5211
  "react-native-widget-picker",
5212
5212
  // seam
5213
+ "react-native-widgetkit",
5214
+ // seam+coverage
5213
5215
  "react-native-wifi-p2p",
5214
5216
  // seam
5215
5217
  "react-native-worklets",
@@ -5801,8 +5803,8 @@ var init_registry = __esm({
5801
5803
  id: "navigation-controls",
5802
5804
  usage: 3,
5803
5805
  importance: 3,
5804
- estimate: 0.7,
5805
- rationale: "Reload and injection work. HTML fragment navigation now follows the observed iOS URL and callback sequence, and local link requests expose decisions with matching payloads. The remaining 30% judgment allowance retains initial-load interception, remote history, stop-loading, failed navigation and form requests."
5806
+ estimate: 0.8,
5807
+ rationale: "Reload and injection work. HTML fragment navigation now follows the observed iOS URL and callback sequence, and local link requests expose decisions with matching payloads. Initial http(s) loads and form submits are asked with matching payloads and resume through the lock callback, a stopped reload reports its start with no finish, and refused local connections and same-origin HTTP errors report onError/onHttpError with native shapes plus renderError. The remaining 20% judgment allowance retains remote history traversal and reload decisions, data:/about:/file initial-load decisions, and remote failure reporting."
5806
5808
  },
5807
5809
  {
5808
5810
  id: "layout-scroll-presentation",
@@ -5828,9 +5830,9 @@ var init_registry = __esm({
5828
5830
  ],
5829
5831
  capabilityCoverage: CAPABILITY_INVENTORY_PACKAGE_COVERAGE["react-native-webview"],
5830
5832
  strictCoverage: REACT_NATIVE_WEBVIEW_STRICT_COVERAGE,
5831
- note: "native-seam only: the published package runs unchanged and routes RNCWebView through the tenant-to-shell hosted iframe surface. the implementation estimate uses reviewed feature weights; the separate equal-surface strict diagnostic includes the shared, iOS, and Android public prop and ref surface. paired native and Peach runtime conformance covers local HTML, base URL, URI loading, load and navigation callbacks, both injection phases, two-way messaging, imperative URI reload and injection, local HTML fragment navigation and link-request decisions, content scrolling, parent translation, and onOpenWindow target delivery.",
5832
- working: "source.uri/source.html/source.baseUrl, Chromium cross-origin URI loading under the shell COEP, same-origin URI cookies, source.html persistent localStorage, document-start ReactNativeWebView bootstrap and injectedJavaScriptBeforeContentLoaded/injectedJavaScript on source.html, strict ReactNativeWebView.postMessage bridge envelope in both directions, onMessage synthetic event with navigation fields, onOpenWindow target delivery for source.html, successful onLoad/Start/End and onNavigationStateChange, ref.reload on URI pages, ref.injectJavaScript/postMessage, local HTML fragment URL/callback behavior, onShouldStartLoadWithRequest for local link clicks with native-shaped request and loading payloads, internal content scrolling, renderLoading, javaScriptEnabled sandbox, layout, inherited clipping and visibility, parked/live presentation",
5833
- missing: "Safari/WebKit cross-origin URI pages without COEP-compatible response headers remain permanently loading because WebKit ignores iframe credentialless; Chromium credentialless cross-origin URI frames send no credentials, including cookies set by their own origin, and use ephemeral storage; iframe pixels in canvas screenshots; script injection, ReactNativeWebView bootstrap, and onOpenWindow for remote pages, which get no bridge at all because a browser cannot script a cross-origin frame; cross-origin ref.goBack/goForward/stopLoading and truthful canGoBack/canGoForward values; failed HTTP/refused navigation callbacks (onError/onHttpError and renderError); initial-load and form onShouldStartLoadWithRequest callbacks, allowsBackForwardNavigationGestures, userAgent, dataDetectorTypes, cacheEnabled, incognito, scalesPageToFit, originWhitelist enforcement"
5833
+ note: "native-seam only: the published package runs unchanged and routes RNCWebView through the tenant-to-shell hosted iframe surface. the implementation estimate uses reviewed feature weights; the separate equal-surface strict diagnostic includes the shared, iOS, and Android public prop and ref surface. paired native and Peach runtime conformance covers local HTML, base URL, URI loading, load and navigation callbacks, both injection phases, two-way messaging, imperative URI reload and injection, local HTML fragment navigation and link-request decisions, initial-load and form request decisions, local stopLoading cancellation, refused-host and restricted-port onError with renderError, content scrolling, parent translation, and onOpenWindow target delivery. same-origin onHttpError follows RNCWebViewImpl but runs on Peach only, since no local HTTP error source is reachable from both targets.",
5834
+ working: "source.uri/source.html/source.baseUrl, Chromium cross-origin URI loading under the shell COEP, same-origin URI cookies, source.html persistent localStorage, document-start ReactNativeWebView bootstrap and injectedJavaScriptBeforeContentLoaded/injectedJavaScript on source.html, strict ReactNativeWebView.postMessage bridge envelope in both directions, onMessage synthetic event with navigation fields, onOpenWindow target delivery for source.html, successful onLoad/Start/End and onNavigationStateChange, ref.reload on URI pages, ref.injectJavaScript/postMessage, local HTML fragment URL/callback behavior, onShouldStartLoadWithRequest for local link clicks with native-shaped request and loading payloads, initial-load onShouldStartLoadWithRequest for http(s) documents with the native other/top-frame/lockIdentifier payload allowed or denied through the lock callback, form-submit onShouldStartLoadWithRequest with navigationType formsubmit and, for a get form, the form data as the url query, resumed in place, ref.stopLoading cancelling an in-flight reload with no following callbacks, onError with the native NSURLErrorDomain code/description for a refused local connection and WebKitErrorDomain 103 for a port on the WebKit blocked list, each plus onLoadEnd and renderError(domain, code, description), onHttpError with the native url/statusCode for a same-origin local HTTP error, internal content scrolling, renderLoading, javaScriptEnabled sandbox, layout, inherited clipping and visibility, parked/live presentation",
5835
+ missing: "Safari/WebKit cross-origin URI pages without COEP-compatible response headers remain permanently loading because WebKit ignores iframe credentialless; Chromium credentialless cross-origin URI frames send no credentials, including cookies set by their own origin, and use ephemeral storage; iframe pixels in canvas screenshots; script injection, ReactNativeWebView bootstrap, and onOpenWindow for remote pages, which get no bridge at all because a browser cannot script a cross-origin frame; cross-origin history truthfulness (goBack/goForward across documents, truthful canGoBack/canGoForward) plus reload and history-traversal policy decisions; initial-load decisions for data:/about:/file documents, which stay ungated with no paired native ask observed; remote HTTP error statuses and refused remote connections, which a cross-origin frame never exposes to the browser, and the HTTP status of a non-get form submit; allowsBackForwardNavigationGestures, userAgent, dataDetectorTypes, cacheEnabled, incognito, scalesPageToFit, originWhitelist enforcement"
5834
5836
  }
5835
5837
  ]
5836
5838
  },
@@ -5861,10 +5863,10 @@ var init_registry = __esm({
5861
5863
  },
5862
5864
  {
5863
5865
  range: ">=56.0.0 <58.0.0",
5864
- coverage: 0.35,
5865
- note: "native-seam only: Peach runtime conformance passes the published Expo 57.0.1 wrapper local-page bridge, script, object, message, scroll, update, and reload paths; native parity has no verdict, so these Peach-only observations do not raise the score.",
5866
- working: "DomWebView/WebView exports, source.uri rendering, onMessage data/url/title, ref.reload/injectJavaScript/scrollTo, document-start ReactNativeWebView bootstrap and injectedJavaScriptBeforeContentLoaded on percent-encoded data: URI pages, injectedJavaScriptObject, reload-on-update behavior for injectedJavaScriptObject/injectedJavaScript, layout and parked/live presentation",
5867
- missing: "Expo modules bridge, any bridge or script injection on a remote page because a browser cannot script a cross-origin frame, cross-origin scrolling, the re-encoded data: URI a page reports as its own location.href, exact native scroll physics and native-only media/keyboard props, iframe pixels in canvas screenshots"
5866
+ coverage: 0.5,
5867
+ note: 'native-seam only: the published Expo 57.0.1 wrapper and an Expo "use dom" component pass the same paired library case on a native iPhone 16 and in Peach (ExpoDomWebViewLibraryCase).',
5868
+ working: 'DomWebView/WebView exports, source.uri rendering, onMessage data/url/title, ref.reload/injectJavaScript/scrollTo, document-start ReactNativeWebView bootstrap and injectedJavaScriptBeforeContentLoaded, injectedJavaScriptObject, reload-on-update behavior for injectedJavaScriptObject/injectedJavaScript, "use dom" components (mount with initial props, prop updates, native actions that resolve or reject, useDOMImperativeHandle methods, reload), layout and parked/live presentation',
5869
+ missing: "Expo modules bridge (unstable_useExpoModulesBridge is accepted and ignored), any bridge or script injection on a remote page because a browser cannot script a cross-origin frame, cross-origin scrolling, the re-encoded data: URI a page reports as its own location.href, exact native scroll physics and native-only media/keyboard props, iframe pixels in canvas screenshots"
5868
5870
  }
5869
5871
  ]
5870
5872
  },
@@ -6145,9 +6147,9 @@ var init_registry = __esm({
6145
6147
  {
6146
6148
  range: ">=11.0.0",
6147
6149
  coverage: 0.95,
6148
- note: "runtime font registry backed by CSS FontFace where available",
6149
- working: "useFonts (URL/object/require sources), loadAsync, isLoaded, getLoadedFonts, isLoading, unloadAsync, unloadAllAsync, processFontFamily",
6150
- missing: "renderToImageAsync (native-only), exact Expo asset integration for numeric bundled font ids"
6150
+ note: "runtime font registry backed by the engine CanvasKit provider; renderToImageAsync rasterizes in the tenant worker on a cpu surface into the tmp file store",
6151
+ working: "useFonts (URL/object/require sources), loadAsync, isLoaded, getLoadedFonts, isLoading, processFontFamily, renderToImageAsync (tenant cpu raster, tmp file uri, dp width/height/scale); a file that is not a font rejects with ERR_FONT_REGISTRATION_FAILED; unloadAsync and unloadAllAsync throw UnavailabilityError, as the iOS and Android modules define neither",
6152
+ missing: "exact Expo asset integration for numeric bundled font ids"
6151
6153
  }
6152
6154
  ]
6153
6155
  },
@@ -6170,10 +6172,10 @@ var init_registry = __esm({
6170
6172
  versions: [
6171
6173
  {
6172
6174
  range: ">=1.0.0",
6173
- coverage: 0.9,
6174
- note: "the real expo-image wrapper renders through the shared sootsim-image host. Paired native and Peach runtime proof covers source-less placeholder rendering and right-edge contentPosition alignment; a negative control observed centered red/blue content before the renderer implemented the position.",
6175
- working: "Image component, ImageBackground, useImage hook, ImageRef class, contentFit, contentPosition right-edge alignment, source-less placeholder rendering and placeholderContentFit, tintColor, onLoad/onLoadStart/onLoadEnd/onError callbacks, source normalization (string/object/array), prefetch, clearMemoryCache, clearDiskCache, getCachePathAsync, configureCache, generateBlurhashAsync, generateThumbhashAsync, loadAsync",
6176
- missing: "transition/animation effects, blurRadius, contentPosition variants beyond the paired right-edge proof, sfEffect/SFSymbol, allowDownscaling, autoplay (GIF), responsivePolicy"
6175
+ coverage: 0.93,
6176
+ note: "the published Image, ImageBackground, and ExpoImage.tsx JS runs unchanged; the ExpoImage native view and native module follow the installed ImageView.swift and ImageModule.swift over the engine's sootsim-image host. paired native and Peach runtime proof covers cover, contain, fill, and scale-down contentFit, right-edge contentPosition, tint, opacity, uri, blurhash, and thumbhash placeholders, the cross-dissolve transition frame by frame, onLoad cache type and image facts, onDisplay, animated GIF frame playback with native durations, blurRadius, onProgress events, the placeholder generators, and a loaded image ref.",
6177
+ working: "Image, ImageBackground, useImage; contentFit cover, contain, fill, none, and scale-down; contentPosition; tintColor; blurRadius with the native halved radius; animated GIF playback with native frame durations and loop count (isAnimated, mediaType, autoplay, startAnimating, stopAnimating); source selection by view size; uri, blurhash, and thumbhash placeholders with placeholderContentFit; transition duration and timing curves with skipOnCacheHit; recyclingKey; onLoadStart, onLoad (cacheType by cachePolicy, url, size, mediaType, isAnimated), onProgress (loaded, total, progress) for data uris and remote sources the browser can read, onLoadEnd, onError, onDisplay; Image.prefetch with its cache policy, clearMemoryCache and clearDiskCache, loadAsync with maxWidth and maxHeight, readFromCacheAsync, writeToCacheAsync, generateBlurhashAsync and generateThumbhashAsync (the swift encoders), ImageRef rendering, a reload after every prop update, lockResourceAsync, unlockResourceAsync, and reloadAsync, the imageLoaded event",
6178
+ missing: "the flip, curl, and SF Symbol transition effects draw a cross-dissolve; SF Symbol sources; the disk cache lasts only as long as the page and has no files, so getCachePathAsync is null; prefetch ignores headers; source.cacheKey is ignored, so an image written with writeToCacheAsync does not render from its key; onProgress for a remote image served without CORS headers; animated WebP and APNG decode through the same player but have no paired proof"
6177
6179
  }
6178
6180
  ]
6179
6181
  },
@@ -6560,10 +6562,10 @@ var init_registry = __esm({
6560
6562
  versions: [
6561
6563
  {
6562
6564
  range: ">=17.0.0",
6563
- coverage: 0.6,
6565
+ coverage: 0.8,
6564
6566
  note: "the published SDK 57 package runs unchanged against one ExpoLocation native seam backed by host browser geolocation and the shared app-scoped permission registry",
6565
- working: "upstream enums, validation, subscriber bookkeeping and permission hooks; getProviderStatusAsync, getCurrentPositionAsync, filtered getLastKnownPositionAsync, watchPositionAsync with native watch-id event routing and removal, foreground/background permission getters and requests, hasServicesEnabledAsync, unavailable motion-permission state, and isBackgroundLocationAvailableAsync=false",
6566
- missing: "device-orientation heading readings, an app-selected geocoder, motion activity readings, background location task registration and delivery, geofencing, and Android network-provider enablement"
6567
+ working: "upstream enums, validation, subscriber bookkeeping and permission hooks; native exception codes and reasons; two-key getProviderStatusAsync with backgroundModeEnabled always true like native; start* gated on the location UIBackgroundModes from the resolved expo config (infoPlist modes or the expo-location plugin flag), unavailable when undeclared; getCurrentPositionAsync, foreground-gated filtered getLastKnownPositionAsync, watchPositionAsync with native watch-id event routing and removal; foreground/background permission getters and requests; hasServicesEnabledAsync; isBackgroundLocationAvailableAsync=true; simulator-exact motion-activity unavailability; background location task registration and { locations } delivery through the task-manager seam with foreground no-deferral and distanceInterval filtering; startGeofencingAsync rejecting with ERR_GEOFENCING_UNAVAILABLE after the background permission check, as the reference iOS 27 simulator does (CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) is false there)",
6568
+ missing: "device-orientation heading readings, an app-selected geocoder, Android network-provider enablement, deferred background delivery (deferredUpdatesInterval and deferredUpdatesDistance batching while the app is backgrounded), and task error delivery when the location manager fails"
6567
6569
  }
6568
6570
  ]
6569
6571
  },
@@ -6573,10 +6575,10 @@ var init_registry = __esm({
6573
6575
  versions: [
6574
6576
  {
6575
6577
  range: ">=0.20.0",
6576
- coverage: 0.93,
6577
- note: 'real local notification delivery via browser Notification + sootsim in-engine overlay; full upstream API surface present; matches iOS-simulator behavior where APNs registration silently fails. per docs/compat-scoring.md "score it high only when the full public API surface is present and returns the same unavailable/clean state an app should expect in development."',
6578
- working: "getPermissionsAsync/requestPermissionsAsync through the shared system permission registry, scheduleNotificationAsync (fires real browser Notification when host capability allows, plus the in-engine banner), cancelScheduledNotificationAsync, cancelAllScheduledNotificationsAsync, getAllScheduledNotificationsAsync, getPresentedNotificationsAsync, dismissNotificationAsync, dismissAllNotificationsAsync, setBadgeCountAsync (navigator.setAppBadge if available), getBadgeCountAsync, addNotificationReceivedListener/ResponseReceivedListener/DroppedListener/PushTokenListener, getLastNotificationResponseAsync + sync, clearLastNotificationResponseAsync + sync, useLastNotificationResponse hook, getNextTriggerDateAsync, setNotificationHandler, presentNotificationAsync, channel + channel-group CRUD, category CRUD, AndroidImportance/AndroidNotificationVisibility/IosAuthorizationStatus/PermissionStatus enums",
6579
- missing: "real push token registration (getDevicePushTokenAsync returns a placeholder UUID \u2014 there is no FCM/APNs provisioning, service-worker push subscription, or backend-uploadable token); background task delivery (registerTaskAsync/unregisterTaskAsync/isTaskRegisteredAsync are noops \u2014 service-worker push wiring is not implemented); scheduled-notification persistence across reload (setTimeout-only \u2014 page refresh drops the queue); subscribeToTopicAsync/unsubscribeFromTopicAsync FCM topic binding; setAutoServerRegistrationEnabledAsync"
6578
+ coverage: 0.95,
6579
+ note: 'real local notification delivery via browser Notification + sootsim in-engine overlay, plus push notifications from Detox sendUserNotification and launchApp({ userNotification }) dispatched the way DetoxUserNotificationDispatcher dispatches them (willPresent for an active app, the tap response otherwise and at launch, held until the emitter exists); full upstream API surface present; matches iOS-simulator behavior where APNs registration silently fails. per docs/compat-scoring.md "score it high only when the full public API surface is present and returns the same unavailable/clean state an app should expect in development."',
6580
+ working: "getPermissionsAsync/requestPermissionsAsync through the shared system permission registry, scheduleNotificationAsync (fires real browser Notification when host capability allows, plus the in-engine banner), cancelScheduledNotificationAsync, cancelAllScheduledNotificationsAsync, getAllScheduledNotificationsAsync, getPresentedNotificationsAsync, dismissNotificationAsync, dismissAllNotificationsAsync, setBadgeCountAsync (navigator.setAppBadge if available), getBadgeCountAsync, addNotificationReceivedListener/ResponseReceivedListener/DroppedListener/PushTokenListener, getLastNotificationResponseAsync + sync, clearLastNotificationResponseAsync + sync, useLastNotificationResponse hook, getNextTriggerDateAsync, setNotificationHandler, presentNotificationAsync, push notifications dispatched like Detox (onDidReceiveNotification with trigger { type: push, payload } and data from the user info body, tap responses with action identifier and user text, the launch tap as the last response), the native handler timeout presenting with the default options, channel + channel-group CRUD, category CRUD, AndroidImportance/AndroidNotificationVisibility/IosAuthorizationStatus/PermissionStatus enums",
6581
+ missing: "real push token registration (getDevicePushTokenAsync returns a placeholder UUID \u2014 there is no FCM/APNs provisioning, service-worker push subscription, or backend-uploadable token); background task delivery (registerTaskAsync/unregisterTaskAsync/isTaskRegisteredAsync are noops \u2014 service-worker push wiring is not implemented); scheduled-notification persistence across reload (setTimeout-only \u2014 page refresh drops the queue); subscribeToTopicAsync/unsubscribeFromTopicAsync FCM topic binding; setAutoServerRegistrationEnabledAsync; push notifications arriving from APNs or simctl push (only the Detox dispatch path exists, so there is no system banner for a push to a backgrounded app and no didReceiveRemoteNotification background delivery)"
6580
6582
  }
6581
6583
  ]
6582
6584
  },
@@ -7416,8 +7418,8 @@ var init_registry = __esm({
7416
7418
  range: ">=2.11.4 <3.0.0",
7417
7419
  coverage: 0.76,
7418
7420
  note: "the real upstream PickerIOS wrapper runs from the guest bundle and resolves codegenNativeComponent('RNCPicker') to the engine UIPickerView wheel. upstream owns Picker.Item mapping, controlled selection, and callback fan-out.",
7419
- working: "real upstream Picker, PickerIOS, Picker.Item label/value, selectedValue, onValueChange(value, index), itemStyle color/fontSize, MODE_DIALOG and MODE_DROPDOWN constants",
7420
- missing: "Picker.Item color/testID, themeVariant, textAlign, itemStyle fontFamily/fontWeight/fontStyle, multi-line numberOfLines behavior, controlled native selection restore proof, deprecated onChange proof, android dialog/dropdown presentation (mode/prompt/dropdownIconColor render as the iOS wheel), focus()/blur() imperative handle, onFocus/onBlur; selectionColor is ignored by the exact 2.11.4 Fabric iOS host and Peach matches that behavior"
7421
+ working: "real upstream Picker, PickerIOS, Picker.Item label/value, selectedValue, onValueChange(value, index), itemStyle color/fontSize/fontFamily/fontWeight/fontStyle (fontSize Int32-truncated, 0 keeps the 21pt system font), per-item textColor over the itemStyle color, Picker.Item testID row labels, themeVariant dark/light interface-style override, numberOfLines label and row height, MODE_DIALOG and MODE_DROPDOWN constants",
7422
+ missing: "textAlign (dead prop on the native host: row alignment reads themeVariant), multi-line numberOfLines proof; the dark/light default label color, a loaded fontFamily, themeVariant-driven label alignment, and fontSize truncation follow RNCPicker but have no paired proof, controlled native selection restore proof, deprecated onChange proof, android dialog/dropdown presentation (mode/prompt/dropdownIconColor render as the iOS wheel), focus()/blur() imperative handle, onFocus/onBlur; selectionColor is ignored by the exact 2.11.4 Fabric iOS host and Peach matches that behavior"
7421
7423
  }
7422
7424
  ]
7423
7425
  },
@@ -7572,10 +7574,10 @@ var init_registry = __esm({
7572
7574
  versions: [
7573
7575
  {
7574
7576
  range: ">=12.0.0",
7575
- coverage: 0.85,
7576
- note: "upstream expo-sharing JS runs unchanged; package-local wrapper only registers the ExpoSharing native module. an empty incoming-share inbox is the state a browser preview should report, so the share-into surface scores as present.",
7577
- working: "isAvailableAsync (real browser check), shareAsync calls navigator.share({url,title}) when available and safely noops otherwise, getSharedPayloads/getResolvedSharedPayloadsAsync/clearSharedPayloads report an empty incoming-share inbox",
7578
- missing: "native share sheet UI, native file readability validation, incoming share extension app-group storage. the share-into functions also diverge on a misconfigured app: iOS throws FailedToResolveAppGroupIdException from all three when the app declares no ExpoShareIntoAppGroupId (expo-sharing/ios/SharingModule.swift:4-11) where sootsim returns the empty inbox, so an app that redboxes on device previews cleanly"
7577
+ coverage: 0.9,
7578
+ note: "upstream expo-sharing JS runs unchanged; package-local wrapper only registers the ExpoSharing native module. shareAsync presents the same canvas share sheet core RN Share uses; a declared app group id reads the empty inbox a browser preview should report.",
7579
+ working: "isAvailableAsync through upstream js, shareAsync presenting the shared canvas sheet and resolving on dismissal whatever the outcome, dialogTitle accepted and not shown as on iOS, ERR_FILE_PERMISSION for unreadable files, ERR_MISSING_CURRENT_VIEW_CONTROLLER without a sheet host, ERR_FAILED_TO_RESOLVE_APP_GROUP_ID from all three share-into entries when the app declares no ExpoShareIntoAppGroupId, empty incoming-share inbox when it does",
7580
+ missing: "incoming share extension app-group storage (no extension writes into the group from a preview, so a declared key always reads empty); UTI/mimeType file restaging and iPad popover anchoring have no visible effect on the iPhone-idiom sheet"
7579
7581
  }
7580
7582
  ]
7581
7583
  },
@@ -8003,10 +8005,10 @@ var init_registry = __esm({
8003
8005
  versions: [
8004
8006
  {
8005
8007
  range: ">=4.0.0",
8006
- coverage: 0.9,
8008
+ coverage: 0.95,
8007
8009
  note: "the published two-function package runs unchanged; its native seam applies processed colors to the tenant-owned app root that Peach synchronizes to the CanvasKit compositor",
8008
- working: "setBackgroundColorAsync and getBackgroundColorAsync round-trip the visible app-surface background, including React Native processed-color conversion; null restores the neutral app surface",
8009
- missing: "the value does not persist across simulator restarts, and null restores Peach\u2019s neutral surface rather than the exact platform default color"
8010
+ working: "setBackgroundColorAsync and getBackgroundColorAsync round-trip the visible app-surface background, including React Native processed-color conversion; set colors persist across restarts through tenant storage and are reapplied at launch (falling back to the app-config background color the config plugin writes to Info.plist); null snapshots the current interface style (black in dark, white otherwise) and clears the stored color; a fresh launch with no stored or app-config color reads that snapshot; the getter returns uppercase #RRGGBB",
8011
+ missing: "persistence across restarts and the app-config fallback have no paired relaunch proof"
8010
8012
  }
8011
8013
  ]
8012
8014
  },
@@ -9986,9 +9988,10 @@ var init_registry = __esm({
9986
9988
  versions: [
9987
9989
  {
9988
9990
  range: ">=2.0.0",
9989
- coverage: 0,
9990
- note: "the reference iOS simulator performs this package's behavior. Peach currently provides only this unsupported shape: TrueSheet component passes children through; present/dismiss/resize/scrollToTop ref methods accepted as noops. imports and API shape receive no behavior credit",
9991
- missing: "no native bottom-sheet UI. recommended swap on web: a regular sootsim modal \u2014 sootsim already simulates iOS sheet semantics natively"
9991
+ coverage: 0.6,
9992
+ note: "the published TrueSheet JS runs unchanged; TrueSheetView presents through the engine's react-native-screens form sheet. paired native and Peach runtime proof covers present at a half detent, resize to the large detent, dismiss, and the mount, willPresent, didPresent, detentChange, willDismiss, and didDismiss events.",
9993
+ working: "present, resize, and dismiss, by ref or by name; fractional detents, initialDetentIndex, maxContentHeight, and maxContentWidth; cornerRadius, backgroundColor, grabber, dimmed, dimmedDetentIndex, and dismissible; header; onMount, onWillPresent, onDidPresent, onDetentChange, onWillDismiss, onDidDismiss, and the focus and blur events",
9994
+ missing: "onPositionChange fires only when a detent settles, and onDragBegin, onDragChange, and onDragEnd never fire; the footer is not pinned to the bottom edge; a sheet mounted with initialDetentIndex presents without onWillFocus and onWillPresent; the auto detent reports the large card position and index -1 while the sheet draws fitted to its content; the peek detent; insetAdjustment never also drops the bottom inset on iPad; scrollable, backgroundBlur, blurOptions, detached, anchor, draggable, and grabberOptions"
9992
9995
  }
9993
9996
  ]
9994
9997
  },
@@ -10046,9 +10049,10 @@ var init_registry = __esm({
10046
10049
  versions: [
10047
10050
  {
10048
10051
  range: ">=1.0.0",
10049
- coverage: 0,
10050
- note: "the reference iOS simulator performs this package's behavior. Peach currently provides only this unsupported shape: ContextMenuView passes children through; long-press shows nothing instead of a native UIContextMenu. apps still render their primary content correctly. imports and API shape receive no behavior credit",
10051
- missing: "no native context menu \u2014 recommended swap on web: a tap-on-icon menu via Pressable + Modal"
10052
+ coverage: 0.75,
10053
+ note: "the published ContextMenu JS runs unchanged; the ContextMenu native view follows the installed ContextMenuView.m over the engine's UIMenu presenter. paired native and Peach runtime proof covers a long-press menu with a title, subtitles, system icons, a submenu, and destructive, selected, and disabled actions, the onPress index, indexPath, and name, onCancel after a backdrop dismiss, and a dropdownMenuMode tap menu.",
10054
+ working: "long-press context menu with a title; actions with subtitle, systemIcon, iconColor, titleColor, destructive, selected, disabled, and nested actions as submenus; onPress with index, indexPath, and name; onCancel with an empty event only when the menu closes without an action; dropdownMenuMode; disabled",
10055
+ missing: "the custom preview and onPreviewPress (the lifted view is the content itself); asset `icon` images; inlineChildren; previewBackgroundColor, the border radius props that clip the lifted preview, and disableShadow; fontName"
10052
10056
  }
10053
10057
  ]
10054
10058
  },
@@ -10220,9 +10224,10 @@ var init_registry = __esm({
10220
10224
  versions: [
10221
10225
  {
10222
10226
  range: ">=1.0.0",
10223
- coverage: 0,
10224
- note: "the reference iOS simulator renders the LiquidGlassView effect. Peach passes children through without connecting the view to its blur primitives, so import safety receives no behavior credit",
10225
- missing: "no native iOS visual effect; consumers can substitute expo-blur which we already polyfill"
10227
+ coverage: 0.8,
10228
+ note: "the published LiquidGlassView and LiquidGlassContainerView JS runs unchanged; the native views draw through the engine's liquid glass material. paired native and Peach runtime proof covers clear, tinted regular, dark regular, and none effects over a striped backdrop, and isLiquidGlassSupported.",
10229
+ working: "LiquidGlassView effect clear, regular, and none, tintColor, colorScheme, interactive, and its border radius; LiquidGlassContainerView layout; isLiquidGlassSupported",
10230
+ missing: "LiquidGlassContainerView does not merge neighbouring glass, so spacing does nothing; an effect change does not animate; isLiquidGlassSupported is always true, including when the app sets UIDesignRequiresCompatibility"
10226
10231
  }
10227
10232
  ]
10228
10233
  },
@@ -11504,9 +11509,10 @@ var init_registry = __esm({
11504
11509
  versions: [
11505
11510
  {
11506
11511
  range: ">=1.0.0",
11507
- coverage: 0,
11508
- note: "the reference iOS simulator performs this package's behavior. Peach currently provides only this unsupported shape: getCurrentConfigurations={}; reload* accepted. imports and API shape receive no behavior credit",
11509
- missing: "no WidgetKit on iOS / web"
11512
+ coverage: 0.9,
11513
+ note: "Widgetkit native module: App Group UserDefaults getItem/setItem, sharing suites with react-native-shared-group-preferences; timeline reloads accepted",
11514
+ working: "getItem, setItem, reloadAllTimelines, reloadTimelines",
11515
+ missing: "no home screen widgets render in Peach, so reloads refresh nothing"
11510
11516
  }
11511
11517
  ]
11512
11518
  },
@@ -18717,7 +18723,7 @@ function formatDetailedScanLines(result) {
18717
18723
  );
18718
18724
  }
18719
18725
  lines.push(
18720
- "SootSim may still work. The simulator will report a concrete runtime error if it finds a gap."
18726
+ "Peach may still work. The simulator will report a concrete runtime error if it finds a gap."
18721
18727
  );
18722
18728
  }
18723
18729
  return lines;
@@ -21108,10 +21114,12 @@ function createRuntimeDelivery(product) {
21108
21114
  );
21109
21115
  import_node_fs6.default.rmSync(tmpDir, { recursive: true, force: true });
21110
21116
  try {
21111
- process.stderr.write(`${product.id}: downloading runtime ${version}\u2026
21117
+ if (!opts.quiet)
21118
+ process.stderr.write(`${product.id}: downloading runtime ${version}\u2026
21112
21119
  `);
21113
21120
  await downloadToFile(tarUrl, runtimeArchivePath);
21114
- process.stderr.write(`${product.id}: extracting runtime ${version}\u2026
21121
+ if (!opts.quiet)
21122
+ process.stderr.write(`${product.id}: extracting runtime ${version}\u2026
21115
21123
  `);
21116
21124
  const actualSha = await sha256File(runtimeArchivePath);
21117
21125
  if (actualSha !== entry.sha256) {
@@ -21453,6 +21461,41 @@ function resolveRuntimeRootForHostname(hostname) {
21453
21461
  const version = runtimeVersionFromHostname(hostname);
21454
21462
  return version ? resolveInstalledRuntimeRoot(version) : null;
21455
21463
  }
21464
+ function sendFileWithRanges(req, res, filePath, total, headers) {
21465
+ const head = (req?.method || "GET").toUpperCase() === "HEAD";
21466
+ const range = /^bytes=(\d*)-(\d*)$/.exec(req?.headers?.range || "");
21467
+ const invalid = range && range[1] && range[2] && Number(range[2]) < Number(range[1]);
21468
+ if (range && (range[1] || range[2]) && !invalid) {
21469
+ const [, startRaw, endRaw] = range;
21470
+ const start = startRaw ? Number(startRaw) : Math.max(0, total - Number(endRaw));
21471
+ const end = startRaw && endRaw ? Math.min(Number(endRaw), total - 1) : total - 1;
21472
+ if (start > end || start >= total) {
21473
+ res.writeHead(416, { ...headers, "Content-Range": `bytes */${total}` });
21474
+ res.end();
21475
+ return;
21476
+ }
21477
+ res.writeHead(206, {
21478
+ ...headers,
21479
+ "Content-Range": `bytes ${start}-${end}/${total}`,
21480
+ "Accept-Ranges": "bytes",
21481
+ "Content-Length": String(end - start + 1)
21482
+ });
21483
+ if (head) res.end();
21484
+ else streamFile(res, import_fs.default.createReadStream(filePath, { start, end }));
21485
+ return;
21486
+ }
21487
+ res.writeHead(200, {
21488
+ ...headers,
21489
+ "Accept-Ranges": "bytes",
21490
+ "Content-Length": String(total)
21491
+ });
21492
+ if (head || total === 0) res.end();
21493
+ else streamFile(res, import_fs.default.createReadStream(filePath, { start: 0, end: total - 1 }));
21494
+ }
21495
+ function streamFile(res, stream) {
21496
+ stream.on("error", (error) => res.destroy(error));
21497
+ stream.pipe(res);
21498
+ }
21456
21499
  var import_fs, import_path, import_url, _monorepoRootCache, RUNTIME_VERSION_HOSTNAME_SUFFIX;
21457
21500
  var init_runtime_assets = __esm({
21458
21501
  "src/runtime-assets.ts"() {
@@ -29799,42 +29842,10 @@ var init_bridge_host = __esm({
29799
29842
  return;
29800
29843
  }
29801
29844
  const contentType = HTTP_MIME_TYPES[import_path3.default.extname(filePath).toLowerCase()] || "application/octet-stream";
29802
- const total = stats.size;
29803
- const range = /^bytes=(\d*)-(\d*)$/.exec(req.headers.range || "");
29804
- if (range) {
29805
- const [, startRaw, endRaw] = range;
29806
- const start = startRaw ? Number(startRaw) : 0;
29807
- const end = endRaw ? Math.min(Number(endRaw), total - 1) : total - 1;
29808
- if (!Number.isFinite(start) || start > end || start >= total) {
29809
- res.writeHead(416, { ...cors, "Content-Range": `bytes */${total}` });
29810
- res.end();
29811
- return;
29812
- }
29813
- res.writeHead(206, {
29814
- ...cors,
29815
- "Content-Type": contentType,
29816
- "Content-Range": `bytes ${start}-${end}/${total}`,
29817
- "Accept-Ranges": "bytes",
29818
- "Content-Length": String(end - start + 1)
29819
- });
29820
- if (method === "HEAD") {
29821
- res.end();
29822
- return;
29823
- }
29824
- import_fs3.default.createReadStream(filePath, { start, end }).pipe(res);
29825
- return;
29826
- }
29827
- res.writeHead(200, {
29845
+ sendFileWithRanges(req, res, filePath, stats.size, {
29828
29846
  ...cors,
29829
- "Content-Type": contentType,
29830
- "Accept-Ranges": "bytes",
29831
- "Content-Length": String(total)
29847
+ "Content-Type": contentType
29832
29848
  });
29833
- if (method === "HEAD") {
29834
- res.end();
29835
- return;
29836
- }
29837
- import_fs3.default.createReadStream(filePath).pipe(res);
29838
29849
  }
29839
29850
  handleServerScan(res) {
29840
29851
  const sendJson = (body) => {
@@ -31357,7 +31368,7 @@ function resolveDefaultDevBridgeLockfile() {
31357
31368
  ) ?? sameCheckout[0];
31358
31369
  }
31359
31370
  }
31360
- return lockfiles.find((lockfile) => lockfile.bridgePort === DEFAULT_SOOTSIM_BRIDGE_PORT) ?? lockfiles[0];
31371
+ return lockfiles.find((lockfile) => lockfile.bridgePort === DEFAULT_SOOTSIM_BRIDGE_PORT) ?? null;
31361
31372
  }
31362
31373
  function resolveBridgeWorld() {
31363
31374
  const homeDir = rnxHomeDir();
@@ -31923,7 +31934,7 @@ var init_app_url = __esm({
31923
31934
  // src/dev-bundle-resolution.ts
31924
31935
  function buildUnresolvedTargetError(targetLabel) {
31925
31936
  return new Error(
31926
- `could not resolve a native bundle for ${targetLabel}. pass an explicit bundle URL or open Connect and choose the app there.`
31937
+ `could not resolve a native bundle for ${targetLabel}. start the app's dev server (for example npx expo start), pass an explicit bundle URL, or open Connect and choose the app there.`
31927
31938
  );
31928
31939
  }
31929
31940
  function inferManifestFramework(launchUrl2, sdkVersion) {
@@ -35606,8 +35617,7 @@ async function runOpenCommand(args, opts = {}) {
35606
35617
  deviceArg,
35607
35618
  false,
35608
35619
  void 0,
35609
- postureArg === "open" ? "open" : "closed",
35610
- true
35620
+ postureArg === "open" ? "open" : "closed"
35611
35621
  ) : void 0;
35612
35622
  const osVersionArg = args.find((_, i) => args[i - 1] === "--os-version");
35613
35623
  if (osVersionArg && !/^\d+\.\d+$/.test(osVersionArg)) {
@@ -37305,11 +37315,9 @@ function normalizeFlowScreenshotSpec(step) {
37305
37315
  function parsePoint(value, bounds) {
37306
37316
  const [rawX, rawY] = value.split(",").map((part) => part.trim());
37307
37317
  const parseCoord = (coord, max) => coord.endsWith("%") ? Number.parseFloat(coord) / 100 * max : Number.parseFloat(coord);
37308
- const originX = bounds?.x ?? 0;
37309
- const originY = bounds?.y ?? 0;
37310
37318
  return {
37311
- x: originX + parseCoord(rawX, bounds?.width ?? SCREEN_W),
37312
- y: originY + parseCoord(rawY, bounds?.height ?? SCREEN_H)
37319
+ x: bounds.x + parseCoord(rawX, bounds.width),
37320
+ y: bounds.y + parseCoord(rawY, bounds.height)
37313
37321
  };
37314
37322
  }
37315
37323
  function normalizeBareStringStep(verb) {
@@ -37345,7 +37353,7 @@ function parseFlowSteps(content) {
37345
37353
  const parsed = import_yaml.default.parse(candidate);
37346
37354
  return Array.isArray(parsed) ? parsed : [];
37347
37355
  }
37348
- var fs19, path19, import_settings3, import_yaml, import_proof_frame, DEFAULT_TIMEOUT, ASSERTION_LOOKUP_MS, FLOW_TIMEOUT_SCALE, scaledTimeout, SCREEN_W, SCREEN_H, MAESTRO_SELECTOR_KEYS, SELECTOR_FILTER_KEYS, UNSUPPORTED_SELECTOR_KEYS, MAESTRO_PERMISSION_KINDS, MAESTRO_PERMISSION_STATUS, SootSimBridgeFlowRunner;
37356
+ var fs19, path19, import_settings3, import_yaml, 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;
37349
37357
  var init_bridge_flow_runner = __esm({
37350
37358
  "cli/bridge-flow-runner.ts"() {
37351
37359
  "use strict";
@@ -37368,8 +37376,6 @@ var init_bridge_flow_runner = __esm({
37368
37376
  ASSERTION_LOOKUP_MS = 17e3;
37369
37377
  FLOW_TIMEOUT_SCALE = flowTimeoutScale();
37370
37378
  scaledTimeout = (ms) => Math.round(ms * FLOW_TIMEOUT_SCALE);
37371
- SCREEN_W = 393;
37372
- SCREEN_H = 852;
37373
37379
  MAESTRO_SELECTOR_KEYS = /* @__PURE__ */ new Set([
37374
37380
  "text",
37375
37381
  "id",
@@ -37494,6 +37500,16 @@ var init_bridge_flow_runner = __esm({
37494
37500
  args
37495
37501
  });
37496
37502
  }
37503
+ async screenBounds() {
37504
+ const snapshot = await this.callTest(
37505
+ "getSurfaceMetricsSnapshot"
37506
+ );
37507
+ const window2 = snapshot?.window;
37508
+ if (!window2 || typeof window2 !== "object" || !("width" in window2) || !("height" in window2) || typeof window2.width !== "number" || typeof window2.height !== "number" || !Number.isFinite(window2.width) || !Number.isFinite(window2.height) || window2.width <= 0 || window2.height <= 0) {
37509
+ throw new Error("live screen dimensions are unavailable");
37510
+ }
37511
+ return { x: 0, y: 0, width: window2.width, height: window2.height };
37512
+ }
37497
37513
  // ordered input batch executed inside the page in one round-trip; the
37498
37514
  // engine emits agent-cursor actions for every step natively.
37499
37515
  async perform(steps) {
@@ -37730,6 +37746,7 @@ var init_bridge_flow_runner = __esm({
37730
37746
  // (match scoped to a container). a nested selector resolves the same way,
37731
37747
  // and a relative filter holds when ANY match of its anchor satisfies it.
37732
37748
  async findElementScoped(opts) {
37749
+ const screen = await this.screenBounds();
37733
37750
  const needsEveryNode = !opts.id || opts.childOf != null || SELECTOR_FILTER_KEYS.some((key) => opts[key] != null);
37734
37751
  return this.evaluate(`(async () => {
37735
37752
  const test = window.__sootsimTest
@@ -37833,7 +37850,7 @@ var init_bridge_flow_runner = __esm({
37833
37850
  // paints nothing, and maestro's hierarchy never holds it.
37834
37851
  const onScreen = candidates.filter((n) => {
37835
37852
  const cy = n.absolutePosition.y + n.layout.height / 2
37836
- return cy > 0 && cy < ${SCREEN_H}
37853
+ return cy > 0 && cy < ${screen.height}
37837
37854
  })
37838
37855
  if (candidates.some(painted)) candidates = candidates.filter(painted)
37839
37856
  else if (onScreen.length > 0) candidates = onScreen
@@ -37998,7 +38015,7 @@ var init_bridge_flow_runner = __esm({
37998
38015
  async resolveInteractionPoint(opts) {
37999
38016
  if (opts.point && !opts.id && !opts.text && opts.index === void 0 && !opts.childOf) {
38000
38017
  return {
38001
- ...parsePoint(opts.point),
38018
+ ...parsePoint(opts.point, await this.screenBounds()),
38002
38019
  element: null,
38003
38020
  target: void 0
38004
38021
  };
@@ -38459,32 +38476,14 @@ var init_bridge_flow_runner = __esm({
38459
38476
  }
38460
38477
  return outputDir;
38461
38478
  }
38462
- async swipe(direction = "UP", duration = 300) {
38463
- const centerX = SCREEN_W / 2;
38464
- const centerY = SCREEN_H / 2;
38465
- const distance = 200;
38466
- let fromX = centerX;
38467
- let fromY = centerY;
38468
- let toX = centerX;
38469
- let toY = centerY;
38470
- switch (direction.toUpperCase()) {
38471
- case "UP":
38472
- fromY += distance;
38473
- toY -= distance;
38474
- break;
38475
- case "DOWN":
38476
- fromY -= distance;
38477
- toY += distance;
38478
- break;
38479
- case "LEFT":
38480
- fromX += distance;
38481
- toX -= distance;
38482
- break;
38483
- case "RIGHT":
38484
- fromX -= distance;
38485
- toX += distance;
38486
- break;
38487
- }
38479
+ // maestro's ios driver: a directional swipe spans the screen, from 90% to
38480
+ // 10% for UP, 20% to 90% for DOWN, and 10% to 90% across for LEFT/RIGHT,
38481
+ // over the command's 400ms default.
38482
+ async swipe(direction = "UP", duration = 400) {
38483
+ const screen = await this.screenBounds();
38484
+ const midX = screen.width / 2;
38485
+ const midY = screen.height / 2;
38486
+ const [fromX, fromY, toX, toY] = direction.toUpperCase() === "DOWN" ? [midX, screen.height * 0.2, midX, screen.height * 0.9] : direction.toUpperCase() === "LEFT" ? [screen.width * 0.9, midY, screen.width * 0.1, midY] : direction.toUpperCase() === "RIGHT" ? [screen.width * 0.1, midY, screen.width * 0.9, midY] : [midX, screen.height * 0.9, midX, screen.height * 0.1];
38488
38487
  const steps = Math.max(10, Math.round(duration / 16));
38489
38488
  await this.drag(fromX, fromY, toX, toY, steps, 16);
38490
38489
  }
@@ -38505,34 +38504,22 @@ var init_bridge_flow_runner = __esm({
38505
38504
  }
38506
38505
  await this.settleAfterInput();
38507
38506
  }
38508
- async swipeCoords(start, end, duration = 300) {
38509
- const from = parsePoint(start);
38510
- const to = parsePoint(end);
38507
+ async swipeCoords(start, end, duration = 400) {
38508
+ const screen = await this.screenBounds();
38509
+ const from = parsePoint(start, screen);
38510
+ const to = parsePoint(end, screen);
38511
38511
  const steps = Math.max(10, Math.round(duration / 16));
38512
38512
  await this.drag(from.x, from.y, to.x, to.y, steps, 16);
38513
38513
  }
38514
- async swipeFrom(from, direction = "UP", duration = 300) {
38514
+ // maestro's ios driver swipes from the element's center to the 10% or 90%
38515
+ // edge in the swipe's direction.
38516
+ async swipeFrom(from, direction = "UP", duration = 400) {
38515
38517
  const element = await this.findElement(from);
38516
38518
  if (!element) throw new Error(`swipeFrom: element not found: ${JSON.stringify(from)}`);
38517
38519
  const startX = element.absolutePosition.x + element.layout.width / 2;
38518
38520
  const startY = element.absolutePosition.y + element.layout.height / 2;
38519
- const distance = 180;
38520
- let endX = startX;
38521
- let endY = startY;
38522
- switch (direction.toUpperCase()) {
38523
- case "UP":
38524
- endY -= distance;
38525
- break;
38526
- case "DOWN":
38527
- endY += distance;
38528
- break;
38529
- case "LEFT":
38530
- endX -= distance;
38531
- break;
38532
- case "RIGHT":
38533
- endX += distance;
38534
- break;
38535
- }
38521
+ const screen = await this.screenBounds();
38522
+ const [endX, endY] = direction.toUpperCase() === "DOWN" ? [startX, screen.height * 0.9] : direction.toUpperCase() === "LEFT" ? [screen.width * 0.1, startY] : direction.toUpperCase() === "RIGHT" ? [screen.width * 0.9, startY] : [startX, screen.height * 0.1];
38536
38523
  const steps = Math.max(10, Math.round(duration / 16));
38537
38524
  await this.drag(startX, startY, endX, endY, steps, 16);
38538
38525
  }
@@ -38617,16 +38604,17 @@ var init_bridge_flow_runner = __esm({
38617
38604
  );
38618
38605
  const deadline = Date.now() + (opts.timeout || 2e4);
38619
38606
  const direction = opts.direction?.toUpperCase() || "DOWN";
38620
- const centerX = SCREEN_W / 2;
38621
- const centerY = SCREEN_H / 2;
38622
- const swipeEnd = direction === "UP" ? { x: centerX, y: SCREEN_H * 0.9 } : direction === "LEFT" ? { x: SCREEN_W * 0.9, y: centerY } : direction === "RIGHT" ? { x: SCREEN_W * 0.1, y: centerY } : { x: centerX, y: SCREEN_H * 0.1 };
38623
38607
  while (Date.now() < deadline) {
38608
+ const screen = await this.screenBounds();
38609
+ const centerX = screen.width / 2;
38610
+ const centerY = screen.height / 2;
38611
+ const swipeEnd = direction === "UP" ? { x: centerX, y: screen.height * 0.9 } : direction === "LEFT" ? { x: screen.width * 0.9, y: centerY } : direction === "RIGHT" ? { x: screen.width * 0.1, y: centerY } : { x: centerX, y: screen.height * 0.1 };
38624
38612
  const element = await this.findElement(target);
38625
38613
  if (element) {
38626
38614
  const { x, y } = element.absolutePosition;
38627
38615
  const { width, height } = element.layout;
38628
- const shownX = Math.max(0, Math.min(x + width, SCREEN_W) - Math.max(x, 0));
38629
- const shownY = Math.max(0, Math.min(y + height, SCREEN_H) - Math.max(y, 0));
38616
+ const shownX = Math.max(0, Math.min(x + width, screen.width) - Math.max(x, 0));
38617
+ const shownY = Math.max(0, Math.min(y + height, screen.height) - Math.max(y, 0));
38630
38618
  const share = width * height > 0 ? shownX * shownY / (width * height) : 0;
38631
38619
  if (share >= required) {
38632
38620
  if (opts.centerElement) {
@@ -39210,7 +39198,10 @@ var init_bridge_flow_runner = __esm({
39210
39198
  await this.swipe(step.swipe.direction, step.swipe.duration);
39211
39199
  }
39212
39200
  } else if (step.scroll) {
39213
- await this.swipe(step.scroll.direction === "DOWN" ? "UP" : "DOWN");
39201
+ const screen = await this.screenBounds();
39202
+ const midX = screen.width / 2;
39203
+ const toY = step.scroll.direction === "UP" ? screen.height * 0.9 : screen.height * 0.1;
39204
+ await this.drag(midX, screen.height / 2, midX, toY, Math.round(333 / 16), 16);
39214
39205
  } else if (step.scrollTo) {
39215
39206
  const target = typeof step.scrollTo.nodeId === "number" ? { nodeId: step.scrollTo.nodeId } : step.scrollTo.id;
39216
39207
  if (!target) throw new Error("scrollTo requires id or nodeId");
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.627 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.627 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-compile.ts
4
4
  import { execFile as execFile2 } from "node:child_process";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.627 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (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 __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.627 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-package.ts
4
4
  function isSwiftManifestFile(file) {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.627 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (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 __defProp = Object.defineProperty;