peachsim 0.1.939 → 0.1.1399

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 (396) hide show
  1. package/README.md +28 -22
  2. package/cli/app-catalog.ts +29 -14
  3. package/cli/app-fonts.ts +5 -3
  4. package/cli/bridge-flow-runner.ts +40 -29
  5. package/cli/cloud-client.ts +57 -21
  6. package/cli/cloud-test-tunnel.ts +12 -18
  7. package/cli/commands/agent.ts +1 -1
  8. package/cli/commands/app-fonts.ts +3 -3
  9. package/cli/commands/assert.ts +8 -8
  10. package/cli/commands/auth.ts +8 -3
  11. package/cli/commands/box/bundle-plane.ts +13 -1
  12. package/cli/commands/box.ts +16 -5
  13. package/cli/commands/compat.ts +1 -1
  14. package/cli/commands/config.ts +2 -2
  15. package/cli/commands/control.ts +125 -71
  16. package/cli/commands/cpu-profile.ts +45 -1
  17. package/cli/commands/daemon.ts +131 -33
  18. package/cli/commands/debug.ts +24 -17
  19. package/cli/commands/desktop.ts +7 -1
  20. package/cli/commands/detect.ts +101 -0
  21. package/cli/commands/detox.ts +6 -6
  22. package/cli/commands/device.ts +12 -12
  23. package/cli/commands/electron.ts +32 -14
  24. package/cli/commands/flow.ts +49 -41
  25. package/cli/commands/guest-host.ts +39 -1
  26. package/cli/commands/inspect/core.ts +14 -8
  27. package/cli/commands/inspect/list.ts +1 -1
  28. package/cli/commands/inspect/settle.ts +5 -3
  29. package/cli/commands/inspect/settling.ts +229 -44
  30. package/cli/commands/inspect/wait-idle.ts +3 -2
  31. package/cli/commands/inspect.ts +105 -93
  32. package/cli/commands/install-cli.ts +36 -15
  33. package/cli/commands/install-desktop.ts +68 -195
  34. package/cli/commands/kotlin.ts +6 -5
  35. package/cli/commands/login.ts +5 -5
  36. package/cli/commands/maestro-compare.ts +554 -0
  37. package/cli/commands/maestro-generate.ts +6 -3
  38. package/cli/commands/maestro.ts +22 -0
  39. package/cli/commands/mode.ts +2 -2
  40. package/cli/commands/no-bridge-hint.ts +6 -2
  41. package/cli/commands/platform.ts +35 -6
  42. package/cli/commands/preview.ts +6 -0
  43. package/cli/commands/project-report.ts +10 -12
  44. package/cli/commands/record.ts +37 -37
  45. package/cli/commands/runtime.ts +9 -9
  46. package/cli/commands/screenshot-layers.ts +4 -4
  47. package/cli/commands/screenshot-mode.ts +2 -2
  48. package/cli/commands/screenshot.ts +8 -8
  49. package/cli/commands/screenshots-capture.ts +2 -2
  50. package/cli/commands/serve.ts +9 -12
  51. package/cli/commands/setup.ts +29 -14
  52. package/cli/commands/shell-boolean-mode.ts +2 -2
  53. package/cli/commands/show-touches-mode.ts +2 -2
  54. package/cli/commands/skills.ts +11 -11
  55. package/cli/commands/swift.ts +29 -11
  56. package/cli/commands/telemetry.ts +2 -2
  57. package/cli/commands/test.ts +5 -0
  58. package/cli/commands/three-mode.ts +3 -3
  59. package/cli/commands/timeline.ts +4 -4
  60. package/cli/commands/upgrade.ts +2 -2
  61. package/cli/commands/upload.ts +15 -15
  62. package/cli/commands/what-happened.ts +2 -2
  63. package/cli/current-sim.ts +6 -3
  64. package/cli/desktop-companion.ts +55 -149
  65. package/cli/dev-build-freshness.ts +2 -2
  66. package/cli/drivers/desktop.ts +17 -9
  67. package/cli/drivers/electron.ts +1 -2
  68. package/cli/drivers/playwright-sim-host.ts +11 -2
  69. package/cli/drivers/playwright.ts +35 -2
  70. package/cli/drivers/types.ts +8 -0
  71. package/cli/maestro-compare.ts +670 -0
  72. package/cli/outbound-endpoints.ts +46 -8
  73. package/cli/recording-access.ts +6 -6
  74. package/cli/run-registry.ts +1 -1
  75. package/cli/runtime-compat.ts +5 -1
  76. package/cli/send-to-box.ts +3 -3
  77. package/cli/shell-init.ts +2 -1
  78. package/cli/ws-bridge.ts +9 -5
  79. package/detox/element-types.ts +1 -0
  80. package/detox/gestures.ts +3 -3
  81. package/detox/index.ts +67 -38
  82. package/detox/navigation-lifecycle.ts +9 -6
  83. package/detox/proof-frame.cjs +6 -4
  84. package/dist-lib/agent-daemon-client.cjs +1 -1
  85. package/dist-lib/agent-events.cjs +1 -1
  86. package/dist-lib/attached-projects.cjs +1 -1
  87. package/dist-lib/auth/shared-session.cjs +1 -1
  88. package/dist-lib/backend-origin.cjs +1 -1
  89. package/dist-lib/beta.cjs +1 -1
  90. package/dist-lib/beta.mjs +1 -1
  91. package/dist-lib/bridge-constants.cjs +1 -1
  92. package/dist-lib/bridge-contract-input.cjs +42 -17
  93. package/dist-lib/bridge-contract-input.mjs +41 -16
  94. package/dist-lib/bridge-contract.cjs +1 -4
  95. package/dist-lib/bridge-contract.mjs +1 -3
  96. package/dist-lib/bundle-channel.cjs +2918 -550
  97. package/dist-lib/capture-contract.cjs +65 -24
  98. package/dist-lib/capture-contract.mjs +63 -24
  99. package/dist-lib/cloud-contract.cjs +2 -2
  100. package/dist-lib/cloud-contract.mjs +2 -2
  101. package/dist-lib/cloud-test-args.cjs +1 -1
  102. package/dist-lib/cloud-test-args.mjs +1 -1
  103. package/dist-lib/cloud.cjs +3 -3
  104. package/dist-lib/cloud.mjs +3 -3
  105. package/dist-lib/config.cjs +15 -5
  106. package/dist-lib/config.mjs +154 -0
  107. package/dist-lib/detox/index.cjs +495 -139
  108. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  109. package/dist-lib/expo-swift-bundle.cjs +1168 -238
  110. package/dist-lib/expo-swift-metadata.cjs +34 -23
  111. package/dist-lib/expo-swift-metadata.mjs +32 -23
  112. package/dist-lib/home-paths.cjs +2 -2
  113. package/dist-lib/host/bridge-host.cjs +2938 -654
  114. package/dist-lib/host/fetch-proxy-handler.cjs +14 -1
  115. package/dist-lib/host/fetch-proxy-overrides.cjs +7 -1
  116. package/dist-lib/host/fetch-proxy-overrides.mjs +5 -1
  117. package/dist-lib/host/replacement-module-handler.cjs +2 -2
  118. package/dist-lib/host/websocket-proxy.cjs +1 -1
  119. package/dist-lib/index.cjs +999 -353
  120. package/dist-lib/jump-to-source-babel.cjs +1 -1
  121. package/dist-lib/jump-to-source-native.cjs +1 -1
  122. package/dist-lib/kotlin-host.cjs +332 -19
  123. package/dist-lib/menu.cjs +1 -1
  124. package/dist-lib/menu.mjs +1 -1
  125. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  126. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  127. package/dist-lib/metro-production-bundle.cjs +7 -7
  128. package/dist-lib/metro-production-bundle.mjs +5 -5
  129. package/dist-lib/metro.cjs +688 -250
  130. package/dist-lib/native-app-identity-project.cjs +2647 -0
  131. package/dist-lib/native-app-identity-project.mjs +1257 -0
  132. package/dist-lib/native-app-identity.cjs +325 -25
  133. package/dist-lib/native-app-identity.mjs +323 -25
  134. package/dist-lib/native-firebase-metadata.cjs +1 -1
  135. package/dist-lib/native-firebase-metadata.mjs +1 -1
  136. package/dist-lib/plist.cjs +1 -1
  137. package/dist-lib/plist.mjs +1 -1
  138. package/dist-lib/profiles.cjs +1 -1
  139. package/dist-lib/public-brand.cjs +1 -1
  140. package/dist-lib/react-native-host-modules.cjs +5 -1
  141. package/dist-lib/react-native-host-modules.mjs +5 -1
  142. package/dist-lib/scripts/dev-server-scanner.cjs +57 -32
  143. package/dist-lib/sdk.cjs +231 -51
  144. package/dist-lib/sdk.mjs +231 -51
  145. package/dist-lib/skills.cjs +4107 -1374
  146. package/dist-lib/swift-compile.cjs +290 -30
  147. package/dist-lib/swift-compile.mjs +290 -30
  148. package/dist-lib/swift-package.cjs +143 -12
  149. package/dist-lib/swift-package.mjs +137 -11
  150. package/dist-lib/swift-submit.cjs +47 -5
  151. package/dist-lib/swift-submit.mjs +42 -5
  152. package/dist-lib/swift-toolchain.cjs +7 -2
  153. package/dist-lib/swift.cjs +1 -1
  154. package/dist-lib/vite-swift.cjs +1392 -458
  155. package/dist-lib/vite.cjs +13 -17
  156. package/dist-lib/worklet-executable.cjs +248 -33
  157. package/dist-lib/worklet-executable.mjs +248 -33
  158. package/install.sh +10 -1
  159. package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Resources.kt +9 -9
  160. package/kotlin/AndroidViewSources/android/graphics/Canvas.kt +20 -0
  161. package/kotlin/AndroidViewSources/android/view/View.kt +211 -0
  162. package/kotlin/AndroidViewSources/android/widget/FrameLayout.kt +61 -0
  163. package/kotlin/AndroidViewSources/android/widget/LinearLayout.kt +49 -0
  164. package/kotlin/AppSources/android/view/Window.kt +36 -0
  165. package/kotlin/AppSources/android/view/WindowInsets.kt +9 -0
  166. package/kotlin/AppSources/android/view/WindowInsetsController.kt +5 -0
  167. package/kotlin/AppSources/android/window/OnBackInvokedDispatcher.kt +48 -0
  168. package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +20 -5
  169. package/kotlin/AppSources/androidx/compose/foundation/layout/WindowInsets.kt +15 -0
  170. package/kotlin/AppSources/androidx/compose/ui/platform/ComposeView.kt +2 -1
  171. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +42 -50
  172. package/kotlin/ComposeSources/PeachComposeHost.kt +200 -84
  173. package/kotlin/ExpoComposeSources/PeachAndroidView.kt +87 -0
  174. package/kotlin/ExpoComposeSources/PeachExpoViewMount.kt +24 -0
  175. package/kotlin/ExpoComposeSources/dev/peach/expo/PeachExpoCompose.kt +28 -0
  176. package/kotlin/ExpoComposeSources/expo/modules/kotlin/views/ExpoComposeView.kt +70 -0
  177. package/kotlin/ExpoSources/dev/peach/expo/PeachExpoRuntime.kt +513 -0
  178. package/kotlin/ExpoSources/dev/peach/expo/PeachExpoTypes.kt +173 -0
  179. package/kotlin/ExpoSources/expo/modules/kotlin/AppContext.kt +20 -0
  180. package/kotlin/ExpoSources/expo/modules/kotlin/Promise.kt +19 -0
  181. package/kotlin/ExpoSources/expo/modules/kotlin/classcomponent/ClassComponentBuilder.kt +198 -0
  182. package/kotlin/ExpoSources/expo/modules/kotlin/exception/CodedException.kt +126 -0
  183. package/kotlin/ExpoSources/expo/modules/kotlin/functions/Functions.kt +132 -0
  184. package/kotlin/ExpoSources/expo/modules/kotlin/jni/JavaScriptTypedArray.kt +81 -0
  185. package/kotlin/ExpoSources/expo/modules/kotlin/modules/Module.kt +98 -0
  186. package/kotlin/ExpoSources/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +236 -0
  187. package/kotlin/ExpoSources/expo/modules/kotlin/records/Record.kt +12 -0
  188. package/kotlin/ExpoSources/expo/modules/kotlin/runtime/Runtime.kt +9 -0
  189. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObject.kt +50 -0
  190. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt +57 -0
  191. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedRef.kt +19 -0
  192. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/ConcreteTypedArrays.kt +158 -0
  193. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/GenericTypedArray.kt +8 -0
  194. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArray.kt +29 -0
  195. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArrayIterator.kt +9 -0
  196. package/kotlin/ExpoSources/expo/modules/kotlin/types/AnyType.kt +155 -0
  197. package/kotlin/ExpoSources/expo/modules/kotlin/types/Either.kt +91 -0
  198. package/kotlin/ExpoSources/expo/modules/kotlin/types/Enumerable.kt +6 -0
  199. package/kotlin/ExpoSources/expo/modules/kotlin/viewevent/ViewEvent.kt +54 -0
  200. package/kotlin/ExpoSources/expo/modules/kotlin/views/ExpoView.kt +23 -0
  201. package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +71 -0
  202. package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewProps.kt +35 -0
  203. package/kotlin/README.md +13 -3
  204. package/kotlin/Sources/android/os/Build.kt +17 -0
  205. package/kotlin/Sources/android/os/Handler.kt +35 -0
  206. package/kotlin/Sources/android/os/Looper.kt +17 -0
  207. package/kotlin/Sources/android/os/MessageQueue.kt +56 -0
  208. package/kotlin/Sources/android/os/SystemClock.kt +14 -0
  209. package/kotlin/Sources/android/util/Log.kt +64 -0
  210. package/kotlin/Sources/java/security/MessageDigest.kt +114 -0
  211. package/kotlin/Sources/kotlin/text/StringFormat.kt +9 -0
  212. package/kotlin/Sources/org/json/JSON.kt +44 -0
  213. package/kotlin/fixtures/counter/Counter.d.kt.ts +1 -0
  214. package/kotlin/fixtures/counter/Counter.kt +10 -3
  215. package/kotlin/fixtures/counter/CounterApp.native.tsx +1 -1
  216. package/kotlin/fixtures/counter/vite.config.ts +2 -2
  217. package/kotlin/fixtures/headless-counter/Headless.kt +7 -6
  218. package/kotlin/fixtures/import-animation/Animation.d.kt.ts +7 -0
  219. package/kotlin/fixtures/import-animation/Animation.kt +80 -0
  220. package/kotlin/fixtures/import-animation/ImportAnimation.native.tsx +13 -0
  221. package/kotlin/fixtures/import-animation/README.md +24 -0
  222. package/kotlin/fixtures/import-animation/app/_layout.tsx +4 -0
  223. package/kotlin/fixtures/import-animation/app/index+spa.tsx +1 -0
  224. package/kotlin/fixtures/import-animation/app/routes.d.ts +18 -0
  225. package/kotlin/fixtures/import-animation/package.json +23 -0
  226. package/kotlin/fixtures/import-animation/tsconfig.json +20 -0
  227. package/kotlin/fixtures/import-animation/vite.config.ts +20 -0
  228. package/kotlin/fixtures/import-editor/Editor.d.kt.ts +7 -0
  229. package/kotlin/fixtures/import-editor/Editor.kt +59 -0
  230. package/kotlin/fixtures/import-editor/ImportEditor.native.tsx +23 -0
  231. package/kotlin/fixtures/import-editor/README.md +27 -0
  232. package/kotlin/fixtures/import-editor/app/_layout.tsx +4 -0
  233. package/kotlin/fixtures/import-editor/app/index+spa.tsx +1 -0
  234. package/kotlin/fixtures/import-editor/package.json +23 -0
  235. package/kotlin/fixtures/import-editor/tsconfig.json +20 -0
  236. package/kotlin/fixtures/import-editor/vite.config.ts +20 -0
  237. package/kotlin/fixtures/import-effects/Effects.d.kt.ts +7 -0
  238. package/kotlin/fixtures/import-effects/Effects.kt +87 -0
  239. package/kotlin/fixtures/import-effects/ImportEffects.native.tsx +13 -0
  240. package/kotlin/fixtures/import-effects/README.md +19 -0
  241. package/kotlin/fixtures/import-effects/app/_layout.tsx +4 -0
  242. package/kotlin/fixtures/import-effects/app/index+spa.tsx +1 -0
  243. package/kotlin/fixtures/import-effects/package.json +23 -0
  244. package/kotlin/fixtures/import-effects/tsconfig.json +20 -0
  245. package/kotlin/fixtures/import-effects/vite.config.ts +20 -0
  246. package/kotlin/fixtures/import-layout/ImportLayout.native.tsx +13 -0
  247. package/kotlin/fixtures/import-layout/Layout.d.kt.ts +7 -0
  248. package/kotlin/fixtures/import-layout/Layout.kt +72 -0
  249. package/kotlin/fixtures/import-layout/README.md +24 -0
  250. package/kotlin/fixtures/import-layout/app/_layout.tsx +4 -0
  251. package/kotlin/fixtures/import-layout/app/index+spa.tsx +1 -0
  252. package/kotlin/fixtures/import-layout/package.json +23 -0
  253. package/kotlin/fixtures/import-layout/tsconfig.json +20 -0
  254. package/kotlin/fixtures/import-layout/vite.config.ts +20 -0
  255. package/kotlin/fixtures/import-navigation/ImportNavigation.native.tsx +18 -0
  256. package/kotlin/fixtures/import-navigation/Navigation.d.kt.ts +7 -0
  257. package/kotlin/fixtures/import-navigation/Navigation.kt +104 -0
  258. package/kotlin/fixtures/import-navigation/README.md +31 -0
  259. package/kotlin/fixtures/import-navigation/app/_layout.tsx +4 -0
  260. package/kotlin/fixtures/import-navigation/app/index+spa.tsx +1 -0
  261. package/kotlin/fixtures/import-navigation/package.json +23 -0
  262. package/kotlin/fixtures/import-navigation/tsconfig.json +20 -0
  263. package/kotlin/fixtures/import-navigation/vite.config.ts +20 -0
  264. package/kotlin/fixtures/import-overlays/ImportOverlays.native.tsx +29 -0
  265. package/kotlin/fixtures/import-overlays/Overlays.d.kt.ts +7 -0
  266. package/kotlin/fixtures/import-overlays/Overlays.kt +93 -0
  267. package/kotlin/fixtures/import-overlays/README.md +34 -0
  268. package/kotlin/fixtures/import-overlays/app/_layout.tsx +4 -0
  269. package/kotlin/fixtures/import-overlays/app/index+spa.tsx +1 -0
  270. package/kotlin/fixtures/import-overlays/package.json +23 -0
  271. package/kotlin/fixtures/import-overlays/tsconfig.json +20 -0
  272. package/kotlin/fixtures/import-overlays/vite.config.ts +20 -0
  273. package/kotlin/fixtures/import-scroll/ImportScroll.native.tsx +13 -0
  274. package/kotlin/fixtures/import-scroll/README.md +23 -0
  275. package/kotlin/fixtures/import-scroll/Scroll.d.kt.ts +7 -0
  276. package/kotlin/fixtures/import-scroll/Scroll.kt +91 -0
  277. package/kotlin/fixtures/import-scroll/app/_layout.tsx +4 -0
  278. package/kotlin/fixtures/import-scroll/app/index+spa.tsx +1 -0
  279. package/kotlin/fixtures/import-scroll/package.json +23 -0
  280. package/kotlin/fixtures/import-scroll/tsconfig.json +20 -0
  281. package/kotlin/fixtures/import-scroll/vite.config.ts +20 -0
  282. package/kotlin/fixtures/import-selection/ImportSelection.native.tsx +44 -0
  283. package/kotlin/fixtures/import-selection/README.md +32 -0
  284. package/kotlin/fixtures/import-selection/Selection.d.kt.ts +8 -0
  285. package/kotlin/fixtures/import-selection/Selection.kt +78 -0
  286. package/kotlin/fixtures/import-selection/app/_layout.tsx +4 -0
  287. package/kotlin/fixtures/import-selection/app/index+spa.tsx +1 -0
  288. package/kotlin/fixtures/import-selection/package.json +23 -0
  289. package/kotlin/fixtures/import-selection/tsconfig.json +20 -0
  290. package/kotlin/fixtures/import-selection/vite.config.ts +20 -0
  291. package/kotlin/fixtures/import-surfaces/ImportSurfaces.native.tsx +13 -0
  292. package/kotlin/fixtures/import-surfaces/README.md +22 -0
  293. package/kotlin/fixtures/import-surfaces/Surfaces.d.kt.ts +7 -0
  294. package/kotlin/fixtures/import-surfaces/Surfaces.kt +64 -0
  295. package/kotlin/fixtures/import-surfaces/app/_layout.tsx +4 -0
  296. package/kotlin/fixtures/import-surfaces/app/index+spa.tsx +1 -0
  297. package/kotlin/fixtures/import-surfaces/package.json +23 -0
  298. package/kotlin/fixtures/import-surfaces/tsconfig.json +20 -0
  299. package/kotlin/fixtures/import-surfaces/vite.config.ts +20 -0
  300. package/kotlin/fixtures/import-values/ImportValues.native.tsx +35 -0
  301. package/kotlin/fixtures/import-values/README.md +23 -0
  302. package/kotlin/fixtures/import-values/Values.d.kt.ts +11 -0
  303. package/kotlin/fixtures/import-values/Values.kt +66 -0
  304. package/kotlin/fixtures/import-values/app/_layout.tsx +4 -0
  305. package/kotlin/fixtures/import-values/app/index+spa.tsx +1 -0
  306. package/kotlin/fixtures/import-values/package.json +23 -0
  307. package/kotlin/fixtures/import-values/tsconfig.json +20 -0
  308. package/kotlin/fixtures/import-values/vite.config.ts +20 -0
  309. package/kotlin/fixtures/kotlin-app-variants/BarBehavior.kt +38 -0
  310. package/kotlin/fixtures/kotlin-app-variants/BarVisibility.kt +36 -0
  311. package/kotlin/fixtures/kotlin-app-variants/Ime.kt +3 -1
  312. package/kotlin/fixtures/kotlin-app-variants/TransientInsets.kt +50 -0
  313. package/kotlin/fork/NOTICE +6 -1
  314. package/kotlin/fork/README.md +15 -7
  315. package/kotlin/fork/build.sh +2 -0
  316. package/kotlin/fork/compose-worker-globals.patch +186 -4
  317. package/kotlin/fork/fork.json +2 -2
  318. package/kotlin/fork/skiko-worker-clock.patch +12 -0
  319. package/kotlin/toolchain.json +12 -12
  320. package/kotlin/tools/expo-types.main.kts +115 -0
  321. package/package.json +17 -23
  322. package/scripts/dev-server-scanner.ts +92 -66
  323. package/skills/contrast/SKILL.md +2 -2
  324. package/skills/peach-debug/SKILL.md +1 -1
  325. package/skills/peach-perf/SKILL.md +1 -1
  326. package/skills/peach-setup/SKILL.md +2 -1
  327. package/skills/peach-test/SKILL.md +8 -0
  328. package/src/assetCatalogContract.ts +43 -1
  329. package/src/bridge-contract-input.ts +46 -19
  330. package/src/bridge-contract.ts +0 -1
  331. package/src/bundle-channel.mjs +197 -84
  332. package/src/bundleGlobalFooter.ts +38 -0
  333. package/src/capture-contract.ts +107 -33
  334. package/src/cloud-contract.ts +5 -1
  335. package/src/cloud.ts +5 -2
  336. package/src/config.ts +22 -3
  337. package/src/configured-bundle-producer.ts +1 -1
  338. package/src/desktop-host-delivery.ts +95 -1
  339. package/src/expo-kotlin-bundle.ts +29 -0
  340. package/src/expo-kotlin-host.ts +201 -0
  341. package/src/expo-swift-host.ts +96 -76
  342. package/src/expoKotlinContract.ts +70 -0
  343. package/src/expoSwiftContract.ts +4 -25
  344. package/src/home-paths.ts +7 -4
  345. package/src/host/bridge-host.ts +23 -3
  346. package/src/host/fetch-proxy-handler.ts +13 -0
  347. package/src/host/fetch-proxy-overrides.ts +11 -0
  348. package/src/host/replacement-module-handler.ts +5 -2
  349. package/src/index.ts +3 -1
  350. package/src/kotlin-host.ts +463 -19
  351. package/src/metro-plugin.ts +56 -28
  352. package/src/metro-production-bundle.ts +2 -2
  353. package/src/native-app-identity-project.ts +190 -0
  354. package/src/native-app-identity.ts +398 -34
  355. package/src/native-firebase-config.ts +69 -15
  356. package/src/native-seam-manifest.ts +6 -0
  357. package/src/one-widget-discovery.ts +2 -1
  358. package/src/openstep-plist.ts +107 -0
  359. package/src/react-native-host-modules.ts +4 -0
  360. package/src/runtime-assets.ts +2 -2
  361. package/src/sim-client.ts +2 -2
  362. package/src/swift-binary-modules.ts +3 -1
  363. package/src/swift-compile.ts +286 -38
  364. package/src/swift-distribution-contract.ts +4 -0
  365. package/src/swift-distribution.lock.json +123 -12
  366. package/src/swift-distribution.ts +7 -3
  367. package/src/swift-host.ts +298 -83
  368. package/src/swift-package-manifest.ts +29 -2
  369. package/src/swift-package.ts +180 -10
  370. package/src/swift-packages.ts +4 -0
  371. package/src/swift-standin-emit.ts +4 -1
  372. package/src/swift-standins.ts +274 -15
  373. package/src/swift-submit.ts +62 -7
  374. package/src/swift-toolchain.ts +6 -1
  375. package/src/swiftContract.ts +126 -3
  376. package/src/vite-plugin-one.ts +13 -16
  377. package/src/vite-plugin-swift.ts +4 -5
  378. package/src/vite-plugin.ts +360 -144
  379. package/src/vite.d.ts +1 -1
  380. package/src/worker-system-chunks.ts +221 -0
  381. package/src/worklet-executable.ts +348 -38
  382. package/src/worklets-babel.ts +1 -1
  383. package/src/xcode-project.ts +243 -127
  384. package/dist-lib/agent-identity.cjs +0 -39
  385. package/dist-lib/agent-sessions.cjs +0 -760
  386. package/dist-lib/cli-constants.cjs +0 -32
  387. package/dist-lib/render-mode.cjs +0 -44
  388. package/kotlin/AppSources/android/view/View.kt +0 -74
  389. package/kotlin/AppSources/android/widget/FrameLayout.kt +0 -6
  390. package/src/render-mode.ts +0 -15
  391. /package/kotlin/{AppSources → AndroidViewSources}/android/content/Context.kt +0 -0
  392. /package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Configuration.kt +0 -0
  393. /package/kotlin/{AppSources → AndroidViewSources}/android/graphics/Color.kt +0 -0
  394. /package/kotlin/{AppSources → AndroidViewSources}/android/util/DisplayMetrics.kt +0 -0
  395. /package/kotlin/{AppSources → AndroidViewSources}/dev/peach/app/JavaFormat.kt +0 -0
  396. /package/kotlin/{AppSources → Sources}/java/lang/Runnable.kt +0 -0
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.939 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! peach v0.1.1399 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -280,7 +280,6 @@ __export(dev_server_scanner_exports, {
280
280
  __shouldReuseScannerCacheEntry: () => __shouldReuseScannerCacheEntry,
281
281
  discoverListeningPorts: () => discoverListeningPorts,
282
282
  discoverListeningProcesses: () => discoverListeningProcesses,
283
- pidFromNetstat: () => pidFromNetstat,
284
283
  probePort: () => probePort,
285
284
  resolveListeningPid: () => resolveListeningPid,
286
285
  resolveProcessCwd: () => resolveProcessCwd,
@@ -470,7 +469,7 @@ function hasOwnKeys(value) {
470
469
  }
471
470
  function hasPeachConfig(config) {
472
471
  if (!config) return false;
473
- return typeof config.runtimeVersion === "string" && config.runtimeVersion.length > 0 || hasOwnKeys(config.modules) || hasOwnKeys(config.turboModules) || hasOwnKeys(config.nativeModules) || hasOwnKeys(config.nativeViews) || hasOwnKeys(config.env) || hasOwnKeys(config.network?.remap) || hasOwnKeys(config.settings) || hasOwnKeys(config.initialState);
472
+ return config.nativeRenderer !== void 0 || typeof config.runtimeVersion === "string" && config.runtimeVersion.length > 0 || hasOwnKeys(config.modules) || hasOwnKeys(config.turboModules) || hasOwnKeys(config.nativeModules) || hasOwnKeys(config.nativeViews) || hasOwnKeys(config.env) || hasOwnKeys(config.network?.remap) || hasOwnKeys(config.settings) || hasOwnKeys(config.initialState);
474
473
  }
475
474
  function applyPeachConfigToUrl(url, config) {
476
475
  const parsed = new URL(url);
@@ -590,9 +589,9 @@ function configuredWidgetFromScan(value, bundleUrl) {
590
589
  }
591
590
 
592
591
  // src/one-widget-discovery.ts
593
- async function readConfiguredOneWidget(bundleUrl) {
592
+ async function readConfiguredOneWidget(bundleUrl, fetchScan = fetch) {
594
593
  try {
595
- const response = await fetch(new URL("/__server-scan", bundleUrl).href, {
594
+ const response = await fetchScan(new URL("/__server-scan", bundleUrl).href, {
596
595
  signal: AbortSignal.timeout(3e4)
597
596
  });
598
597
  if (!response.ok || !response.headers.get("content-type")?.includes("json"))
@@ -1215,30 +1214,19 @@ async function discoverListeningPorts(excludePorts = []) {
1215
1214
  const processes = await discoverListeningProcesses(excludePorts);
1216
1215
  return processes.map((p) => p.port);
1217
1216
  }
1218
- function pidFromNetstat(stdout, port) {
1219
- for (const line of stdout.split("\n")) {
1220
- if (!line.includes("LISTEN")) continue;
1221
- const parts = line.trim().split(/\s+/);
1222
- if (!parts[3]?.endsWith(`.${port}`)) continue;
1223
- for (const token of parts) {
1224
- const owner = token.match(/^\S+:(\d+)$/);
1225
- if (owner) {
1226
- const pid = Number(owner[1]);
1227
- if (pid > 0) return pid;
1228
- }
1229
- }
1230
- }
1231
- return null;
1232
- }
1233
1217
  async function resolveListeningPid(port) {
1234
1218
  if (!Number.isInteger(port) || port <= 0) return null;
1235
- try {
1236
- const { stdout } = await execGuarded("netstat -anv -p tcp 2>/dev/null", 8e3, {
1237
- maxBuffer: 16 * 1024 * 1024
1238
- });
1239
- const pid = pidFromNetstat(stdout, port);
1240
- if (pid !== null) return pid;
1241
- } catch {
1219
+ if (process.platform === "darwin") {
1220
+ try {
1221
+ const { stdout } = await execGuarded(
1222
+ `lsof -nP -a -iTCP:${port} -sTCP:LISTEN -t 2>/dev/null`,
1223
+ 8e3
1224
+ );
1225
+ const pids = [...new Set(stdout.trim().split(/\s+/).map(Number))];
1226
+ return pids.length === 1 && Number.isInteger(pids[0]) && pids[0] > 0 ? pids[0] : null;
1227
+ } catch {
1228
+ return null;
1229
+ }
1242
1230
  }
1243
1231
  try {
1244
1232
  const { stdout } = await execGuarded(`ss -tlnp 2>/dev/null`, 8e3, {
@@ -1482,15 +1470,53 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
1482
1470
  ...ownerCwd ? { cwd: ownerCwd } : {}
1483
1471
  };
1484
1472
  const onePath = `/node_modules/one/metro-entry.bundle?platform=ios&dev=true`;
1485
- const [sootsimRes, statusRes, manifestRes, expoRes, swiftRes] = await Promise.all([
1486
- knownNonPatched.has(port) ? Promise.resolve(null) : httpGet(probeHostname, port, "/__soot/"),
1473
+ const [sootsimRes, statusRes, manifestRes, expoRes, swiftRes, oneScanRes] = await Promise.all([
1474
+ knownNonPatched.has(port) ? Promise.resolve(null) : httpGet(probeHostname, port, "/__peachsim/"),
1487
1475
  httpGet(probeHostname, port, "/status"),
1488
1476
  knownOne.has(port) ? Promise.resolve(null) : httpGet(probeHostname, port, "/", "GET", MANIFEST_TIMEOUT_MS, {
1489
1477
  "expo-platform": "ios"
1490
1478
  }),
1491
1479
  knownNonExpo.has(port) ? Promise.resolve(null) : httpGet(probeHostname, port, "/_expo/status"),
1492
- httpGet(probeHostname, port, SWIFT_DISCOVERY_PATH)
1480
+ httpGet(probeHostname, port, SWIFT_DISCOVERY_PATH),
1481
+ httpGet(probeHostname, port, "/__server-scan", "GET", MANIFEST_TIMEOUT_MS)
1493
1482
  ]);
1483
+ const oneScan = oneScanRes?.statusCode === 200 ? safeParseManifest(oneScanRes.body) : null;
1484
+ if (Array.isArray(oneScan)) {
1485
+ for (const entry of oneScan) {
1486
+ if (!entry || typeof entry !== "object" || entry.framework !== "one" || entry.port !== port || typeof entry.bundleUrl !== "string")
1487
+ continue;
1488
+ let bundle;
1489
+ try {
1490
+ bundle = new URL(entry.bundleUrl);
1491
+ } catch {
1492
+ continue;
1493
+ }
1494
+ if (bundle.protocol !== "http:" || !isLoopbackHost(bundle.hostname) || bundle.port !== String(port) || !bundle.pathname.endsWith("/one/metro-entry.bundle"))
1495
+ continue;
1496
+ knownOne.add(port);
1497
+ return enrichWithManifest(
1498
+ applyManifest(
1499
+ {
1500
+ port,
1501
+ framework: "one",
1502
+ bundleUrl: withRuntimeConfig(
1503
+ port,
1504
+ alignLoopbackBundleUrlWithProbe(
1505
+ bundle.href,
1506
+ buildLoopbackProbeBaseUrl("http:", probeHostname, port)
1507
+ ),
1508
+ owner.cwd
1509
+ ),
1510
+ lastSeen: Date.now(),
1511
+ ...owner
1512
+ },
1513
+ manifestRes,
1514
+ buildIconProxyUrl
1515
+ ),
1516
+ buildIconProxyUrl
1517
+ );
1518
+ }
1519
+ }
1494
1520
  const swift = swiftRes?.statusCode === 200 ? parseSwiftHostDiscovery(safeParseManifest(swiftRes.body)) : null;
1495
1521
  if (swift) {
1496
1522
  const base = buildLoopbackProbeBaseUrl("http:", probeHostname, port);
@@ -1560,7 +1586,7 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
1560
1586
  framework: "one",
1561
1587
  bundleUrl: withRuntimeConfig(
1562
1588
  port,
1563
- `${buildLoopbackProbeBaseUrl("http:", probeHostname, port)}/__soot/bundle.js`,
1589
+ `${buildLoopbackProbeBaseUrl("http:", probeHostname, port)}/__peachsim/bundle.js`,
1564
1590
  owner.cwd
1565
1591
  ),
1566
1592
  hmrUrl: `${buildLoopbackProbeBaseUrl("ws:", probeHostname, port)}/hot`,
@@ -1753,7 +1779,6 @@ async function scanDevServers(opts = {}) {
1753
1779
  __shouldReuseScannerCacheEntry,
1754
1780
  discoverListeningPorts,
1755
1781
  discoverListeningProcesses,
1756
- pidFromNetstat,
1757
1782
  probePort,
1758
1783
  resolveListeningPid,
1759
1784
  resolveProcessCwd,
package/dist-lib/sdk.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.939 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! peach v0.1.1399 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -109,7 +109,6 @@ module.exports = __toCommonJS(sdk_exports);
109
109
 
110
110
  // src/bridge-contract.ts
111
111
  var SEMANTIC_READY_CONTENT_NODE_FLOOR = 100;
112
- var PUBLIC_NO_NETWORK_MARKER = "public Peach Cloud simulators have no network";
113
112
  var HEADLESS_TENANT_FETCH_HUNG_MARKER = "headless tenant fetch hung";
114
113
  function readyProbeHasContent(probe) {
115
114
  return (probe.browserSurfaceCount ?? 0) > 0 || probe.liveFrameActive || probe.targets > 0 || probe.nodes >= SEMANTIC_READY_CONTENT_NODE_FLOOR;
@@ -850,7 +849,7 @@ function shellEscape(value) {
850
849
  }
851
850
  function waitReadyReason(status) {
852
851
  if (status.bridgeError) {
853
- if (status.bridgeError.includes(HEADLESS_TENANT_FETCH_HUNG_MARKER) || status.bridgeError.includes(PUBLIC_NO_NETWORK_MARKER)) {
852
+ if (status.bridgeError.includes(HEADLESS_TENANT_FETCH_HUNG_MARKER)) {
854
853
  return status.bridgeError;
855
854
  }
856
855
  return `sim unreachable: ${status.bridgeError} \u2014 the target tab is closed or no sim is connected (check \`peach list\`, reopen with \`peach open\`)`;
@@ -870,13 +869,18 @@ async function inspectCaptureReady(bridge, timeoutMs) {
870
869
  if (snapshot.ready) return snapshot;
871
870
  return inspectWaitReady(bridge, timeoutMs);
872
871
  }
873
- var READY_SNAPSHOT_SEND_TIMEOUT_MS = 2e3;
872
+ var READY_SNAPSHOT_SEND_TIMEOUT_MS = 15e3;
874
873
  async function inspectReadySnapshot(bridge, budgetMs, simId) {
875
874
  const deadline = Date.now() + budgetMs;
876
875
  let result;
877
876
  do {
878
- result = await sendReadyWait(bridge, 1, READY_SNAPSHOT_SEND_TIMEOUT_MS, simId);
879
- } while (result.polls !== 1 && !result.error && Date.now() < deadline);
877
+ const remaining = Math.max(0, deadline - Date.now());
878
+ const sendTimeoutMs = Math.max(
879
+ READY_SNAPSHOT_SEND_TIMEOUT_MS,
880
+ Math.min(remaining, 6e4)
881
+ );
882
+ result = await sendReadyWait(bridge, 1, sendTimeoutMs, simId);
883
+ } while (result.polls !== 1 && !result.error?.startsWith("sim disconnected") && !result.error?.startsWith("bridge never reconnected") && Date.now() < deadline);
880
884
  return waitReadyStatus(
881
885
  result,
882
886
  result.polls === 1 && result.probe ? readyProbeIsReady(result.probe) : false
@@ -1520,6 +1524,17 @@ async function resolveTargetCoords(bridge, target) {
1520
1524
  }
1521
1525
 
1522
1526
  // cli/commands/inspect/settling.ts
1527
+ var RuntimeReadinessUnsupportedError = class extends Error {
1528
+ constructor(runtimeVersion, missingFields) {
1529
+ super(
1530
+ `runtime ${runtimeVersion} cannot report animation readiness (${missingFields.join(", ")} missing); run \`peach upgrade\` and reopen the simulator`
1531
+ );
1532
+ this.runtimeVersion = runtimeVersion;
1533
+ this.missingFields = missingFields;
1534
+ this.name = "RuntimeReadinessUnsupportedError";
1535
+ }
1536
+ code = "runtime_readiness_unsupported";
1537
+ };
1523
1538
  async function waitForSootsimIdle({
1524
1539
  bridge,
1525
1540
  simId,
@@ -1529,16 +1544,23 @@ async function waitForSootsimIdle({
1529
1544
  strict = false,
1530
1545
  paintStill = false
1531
1546
  }) {
1532
- const result = await bridge.send(
1533
- {
1534
- type: "evaluate",
1535
- simId,
1536
- code: `(async () => {
1547
+ let result;
1548
+ try {
1549
+ result = await bridge.send(
1550
+ {
1551
+ type: "evaluate",
1552
+ simId,
1553
+ code: `(async () => {
1537
1554
  const start = Date.now()
1538
1555
  const deadline = start + ${Math.max(0, Math.round(maxMs))}
1539
1556
  const pollMs = ${Math.max(1, Math.round(pollMs))}
1540
1557
  const strict = ${strict ? "true" : "false"}
1541
1558
  const paintStill = ${paintStill ? "true" : "false"}
1559
+ // full mounted-geometry observation needs at least two hierarchy reads
1560
+ // plus the stillness window, which a sub-second budget cannot afford:
1561
+ // tight budgets (auto-wait) settle on worker stillness plus root
1562
+ // membership, generous ones confirm the geometry too.
1563
+ const fullGeometry = ${maxMs >= 1e3 ? "true" : "false"}
1542
1564
  const requiredStillMs = paintStill ? 0 : ${Math.max(1, Math.round(stablePolls)) * Math.max(1, Math.round(pollMs))}
1543
1565
  const minimumStablePolls = paintStill ? 1 : ${Math.min(2, Math.max(1, Math.round(stablePolls)))}
1544
1566
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
@@ -1575,58 +1597,177 @@ async function waitForSootsimIdle({
1575
1597
  }
1576
1598
  } catch {}
1577
1599
 
1600
+ // a worker that cannot answer within the remaining budget is busy, not
1601
+ // idle: its poll reads as unsettled. bounding each read by the budget
1602
+ // keeps a busy worker from holding a poll past the deadline.
1603
+ const BUSY = { busy: true }
1604
+ const answer = (read) =>
1605
+ Promise.race([
1606
+ Promise.resolve().then(read),
1607
+ new Promise((resolve) =>
1608
+ setTimeout(() => resolve(BUSY), Math.max(deadline - Date.now(), 100)),
1609
+ ),
1610
+ ])
1611
+
1578
1612
  const readSnapshot = async () => {
1579
1613
  let animating = false
1580
1614
  let layoutDirty = false
1581
1615
  let pendingFetches = 0
1616
+ let workerBusy = false
1582
1617
  let requestTotal = 0
1583
1618
  let requestInFlight = 0
1619
+ let appCommits = null
1620
+ let nodeCount = null
1584
1621
  let renderStatsAvailable = false
1585
1622
  let shellAnimating = false
1623
+ let webViewStarting = false
1624
+ let webViewBlocker = ''
1586
1625
  // strict also waits out shell motion: a system alert, sheet or menu
1587
1626
  // animates in the shell worker while the app's own stats read still,
1588
1627
  // so its presentation is complete only once the shell's list empties.
1589
1628
  if (strict) {
1590
- try {
1591
- const motion = await window.__sootsimTest?.listAnimations?.()
1592
- shellAnimating = Array.isArray(motion) && motion.some((anim) => anim.realm === 'shell')
1593
- } catch {}
1629
+ const motion = await answer(() => window.__sootsimTest?.listAnimations?.()).catch(() => null)
1630
+ if (motion === BUSY) workerBusy = true
1631
+ else shellAnimating = Array.isArray(motion) && motion.some((anim) => anim.realm === 'shell' && anim.priority !== 'low')
1594
1632
  }
1595
- try {
1596
- const stats = await window.__sootsimRenderHost?.queryStats?.()
1597
- if (stats) {
1598
- renderStatsAvailable = true
1599
- animating =
1600
- stats.hasActiveAnims === true ||
1601
- stats.hasActiveNativeAnimations === true ||
1602
- stats.hasPendingAnimationFrames === true
1603
- layoutDirty =
1604
- stats.layoutDirty === true && stats.renderRequested === true
1605
- // a freshly-pushed screen showing a skeleton is layout-stable
1606
- // but not actually settled \u2014 its data/images are still in
1607
- // flight. treat bounded image-loader fetches as not-idle so
1608
- // settle waits for real content, capped by the budget.
1609
- const pf = stats.memory && stats.memory.imageLoader
1610
- ? stats.memory.imageLoader.pendingFetches
1611
- : 0
1612
- pendingFetches = typeof pf === 'number' ? pf : 0
1633
+ const stats = await answer(() => window.__sootsimRenderHost?.queryStats?.()).catch(() => null)
1634
+ if (stats === BUSY) {
1635
+ workerBusy = true
1636
+ } else if (stats) {
1637
+ renderStatsAvailable = true
1638
+ const missingFields = ['hasActiveBlockingNativeAnimations', 'hasPendingBlockingAnimationFrames']
1639
+ .filter((field) => typeof stats[field] !== 'boolean')
1640
+ if (strict && missingFields.length > 0) {
1641
+ return {
1642
+ runtimeUnsupported: {
1643
+ error: 'runtime_readiness_unsupported',
1644
+ runtimeVersion: window.__sootsimRuntimeVersion || 'unknown',
1645
+ missingFields,
1646
+ },
1647
+ }
1613
1648
  }
1614
- } catch {}
1649
+ // positive checks only: an older runtime without the blocking
1650
+ // fields reads as still, and strict rejects that runtime above
1651
+ // instead of ever settling on unknown motion.
1652
+ animating =
1653
+ stats.hasActiveAnims === true ||
1654
+ stats.hasActiveBlockingNativeAnimations === true ||
1655
+ stats.hasPendingBlockingAnimationFrames === true
1656
+ layoutDirty =
1657
+ stats.layoutDirty === true && stats.renderRequested === true
1658
+ // a freshly-pushed screen showing a skeleton is layout-stable
1659
+ // but not actually settled \u2014 its data/images are still in
1660
+ // flight. treat bounded image-loader fetches as not-idle so
1661
+ // settle waits for real content, capped by the budget.
1662
+ const pf = stats.memory && stats.memory.imageLoader
1663
+ ? stats.memory.imageLoader.pendingFetches
1664
+ : 0
1665
+ pendingFetches = typeof pf === 'number' ? pf : 0
1666
+ // tenant layout commits: a screen that keeps committing is still
1667
+ // mounting or remounting, even with no animation flag raised.
1668
+ appCommits = typeof stats.appCommitCount === 'number' ? stats.appCommitCount : null
1669
+ }
1670
+ // image preparation ends in the tenant before the compositor decodes
1671
+ // and realizes its pixels. both owners must finish before settling.
1672
+ // the compositor rejects a stats request it is too busy to answer.
1673
+ const compositor = await answer(() => window.__sootsimCompositor.getStats()).catch(() => BUSY)
1674
+ if (compositor === BUSY) {
1675
+ workerBusy = true
1676
+ } else {
1677
+ pendingFetches += compositor.images.pendingFetches
1678
+ pendingFetches += compositor.imageRealization.pending
1679
+ if (compositor.webViewCaptureSettled === false) {
1680
+ webViewStarting = true
1681
+ webViewBlocker = 'compositor webView capture not settled'
1682
+ }
1683
+ }
1615
1684
  try {
1616
1685
  const counts = await window.__sootsimTest?.getRequestCounts?.()
1617
1686
  requestTotal = typeof counts?.total === 'number' ? counts.total : 0
1618
1687
  requestInFlight = typeof counts?.inFlight === 'number' ? counts.inFlight : 0
1619
1688
  } catch {}
1620
- const root = window.__sootsimRoot
1689
+ // mounted-root membership: the live node count moves while the tree
1690
+ // mounts, unmounts or recycles, in either render mode.
1691
+ try {
1692
+ const total = await window.__sootsimTest?.getNodeCount?.()
1693
+ nodeCount = typeof total === 'number' ? total : null
1694
+ } catch {}
1695
+ // the mounted hierarchy in render-worker mode comes from the test
1696
+ // bridge: window.__sootsimRoot is an empty placeholder there, so a
1697
+ // walk over it hashes every screen to the same empty string while
1698
+ // it moves. listInspectable is the worker-backed read describe
1699
+ // uses; a worker too busy to answer it reads as unsettled. tight
1700
+ // budgets skip this read and settle on the cheaper signals above.
1701
+ let hierarchy = null
1702
+ if (fullGeometry && typeof window.__sootsimTest?.listInspectable === 'function') {
1703
+ try {
1704
+ hierarchy = await answer(() => window.__sootsimTest.listInspectable({}))
1705
+ } catch {
1706
+ hierarchy = null
1707
+ }
1708
+ }
1709
+ if (hierarchy === BUSY) {
1710
+ workerBusy = true
1711
+ hierarchy = null
1712
+ } else if (!Array.isArray(hierarchy)) {
1713
+ hierarchy = null
1714
+ }
1621
1715
  const nodes = []
1622
- if (root) {
1623
- const walk = (n) => {
1624
- if (n.layout && n.layout.width > 0) {
1625
- nodes.push(Math.round(n.layout.x) + ',' + Math.round(n.layout.y) + ',' + Math.round(n.layout.width))
1716
+ const PREVIEW_PROGRESS_REGEX = /^(creating webview|loading html|fetching bundle|waiting for first render|starting preview|loading simulator|fetching app code|waiting for native preview|starting design|loading canvas|fetching screens|painting the canvas|waiting for webview|waiting for preview|waiting for canvas|preview stalled)/i
1717
+ const notePreviewProgress = (tid, text) => {
1718
+ if (typeof tid === 'string' && (tid === 'web-preview-progress' || tid.endsWith('-preview-progress'))) {
1719
+ webViewStarting = true
1720
+ if (!webViewBlocker) webViewBlocker = 'preview progress active (' + tid + ')'
1721
+ }
1722
+ if (typeof text === 'string' && PREVIEW_PROGRESS_REGEX.test(text.trim())) {
1723
+ webViewStarting = true
1724
+ if (!webViewBlocker) webViewBlocker = 'webView startup in progress ("' + text.trim() + '")'
1725
+ }
1726
+ }
1727
+ if (hierarchy) {
1728
+ for (const n of hierarchy) {
1729
+ const layout = n && n.layout
1730
+ if (layout && layout.width > 0) {
1731
+ const abs = (n && (n.absolute || n.absolutePosition)) || layout
1732
+ nodes.push(Math.round(abs.x) + ',' + Math.round(abs.y) + ',' + Math.round(layout.width))
1733
+ }
1734
+ notePreviewProgress(
1735
+ n.testID || n.testId,
1736
+ typeof n.text === 'string' ? n.text : '',
1737
+ )
1738
+ }
1739
+ } else {
1740
+ const root = window.__sootsimRoot
1741
+ if (root) {
1742
+ const walk = (n) => {
1743
+ if (n.layout && n.layout.width > 0) {
1744
+ nodes.push(Math.round(n.layout.x) + ',' + Math.round(n.layout.y) + ',' + Math.round(n.layout.width))
1745
+ }
1746
+ notePreviewProgress(
1747
+ n.testID || n.props?.testID || n.props?.testId,
1748
+ n.textContent || (typeof n.props?.children === 'string' ? n.props.children : ''),
1749
+ )
1750
+ for (const c of n.children || []) walk(c)
1751
+ }
1752
+ walk(root)
1753
+ }
1754
+ }
1755
+ if (!webViewStarting && typeof document !== 'undefined') {
1756
+ const progressEl = document.querySelector(
1757
+ '[data-testid$="-preview-progress"], [data-testid="web-preview-progress"], [data-testid="ios-preview-progress"], [data-testid="design-preview-progress"]'
1758
+ )
1759
+ if (progressEl) {
1760
+ webViewStarting = true
1761
+ webViewBlocker = 'preview progress element displayed'
1762
+ } else {
1763
+ const unreadyWebView = document.querySelector(
1764
+ '[data-sootsim-webview-loading="true"], [data-sootsim-webview-mode="placeholder"]'
1765
+ )
1766
+ if (unreadyWebView) {
1767
+ webViewStarting = true
1768
+ webViewBlocker = 'webView document still loading'
1626
1769
  }
1627
- for (const c of n.children || []) walk(c)
1628
1770
  }
1629
- walk(root)
1630
1771
  }
1631
1772
  if (paintStill) {
1632
1773
  const canvas = composite.getCanvas()
@@ -1640,7 +1781,7 @@ async function waitForSootsimIdle({
1640
1781
  pixelFrameToken = token
1641
1782
  }
1642
1783
  }
1643
- return { layout: nodes.join(';'), screenPixels, animating, shellAnimating, layoutDirty, pendingFetches, renderStatsAvailable, requestTotal, requestInFlight }
1784
+ return { layout: nodes.join(';'), screenPixels, animating, shellAnimating, layoutDirty, pendingFetches, workerBusy, webViewStarting, webViewBlocker, renderStatsAvailable, requestTotal, requestInFlight, appCommits, nodeCount }
1644
1785
  }
1645
1786
 
1646
1787
  // how long generic background network is allowed to keep us waiting AFTER
@@ -1658,6 +1799,8 @@ async function waitForSootsimIdle({
1658
1799
  let lastScreenPixels = null
1659
1800
  let screenChangedPolls = 0
1660
1801
  let lastRequestTotal = -1
1802
+ let lastAppCommits = null
1803
+ let lastNodeCount = null
1661
1804
  let stable = 0
1662
1805
  let visuallyStillSince = 0
1663
1806
  // counts across the whole budget: a flag that is true on every poll is a
@@ -1667,6 +1810,7 @@ async function waitForSootsimIdle({
1667
1810
  let dirtyPolls = 0
1668
1811
  let animatingPolls = 0
1669
1812
  let layoutChangedPolls = 0
1813
+ let webViewStartingPolls = 0
1670
1814
  const screenUnchanged = (pixels) => {
1671
1815
  if (!pixels || !lastScreenPixels || pixels.width !== lastScreenPixels.width || pixels.height !== lastScreenPixels.height) return false
1672
1816
  if (pixels === lastScreenPixels) return true
@@ -1677,9 +1821,11 @@ async function waitForSootsimIdle({
1677
1821
  }
1678
1822
  while (Date.now() < deadline) {
1679
1823
  const snapshot = await readSnapshot()
1824
+ if (snapshot.runtimeUnsupported) return snapshot.runtimeUnsupported
1680
1825
  polls++
1681
1826
  if (snapshot.layoutDirty) dirtyPolls++
1682
1827
  if (snapshot.animating || snapshot.shellAnimating) animatingPolls++
1828
+ if (snapshot.webViewStarting) webViewStartingPolls++
1683
1829
  if (polls > 1 && snapshot.layout !== lastLayout) layoutChangedPolls++
1684
1830
  const screenChanged = paintStill && !screenUnchanged(snapshot.screenPixels)
1685
1831
  if (polls > 1 && screenChanged) screenChangedPolls++
@@ -1689,12 +1835,24 @@ async function waitForSootsimIdle({
1689
1835
  !snapshot.animating &&
1690
1836
  !snapshot.shellAnimating &&
1691
1837
  !snapshot.layoutDirty)
1692
- // visually settled: no animations, no image content loading, layout
1693
- // unchanged since the last poll.
1838
+ // visually settled: no worker motion or layout work, no new layout
1839
+ // commits, a stable mounted node count, no image content loading,
1840
+ // no webview startup in progress, layout unchanged since the last
1841
+ // poll. both modes wait out finite worker motion now; strict
1842
+ // additionally implies the flags through strictOk and adds shell
1843
+ // motion plus the blocking-stats floor. a null count or commit
1844
+ // reading means the runtime does not report it, which reads as
1845
+ // stable the way a missing stats block does.
1694
1846
  const visuallyStill =
1695
1847
  strictOk &&
1848
+ !snapshot.animating &&
1849
+ !snapshot.layoutDirty &&
1850
+ (snapshot.appCommits === null || lastAppCommits === null || snapshot.appCommits === lastAppCommits) &&
1851
+ (snapshot.nodeCount === null || lastNodeCount === null || snapshot.nodeCount === lastNodeCount) &&
1696
1852
  !screenChanged &&
1697
1853
  snapshot.pendingFetches === 0 &&
1854
+ !snapshot.workerBusy &&
1855
+ !snapshot.webViewStarting &&
1698
1856
  snapshot.layout === lastLayout
1699
1857
  // generic network quiet: nothing in flight and no new requests issued.
1700
1858
  const networkQuiet =
@@ -1719,15 +1877,22 @@ async function waitForSootsimIdle({
1719
1877
  lastLayout = snapshot.layout
1720
1878
  lastScreenPixels = snapshot.screenPixels
1721
1879
  lastRequestTotal = snapshot.requestTotal
1880
+ lastAppCommits = snapshot.appCommits
1881
+ lastNodeCount = snapshot.nodeCount
1722
1882
  await sleep(pollMs)
1723
1883
  }
1724
1884
  const last = await readSnapshot()
1885
+ if (last.runtimeUnsupported) return last.runtimeUnsupported
1725
1886
  const blockers = []
1726
1887
  if (strict && !last.renderStatsAvailable) blockers.push('render stats unavailable')
1727
1888
  if (last.animating) blockers.push('animations still running')
1728
1889
  if (last.shellAnimating) blockers.push('shell animations still running')
1729
1890
  if (last.layoutDirty) blockers.push('layout still dirty')
1891
+ if (last.appCommits !== null && lastAppCommits !== null && last.appCommits !== lastAppCommits) blockers.push('layout commits still landing')
1892
+ if (last.nodeCount !== null && lastNodeCount !== null && last.nodeCount !== lastNodeCount) blockers.push('node count still changing')
1730
1893
  if (last.pendingFetches > 0) blockers.push(last.pendingFetches + ' image fetches pending')
1894
+ if (last.workerBusy) blockers.push('a render worker too busy to report')
1895
+ if (last.webViewStarting) blockers.push(last.webViewBlocker || 'webView still starting')
1731
1896
  if (last.layout !== lastLayout) blockers.push('layout still changing')
1732
1897
  if (paintStill && !last.screenPixels) blockers.push('screen pixels unavailable')
1733
1898
  else if (paintStill && !screenUnchanged(last.screenPixels)) blockers.push('screen pixels still changing')
@@ -1740,17 +1905,31 @@ async function waitForSootsimIdle({
1740
1905
  blockedBy:
1741
1906
  (blockers.length ? blockers.join(', ') : 'unknown') +
1742
1907
  ' over ' + polls + ' polls (layout changed ' + layoutChangedPolls +
1743
- 'x, layoutDirty ' + dirtyPolls + 'x, animating ' + animatingPolls + 'x, screen changed ' + screenChangedPolls + 'x)',
1908
+ 'x, layoutDirty ' + dirtyPolls + 'x, animating ' + animatingPolls + 'x, webViewStarting ' + webViewStartingPolls + 'x, screen changed ' + screenChangedPolls + 'x)',
1744
1909
  }
1745
1910
  } finally {
1746
1911
  unsubscribe?.()
1747
1912
  }
1748
1913
  })()`
1749
- },
1750
- {
1751
- timeoutMs: maxMs + 1e3
1752
- }
1753
- );
1914
+ },
1915
+ {
1916
+ timeoutMs: Math.max(5e3, maxMs + 2e3)
1917
+ }
1918
+ );
1919
+ } catch (error) {
1920
+ const message = error instanceof Error ? error.message : String(error);
1921
+ return {
1922
+ settled: false,
1923
+ elapsed: maxMs,
1924
+ blockedBy: message
1925
+ };
1926
+ }
1927
+ if (result?.error === "runtime_readiness_unsupported" && typeof result.runtimeVersion === "string" && Array.isArray(result.missingFields) && result.missingFields.every((field) => typeof field === "string")) {
1928
+ throw new RuntimeReadinessUnsupportedError(
1929
+ result.runtimeVersion,
1930
+ result.missingFields
1931
+ );
1932
+ }
1754
1933
  const { elapsed, settled, blockedBy } = result ?? {};
1755
1934
  return {
1756
1935
  elapsed: typeof elapsed === "number" ? elapsed : maxMs,
@@ -1768,6 +1947,7 @@ async function waitReadyAndSettle(bridge, timeoutMs, options = {}) {
1768
1947
  });
1769
1948
  let idle = null;
1770
1949
  for (; ; ) {
1950
+ idle = null;
1771
1951
  const ready = await inspectWaitReady(bridge, remaining(), options);
1772
1952
  if (!ready.ready) return result(ready, idle);
1773
1953
  idle = await waitForSootsimIdle({
@@ -2268,7 +2448,7 @@ function buildSimOpenUrl(currentUrl, target) {
2268
2448
  const url = new URL(currentUrl);
2269
2449
  for (const param of TARGET_SEARCH_PARAMS) url.searchParams.delete(param);
2270
2450
  const pathname = url.pathname.replace(/\/+$/, "") || "/";
2271
- const prefix = pathname === "/__soot" || pathname.startsWith("/__soot/") ? "/__soot" : "";
2451
+ const prefix = pathname === "/__peachsim" || pathname.startsWith("/__peachsim/") ? "/__peachsim" : "";
2272
2452
  const normalized = String(target).trim();
2273
2453
  if (/^\d+$/.test(normalized)) {
2274
2454
  url.pathname = `${prefix}/rn/${normalized}`;