peachsim 0.1.2224 → 0.1.2228

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 (171) hide show
  1. package/cli/bridge-flow-runner.ts +56 -3
  2. package/cli/commands/control.ts +27 -1
  3. package/cli/commands/flow.ts +26 -0
  4. package/cli/commands/package.ts +9 -2
  5. package/cli/drivers/playwright.ts +1 -0
  6. package/cli/drivers/types.ts +3 -0
  7. package/cli/internal-child.ts +4 -2
  8. package/dist-lib/agent-daemon-client.cjs +1 -1
  9. package/dist-lib/agent-events.cjs +1 -1
  10. package/dist-lib/attached-projects.cjs +1 -1
  11. package/dist-lib/auth/shared-session.cjs +1 -1
  12. package/dist-lib/backend-origin.cjs +1 -1
  13. package/dist-lib/beta.cjs +1 -1
  14. package/dist-lib/beta.mjs +1 -1
  15. package/dist-lib/bridge-constants.cjs +1 -1
  16. package/dist-lib/bridge-contract-input.cjs +1 -1
  17. package/dist-lib/bridge-contract-input.mjs +1 -1
  18. package/dist-lib/bridge-contract.cjs +1 -1
  19. package/dist-lib/bridge-contract.mjs +1 -1
  20. package/dist-lib/bundle-channel.cjs +931 -455
  21. package/dist-lib/capture-contract.cjs +1 -1
  22. package/dist-lib/capture-contract.mjs +1 -1
  23. package/dist-lib/cloud-contract.cjs +1 -1
  24. package/dist-lib/cloud-contract.mjs +1 -1
  25. package/dist-lib/cloud-test-args.cjs +1 -1
  26. package/dist-lib/cloud-test-args.mjs +1 -1
  27. package/dist-lib/cloud.cjs +1 -1
  28. package/dist-lib/cloud.mjs +1 -1
  29. package/dist-lib/config.cjs +1 -1
  30. package/dist-lib/config.mjs +1 -1
  31. package/dist-lib/detox/index.cjs +2 -2
  32. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  33. package/dist-lib/expo-swift-bundle.cjs +867 -391
  34. package/dist-lib/expo-swift-metadata.cjs +1 -1
  35. package/dist-lib/expo-swift-metadata.mjs +1 -1
  36. package/dist-lib/home-paths.cjs +1 -1
  37. package/dist-lib/host/bridge-host.cjs +926 -450
  38. package/dist-lib/host/fetch-proxy-handler.cjs +55 -12
  39. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  40. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  41. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  42. package/dist-lib/host/websocket-proxy.cjs +1 -1
  43. package/dist-lib/index.cjs +60 -16
  44. package/dist-lib/jump-to-source-babel.cjs +1 -1
  45. package/dist-lib/jump-to-source-native.cjs +1 -1
  46. package/dist-lib/kotlin-host.cjs +1 -1
  47. package/dist-lib/menu.cjs +1 -1
  48. package/dist-lib/menu.mjs +1 -1
  49. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  50. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  51. package/dist-lib/metro-production-bundle.cjs +11 -4
  52. package/dist-lib/metro-production-bundle.mjs +10 -4
  53. package/dist-lib/metro.cjs +60 -16
  54. package/dist-lib/native-app-identity-project.cjs +53 -9
  55. package/dist-lib/native-app-identity-project.mjs +84 -37
  56. package/dist-lib/native-app-identity.cjs +1 -1
  57. package/dist-lib/native-app-identity.mjs +1 -1
  58. package/dist-lib/native-firebase-metadata.cjs +1 -1
  59. package/dist-lib/native-firebase-metadata.mjs +1 -1
  60. package/dist-lib/pdfium-raster.cjs +1 -1
  61. package/dist-lib/pdfium-raster.mjs +1 -1
  62. package/dist-lib/plist.cjs +1 -1
  63. package/dist-lib/plist.mjs +1 -1
  64. package/dist-lib/profiles.cjs +1 -1
  65. package/dist-lib/public-brand.cjs +1 -1
  66. package/dist-lib/public-origins-lib-esm.json +26 -24
  67. package/dist-lib/public-origins-lib.json +66 -54
  68. package/dist-lib/react-native-host-modules.cjs +1 -1
  69. package/dist-lib/react-native-host-modules.mjs +1 -1
  70. package/dist-lib/reap-browser.cjs +1 -1
  71. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  72. package/dist-lib/sdk.cjs +1 -1
  73. package/dist-lib/sdk.mjs +1 -1
  74. package/dist-lib/skills.cjs +1049 -512
  75. package/dist-lib/swift-compile.cjs +557 -121
  76. package/dist-lib/swift-compile.mjs +693 -258
  77. package/dist-lib/swift-package.cjs +1 -1
  78. package/dist-lib/swift-package.mjs +1 -1
  79. package/dist-lib/swift-submit.cjs +1 -1
  80. package/dist-lib/swift-submit.mjs +1 -1
  81. package/dist-lib/swift-toolchain.cjs +38 -2
  82. package/dist-lib/swift.cjs +1 -1
  83. package/dist-lib/vite-swift.cjs +1726 -1209
  84. package/dist-lib/vite.cjs +55 -12
  85. package/dist-lib/worklet-executable.cjs +1 -1
  86. package/dist-lib/worklet-executable.mjs +1 -1
  87. package/dist-types/packages/peach-engine/src/bundle-parse-receipt.d.ts +17 -0
  88. package/dist-types/packages/peach-engine/src/engine/canvaskit-api.d.ts +2 -1
  89. package/dist-types/packages/peach-engine/src/engine/font-config.d.ts +2 -2
  90. package/dist-types/packages/peach-engine/src/engine/native-layout.d.ts +1 -0
  91. package/dist-types/packages/peach-engine/src/engine/native-paragraph.d.ts +4 -0
  92. package/dist-types/packages/peach-engine/src/engine/node.d.ts +2 -0
  93. package/dist-types/packages/peach-engine/src/engine/scene-table.d.ts +1 -1
  94. package/dist-types/packages/peach-engine/src/engine/types.d.ts +7 -0
  95. package/dist-types/packages/peach-engine/src/engine/yoga-layout.d.ts +2 -0
  96. package/dist-types/packages/peach-engine/src/metro-fingerprint.d.ts +3 -2
  97. package/dist-types/packages/peach-engine/src/preview/preview-room-contract.d.ts +6 -0
  98. package/dist-types/packages/peach-engine/src/render-worker/compositor-protocol.d.ts +1 -0
  99. package/dist-types/packages/peach-engine/src/render-worker/protocol.d.ts +15 -3
  100. package/dist-types/packages/peach-engine/src/rolldown-bundle-inventory.d.ts +0 -5
  101. package/dist-types/packages/peach-kit/src/capabilities.d.ts +2 -0
  102. package/dist-types/packages/peach-kit/src/internal.d.ts +1 -1
  103. package/dist-types/packages/peach-kit/src/ios-tint.d.ts +2 -0
  104. package/kotlin/sdk.tar.gz +0 -0
  105. package/package.json +1 -1
  106. package/runtime-companion.json +1 -1
  107. package/sdk/ios/System/Library/Frameworks/CoreFoundation.framework/Headers/CFCGTypes.h +0 -6
  108. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSCharacterSet.h +1 -1
  109. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSURL.h +1 -1
  110. package/sdk/ios/System/Library/Frameworks/UIKit.framework/Headers/UIControl.h +0 -26
  111. package/sdk/ios/System/Library/Frameworks/UIKitTypes.framework/Headers/UIKitTypes.h +27 -0
  112. package/sdk/ios/usr/include/AvailabilityInternal.h +2 -2
  113. package/sdk/ios/usr/include/ObjectiveC.apinotes +380 -7
  114. package/sdk/ios/usr/include/peach/pthread.h +1 -7
  115. package/sdk/ios/wasm/include/TargetConditionals.h +2 -3
  116. package/sdk/ios/wasm/include/__struct_timeval.h +15 -0
  117. package/sdk/ios/wasm/include/__typedef_suseconds_t.h +9 -0
  118. package/sdk/ios/wasm/include/limits.h +1 -1
  119. package/sdk/ios/wasm/include/machine/_types.h +1 -1
  120. package/sdk/ios/wasm/include/machine/limits.h +1 -1
  121. package/sdk/ios/wasm/include/machine/param.h +2 -2
  122. package/sdk/ios/wasm/include/math.h +13 -0
  123. package/sdk/ios/wasm/include/pthread.h +1 -13
  124. package/sdk/ios/wasm/include/sys/resource.h +3 -0
  125. package/sdk/ios/wasm/include/sys/socket.h +0 -10
  126. package/sdk/ios/wasm/include/sys/stat.h +5 -2
  127. package/sdk/ios/wasm/include/sys/time.h +16 -3
  128. package/sdk/ios/wasm/include/sys/types.h +4 -1
  129. package/sdk/ios/wasm/include/time.h +2 -0
  130. package/sdk/ios/wasm/storage-include/TargetConditionals.h +2 -3
  131. package/sdk/native-hashes.json +14 -14
  132. package/src/metro-production-bundle.ts +15 -2
  133. package/src/native-slices.ts +3 -2
  134. package/src/native-static-libraries.ts +23 -23
  135. package/src/native-swift-slices.ts +2 -2
  136. package/src/nitro-adapter.lock.json +288 -288
  137. package/src/nitro-compiler-digest.ts +0 -1
  138. package/src/nitro-dependencies.ts +8 -9
  139. package/src/nitro-superset.lock.json +4 -4
  140. package/src/react-native-core-build.ts +2 -2
  141. package/src/swift-compile.ts +209 -47
  142. package/src/swift-compiler-digest.ts +2 -0
  143. package/src/swift-cxx-standin.ts +1 -1
  144. package/src/swift-darwin-records.ts +4 -4
  145. package/src/swift-distribution-contract.ts +2 -2
  146. package/src/swift-distribution.lock.json +13 -12
  147. package/src/swift-guest-compile.ts +6 -5
  148. package/src/swift-guest-image-ledger.ts +5 -5
  149. package/src/swift-guest-link.ts +1 -1
  150. package/src/swift-guest-linked-spans.ts +7 -4
  151. package/src/swift-guest-manifest.ts +2 -2
  152. package/src/swift-guest-source-inputs.ts +16 -5
  153. package/src/swift-host.ts +10 -5
  154. package/src/swift-icu.ts +1 -1
  155. package/src/swift-objc-compile.ts +9 -4
  156. package/src/swift-objc-standin.ts +1 -4
  157. package/src/swift-package-manifest.ts +5 -1
  158. package/src/swift-packages.ts +2 -2
  159. package/src/swift-shared-sdk.ts +1 -1
  160. package/src/swift-standin-emit.ts +3 -16
  161. package/src/swift-standins.ts +119 -87
  162. package/src/swift-toolchain.ts +37 -1
  163. package/src/swift-uniform-type-constants.ts +1 -1
  164. package/src/swift-wasm-prune.ts +17 -12
  165. package/src/swiftGuestImageContract.ts +4 -4
  166. package/src/swiftNativePinContract.ts +1 -1
  167. package/src/swiftRuntimeProductContract.ts +1 -1
  168. package/src/turbo-runtime.lock.json +28 -28
  169. package/src/wasm-narrow-boundary.ts +296 -0
  170. package/src/wasm-optimize.ts +59 -14
  171. package/src/xcode-project.ts +6 -4
