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
@@ -14,8 +14,10 @@ import { createRequire } from 'module'
14
14
  import { runInNewContext, Script } from 'node:vm'
15
15
  import path from 'path'
16
16
  import { fileURLToPath, pathToFileURL } from 'url'
17
- import { minify } from 'terser'
17
+ import { parseSync } from 'oxc-parser'
18
+ import { minifySync, transformSync } from 'rolldown/utils'
18
19
  import { type Plugin, transformWithOxc } from 'vite'
20
+ import { renderWorkerSystemChunk } from './worker-system-chunks.ts'
19
21
 
20
22
  declare module '@babel/core' {
21
23
  interface TransformCaller {
@@ -34,7 +36,10 @@ import {
34
36
  reactNativeDeepStubsForBuildResolver,
35
37
  } from '../../peach-compat/src/stub-manifest.ts'
36
38
  import { WEBVIEW_SAME_ORIGIN_BOOTSTRAP_SOURCE } from '../../peach-engine/src/components/native-ui/webview-frame-bridge.ts'
37
- import { isExpoHostSocketModule } from '../../peach-engine/src/guest-module-overrides.ts'
39
+ import {
40
+ isExpoHostSocketModule,
41
+ isReactNativeEnvironmentSetupModule,
42
+ } from '../../peach-engine/src/guest-module-overrides.ts'
38
43
  import { readPeachWarmManifest } from '../../peach-engine/src/peach-warm-manifest.ts'
39
44
  import { resolveSootsimBridgePort } from './bridge-constants.ts'
40
45
  import { isRecord } from './is-record.ts'
@@ -91,6 +96,10 @@ const sootsimBrowserSourceAliases = [
91
96
  find: 'peachsim/bridge-constants',
92
97
  replacement: path.resolve(sootsimRoot, 'src/bridge-constants.ts'),
93
98
  },
99
+ {
100
+ find: 'peachsim/config',
101
+ replacement: path.resolve(sootsimRoot, 'src/config.ts'),
102
+ },
94
103
  {
95
104
  find: 'peachsim/dev-bundle-resolution',
96
105
  replacement: path.resolve(sootsimRoot, 'src/dev-bundle-resolution.ts'),
@@ -136,6 +145,19 @@ const rnLibraryStubs = Object.fromEntries(
136
145
  ]),
137
146
  )
138
147
 
148
+ function resolveReactNativeLibraryStub(source: string, importer?: string) {
149
+ const candidate =
150
+ source.startsWith('.') && importer
151
+ ? path.resolve(path.dirname(stripViteQuery(importer)), stripViteQuery(source))
152
+ : stripViteQuery(source)
153
+ const match = /(?:^|\/)(react-native\/Libraries\/.+)$/.exec(candidate)
154
+ if (!match) return undefined
155
+ const specifier = match[1]
156
+ .replace(/\.(?:[cm]?[jt]sx?)$/, '')
157
+ .replace(/\.(?:native|web|ios|android)$/, '')
158
+ return rnLibraryStubs[specifier]
159
+ }
160
+
139
161
  type PublishedBrowserPackage = {
140
162
  specifier: string
141
163
  packageDir: string
@@ -356,7 +378,7 @@ export function sootsim(options: SootSimOptions = {}): Plugin[] {
356
378
  },
357
379
  },
358
380
  sootsimConfigPlugin(appDir, extraSources, publishedBrowserPackages),
359
- expoHostSocketBootstrapRedirect(),
381
+ hostEnvironmentBootstrapRedirect(),
360
382
  fixJsxRuntimeExports(),
361
383
  flowStripTransform(),
362
384
  nodeModulesJsxTransform(),
@@ -375,31 +397,56 @@ export function sootsim(options: SootSimOptions = {}): Plugin[] {
375
397
  metroNativeResolve(isAppSource),
376
398
  reactNativeRequirePlugin(isAppSource),
377
399
  ...(appDir ? [externalAppResolvePlugin(appDir, isAppSource, ownedPackages)] : []),
378
- ...(appDir || extraSources.length > 0
379
- ? [externalAppTransformPlugin(isAppSource)]
380
- : []),
400
+ externalAppTransformPlugin(isAppSource),
381
401
  stubMissingNativeDeps(appDir),
382
402
  stubMissingImages(isAppSource),
383
403
  ]
384
404
  }
385
405
 
