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
@@ -448,55 +448,27 @@ export async function discoverListeningPorts(
448
448
  return processes.map((p) => p.port)
449
449
  }
450
450
 
451
- /** parse `netstat -anv -p tcp` output for the pid listening on one port.
452
- *
453
- * macOS lays a LISTEN row out as
454
- * tcp4 0 0 127.0.0.1.8090 *.* LISTEN 0 0 131072 131072 node:97330 ...
455
- * so the local address is the 4th column with a DOT before the port (which is
456
- * what keeps port 8137 from matching a row for 18137), and the owner is the
457
- * only token shaped "<name>:<pid>".
458
- *
459
- * exported because this column layout is the fragile part and a child process
460
- * cannot exercise it under bun's test runner, which hands back empty stdout. */
461
- export function pidFromNetstat(stdout: string, port: number): number | null {
462
- for (const line of stdout.split('\n')) {
463
- if (!line.includes('LISTEN')) continue
464
- const parts = line.trim().split(/\s+/)
465
- if (!parts[3]?.endsWith(`.${port}`)) continue
466
- for (const token of parts) {
467
- const owner = token.match(/^\S+:(\d+)$/)
468
- if (owner) {
469
- const pid = Number(owner[1])
470
- if (pid > 0) return pid
471
- }
472
- }
473
- }
474
- return null
475
- }
476
-
477
- /** resolve the pid of the process listening on one local port.
478
- *
479
- * `lsof` walks every descriptor of every process, which measures 2-4s on a
480
- * loaded machine — longer than any timeout worth giving it, so
481
- * `discoverListeningProcesses` above degrades to its pid-less port list
482
- * exactly when the machine is busy. netstat (macOS/BSD) and ss (linux) read
483
- * the kernel socket table instead and answer in well under a second, and both
484
- * carry the owning pid. use this whenever the question is "who owns this one
485
- * port"; `discoverListeningProcesses` answers the different question of what
486
- * is listening at all. */
451
+ /** resolve the pid listening on one local port without scanning unrelated fds.
452
+ * macOS netstat can return empty stdout to child processes, so resolve its
453
+ * listener through the targeted lsof query. Linux exposes ownership through ss.
454
+ */
487
455
  export async function resolveListeningPid(port: number): Promise<number | null> {
488
456
  if (!Number.isInteger(port) || port <= 0) return null
489
457
 
490
- // macOS netstat: "tcp4 0 0 127.0.0.1.8090 *.* LISTEN ... node:97330 ..."
491
- // the local address uses a dot before the port, and the owner column is the
492
- // only token shaped "<name>:<pid>".
493
- try {
494
- const { stdout } = await execGuarded('netstat -anv -p tcp 2>/dev/null', 8000, {
495
- maxBuffer: 16 * 1024 * 1024,
496
- })
497
- const pid = pidFromNetstat(stdout, port)
498
- if (pid !== null) return pid
499
- } catch {}
458
+ if (process.platform === 'darwin') {
459
+ try {
460
+ const { stdout } = await execGuarded(
461
+ `lsof -nP -a -iTCP:${port} -sTCP:LISTEN -t 2>/dev/null`,
462
+ 8000,
463
+ )
464
+ const pids = [...new Set(stdout.trim().split(/\s+/).map(Number))]
465
+ return pids.length === 1 && Number.isInteger(pids[0]) && pids[0]! > 0
466
+ ? pids[0]!
467
+ : null
468
+ } catch {
469
+ return null
470
+ }
471
+ }
500
472
 
501
473
  // linux ss: 'LISTEN 0 511 0.0.0.0:8081 0.0.0.0:* users:(("node",pid=1234,fd=5))'
502
474
  try {
@@ -846,7 +818,7 @@ async function fetchManifestEnrichment(
846
818
  }
847
819
 
848
820
  // ports confirmed to be running a non-sootsim dev server — safe to skip the
849
- // /__soot/ probe on subsequent scans. cleared when the port disappears from the
821
+ // /__peachsim/ probe on subsequent scans. cleared when the port disappears from the
850
822
  // listening set (process restarted, could now be a different server).
851
823
  const knownNonPatched = new Set<number>()
852
824
 
@@ -868,7 +840,7 @@ const knownOne = new Set<number>()
868
840
  // fire signature probes in waves and pick the best match by precedence:
869
841
  // 1. expo manifest (JSON manifest at /)
870
842
  // 2. metro/expo (packager-status:running on /status)
871
- // 3. sootsim-patched (/__soot/)
843
+ // 3. sootsim-patched (/__peachsim/)
872
844
  // 4. one/vxrn (HEAD on /node_modules/one/metro-entry.bundle) — only
873
845
  // fires as a true last resort; on expo apps this probe
874
846
  // crashes metro by triggering HmrServer.registerEntryPoint
@@ -905,21 +877,75 @@ export async function probePort(
905
877
  const onePath = `/node_modules/one/metro-entry.bundle?platform=ios&dev=true`
906
878
 
907
879
  // wave 1 — safe parallel probes. none of these mutate metro state.
908
- const [sootsimRes, statusRes, manifestRes, expoRes, swiftRes] = await Promise.all([
909
- knownNonPatched.has(port)
910
- ? Promise.resolve(null)
911
- : httpGet(probeHostname, port, '/__soot/'),
912
- httpGet(probeHostname, port, '/status'),
913
- knownOne.has(port)
914
- ? Promise.resolve(null)
915
- : httpGet(probeHostname, port, '/', 'GET', MANIFEST_TIMEOUT_MS, {
916
- 'expo-platform': 'ios',
917
- }),
918
- knownNonExpo.has(port)
919
- ? Promise.resolve(null)
920
- : httpGet(probeHostname, port, '/_expo/status'),
921
- httpGet(probeHostname, port, SWIFT_DISCOVERY_PATH),
922
- ])
880
+ const [sootsimRes, statusRes, manifestRes, expoRes, swiftRes, oneScanRes] =
881
+ await Promise.all([
882
+ knownNonPatched.has(port)
883
+ ? Promise.resolve(null)
884
+ : httpGet(probeHostname, port, '/__peachsim/'),
885
+ httpGet(probeHostname, port, '/status'),
886
+ knownOne.has(port)
887
+ ? Promise.resolve(null)
888
+ : httpGet(probeHostname, port, '/', 'GET', MANIFEST_TIMEOUT_MS, {
889
+ 'expo-platform': 'ios',
890
+ }),
891
+ knownNonExpo.has(port)
892
+ ? Promise.resolve(null)
893
+ : httpGet(probeHostname, port, '/_expo/status'),
894
+ httpGet(probeHostname, port, SWIFT_DISCOVERY_PATH),
895
+ httpGet(probeHostname, port, '/__server-scan', 'GET', MANIFEST_TIMEOUT_MS),
896
+ ])
897
+
898
+ // the One plugin advertises its exact native entry, including replacement
899
+ // identity. native.app's Expo Go manifest can instead advertise index.bundle.
900
+ const oneScan =
901
+ oneScanRes?.statusCode === 200 ? safeParseManifest(oneScanRes.body) : null
902
+ if (Array.isArray(oneScan)) {
903
+ for (const entry of oneScan) {
904
+ if (
905
+ !entry ||
906
+ typeof entry !== 'object' ||
907
+ entry.framework !== 'one' ||
908
+ entry.port !== port ||
909
+ typeof entry.bundleUrl !== 'string'
910
+ )
911
+ continue
912
+ let bundle: URL
913
+ try {
914
+ bundle = new URL(entry.bundleUrl)
915
+ } catch {
916
+ continue
917
+ }
918
+ if (
919
+ bundle.protocol !== 'http:' ||
920
+ !isLoopbackHost(bundle.hostname) ||
921
+ bundle.port !== String(port) ||
922
+ !bundle.pathname.endsWith('/one/metro-entry.bundle')
923
+ )
924
+ continue
925
+ knownOne.add(port)
926
+ return enrichWithManifest(
927
+ applyManifest(
928
+ {
929
+ port,
930
+ framework: 'one',
931
+ bundleUrl: withRuntimeConfig(
932
+ port,
933
+ alignLoopbackBundleUrlWithProbe(
934
+ bundle.href,
935
+ buildLoopbackProbeBaseUrl('http:', probeHostname, port),
936
+ ),
937
+ owner.cwd,
938
+ ),
939
+ lastSeen: Date.now(),
940
+ ...owner,
941
+ },
942
+ manifestRes,
943
+ buildIconProxyUrl,
944
+ ),
945
+ buildIconProxyUrl,
946
+ )
947
+ }
948
+ }
923
949
 
924
950
  const swift =
925
951
  swiftRes?.statusCode === 200
@@ -1043,7 +1069,7 @@ export async function probePort(
1043
1069
  )
1044
1070
  }
1045
1071
 
1046
- // patched sootsim — fallback for legacy /__soot/ patched servers. also
1072
+ // patched sootsim — fallback for legacy /__peachsim/ patched servers. also
1047
1073
  // checked before the one HEAD for the same reason.
1048
1074
  if (
1049
1075
  sootsimRes &&
@@ -1058,7 +1084,7 @@ export async function probePort(
1058
1084
  framework: 'one',
1059
1085
  bundleUrl: withRuntimeConfig(
1060
1086
  port,
1061
- `${buildLoopbackProbeBaseUrl('http:', probeHostname, port)}/__soot/bundle.js`,
1087
+ `${buildLoopbackProbeBaseUrl('http:', probeHostname, port)}/__peachsim/bundle.js`,
1062
1088
  owner.cwd,
1063
1089
  ),
1064
1090
  hmrUrl: `${buildLoopbackProbeBaseUrl('ws:', probeHostname, port)}/hot`,
@@ -1296,7 +1322,7 @@ export async function scanDevServers(
1296
1322
  }
1297
1323
  // pid changed or cache expired — drop any endpoint-specific verdicts so
1298
1324
  // the re-probe starts from scratch. otherwise a restarted process that
1299
- // swapped frameworks keeps the old skip-/__soot/ or skip-/_expo/status
1325
+ // swapped frameworks keeps the old skip-/__peachsim/ or skip-/_expo/status
1300
1326
  // decisions, and the scanner never discovers the new server.
1301
1327
  if (cached && cached.pid !== pid) {
1302
1328
  knownNonPatched.delete(port)
@@ -101,8 +101,8 @@ skills for working with the Peach native preview engine:
101
101
 
102
102
  | skill | description |
103
103
  | --- | --- |
104
- | `/peach-debug` | Debug rendering, animation, gesture, performance, and accessibility issues in a React Native app running under Peach. Reach for this whenever an animation looks wrong (janky, stutters, snaps, only updates once, or doesn't move at all), a swipe/transition/gesture misbehaves or feels broken, scrolling drops frames or feels slow, the UI renders the wrong tree/props, or a screen reader can't reach something — and drive the peach CLI debug tooling first, not screenshots or ad-hoc logging. |
105
- | `/peach-perf` | Optimize Peach engine and app performance — scroll jank, dropped frames, slow animation, idle CPU, memory growth, and "smooth normally but laggy under load" (video calls, busy machines). Covers measuring on the right thread with the right browser, proving each perf tier actually engages via its counters, a CPU-contention test harness, and the map of existing perf machinery so you improve it instead of reinventing it. For general app debugging load /peach-debug first. |
104
+ | `/peach-debug` | Debug rendering, animation, gesture, performance, and accessibility issues in a React Native app under Peach. Use when an animation janks, snaps, or stalls, a gesture misbehaves, scrolling drops frames, the tree or props look wrong, or a screen reader cannot reach something. Use the peach CLI first. |
105
+ | `/peach-perf` | Optimize Peach engine and app performance: scroll jank, dropped frames, slow animation, idle CPU, memory growth, and lag under load. Covers measuring on the right thread, proving perf tiers engage via counters, a CPU-contention harness, and perf machinery. For debugging load peach-debug first. |
106
106
  | `/peach-setup` | Get Peach connected to your React Native app with no project setup |
107
107
  | `/peach-test` | Run automated tests against your React Native app under Peach with Maestro YAML or Detox/Jest |
108
108
  | `/peach-visual` | Pixel-level rendering comparison between Peach and native iOS — fonts, gradients, shadows, image fidelity |
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: peach-debug
3
3
  model: sm
4
- description: Debug rendering, animation, gesture, performance, and accessibility issues in a React Native app running under Peach. Reach for this whenever an animation looks wrong (janky, stutters, snaps, only updates once, or doesn't move at all), a swipe/transition/gesture misbehaves or feels broken, scrolling drops frames or feels slow, the UI renders the wrong tree/props, or a screen reader can't reach something — and drive the peach CLI debug tooling first, not screenshots or ad-hoc logging.
4
+ description: Debug rendering, animation, gesture, performance, and accessibility issues in a React Native app under Peach. Use when an animation janks, snaps, or stalls, a gesture misbehaves, scrolling drops frames, the tree or props look wrong, or a screen reader cannot reach something. Use the peach CLI first.
5
5
  ---
6
6
 
7
7
  # Peach debug
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: peach-perf
3
3
  model: sm
4
- description: Optimize Peach engine and app performance — scroll jank, dropped frames, slow animation, idle CPU, memory growth, and "smooth normally but laggy under load" (video calls, busy machines). Covers measuring on the right thread with the right browser, proving each perf tier actually engages via its counters, a CPU-contention test harness, and the map of existing perf machinery so you improve it instead of reinventing it. For general app debugging load /peach-debug first.
4
+ description: Optimize Peach engine and app performance: scroll jank, dropped frames, slow animation, idle CPU, memory growth, and lag under load. Covers measuring on the right thread, proving perf tiers engage via counters, a CPU-contention harness, and perf machinery. For debugging load peach-debug first.
5
5
  ---
6
6
 
7
7
  # Peach performance
@@ -56,7 +56,8 @@ debugging or `/peach-test` for writing tests.
56
56
  `--new`, `--profile`, and `--ephemeral` create another isolated browser
57
57
  tree. use them only when separate storage or a genuinely concurrent sim is
58
58
  required. repeated new trees consume enough memory and CPU to starve other
59
- work on a shared machine.
59
+ work on a shared machine. `--ephemeral` also boots on default settings and
60
+ keeps its setting changes to itself, so use it for proof captures.
60
61
  - **pin the sim before driving it.** `peach open` records the new sim for the
61
62
  current CLI identity. read its id and pin it explicitly before a longer flow:
62
63
 
@@ -39,6 +39,14 @@ Do not invent a third runner for how a Maestro flow was authored.
39
39
  - **pin a sim before running a suite.** parallel CI jobs colliding
40
40
  on the default bridge port is the #1 flake source. pass `--port` in
41
41
  CI and `peach use <id>` locally.
42
+ - **admit a bounded stack and flow together.** if your environment requires
43
+ resource admission, keep stack startup, readiness, sim opening and pinning,
44
+ the flow, and cleanup of your owned stack in one admitted process group.
45
+ A stack started in an admitted background command can hold the slot that a
46
+ later tool call needs for the flow. Nested commands must inherit the real
47
+ admission; never set admission flags by hand. Before the flow, confirm
48
+ `peach describe --sim <id>` returns a tree: a saved sim id does not prove
49
+ the connection survived the wait.
42
50
  - **every flow ends with an assert, not a screenshot.** a screenshot
43
51
  passes as long as the flow didn't throw. `assertVisible` (or
44
52
  `expect(...).toBeVisible()`) actually verifies the happy path.
@@ -4,6 +4,46 @@ export interface SwiftImageSource {
4
4
  scale: number
5
5
  }
6
6
 
7
+ // the scale variants stay available until the runtime knows its device.
8
+ export type SwiftImageVariants = SwiftImageSource[]
9
+
10
+ export function isSwiftImageVariants(value: unknown): value is SwiftImageVariants {
11
+ return (
12
+ Array.isArray(value) &&
13
+ value.length > 0 &&
14
+ value.every(
15
+ (image) =>
16
+ typeof image === 'object' &&
17
+ image !== null &&
18
+ typeof image.uri === 'string' &&
19
+ typeof image.scale === 'number' &&
20
+ Number.isFinite(image.scale) &&
21
+ image.scale > 0,
22
+ )
23
+ )
24
+ }
25
+
26
+ // react native's asset scale rule, shared by metro and native catalog images.
27
+ export function assetScaleForDevice(scales: readonly number[], target: number): number {
28
+ for (const scale of scales) if (scale >= target) return scale
29
+ return scales[scales.length - 1] ?? 1
30
+ }
31
+
32
+ export function scaledImageSource(
33
+ variants: SwiftImageVariants | undefined,
34
+ deviceScale: number,
35
+ ): SwiftImageSource | undefined {
36
+ if (!variants) return undefined
37
+ const scale = assetScaleForDevice(
38
+ variants.map((image) => image.scale),
39
+ deviceScale,
40
+ )
41
+ return variants.find((image) => image.scale === scale)
42
+ }
43
+
44
+ // relative bundle path to the asset names owned by that bundle; '.' is main.
45
+ export type SwiftImageResources = Record<string, Record<string, SwiftImageVariants>>
46
+
7
47
  // an app catalog color resolves by appearance or names a system color.
8
48
  export type AppCatalogColor = { light: string; dark: string } | { system: string }
9
49
 
@@ -11,6 +51,7 @@ export type AppCatalogColor = { light: string; dark: string } | { system: string
11
51
  export interface SfSymbolImageDrawing {
12
52
  referencePointSize: number
13
53
  basePointSize: number
54
+ baselineOffsetUnrounded: number
14
55
  referenceCanvasSize: readonly [number, number]
15
56
  insets: readonly [number, number, number, number]
16
57
  layers: readonly { d: string; opacity: number; eraser: boolean }[]
@@ -26,8 +67,9 @@ export function sfSymbolImageDimensions(
26
67
  scale: number,
27
68
  ) {
28
69
  const glyphScale = (pointSize * scale) / drawing.referencePointSize
70
+ // negative alignment bearings do not become negative UIImage content padding.
29
71
  const insets = drawing.insets.map((inset) =>
30
- Math.round((inset * pointSize * scale) / drawing.basePointSize),
72
+ Math.max(0, Math.round((inset * pointSize * scale) / drawing.basePointSize)),
31
73
  )
32
74
  return {
33
75
  glyphScale,
@@ -64,6 +64,17 @@ function optionalNumber(
64
64
  : numberValue(record[key], `${path}.${key}`)
65
65
  }
66
66
 
67
+ function optionalPointerId(
68
+ record: Record<string, unknown>,
69
+ path: string,
70
+ ): number | undefined {
71
+ const pointerId = optionalNumber(record, 'pointerId', path)
72
+ if (pointerId !== undefined && !Number.isSafeInteger(pointerId)) {
73
+ validationFailure(`${path}.pointerId`, 'must be a safe integer')
74
+ }
75
+ return pointerId
76
+ }
77
+
67
78
  function optionalBoolean(
68
79
  record: Record<string, unknown>,
69
80
  key: string,
@@ -96,7 +107,7 @@ export function parsePerformStep(value: unknown, index: number): SimPerformStep
96
107
  case 'touchDown':
97
108
  case 'touchMove':
98
109
  case 'touchUp': {
99
- const pointerId = optionalNumber(step, 'pointerId', path)
110
+ const pointerId = optionalPointerId(step, path)
100
111
  return {
101
112
  type,
102
113
  x: numberValue(step.x, `${path}.x`),
@@ -105,7 +116,7 @@ export function parsePerformStep(value: unknown, index: number): SimPerformStep
105
116
  }
106
117
  }
107
118
  case 'touchCancel': {
108
- const pointerId = optionalNumber(step, 'pointerId', path)
119
+ const pointerId = optionalPointerId(step, path)
109
120
  return { type, ...(pointerId === undefined ? {} : { pointerId }) }
110
121
  }
111
122
  case 'tap':
@@ -282,38 +293,54 @@ export function parsePerformStep(value: unknown, index: number): SimPerformStep
282
293
  }
283
294
  }
284
295
 
285
- // an unclosed touchDown leaves a pointer stuck down, and the batch abort path
286
- // would cancel it after the caller was told the batch failed. every batch is
287
- // self-contained (pointers never survive it), so a touchDown without its
288
- // touchUp or touchCancel in the same batch is rejected before anything runs.
289
- export function findUnbalancedTouchDown(
296
+ // every batch is self-contained, so a pointer must be opened before it moves
297
+ // or closes and every opened pointer must close before the batch ends.
298
+ export function findInvalidPointerSequence(
290
299
  steps: readonly SimPerformStep[],
291
- ): { index: number; pointerId: number } | null {
300
+ ): { index: number; error: string } | null {
292
301
  const open = new Map<number, number>()
293
- const pointerIdOf = (step: SimPerformStep): number | null => {
302
+ for (const [index, step] of steps.entries()) {
294
303
  if (
295
304
  step.type !== 'touchDown' &&
296
305
  step.type !== 'touchMove' &&
297
306
  step.type !== 'touchUp' &&
298
307
  step.type !== 'touchCancel'
299
308
  ) {
300
- return null
309
+ continue
301
310
  }
302
311
  const pointerId = step.pointerId ?? 999
303
- return Number.isSafeInteger(pointerId) ? pointerId : null
304
- }
305
- for (const [index, step] of steps.entries()) {
312
+ if (!Number.isSafeInteger(pointerId)) {
313
+ return {
314
+ index,
315
+ error: `perform.steps[${index}] (${step.type}) has an invalid pointer id`,
316
+ }
317
+ }
306
318
  if (step.type === 'touchDown') {
307
- const pointerId = pointerIdOf(step) ?? 999
308
- if (!open.has(pointerId)) open.set(pointerId, index)
309
- } else if (step.type === 'touchUp' || step.type === 'touchCancel') {
310
- const pointerId = pointerIdOf(step)
311
- if (pointerId !== null) open.delete(pointerId)
319
+ if (open.has(pointerId)) {
320
+ return {
321
+ index,
322
+ error: `perform.steps[${index}] (touchDown) starts pointer ${pointerId} before its previous touchUp or touchCancel`,
323
+ }
324
+ }
325
+ open.set(pointerId, index)
326
+ continue
327
+ }
328
+ if (!open.has(pointerId)) {
329
+ return {
330
+ index,
331
+ error: `perform.steps[${index}] (${step.type}) has no matching touchDown for pointer ${pointerId}`,
332
+ }
333
+ }
334
+ if (step.type === 'touchUp' || step.type === 'touchCancel') {
335
+ open.delete(pointerId)
312
336
  }
313
337
  }
314
338
  const next = open.entries().next()
315
339
  if (next.done) return null
316
- return { index: next.value[1], pointerId: next.value[0] }
340
+ return {
341
+ index: next.value[1],
342
+ error: `perform.steps[${next.value[1]}] (touchDown) has no matching touchUp or touchCancel for pointer ${next.value[0]}`,
343
+ }
317
344
  }
318
345
 
319
346
  export function parseStateOptions(value: unknown): SimStateOptions {
@@ -374,7 +374,6 @@ export const SEMANTIC_READY_CONTENT_NODE_FLOOR = 100
374
374
  export const SEMANTIC_READY_NODE_STABLE_MS = 750
375
375
  export const SEMANTIC_READY_POLL_MS = 150
376
376
 
377
- export const PUBLIC_NO_NETWORK_MARKER = 'public Peach Cloud simulators have no network'
378
377
  export const HEADLESS_TENANT_FETCH_HUNG_MARKER = 'headless tenant fetch hung'
379
378
 
380
379
  export function readyProbeHasContent(