@@ -840,6 +840,38 @@ const MAESTRO_LOCATION_STATUS: Record<
840
840
  ],
841
841
  }
842
842
 
843
+ export type LaunchPermissionStatuses = ReadonlyMap<
844
+ SystemPermissionKind,
845
+ SystemPermissionStatus
846
+ >
847
+
848
+ // the permissions of a flow whose first step is a launchApp that only launches:
849
+ // no state or keychain reset, arguments, runtime reset or foreground. a sim
850
+ // opened for that flow can deposit them before its app first boots.
851
+ export function firstPlainLaunchPermissions(
852
+ steps: readonly (MaestroStep | string)[],
853
+ ): LaunchPermissionStatuses | null {
854
+ const first = steps[0]
855
+ if (first === undefined) return null
856
+ const step = typeof first === 'string' ? normalizeBareStringStep(first) : first
857
+ if (!step.launchApp || JSON.stringify(step.launchApp).includes('${')) return null
858
+ return plainLaunchPermissions(step.launchApp)
859
+ }
860
+
861
+ function plainLaunchPermissions(opts: unknown): LaunchPermissionStatuses | null {
862
+ if (
863
+ isObjectRecord(opts) &&
864
+ (opts.clearState ||
865
+ opts.clearKeychain === true ||
866
+ opts.resetRuntime === true ||
867
+ opts.stopApp === false ||
868
+ Object.prototype.hasOwnProperty.call(opts, 'arguments'))
869
+ ) {
870
+ return null
871
+ }
872
+ return maestroLaunchPermissions(isObjectRecord(opts) ? opts.permissions : undefined)
873
+ }
874
+
843
875
  function maestroLaunchPermissions(
844
876
  permissions: unknown,
845
877
  ): Map<SystemPermissionKind, SystemPermissionStatus> {
@@ -1068,6 +1100,10 @@ class CaptureSetupError extends Error {
1068
1100
  export class SootSimBridgeFlowRunner {
1069
1101
  stepDelay = 0
1070
1102
 
1103
+ // the launch permissions the open that started this sim deposited before
1104
+ // its app booted, while that boot is still the app's only one
1105
+ seededLaunchPermissions: LaunchPermissionStatuses | null = null
1106
+
1071
1107
  // maestro-compatible JS context for the whole flow run (shared with
1072
1108
  // runFlow sub-flows): `${...}` templates, runScript, evalScript, output,
1073
1109
  // maestro.copiedText. one per runner = one per flow file, like upstream.
@@ -3046,7 +3082,10 @@ export class SootSimBridgeFlowRunner {
3046
3082
  if (!this.firstLaunchDone && !this.appStopped) await this.waitForTree(120000)
3047
3083
  await this.clearKeychain()
3048
3084
  }
3049
- await this.setLaunchPermissions(isObjectRecord(opts) ? opts.permissions : undefined)
3085
+ const bootedByOpen = this.openBootIsLaunch(opts)
3086
+ if (!bootedByOpen) {
3087
+ await this.setLaunchPermissions(isObjectRecord(opts) ? opts.permissions : undefined)
3088
+ }
3050
3089
 
3051
3090
  if (!this.firstLaunchDone) {
3052
3091
  this.firstLaunchDone = true
@@ -3083,8 +3122,9 @@ export class SootSimBridgeFlowRunner {
3083
3122
  await this.waitForTree(120000)
3084
3123
  await this.waitForTree(30000)
3085
3124
  } else {
3086
- const launched = await this.launchShellAppFromHomeIfNeeded(opts)
3087
- if (!launched && !wantsForeground) {
3125
+ const launched =
3126
+ !bootedByOpen && (await this.launchShellAppFromHomeIfNeeded(opts))
3127
+ if (!launched && !wantsForeground && !bootedByOpen) {
3088
3128
  await this.waitForTree(120000)
3089
3129
  await this.reloadGuestAppRuntime({
3090
3130
  launchArguments: null,
@@ -3134,6 +3174,19 @@ export class SootSimBridgeFlowRunner {
3134
3174
  }
3135
3175
  }
3136
3176
 
3177
+ // maestro sets permissions and then launches. a sim opened with this
3178
+ // step's permissions already deposited has done both in its one boot.
3179
+ private openBootIsLaunch(opts: unknown): boolean {
3180
+ const seeded = this.seededLaunchPermissions
3181
+ if (!seeded || this.firstLaunchDone || this.appStopped) return false
3182
+ const wanted = plainLaunchPermissions(opts)
3183
+ return (
3184
+ wanted !== null &&
3185
+ wanted.size === seeded.size &&
3186
+ [...wanted].every(([kind, status]) => seeded.get(kind) === status)
3187
+ )
3188
+ }
3189
+
3137
3190
  private async setLaunchPermissions(permissions: unknown) {
3138
3191
  const statuses = maestroLaunchPermissions(permissions)
3139
3192
  const appId = await this.callTest<string>('permissionGuestAppId')
@@ -8,6 +8,7 @@ import path from 'node:path'
8
8
  import { compareSemver } from '@contrast/runtime-delivery'
9
9
  import { WebSocket } from 'ws'
10
10
  import { formatLockOwner } from '../../../peach-kit/src/agent-identity'
11
+ import { buildPermissionSeedScript } from '../../detox/launch-permissions'
11
12
  import { ANDROID_ROOT_MODULE_QUERY_PARAM } from '../../src/androidRootModuleContract'
12
13
  import { isAppDeepLinkTarget, openAppUrl } from '../../src/app-url'
13
14
  import { isLoopbackHost } from '../../src/backend-origin'
@@ -115,6 +116,7 @@ import {
115
116
  } from './shell-url'
116
117
  import { hasInstalledPeachSkills } from './skills'
117
118
  import type { DriverId, DriverLaunchResult } from '../drivers/types'
119
+ import type { SystemPermissionBootSeed } from '@peach/globals'
118
120
 
119
121
  interface ControlCommandOptions {
120
122
  port?: number
@@ -123,6 +125,8 @@ interface ControlCommandOptions {
123
125
  }
124
126
 
125
127
  export interface OpenCommandResources {
128
+ // the launch permissions were deposited ahead of the opened app's boot
129
+ launchPermissionsSeeded?: boolean
126
130
  bridge?: ReturnType<typeof spawn>
127
131
  bridgeReady?: boolean
128
132
  port?: number
@@ -1383,6 +1387,9 @@ async function dispatchAppLink(
1383
1387
  }
1384
1388
 
1385
1389
  type OpenCommandOptions = ControlCommandOptions & {
1390
+ // permissions the app this open boots holds from its first read, as a
1391
+ // test runner sets them before it launches the app
1392
+ launchPermissions?: SystemPermissionBootSeed['presets']
1386
1393
  resources?: OpenCommandResources
1387
1394
  signal?: AbortSignal
1388
1395
  ownerPid?: number
@@ -2270,8 +2277,23 @@ async function openSim(
2270
2277
  }
2271
2278
  const connectWait = resolveDriverConnectWaitOptions()
2272
2279
  opts.signal?.throwIfAborted()
2280
+ // the engine applies an ownerless seed to the app its url boots, on ios
2281
+ const seededLaunchPermissions =
2282
+ driverId === 'playwright' &&
2283
+ shellUrlPlatform(builtShellUrl) === 'ios' &&
2284
+ getCliRuntime().render_worker_app_load.isAppBootTargetUrl(builtShellUrl)
2285
+ ? opts.launchPermissions
2286
+ : undefined
2273
2287
  const result = await driver.launch({
2274
2288
  url: openUrl,
2289
+ bootScript:
2290
+ seededLaunchPermissions &&
2291
+ buildPermissionSeedScript({
2292
+ appId: null,
2293
+ platform: 'ios',
2294
+ presets: seededLaunchPermissions,
2295
+ grantInstallPermissions: false,
2296
+ }),
2275
2297
  // the shell serves the sim page but its WS bridge may have drifted off
2276
2298
  // the shell-port-implied default. inject the bridge this command settled
2277
2299
  // on so the page does not reconnect to a stale world. re-deriving it from
@@ -2300,7 +2322,11 @@ async function openSim(
2300
2322
  : undefined),
2301
2323
  connectTimeoutMs: connectWait.hostTimeoutMs,
2302
2324
  })
2303
- if (opts.resources) opts.resources.driver = { ...result, driverId }
2325
+ if (opts.resources) {
2326
+ opts.resources.driver = { ...result, driverId }
2327
+ opts.resources.launchPermissionsSeeded =
2328
+ seededLaunchPermissions !== undefined && result.launched
2329
+ }
2304
2330
  opts.signal?.throwIfAborted()
2305
2331
  if (!result.launched) {
2306
2332
  console.error(` ${driver.name} driver: ${result.message}`)
@@ -12,7 +12,9 @@ import { devices, type DeviceModel } from '../../src/device-catalog.ts'
12
12
  import { SOOTSIM_SHELL_COMMAND_NAME } from '../../src/menu'
13
13
  import { printBridgeFailureDiagnostics } from '../bridge-diagnostics'
14
14
  import {
15
+ firstPlainLaunchPermissions,
15
16
  SootSimBridgeFlowRunner,
17
+ type LaunchPermissionStatuses,
16
18
  type MaestroStep,
17
19
  type SootSimFlowProfileResult,
18
20
  type SootSimFlowTraceStep,
@@ -563,6 +565,11 @@ flow extension:
563
565
  let closeFlowOwnedSimAfterRun = false
564
566
  let openedSimId: string | undefined
565
567
  const ownedSimIds = new Set<string>()
568
+ // maestro sets a launchApp's permissions and then launches. a sim this run
569
+ // opens takes them, and the flow's theme, ahead of its app's boot, so that
570
+ // boot is the launch and the step boots nothing again.
571
+ const firstLaunchPermissions = firstPlainLaunchPermissions(steps)
572
+ let seededLaunchPermissions: LaunchPermissionStatuses | null = null
566
573
 
567
574
  // if we weren't told exactly where to go, and there's already a primary
568
575
  // open tab, just reuse it. opening a fresh tab that auto-discovers some
@@ -614,6 +621,12 @@ flow extension:
614
621
  const openArgs = [openTarget]
615
622
  if (pinnedShellBaseUrl) openArgs.push('--base-url', pinnedShellBaseUrl)
616
623
  if (targetDevice) openArgs.push('--device', targetDevice)
624
+ if (
625
+ firstLaunchPermissions &&
626
+ (targetTheme === 'light' || targetTheme === 'dark' || targetTheme === 'auto')
627
+ ) {
628
+ openArgs.push('--theme', targetTheme)
629
+ }
617
630
  const iosTarget = getFlag('--ios-target')
618
631
  if (iosTarget) openArgs.push('--ios-target', iosTarget)
619
632
  const appFontsDir = getFlag('--app-fonts-dir')
@@ -647,6 +660,14 @@ flow extension:
647
660
  ...(parsedBridgeArgs.explicitPort ? { port: parsedBridgeArgs.wsPort } : {}),
648
661
  timeoutMs: parsedBridgeArgs.commandTimeoutMs,
649
662
  resources: openResources,
663
+ ...(firstLaunchPermissions
664
+ ? {
665
+ launchPermissions: [...firstLaunchPermissions].map(([kind, status]) => ({
666
+ kind,
667
+ mutation: { status },
668
+ })),
669
+ }
670
+ : {}),
650
671
  // a prebuilt bundle file carries its own configuration footer; the registry's
651
672
  // copy names files by this checkout's paths and would never match it
652
673
  runtimeConfig: openTarget.endsWith('.js')
@@ -654,6 +675,9 @@ flow extension:
654
675
  : targetAppResolution.runtimeConfig,
655
676
  })
656
677
  openedSimId = opened?.simId
678
+ if (openResources.launchPermissionsSeeded) {
679
+ seededLaunchPermissions = firstLaunchPermissions
680
+ }
657
681
  // the steps must talk to the bridge open actually settled on, not the
658
682
  // pre-open default. without this a default 7668 held by an unrelated
659
683
  // server keeps the flow on 7668 while open serves from 7669.
@@ -872,6 +896,8 @@ flow extension:
872
896
  }
873
897
  const deviceChanged = await applySimSettings()
874
898
  if (deviceChanged && !startsWithLaunchApp) await driver.waitForTree(120000)
899
+ // a device change reloads the page, which is a second boot without the seed
900
+ if (!deviceChanged) driver.seededLaunchPermissions = seededLaunchPermissions
875
901
  // preview replay must reproduce the SAME initial conditions the agent
876
902
  // explored from. the explore session runs on a fresh ephemeral profile
877
903
  // (cold, logged-out), so its recorded steps assume that cold start —
@@ -125,11 +125,18 @@ async function primeCodeCache(app: string, home: string): Promise<void> {
125
125
  fs.readFileSync(record, 'utf8'),
126
126
  )
127
127
  const bridgeEnv = { ...env, PEACH_BRIDGE_PORT: String(service.bridgePort) }
128
- // the cli finds no sim until the host's page reaches the app's service
128
+ // the cli finds no sim until the host's page reaches the app's service. a first screen may
129
+ // animate for as long as it shows (a spinner while it connects), so the launch counts once painted
129
130
  for (;;) {
130
131
  const ready = spawnSync(
131
132
  peach,
132
- ['wait', 'ready', '--max-ms', String(Math.max(1000, deadline - Date.now()))],
133
+ [
134
+ 'wait',
135
+ 'ready',
136
+ '--painted',
137
+ '--max-ms',
138
+ String(Math.max(1000, deadline - Date.now())),
139
+ ],
133
140
  { env: bridgeEnv, encoding: 'utf8' },
134
141
  )
135
142
  if (ready.status === 0) break
@@ -272,6 +272,7 @@ async function launch(opts: DriverLaunchOptions): Promise<DriverLaunchResult> {
272
272
  PEACH_PW_FETCH_PROXY_SPKI: fetchProxySpki,
273
273
  SOOTSIM_PW_BRIDGE_PORT: opts.bridgePort ? String(opts.bridgePort) : '',
274
274
  SOOTSIM_PW_HEADLESS: headless ? '1' : '0',
275
+ PEACH_PW_BOOT_SCRIPT: opts.bootScript ?? '',
275
276
  SOOTSIM_PW_USERDATADIR: resolveUserDataDir(opts),
276
277
  SOOTSIM_PW_CDP_PORT: cdpPort ? String(cdpPort) : '',
277
278
  PEACH_PW_HOST_COMMAND: hostCommand ? JSON.stringify(hostCommand) : '',
@@ -71,6 +71,9 @@ export interface DriverLaunchOptions {
71
71
  // boot -> bridge on :7669 while the shell kept :5173): the page's own
72
72
  // shell-port heuristic would compute the stale :7668 and never connect.
73
73
  bridgePort?: number
74
+ // a script the sim page runs ahead of its own, for state the engine reads
75
+ // before its first boot. only the playwright driver honors it.
76
+ bootScript?: string
74
77
  }
75
78
 
76
79
  export interface DriverCompletion {
@@ -16,6 +16,7 @@ import {
16
16
  accessSync,
17
17
  constants,
18
18
  existsSync,
19
+ realpathSync,
19
20
  readdirSync,
20
21
  readFileSync,
21
22
  writeFileSync,
@@ -194,8 +195,9 @@ export async function runScript(scriptPath: string, args: string[]): Promise<voi
194
195
  const stripper = new (nodeRequire('bun').Transpiler)({ loader: 'ts' })
195
196
  nodeModule.stripTypeScriptTypes = (code: string) => stripper.transformSync(code)
196
197
  }
197
- process.argv = [process.argv[0], scriptPath, ...args]
198
- await import(/* @vite-ignore */ pathToFileURL(scriptPath).href)
198
+ const entryPath = realpathSync(scriptPath)
199
+ process.argv = [process.argv[0], entryPath, ...args]
200
+ await import(/* @vite-ignore */ pathToFileURL(entryPath).href)
199
201
  }
200
202
 
201
203
  /** the detached browser host. its source is CJS that resolves playwright from
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = '', __sootsim_import_meta_filename = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; __sootsim_import_meta_filename = __filename; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = '', __sootsim_import_meta_filename = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; __sootsim_import_meta_filename = __filename; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = '', __sootsim_import_meta_filename = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; __sootsim_import_meta_filename = __filename; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = '', __sootsim_import_meta_filename = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; __sootsim_import_meta_filename = __filename; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = '', __sootsim_import_meta_filename = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; __sootsim_import_meta_filename = __filename; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/beta.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = '', __sootsim_import_meta_filename = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; __sootsim_import_meta_filename = __filename; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/beta.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
 
3
3
  // src/beta.ts
4
4
  var IS_BETA = true;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = '', __sootsim_import_meta_filename = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; __sootsim_import_meta_filename = __filename; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = '', __sootsim_import_meta_filename = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; __sootsim_import_meta_filename = __filename; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var SIM_LONG_PRESS_MAX_MS = 5e3;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = '', __sootsim_import_meta_filename = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; __sootsim_import_meta_filename = __filename; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.2224 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.2228 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var REFUSED_PERFORM_STEP_TYPES = [