386
- function expoHostSocketBootstrapRedirect(): Plugin {
387
- const target = '\0sootsim:expo-host-socket-bootstrap'
406
+ function hostEnvironmentBootstrapRedirect(): Plugin {
407
+ const target = '\0peach:host-environment-bootstrap'
408
+ const isHostBootstrap = (id: string) =>
409
+ isExpoHostSocketModule(id) ||
410
+ ((id.startsWith('react-native/') || id.includes('/react-native/')) &&
411
+ isReactNativeEnvironmentSetupModule(id))
388
412
  return {
389
- name: 'sootsim-expo-host-socket-bootstrap',
413
+ name: 'peach-host-environment-bootstrap',
390
414
  enforce: 'pre',
391
415
  resolveId(source, importer) {
392
- if (isExpoHostSocketModule(stripViteQuery(source))) return target
416
+ if (source === target || isHostBootstrap(stripViteQuery(source))) return target
393
417
  if (!importer || !source.startsWith('.')) return null
394
418
  const candidate = path.resolve(
395
419
  path.dirname(stripViteQuery(importer)),
396
420
  stripViteQuery(source),
397
421
  )
398
- return isExpoHostSocketModule(candidate) ? target : null
422
+ return isHostBootstrap(candidate) ? target : null
399
423
  },
400
424
  load(id) {
401
425
  if (id !== target) return null
402
- return 'export default function noopExpoHostSocketBootstrap() {}'
426
+ return 'export default function noopHostEnvironmentBootstrap() {}'
427
+ },
428
+ transform: {
429
+ filter: { id: /\/node_modules\//, code: /require\(/ },
430
+ handler(code, id) {
431
+ let changed = false
432
+ const rewritten = code.replace(
433
+ /\brequire\((['"])([^'"]+)\1\)/g,
434
+ (call, _quote, source: string) => {
435
+ const candidate = source.startsWith('.')
436
+ ? path.resolve(path.dirname(stripViteQuery(id)), source)
437
+ : source
438
+ if (!isHostBootstrap(candidate)) return call
439
+ changed = true
440
+ return '__peach_host_bootstrap__'
441
+ },
442
+ )
443
+ return changed
444
+ ? {
445
+ code: `import * as __peach_host_bootstrap__ from ${JSON.stringify(target)};\n${rewritten}`,
446
+ map: null,
447
+ }
448
+ : null
449
+ },
403
450
  },
404
451
  }
405
452
  }
@@ -575,24 +622,20 @@ function warmManifestJsonPlugin(): Plugin {
575
622
  }
576
623
  }
577
624
 
578
- // a worker cannot modulepreload, so every static level under its
579
- // implementation root and every dynamic import on its boot path is a serial
580
- // round trip before the worker is ready. this plugin bundles each declared
581
- // worker into one file and fails the build when that file grows past its byte
582
- // cap, so a worker feature cannot silently add startup bytes.
583
- //
584
- // the entries stay export-free and import-free so webkit keeps the one-way
585
- // dynamic boundary (see shell-worker-entry.ts); with every chunk inlined no
586
- // other file can import the worker script, so nothing can evaluate it twice.
587
- // inlined dynamic roots still evaluate at their `import()` (the reanimated
588
- // passthrough must install before upstream reanimated, the worklet runtime
589
- // must exist before shell-in-worker): with code splitting off rolldown wraps
590
- // every dynamic-import target in an init function that runs on first import.
591
- // shell-worker.ts throws at init if a bundle ever hoists one of them.
625
+ // workers load feature chunks on demand through one script-compatible module
626
+ // protocol. the entry owns its loader and remains a one-way bootstrap so lazy
627
+ // chunks never re-evaluate a worker's message listeners. the boot byte cap
628
+ // applies to that entry; cold Home proof also measures the downloaded startup
629
+ // graph and serial requests, since moving bytes into chunks alone is no gain.
630
+ // reanimated and shell-in-worker still evaluate at their original import()
631
+ // boundaries after the worklet runtime installs.
592
632
  export type WorkerBootClosure = {
593
633
  // substring of the worker entry module id this applies to
594
634
  entry: string
595
- // byte cap on the rendered worker file; fix excess boot dependencies and
635
+ // implementation roots loaded before Home; only their static dependencies
636
+ // share a chunk. other dynamic roots retain their first-use boundaries.
637
+ bootRoots?: readonly string[]
638
+ // byte cap on the rendered entry chunk; fix excess boot dependencies and
596
639
  // unneeded inclusions rather than raising the cap.
597
640
  // a cap failure in a worktree whose node_modules is symlinked from another
598
641
  // tree is suspect: workspace modules resolve into the other tree and bundle
@@ -600,31 +643,57 @@ export type WorkerBootClosure = {
600
643
  maxBytes: number
601
644
  }
602
645
 
646
+ const SHELL_WORKER_BOOT_ROOTS = [
647
+ 'src/render-worker/shell-worker-canvaskit.ts',
648
+ 'src/render-worker/shell-worker.ts',
649
+ 'src/render-worker/shell-reanimated-boot.ts',
650
+ 'src/render-worker/shell-in-worker.tsx',
651
+ 'src/render-worker/aux-roots.tsx',
652
+ 'src/ios/HomeTreeRoot.tsx',
653
+ 'src/ios/connect-app-icon.ts',
654
+ ]
655
+ const COMPOSITOR_WORKER_BOOT_ROOTS = [
656
+ 'src/render-worker/compositor-worker-adapter.ts',
657
+ 'src/render-worker/compositor-worker.ts',
658
+ 'src/render-worker/shell-tree-materializer.ts',
659
+ ]
660
+ const GRAPHITE_WORKER_BOOT_ROOTS = [
661
+ 'src/render-worker/canvaskit-webgpu.ts',
662
+ 'src/render-worker/compositor-gpu-graphite.ts',
663
+ 'vendor/canvaskit-graphite.js',
664
+ ]
665
+
603
666
  const ENGINE_WORKER_BOOT_CLOSURES: WorkerBootClosure[] = [
604
- // these workers inline their dynamic imports, so anything reachable from the
605
- // entry lands in the boot file whether or not it runs. that is why the font
606
- // parser is not reachable from any of them: only the tenant reads a face's
607
- // name table, and it sends the aliases and metrics it read on the wire
667
+ // only the tenant reads a face's name table, and it sends the aliases and
668
+ // metrics it read on the wire
608
669
  // (engine/app-font-registry.ts registerForwardedAppFonts). keep it that way
609
670
  // when adding font work here, or every cap takes the parser again.
610
- // measured 3,406,869 bytes after the shell engine config split: the shell
611
- // worker installs a slim config without the list, modal, slider, swipeable,
612
- // and tab components nothing shell-side reads (compat-bridge composes the
613
- // full config for main, tenant, headless, and external realms on top of
614
- // it), and the gesture-handler stub imports TouchableNativeFeedback
615
- // statically instead of require()ing the whole react-native namespace into
616
- // the boot file. the shell worker is one file per compositor backend, so
617
- // each carries only its own CanvasKit glue: 3,263,311 graphite and
618
- // 3,256,832 ganesh bytes when the split landed, down from 3,386,929 with both.
619
- { entry: 'src/render-worker/shell-worker-entry.ts', maxBytes: 3_310_000 },
671
+ // each entry selects one backend; its chunks retain only that backend's
672
+ // CanvasKit glue. feature imports keep their evaluation boundaries.
673
+ {
674
+ entry: 'src/render-worker/shell-worker-entry.ts',
675
+ maxBytes: 3_310_000,
676
+ bootRoots: [...SHELL_WORKER_BOOT_ROOTS, ...GRAPHITE_WORKER_BOOT_ROOTS],
677
+ },
620
678
  // nate, 2026-10-06: "we can increase Ganesh. We can increase that to like 3.6",
621
679
  // as work moves from the javascript into native code.
622
- { entry: 'src/render-worker/shell-worker-ganesh-entry.ts', maxBytes: 3_600_000 },
623
- { entry: 'src/render-worker/compositor-worker-entry.ts', maxBytes: 1_200_000 },
624
- // both compositor entries boot the one compositor-worker.ts and differ only
625
- // in their backend and CanvasKit glue (plans/peach/compositor-one-path.md):
626
- // 1,123,002 ganesh bytes when the merge landed, against 1,173,302 graphite.
627
- { entry: 'src/render-worker/compositor-worker-ganesh-entry.ts', maxBytes: 1_150_000 },
680
+ {
681
+ entry: 'src/render-worker/shell-worker-ganesh-entry.ts',
682
+ maxBytes: 3_600_000,
683
+ bootRoots: [...SHELL_WORKER_BOOT_ROOTS, 'vendor/canvaskit-ganesh.js'],
684
+ },
685
+ {
686
+ entry: 'src/render-worker/compositor-worker-entry.ts',
687
+ maxBytes: 1_200_000,
688
+ bootRoots: [...COMPOSITOR_WORKER_BOOT_ROOTS, ...GRAPHITE_WORKER_BOOT_ROOTS],
689
+ },
690
+ // both entries boot compositor-worker.ts with their backend and CanvasKit
691
+ // glue (plans/peach/compositor-one-path.md).
692
+ {
693
+ entry: 'src/render-worker/compositor-worker-ganesh-entry.ts',
694
+ maxBytes: 1_150_000,
695
+ bootRoots: [...COMPOSITOR_WORKER_BOOT_ROOTS, 'vendor/canvaskit-ganesh.js'],
696
+ },
628
697
  ]
629
698
 
630
699
  // packages whose manifests omit `sideEffects` (metro never tree-shakes, so
@@ -644,9 +713,64 @@ const WORKER_SIDE_EFFECT_FREE_PACKAGES = [
644
713
  },
645
714
  ]
646
715
 
716
+ function collectStaticBootModules(
717
+ context: {
718
+ getModuleIds(): IterableIterator<string>
719
+ getModuleInfo(id: string): { importedIds: string[] } | null
720
+ },
721
+ roots: readonly string[],
722
+ ) {
723
+ const modules = new Set<string>()
724
+ const visit = (id: string) => {
725
+ if (modules.has(id)) return
726
+ modules.add(id)
727
+ for (const dependency of context.getModuleInfo(id)?.importedIds ?? [])
728
+ visit(dependency)
729
+ }
730
+ for (const id of context.getModuleIds())
731
+ if (roots.some((root) => id.endsWith(`/${root}`))) visit(id)
732
+ return modules
733
+ }
734
+
735
+ // the host's already-eager static closure shares one download. optional host
736
+ // services keep their dynamic imports and ordered first-use evaluation.
737
+ export function hostBootChunkPlugin(): Plugin {
738
+ let modules = new Set<string>()
739
+ return {
740
+ name: 'peach-host-boot-chunk',
741
+ apply: 'build',
742
+ buildEnd(error) {
743
+ if (!error) {
744
+ modules = collectStaticBootModules(this, ['src/main.tsx'])
745
+ // the entry must retain its filename and manifest identity.
746
+ for (const id of modules) if (id.endsWith('/src/main.tsx')) modules.delete(id)
747
+ }
748
+ },
749
+ outputOptions(options) {
750
+ return {
751
+ ...options,
752
+ // host resource urls are relative to the runtime root.
753
+ chunkFileNames: (chunk) =>
754
+ chunk.name === 'host-home' ? 'host-home-[hash].js' : 'assets/[name]-[hash].js',
755
+ strictExecutionOrder: true,
756
+ codeSplitting: {
757
+ groups: [
758
+ {
759
+ name: 'host-home',
760
+ test: (id) => modules.has(id),
761
+ includeDependenciesRecursively: false,
762
+ },
763
+ ],
764
+ },
765
+ }
766
+ },
767
+ }
768
+ }
769
+
647
770
  export function workerBootChunkPlugin(closures: WorkerBootClosure[]): Plugin {
648
771
  let closure: WorkerBootClosure | null = null
649
772
  let isWorker = false
773
+ let bootModules = new Set<string>()
650
774
  return {
651
775
  name: 'sootsim-worker-boot-chunk',
652
776
  apply: 'build',
@@ -667,48 +791,59 @@ export function workerBootChunkPlugin(closures: WorkerBootClosure[]): Plugin {
667
791
  treeshake: { ...treeshake, moduleSideEffects: WORKER_SIDE_EFFECT_FREE_PACKAGES },
668
792
  }
669
793
  },
794
+ buildEnd(error) {
795
+ if (!isWorker || error || !closure?.bootRoots) return
796
+ bootModules = collectStaticBootModules(this, closure.bootRoots)
797
+ },
670
798
  outputOptions(options) {
671
799
  if (!isWorker) return null
672
- return { ...options, codeSplitting: false, format: 'es', strict: true }
800
+ return {
801
+ ...options,
802
+ codeSplitting: closure?.bootRoots
803
+ ? {
804
+ groups: [
805
+ {
806
+ name: 'worker-home',
807
+ test: (id) => bootModules.has(id),
808
+ includeDependenciesRecursively: false,
809
+ },
810
+ ],
811
+ }
812
+ : true,
813
+ strictExecutionOrder: true,
814
+ format: 'es',
815
+ strict: true,
816
+ // keep retained chunk source one-byte even when literals contain unicode.
817
+ minify: { codegen: { asciiOnly: true } },
818
+ }
673
819
  },
674
820
  renderChunk: {
675
821
  order: 'post',
676
822
  async handler(code, chunk) {
677
823
  if (!isWorker) return null
678
- // vite introduces relative child-worker urls during rendering. resolve
679
- // their module base after that pass, using syntax-aware replacement.
680
- const transformed = await transformWithOxc(code, chunk.fileName, {
681
- define: { 'import.meta.url': 'globalThis.location.href' },
682
- sourcemap: true,
683
- })
684
- if (!transformed.map) this.error('worker transform produced no source map')
685
- const compact = await minify(transformed.code, {
686
- module: true,
687
- compress: { passes: 4 },
688
- mangle: true,
689
- sourceMap: { content: JSON.stringify(transformed.map) },
690
- })
691
- if (!compact.code) this.error('worker minification produced no code')
692
- if (typeof compact.map !== 'string')
693
- this.error('worker minification produced no encoded source map')
694
- return { code: compact.code, map: compact.map }
824
+ // lower vite's final module urls into the shared worker chunk protocol.
825
+ // each chunk retains its own module base rather than the entry's location.
826
+ return renderWorkerSystemChunk(code, chunk.isEntry)
695
827
  },
696
828
  },
697
829
  generateBundle(_options, bundle) {
698
830
  if (!isWorker) return
699
831
  const chunks = Object.values(bundle).filter((output) => output.type === 'chunk')
700
- if (chunks.length !== 1 || !chunks[0].isEntry) {
832
+ const entries = chunks.filter((chunk) => chunk.isEntry)
833
+ if (entries.length !== 1) {
701
834
  this.error(
702
- `worker ${closure?.entry ?? 'entry'}: expected one inlined chunk, got ${chunks.map((chunk) => chunk.fileName).join(', ')}`,
835
+ `worker ${closure?.entry ?? 'entry'}: expected one boot entry, got ${entries.map((chunk) => chunk.fileName).join(', ')}`,
703
836
  )
704
837
  }
705
838
  // the native host evaluates the same module body in an async strict closure.
706
839
  // reject remaining module syntax before it can reach JSContext.
707
- new Script(`(async () => { 'use strict';\n${chunks[0].code}\n})()`)
840
+ for (const chunk of chunks)
841
+ new Script(`(async () => { 'use strict';\n${chunk.code}\n})()`)
708
842
  if (!closure) return
709
- const bytes = Buffer.byteLength(chunks[0].code)
843
+ const bootChunk = entries[0]
844
+ const bytes = Buffer.byteLength(bootChunk.code)
710
845
  if (bytes > closure.maxBytes) {
711
- const chunk = chunks[0]
846
+ const chunk = bootChunk
712
847
  const pkgs = new Map<string, Set<string>>()
713
848
  for (const id of chunk.moduleIds) {
714
849
  // a virtual `\0` id (the jsx-runtime re-export shim) names a
@@ -745,7 +880,7 @@ export function workerBootChunkPlugin(closures: WorkerBootClosure[]): Plugin {
745
880
  `${module.renderedLength} ${path.relative(process.cwd(), id)}`,
746
881
  )
747
882
  this.error(
748
- `worker ${closure.entry}: ${chunks[0].fileName} is ${bytes} bytes, over the ${closure.maxBytes} byte cap; eliminate excess boot dependencies rather than raising the cap${dupDetail}\nlargest modules (rendered bytes):\n ${largest.join('\n ')}`,
883
+ `worker ${closure.entry}: ${bootChunk.fileName} is ${bytes} bytes, over the ${closure.maxBytes} byte cap; eliminate excess boot dependencies rather than raising the cap${dupDetail}\nlargest modules (rendered bytes):\n ${largest.join('\n ')}`,
749
884
  )
750
885
  }
751
886
  },
@@ -1021,10 +1156,12 @@ export function manifestNativeSeamRedirect(): Plugin {
1021
1156
  }
1022
1157
  const targets = new Set(NATIVE_SEAM_MANIFEST.map((entry) => entry.target))
1023
1158
  const sourceFilter = new RegExp(
1024
- `(?:^|/)(?:${[...byPkg.values()]
1025
- .flatMap((seams) => [...seams.keys()])
1159
+ `(?:^|/)(?:${[
1160
+ ...[...byPkg.values()].flatMap((seams) => [...seams.keys()]),
1161
+ ...Object.keys(rnLibraryStubs).map((specifier) => path.posix.basename(specifier)),
1162
+ ]
1026
1163
  .map((basename) => basename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
1027
- .join('|')})(?:\\.(?:native|web|ios|android))?$`,
1164
+ .join('|')})(?:\\.(?:native|web|ios|android))?(?:\\.[cm]?[jt]sx?)?$`,
1028
1165
  )
1029
1166
  return {
1030
1167
  name: 'sootsim-manifest-native-seam-redirect',
@@ -1032,24 +1169,27 @@ export function manifestNativeSeamRedirect(): Plugin {
1032
1169
  resolveId: {
1033
1170
  filter: { id: sourceFilter },
1034
1171
  handler(source, importer) {
1172
+ const nativeLibraryStub = resolveReactNativeLibraryStub(source, importer)
1173
+ if (nativeLibraryStub) return nativeLibraryStub
1035
1174
  if (!importer || byPkg.size === 0 || targets.has(importer)) return null
1036
- // find the package whose path appears in the importer. matches both
1175
+ // find the innermost package whose path appears in the importer. matches both
1037
1176
  // `/<pkg>/` (unscoped) and `/@scope/<pkg>/` (scoped — the leading `@`
1038
1177
  // is part of the pkg name in the manifest, e.g. `@notifee/react-native`).
1039
1178
  let seams: Map<string, { target: string; notes?: string }> | undefined
1040
1179
  let matchedPkg: string | undefined
1180
+ let matchedIndex = -1
1041
1181
  for (const [pkg, entry] of byPkg) {
1042
- if (importer.includes(`/${pkg}/`)) {
1182
+ const index = importer.lastIndexOf(`/${pkg}/`)
1183
+ if (index > matchedIndex) {
1043
1184
  seams = entry
1044
1185
  matchedPkg = pkg
1045
- break
1186
+ matchedIndex = index
1046
1187
  }
1047
1188
  }
1048
1189
  if (!seams) return null
1049
- const basename = (source.split('/').pop() ?? '').replace(
1050
- /\.(native|web|ios|android)$/,
1051
- '',
1052
- )
1190
+ const basename = (source.split('/').pop() ?? '')
1191
+ .replace(/\.[cm]?[jt]sx?$/, '')
1192
+ .replace(/\.(native|web|ios|android)$/, '')
1053
1193
  const seam = seams.get(basename)
1054
1194
  if (!seam) return null
1055
1195
  if (process.env.SOOTSIM_NATIVE_SEAM_DEBUG) {
@@ -1070,7 +1210,7 @@ export function manifestNativeSeamRedirect(): Plugin {
1070
1210
  }
1071
1211
 
1072
1212
  interface ViteBridgeProcessEntry {
1073
- boundPort: Promise<number | null>
1213
+ boundPort: Promise<number>
1074
1214
  close: () => Promise<void>
1075
1215
  owners: Set<object>
1076
1216
  shellPort: { read: () => number | null }
@@ -1106,7 +1246,7 @@ export function wsBridgePlugin(options: { ownedBridgePort?: number } = {}): Plug
1106
1246
  // websocket console error during boot.
1107
1247
  // a parent that already owns a bridge supplies its receipt here. the shell
1108
1248
  // publishes that exact port and never starts a competing development bridge.
1109
- let boundPort: Promise<number | null> | null =
1249
+ let boundPort: Promise<number> | null =
1110
1250
  options.ownedBridgePort === undefined
1111
1251
  ? null
1112
1252
  : Promise.resolve(options.ownedBridgePort)
@@ -1122,10 +1262,10 @@ export function wsBridgePlugin(options: { ownedBridgePort?: number } = {}): Plug
1122
1262
  async closeBundle() {
1123
1263
  await releaseBridge?.()
1124
1264
  },
1125
- configureServer(server) {
1265
+ async configureServer(server) {
1126
1266
  if (!boundPort) {
1127
1267
  const preferredPort = resolveSootsimBridgePort({
1128
- explicitPort: process.env.SOOTSIM_BRIDGE_PORT,
1268
+ explicitPort: process.env.PEACH_BRIDGE_PORT,
1129
1269
  portOffset: process.env.PORT_OFFSET,
1130
1270
  })
1131
1271
  const readShellPort = () => {
@@ -1141,8 +1281,7 @@ export function wsBridgePlugin(options: { ownedBridgePort?: number } = {}): Plug
1141
1281
  } else {
1142
1282
  // lazy import to avoid pulling ws into the main bundle.
1143
1283
  // note: the .ts extension is required on vite 8 / node ESM — without
1144
- // it, import() throws "Cannot find module" and the bridge silently
1145
- // never starts.
1284
+ // it, import() throws "Cannot find module" before the bridge starts.
1146
1285
  const shellPort = { read: readShellPort }
1147
1286
  const host = import('./host/bridge-host.ts').then(({ SootSimBridgeHost }) => {
1148
1287
  return new SootSimBridgeHost({
@@ -1154,13 +1293,12 @@ export function wsBridgePlugin(options: { ownedBridgePort?: number } = {}): Plug
1154
1293
  let entry: ViteBridgeProcessEntry
1155
1294
  boundPort = host
1156
1295
  .then((bridgeHost) => bridgeHost.startAsync({ silent: true }))
1157
- .catch((err: unknown) => {
1296
+ .catch(async (err: unknown) => {
1158
1297
  if (viteBridgeProcesses.get(preferredPort) === entry) {
1159
1298
  viteBridgeProcesses.delete(preferredPort)
1160
1299
  }
1161
- const message = err instanceof Error ? err.message : String(err)
1162
- console.warn('[peach] ws bridge failed to start:', message)
1163
- return null
1300
+ await entry.close()
1301
+ throw err
1164
1302
  })
1165
1303
  entry = {
1166
1304
  boundPort,
@@ -1185,31 +1323,27 @@ export function wsBridgePlugin(options: { ownedBridgePort?: number } = {}): Plug
1185
1323
  }
1186
1324
 
1187
1325
  if (!boundPort) throw new Error('peach bridge identity was not initialized')
1188
- const identityPort = boundPort
1326
+ // vite awaits this hook before it can listen. a failed bridge must also
1327
+ // close vite's watchers and transports instead of publishing a ready shell.
1328
+ let bridgePort: number
1329
+ try {
1330
+ bridgePort = await boundPort
1331
+ } catch (err) {
1332
+ await server.close()
1333
+ throw err
1334
+ }
1189
1335
  server.middlewares.use((req, res, next) => {
1190
1336
  if (req.url?.split('?')[0] !== SHELL_BRIDGE_IDENTITY_PATH) {
1191
1337
  next()
1192
1338
  return
1193
1339
  }
1194
- void identityPort
1195
- .then((bridgePort) => {
1196
- if (bridgePort === null) {
1197
- res.statusCode = 503
1198
- res.end('peach bridge unavailable')
1199
- return
1200
- }
1201
- const identity = {
1202
- schema: 1,
1203
- bridgePort,
1204
- } satisfies ShellBridgeIdentity
1205
- res.setHeader('Content-Type', 'application/json')
1206
- res.setHeader('Cache-Control', 'no-store')
1207
- res.end(JSON.stringify(identity))
1208
- })
1209
- .catch(() => {
1210
- res.statusCode = 503
1211
- res.end('peach bridge unavailable')
1212
- })
1340
+ const identity = {
1341
+ schema: 1,
1342
+ bridgePort,
1343
+ } satisfies ShellBridgeIdentity
1344
+ res.setHeader('Content-Type', 'application/json')
1345
+ res.setHeader('Cache-Control', 'no-store')
1346
+ res.end(JSON.stringify(identity))
1213
1347
  })
1214
1348
  },
1215
1349
  // publish the bound port to the page. head-prepend so it lands before the
@@ -1274,7 +1408,13 @@ function sootsimConfigPlugin(
1274
1408
  }),
1275
1409
  )
1276
1410
 
1277
- const dedupe = ['react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime']
1411
+ const dedupe = [
1412
+ 'react',
1413
+ 'react-dom',
1414
+ 'react/jsx-runtime',
1415
+ 'react/jsx-dev-runtime',
1416
+ 'expo-asset',
1417
+ ]
1278
1418
 
1279
1419
  // internal sootsim builds must keep the entire Tamagui package family on a
1280
1420
  // single module graph. mixing raw @fs modules with prebundled .vite/deps
@@ -1330,10 +1470,18 @@ function sootsimConfigPlugin(
1330
1470
  'react-dom',
1331
1471
  'react-dom/client',
1332
1472
  'react-reconciler',
1333
- 'react-reconciler/constants',
1473
+ 'react-reconciler/constants.js',
1334
1474
  'react/jsx-runtime',
1335
1475
  'react/jsx-dev-runtime',
1336
1476
  '@react-native/normalize-color',
1477
+ '@react-native/normalize-colors',
1478
+ // these upstream native entries contain CJS even after Flow is stripped.
1479
+ // force interop for workers, whose imports dependency discovery cannot see.
1480
+ 'react-native/asset-registry',
1481
+ '@react-native/assets-registry/registry',
1482
+ '@peach-internal/react-native/BatchedBridge/BatchedBridge',
1483
+ '@peach-internal/react-native/Components/View/ReactNativeStyleAttributes',
1484
+ '@peach-internal/react-native/StyleSheet/processColor',
1337
1485
  // CJS deps transitively imported by @gorhom/bottom-sheet and
1338
1486
  // @gorhom/portal (both excluded above). vite's CJS→ESM interop
1339
1487
  // only kicks in via pre-bundle, so these must be pre-bundled even
@@ -1367,6 +1515,17 @@ function sootsimConfigPlugin(
1367
1515
  ],
1368
1516
  },
1369
1517
  plugins: [
1518
+ flowStripTransform(),
1519
+ {
1520
+ name: 'peach-react-native-boundaries',
1521
+ resolveId(source: string, importer: string | undefined) {
1522
+ const nativeLibraryStub = resolveReactNativeLibraryStub(
1523
+ source,
1524
+ importer,
1525
+ )
1526
+ return nativeLibraryStub ? { id: nativeLibraryStub } : null
1527
+ },
1528
+ },
1370
1529
  {
1371
1530
  name: 'sootsim-published-browser-internals',
1372
1531
  resolveId(source: string, importer: string | undefined) {
@@ -1429,8 +1588,31 @@ function sootsimConfigPlugin(
1429
1588
  find: /^@react-native\/assets-registry\/registry$/,
1430
1589
  replacement: rnAssetRegistryPath,
1431
1590
  },
1591
+ // upstream virtualized-lists imports this private module by its bare
1592
+ // path, which react-native's exports map hides. load the installed
1593
+ // upstream javascript unchanged, including its native flag getters.
1594
+ {
1595
+ find: /^react-native\/src\/private\/featureflags\/ReactNativeFeatureFlags(?:\.js)?$/,
1596
+ replacement: path.resolve(
1597
+ workspaceNodeModules,
1598
+ 'react-native/src/private/featureflags/ReactNativeFeatureFlags.js',
1599
+ ),
1600
+ },
1601
+ ...[
1602
+ 'BatchedBridge/BatchedBridge',
1603
+ 'Components/View/ReactNativeStyleAttributes',
1604
+ 'StyleSheet/processColor',
1605
+ ].map((module) => ({
1606
+ find: `@peach-internal/react-native/${module}`,
1607
+ replacement: path.resolve(
1608
+ workspaceNodeModules,
1609
+ `react-native/Libraries/${module}.js`,
1610
+ ),
1611
+ })),
1432
1612
  ...Object.entries(rnLibraryStubs).map(([find, replacement]) => ({
1433
- find,
1613
+ find: new RegExp(
1614
+ `^${find.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\.[cm]?[jt]sx?)?$`,
1615
+ ),
1434
1616
  replacement,
1435
1617
  })),
1436
1618
  { find: /^react-native\/Libraries\/.*/, replacement: rnDeepStubDefault },
@@ -1501,6 +1683,7 @@ function reactBridgePlugin(): Plugin {
1501
1683
  importer.includes('/peachsim/src/') ||
1502
1684
  importer.includes('/peach-engine/src/') ||
1503
1685
  importer.includes('/peach-compat/src/') ||
1686
+ importer.includes('/peach-compat/dist-pressability/') ||
1504
1687
  importer.includes('/peach-kit/src/') ||
1505
1688
  importer.includes('react-reconciler')
1506
1689
 
@@ -1558,6 +1741,7 @@ export function workerReactBridgePlugin(): Plugin {
1558
1741
  importer.includes('/peachsim/src/') ||
1559
1742
  importer.includes('/peach-engine/src/') ||
1560
1743
  importer.includes('/peach-compat/src/') ||
1744
+ importer.includes('/peach-compat/dist-pressability/') ||
1561
1745
  importer.includes('/peach-kit/src/') ||
1562
1746
  importer.includes('react-reconciler') ||
1563
1747
  importer.includes('/node_modules/')
@@ -1652,6 +1836,10 @@ function sootsimEsbuildPlugin(appDir: string) {
1652
1836
  return {
1653
1837
  name: 'sootsim-esbuild',
1654
1838
  setup(build: any) {
1839
+ build.onResolve({ filter: /^(?:\.|\/)/ }, (args: any) => {
1840
+ const nativeLibraryStub = resolveReactNativeLibraryStub(args.path, args.importer)
1841
+ return nativeLibraryStub ? { path: nativeLibraryStub } : null
1842
+ })
1655
1843
  // metro-style .ios > .native resolution
1656
1844
  build.onResolve({ filter: /^\./ }, (args: any) => {
1657
1845
  if (!args.importer.includes('node_modules') && !isAppSourceEsbuild(args.importer))
@@ -1687,16 +1875,8 @@ function sootsimEsbuildPlugin(appDir: string) {
1687
1875
  })
1688
1876
 
1689
1877
  // react-native deep path stubs
1690
- for (const [importPath, stubPath] of Object.entries(rnLibraryStubs)) {
1691
- build.onResolve(
1692
- { filter: new RegExp(`^${importPath.replace(/\//g, '\\/')}$`) },
1693
- () => ({
1694
- path: stubPath,
1695
- }),
1696
- )
1697
- }
1698
1878
  build.onResolve({ filter: /^react-native\/Libraries\// }, (args: any) => {
1699
- return { path: rnLibraryStubs[args.path] || rnDeepStubDefault }
1879
+ return { path: resolveReactNativeLibraryStub(args.path) || rnDeepStubDefault }
1700
1880
  })
1701
1881
 
1702
1882
  // resolve builtin stubs (react-native-safe-area-context, expo-*, etc.)
@@ -1856,13 +2036,13 @@ function loadFlowBabel(): void {
1856
2036
  '@babel/plugin-syntax-jsx',
1857
2037
  ) as import('@babel/core').PluginItem
1858
2038
  }
1859
- function flowStripTransform(): Plugin {
2039
+ function flowStripTransform() {
1860
2040
  return {
1861
2041
  name: 'sootsim-flow-strip',
1862
2042
  enforce: 'pre',
1863
2043
  transform: {
1864
2044
  filter: {
1865
- id: /\/node_modules\/.*\.[cm]?jsx?$/,
2045
+ id: /\/node_modules\/.*\.[cm]?jsx?(?:\?.*)?$/,
1866
2046
  code: /@flow/,
1867
2047
  },
1868
2048
  handler(code, id) {
@@ -1879,7 +2059,7 @@ function flowStripTransform(): Plugin {
1879
2059
  return { code: result.code, map: result.map ?? null }
1880
2060
  },
1881
2061
  },
1882
- }
2062
+ } satisfies Plugin
1883
2063
  }
1884
2064
 
1885
2065
  function nodeModulesJsxTransform(): Plugin {
@@ -1888,7 +2068,7 @@ function nodeModulesJsxTransform(): Plugin {
1888
2068
  enforce: 'pre',
1889
2069
  transform: {
1890
2070
  filter: {
1891
- id: /\/node_modules\/.*\.js$/,
2071
+ id: /\/node_modules\/.*\.js(?:\?.*)?$/,
1892
2072
  code: /</,
1893
2073
  },
1894
2074
  async handler(code, id) {
@@ -2004,7 +2184,8 @@ function reactNativeRequirePlugin(isAppSource: (p: string) => boolean): Plugin {
2004
2184
  const importPath = match[1]
2005
2185
  if (!seenPaths.has(importPath)) {
2006
2186
  const varName = `__soot_rn_lib_${importCounter++}__`
2007
- const stubPath = rnLibraryStubs[importPath] || rnDeepStubDefault
2187
+ const stubPath =
2188
+ resolveReactNativeLibraryStub(importPath) || rnDeepStubDefault
2008
2189
  imports += `import * as ${varName} from ${JSON.stringify(stubPath)};\n`
2009
2190
  seenPaths.set(importPath, varName)
2010
2191
  }
@@ -2064,28 +2245,59 @@ function externalAppResolvePlugin(
2064
2245
 
2065
2246
  // convert require() calls and handle JSX in .js files from external app sources
2066
2247
  function externalAppTransformPlugin(isAppSource: (p: string) => boolean): Plugin {
2248
+ const defines: Record<string, string> = {}
2067
2249
  return {
2068
2250
  name: 'sootsim-external-app-transform',
2069
2251
  enforce: 'pre',
2252
+ configResolved(config) {
2253
+ for (const [name, value] of Object.entries(config.define ?? {}))
2254
+ defines[name] = typeof value === 'string' ? value : JSON.stringify(value)
2255
+ },
2070
2256
  transform: {
2071
2257
  filter: { code: /require\(|</ },
2072
2258
  async handler(code, id) {
2073
- if (!isAppSource(id)) return null
2259
+ // expo's winter polyfills use Metro-local requires in lazy getters.
2260
+ // their module imports must never reach the host's native-package registry.
2261
+ if (!isAppSource(id) && !/\/node_modules\/expo\/(?:src|build)\/winter\//.test(id))
2262
+ return null
2074
2263
 
2075
- let result = code
2264
+ // prune upstream's disabled native branches before lifting module imports.
2265
+ let result = /\/node_modules\/expo\/(?:src|build)\/winter\//.test(id)
2266
+ ? minifySync(
2267
+ stripViteQuery(id),
2268
+ transformSync(stripViteQuery(id), code, { define: defines }).code,
2269
+ { mangle: false },
2270
+ ).code
2271
+ : code
2076
2272
  let imports = ''
2077
2273
  let importCounter = 0
2078
2274
  let hasChanges = false
2079
2275
 
2080
2276
  if (code.includes('require(')) {
2081
- const requireRegex = /require\(["']([^"']+)["']\)/g
2082
- const replacements: Array<{ full: string; varName: string }> = []
2277
+ const replacements: Array<{ start: number; end: number; varName: string }> = []
2083
2278
  const seenPaths = new Map<string, string>()
2084
- let match
2085
-
2086
- requireRegex.lastIndex = 0
2087
- while ((match = requireRegex.exec(result)) !== null) {
2088
- const reqPath = match[1]
2279
+ const pending: unknown[] = [parseSync(stripViteQuery(id), result).program]
2280
+ while (pending.length) {
2281
+ const node = pending.pop()
2282
+ if (!isRecord(node)) continue
2283
+ for (const value of Object.values(node)) {
2284
+ if (Array.isArray(value)) pending.push(...value)
2285
+ else if (isRecord(value)) pending.push(value)
2286
+ }
2287
+ if (
2288
+ node.type !== 'CallExpression' ||
2289
+ !isRecord(node.callee) ||
2290
+ node.callee.type !== 'Identifier' ||
2291
+ node.callee.name !== 'require' ||
2292
+ !Array.isArray(node.arguments) ||
2293
+ node.arguments.length !== 1 ||
2294
+ !isRecord(node.arguments[0]) ||
2295
+ typeof node.arguments[0].value !== 'string' ||
2296
+ typeof node.start !== 'number' ||
2297
+ typeof node.end !== 'number'
2298
+ )
2299
+ continue
2300
+ const reqPath = node.arguments[0].value
2089
2301
  if (reqPath === 'react-native' || reqPath.startsWith('react-native/'))
2090
2302
  continue
2091
2303
 
@@ -2094,12 +2306,16 @@ function externalAppTransformPlugin(isAppSource: (p: string) => boolean): Plugin
2094
2306
  imports += `import * as ${varName} from ${JSON.stringify(reqPath)};\n`
2095
2307
  seenPaths.set(reqPath, varName)
2096
2308
  }
2097
- replacements.push({ full: match[0], varName: seenPaths.get(reqPath)! })
2309
+ replacements.push({
2310
+ start: node.start,
2311
+ end: node.end,
2312
+ varName: seenPaths.get(reqPath)!,
2313
+ })
2098
2314
  }
2099
2315
 
2100
2316
  if (replacements.length > 0) {
2101
- for (const rep of replacements) {
2102
- result = result.replace(rep.full, rep.varName)
2317
+ for (const rep of replacements.sort((a, b) => b.start - a.start)) {
2318
+ result = result.slice(0, rep.start) + rep.varName + result.slice(rep.end)
2103
2319
  }
2104
2320
  hasChanges = true
2105
2321
  }
@@ -2552,10 +2768,6 @@ export function expoDomComponentsPlugin(
2552
2768
  if (fs.existsSync(resolved)) {
2553
2769
  try {
2554
2770
  const stat = fs.statSync(resolved)
2555
- if (stat.isFile()) {
2556
- domWebModuleIds.add(resolved)
2557
- return resolved
2558
- }
2559
2771
  if (stat.isDirectory()) {
2560
2772
  const indexCandidates = [
2561
2773
  path.join(resolved, 'index.web.tsx'),
@@ -2596,6 +2808,10 @@ export function expoDomComponentsPlugin(
2596
2808
  return candidate
2597
2809
  }
2598
2810
  }
2811
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) {
2812
+ domWebModuleIds.add(resolved)
2813
+ return resolved
2814
+ }
2599
2815
  const indexCandidates = [
2600
2816
  path.join(resolved, 'index.web.tsx'),
2601
2817
  path.join(resolved, 'index.web.ts'),