peachsim 0.1.527 → 0.1.570

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 (158) hide show
  1. package/README.md +26 -26
  2. package/cli/app-fonts.ts +1 -1
  3. package/cli/app-splash.ts +1 -1
  4. package/cli/bin.ts +1 -1
  5. package/cli/bridge-diagnostics.ts +1 -1
  6. package/cli/bridge-flow-runner.ts +57 -5
  7. package/cli/cloud-client.ts +273 -6
  8. package/cli/cloud-dispatch.ts +22 -2
  9. package/cli/cloud-session.ts +60 -4
  10. package/cli/cloud-test-tunnel.ts +354 -0
  11. package/cli/commands/box/bundle-plane.ts +2 -2
  12. package/cli/commands/box/checkout-plane.ts +1 -1
  13. package/cli/commands/box/rnx-command.ts +2 -2
  14. package/cli/commands/box/search-command.ts +2 -2
  15. package/cli/commands/box.ts +8 -8
  16. package/cli/commands/compat.ts +1 -1
  17. package/cli/commands/control.ts +22 -3
  18. package/cli/commands/detox.ts +1 -1
  19. package/cli/commands/device.ts +1 -1
  20. package/cli/commands/diagnose.ts +1 -1
  21. package/cli/commands/film.ts +1 -1
  22. package/cli/commands/flow.ts +60 -3
  23. package/cli/commands/inspect/actions.ts +1 -1
  24. package/cli/commands/inspect/core.ts +6 -7
  25. package/cli/commands/inspect/find.ts +2 -1
  26. package/cli/commands/inspect/resolve-target.ts +5 -3
  27. package/cli/commands/inspect/shared.ts +1 -1
  28. package/cli/commands/inspect/wait-event.ts +1 -1
  29. package/cli/commands/inspect/wait-selector.ts +15 -4
  30. package/cli/commands/inspect.ts +19 -14
  31. package/cli/commands/maestro.ts +464 -43
  32. package/cli/commands/permissions.ts +1 -1
  33. package/cli/commands/platform.ts +205 -8
  34. package/cli/commands/preview.ts +2 -2
  35. package/cli/commands/record.ts +2 -2
  36. package/cli/commands/report-issue.ts +2 -2
  37. package/cli/commands/screenshot-layers.ts +1 -1
  38. package/cli/commands/screenshot.ts +1 -1
  39. package/cli/commands/setup.ts +2 -2
  40. package/cli/commands/test.ts +440 -115
  41. package/cli/commands/three-mode.ts +1 -1
  42. package/cli/commands/upload.ts +5 -5
  43. package/cli/commands/what-happened.ts +1 -1
  44. package/cli/desktop-companion.ts +3 -3
  45. package/cli/drivers/playwright-sim-host.ts +2 -2
  46. package/cli/flow-file.ts +7 -0
  47. package/cli/flow-live-status.ts +65 -8
  48. package/cli/help-core.ts +3 -3
  49. package/cli/hidden-runtime-alias.ts +1 -1
  50. package/cli/maestro-report.ts +178 -0
  51. package/cli/main.ts +1 -1
  52. package/cli/outbound-endpoints.ts +185 -136
  53. package/cli/parse-args.ts +1 -1
  54. package/cli/registry.ts +1 -1
  55. package/cli/resolve-assets.ts +1 -1
  56. package/cli/run-registry.ts +8 -0
  57. package/cli/send-to-box.ts +4 -4
  58. package/cli/ws-bridge.ts +3 -5
  59. package/detox/expectations.ts +1 -1
  60. package/detox/gestures.ts +2 -2
  61. package/detox/index.ts +71 -29
  62. package/detox/jest-preset.cjs +3 -3
  63. package/detox/jest-setup-after-env.cjs +1 -1
  64. package/dist-lib/agent-daemon-client.cjs +2 -2
  65. package/dist-lib/agent-events.cjs +1 -1
  66. package/dist-lib/agent-identity.cjs +1 -1
  67. package/dist-lib/agent-sessions.cjs +2 -2
  68. package/dist-lib/attached-projects.cjs +2 -2
  69. package/dist-lib/auth/shared-session.cjs +1 -1
  70. package/dist-lib/backend-origin.cjs +1 -1
  71. package/dist-lib/beta.cjs +1 -1
  72. package/dist-lib/beta.mjs +1 -1
  73. package/dist-lib/bridge-constants.cjs +1 -1
  74. package/dist-lib/bridge-contract-input.cjs +1 -1
  75. package/dist-lib/bridge-contract-input.mjs +1 -1
  76. package/dist-lib/bridge-contract.cjs +1 -1
  77. package/dist-lib/bridge-contract.mjs +1 -1
  78. package/dist-lib/capture-contract.cjs +94 -3
  79. package/dist-lib/capture-contract.mjs +93 -3
  80. package/dist-lib/cli-constants.cjs +1 -1
  81. package/dist-lib/cloud-contract.cjs +1 -1
  82. package/dist-lib/cloud-contract.mjs +1 -1
  83. package/dist-lib/cloud.cjs +2 -2
  84. package/dist-lib/cloud.mjs +2 -2
  85. package/dist-lib/config.cjs +1 -1
  86. package/dist-lib/detox/index.cjs +106 -25
  87. package/dist-lib/detox/jest-preset.cjs +3 -3
  88. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  89. package/dist-lib/home-paths.cjs +1 -1
  90. package/dist-lib/host/bridge-host.cjs +137 -61
  91. package/dist-lib/host/fetch-proxy-handler.cjs +28 -2
  92. package/dist-lib/host/fetch-proxy-overrides.cjs +33 -3
  93. package/dist-lib/host/fetch-proxy-overrides.mjs +30 -2
  94. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  95. package/dist-lib/host/websocket-proxy.cjs +1 -1
  96. package/dist-lib/index.cjs +209 -16
  97. package/dist-lib/jump-to-source-babel.cjs +1 -1
  98. package/dist-lib/jump-to-source-native.cjs +1 -1
  99. package/dist-lib/menu.cjs +2 -2
  100. package/dist-lib/menu.mjs +2 -2
  101. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  102. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  103. package/dist-lib/metro-production-bundle.cjs +3 -3
  104. package/dist-lib/metro-production-bundle.mjs +3 -3
  105. package/dist-lib/metro.cjs +8 -8
  106. package/dist-lib/profiles.cjs +1 -1
  107. package/dist-lib/public-brand.cjs +2 -2
  108. package/dist-lib/react-native-host-modules.cjs +1 -1
  109. package/dist-lib/react-native-host-modules.mjs +1 -1
  110. package/dist-lib/render-mode.cjs +1 -1
  111. package/dist-lib/scripts/dev-server-scanner.cjs +31 -15
  112. package/dist-lib/sdk.cjs +3 -2
  113. package/dist-lib/sdk.mjs +3 -2
  114. package/dist-lib/skills.cjs +362 -163
  115. package/dist-lib/swift-submit.cjs +1 -1
  116. package/dist-lib/swift-submit.mjs +1 -1
  117. package/dist-lib/swift.cjs +583 -16
  118. package/dist-lib/vite.cjs +29 -14
  119. package/package.json +9 -9
  120. package/scripts/dev-server-scanner.ts +41 -14
  121. package/scripts/optional-demo-registry.ts +3 -3
  122. package/skills/contrast/SKILL.md +9 -9
  123. package/skills/rnx-debug/SKILL.md +14 -4
  124. package/skills/rnx-perf/SKILL.md +5 -5
  125. package/skills/rnx-setup/SKILL.md +11 -11
  126. package/skills/rnx-test/SKILL.md +11 -11
  127. package/skills/rnx-visual/SKILL.md +6 -6
  128. package/src/agent-sessions.ts +3 -3
  129. package/src/attached-projects.ts +1 -1
  130. package/src/bridge-contract-input.ts +1 -1
  131. package/src/bridge-contract.ts +23 -5
  132. package/src/capture-contract.ts +145 -3
  133. package/src/config.ts +2 -2
  134. package/src/connect.ts +2 -2
  135. package/src/home-paths.ts +3 -3
  136. package/src/host/bridge-host.ts +2 -2
  137. package/src/host/fetch-proxy-handler.ts +4 -3
  138. package/src/host/fetch-proxy-overrides.ts +41 -0
  139. package/src/host/plane-host.ts +2 -2
  140. package/src/index.ts +3 -0
  141. package/src/metro-plugin.ts +4 -4
  142. package/src/metro-production-bundle.ts +2 -2
  143. package/src/native-seam-manifest.ts +6 -6
  144. package/src/public-brand.ts +1 -1
  145. package/src/rolldown-plugin-swift.ts +8 -5
  146. package/src/runtime-assets.ts +8 -6
  147. package/src/screenshots/frame-compose.ts +24 -56
  148. package/src/screenshots/org-deck.ts +1 -1
  149. package/src/screenshots/registry.ts +4 -4
  150. package/src/screenshots/schema.ts +1 -1
  151. package/src/sdk.ts +2 -2
  152. package/src/settings-bundle.ts +1 -1
  153. package/src/sim-client.ts +2 -4
  154. package/src/skills/builtin/compat-check.ts +1 -1
  155. package/src/vite-plugin-one.ts +1 -1
  156. package/src/vite-plugin-swift.ts +11 -6
  157. package/src/vite-plugin.ts +30 -17
  158. package/src/worklets-babel.ts +2 -2
@@ -1,11 +1,11 @@
1
1
  // the wire contract for the sootsim ws bridge. the engine implements it in
2
- // sootsim-engine/src/mcp/ws-bridge.ts; the rnxsim cli client, the SimClient
2
+ // sootsim-engine/src/mcp/ws-bridge.ts; the peachsim cli client, the SimClient
3
3
  // sdk, and the flow runner speak it. it is the single owner of these shapes,
4
4
  // and neither side redeclares them.
5
5
  //
6
- // it lives in rnxsim rather than the engine because rnxsim is published and
7
- // sootsim-engine is `private: true`. the engine already depends on rnxsim
8
- // (see its `rnxsim` dependency and its `rnxsim/bridge-constants` import), so
6
+ // it lives in peachsim rather than the engine because peachsim is published and
7
+ // sootsim-engine is `private: true`. the engine already depends on peachsim
8
+ // (see its `peachsim` dependency and its `peachsim/bridge-constants` import), so
9
9
  // this is the direction that ships.
10
10
 
11
11
  import type {
@@ -13,7 +13,7 @@ import type {
13
13
  SootSimCaptureFilter,
14
14
  SootSimExternalAppLifecycleOptions,
15
15
  SootSimStorageResetStrategy,
16
- } from '@rnx/globals'
16
+ } from '@peach/globals'
17
17
 
18
18
  export type BridgeCommandType =
19
19
  | 'evaluate'
@@ -42,11 +42,29 @@ export type BridgeCommandType =
42
42
  | 'diagnostics'
43
43
  | 'storageSnapshot'
44
44
 
45
+ // the bridge host answers these itself, before the engine's handleCommand ever
46
+ // sees them. they are not engine commands and the cloud plane does not route
47
+ // them, so they stay out of BridgeCommandType and out of its cloud scope table.
48
+ export type BridgeHostCommandType = 'bridge:list-sims' | 'bridge:claim'
49
+
50
+ // what a caller hands a bridge's `send`: the command minus the correlation id,
51
+ // which the transport assigns. every send seam names this type rather than an
52
+ // index-signature bag, because an index signature erases excess-property
53
+ // checking and a field spelled wrong then compiles and arrives as `undefined`.
54
+ export type WsBridgeCommand = Omit<WsCommand, 'id' | 'type'> & {
55
+ type: BridgeCommandType | BridgeHostCommandType
56
+ }
57
+
45
58
  export interface WsCommand {
46
59
  id: string | number
47
60
  type: BridgeCommandType
48
61
  // local daemon routing; cloud transport strips it before execution.
49
62
  simId?: string
63
+ // take or refresh the cli lease on the target sim before running. reads omit
64
+ // it; anything that mutates user-facing state sets it. read in bridge-host.
65
+ acquireLock?: boolean
66
+ // bridge:claim — take the lease even when another cli holds it.
67
+ force?: boolean
50
68
  // flowStatus — live flow-runner progress payload (see flow-run-store)
51
69
  status?: Record<string, unknown>
52
70
  code?: string
@@ -188,7 +188,8 @@ export function selectRnxRasterNodeIds(tree: RnxRasterRegionTree): Set<number> {
188
188
  node.text,
189
189
  ))) ||
190
190
  node.style._liquidGlass !== undefined ||
191
- node.style._liquidGlassEdge !== undefined
191
+ node.style._liquidGlassEdge !== undefined ||
192
+ node.style._liquidGlassMaterialTint !== undefined
192
193
  ) {
193
194
  unsupported.add(node.nodeId)
194
195
  }
@@ -318,6 +319,146 @@ export function rnxCaptureRasterRegions(
318
319
  })
319
320
  }
320
321
 
322
+ /** a region request snapped to the surface pixel grid and placed in the atlas */
323
+ export interface RnxPackedCaptureRegion extends RnxCaptureRegionRequest {
324
+ /** top-left of the region on the app surface, in pixels */
325
+ sourceX: number
326
+ sourceY: number
327
+ pixelWidth: number
328
+ pixelHeight: number
329
+ /** top-left of the region inside the atlas, in pixels */
330
+ atlasX: number
331
+ atlasY: number
332
+ }
333
+
334
+ export interface RnxPackedCaptureAtlas {
335
+ /** logical atlas dimensions; pixel dimensions are these times the pixel ratio */
336
+ width: number
337
+ height: number
338
+ pixelWidth: number
339
+ pixelHeight: number
340
+ regions: RnxPackedCaptureRegion[]
341
+ /** the wire regions a producer publishes beside the encoded atlas */
342
+ atlasRegions: RnxCaptureAtlasRegion[]
343
+ }
344
+
345
+ // the bounded selective-layer atlas every producer ships: regions snapped to
346
+ // whole surface pixels, shelf-packed into the smallest of a few candidate
347
+ // widths, and mapped back to logical atlas coordinates. one packer for the
348
+ // headless renderer (which draws isolated subtrees into the slots) and the
349
+ // browser producer (which crops them out of a screen-sized screenshot), so a
350
+ // consumer never learns which one made the artifact from the layout.
351
+ export function packRnxCaptureRegions(
352
+ regions: readonly RnxCaptureRegionRequest[],
353
+ pixelRatio: number,
354
+ surface: { pixelWidth: number; pixelHeight: number },
355
+ ): RnxPackedCaptureAtlas {
356
+ const physical = regions.map((region) => {
357
+ const left = Math.floor(region.frame.x * pixelRatio)
358
+ const top = Math.floor(region.frame.y * pixelRatio)
359
+ const right = Math.ceil((region.frame.x + region.frame.width) * pixelRatio)
360
+ const bottom = Math.ceil((region.frame.y + region.frame.height) * pixelRatio)
361
+ if (
362
+ left < 0 ||
363
+ top < 0 ||
364
+ right > surface.pixelWidth ||
365
+ bottom > surface.pixelHeight ||
366
+ right <= left ||
367
+ bottom <= top
368
+ ) {
369
+ throw new Error(`capture region ${region.key} is outside the app surface`)
370
+ }
371
+ return {
372
+ key: region.key,
373
+ nodeId: region.nodeId,
374
+ frame: {
375
+ x: left / pixelRatio,
376
+ y: top / pixelRatio,
377
+ width: (right - left) / pixelRatio,
378
+ height: (bottom - top) / pixelRatio,
379
+ },
380
+ sourceX: left,
381
+ sourceY: top,
382
+ pixelWidth: right - left,
383
+ pixelHeight: bottom - top,
384
+ }
385
+ })
386
+ const largestWidth = Math.max(...physical.map((region) => region.pixelWidth))
387
+ const totalArea = physical.reduce(
388
+ (area, region) => area + region.pixelWidth * region.pixelHeight,
389
+ 0,
390
+ )
391
+ if (totalArea > 4_194_304) {
392
+ throw new Error('capture region pixels exceed the atlas budget')
393
+ }
394
+ const candidates = [
395
+ largestWidth,
396
+ Math.max(largestWidth, Math.ceil(Math.sqrt(totalArea))),
397
+ Math.max(largestWidth, surface.pixelWidth),
398
+ 4_096,
399
+ ]
400
+ const ordered = [...physical].sort(
401
+ (left, right) =>
402
+ right.pixelHeight - left.pixelHeight ||
403
+ right.pixelWidth - left.pixelWidth ||
404
+ left.key.localeCompare(right.key),
405
+ )
406
+ let best: { width: number; height: number; regions: RnxPackedCaptureRegion[] } | null =
407
+ null
408
+ for (const candidate of [...new Set(candidates)]) {
409
+ if (candidate > 4_096) continue
410
+ let x = 0
411
+ let y = 0
412
+ let rowHeight = 0
413
+ let usedWidth = 0
414
+ const packed: RnxPackedCaptureRegion[] = []
415
+ for (const region of ordered) {
416
+ if (x > 0 && x + region.pixelWidth > candidate) {
417
+ y += rowHeight
418
+ x = 0
419
+ rowHeight = 0
420
+ }
421
+ packed.push({ ...region, atlasX: x, atlasY: y })
422
+ x += region.pixelWidth
423
+ rowHeight = Math.max(rowHeight, region.pixelHeight)
424
+ usedWidth = Math.max(usedWidth, x)
425
+ }
426
+ const height = y + rowHeight
427
+ if (height > 4_096 || usedWidth * height > 4_194_304) continue
428
+ const result = { width: usedWidth, height, regions: packed }
429
+ if (!best || result.width * result.height < best.width * best.height) best = result
430
+ }
431
+ if (!best) throw new Error('capture regions cannot fit in one bounded atlas')
432
+ const width = best.width / pixelRatio
433
+ const height = best.height / pixelRatio
434
+ return {
435
+ width,
436
+ height,
437
+ pixelWidth: best.width,
438
+ pixelHeight: best.height,
439
+ regions: best.regions,
440
+ atlasRegions: best.regions.map((region) => ({
441
+ key: region.key,
442
+ nodeId: region.nodeId,
443
+ frame: region.frame,
444
+ // a region flush with the far edge is anchored to the logical edge so
445
+ // its source never runs past the atlas by a rounding hair
446
+ source: {
447
+ x:
448
+ region.atlasX + region.pixelWidth === best.width
449
+ ? width - region.frame.width
450
+ : region.atlasX / pixelRatio,
451
+ y:
452
+ region.atlasY + region.pixelHeight === best.height
453
+ ? height - region.frame.height
454
+ : region.atlasY / pixelRatio,
455
+ width: region.frame.width,
456
+ height: region.frame.height,
457
+ },
458
+ })),
459
+ }
460
+ }
461
+
321
462
  export interface RnxCaptureAtlasRegion extends RnxCaptureRegionRequest {
322
463
  /** source rectangle inside the packed atlas, in logical atlas coordinates */
323
464
  source: RnxCaptureRect
@@ -325,7 +466,7 @@ export interface RnxCaptureAtlasRegion extends RnxCaptureRegionRequest {
325
466
 
326
467
  export interface RnxCaptureRegionAtlas {
327
468
  kind: 'png-atlas'
328
- producer: 'headless-cpu-app-surface'
469
+ producer: 'headless-cpu-app-surface' | 'browser-compositor-app-surface'
329
470
  dataUri: string
330
471
  /** logical atlas dimensions; the encoded PNG uses pixelWidth × pixelHeight */
331
472
  width: number
@@ -533,7 +674,8 @@ export function isRnxCaptureRegionAtlas(value: unknown): value is RnxCaptureRegi
533
674
  if (
534
675
  !captureRecord(value) ||
535
676
  value.kind !== 'png-atlas' ||
536
- value.producer !== 'headless-cpu-app-surface' ||
677
+ (value.producer !== 'headless-cpu-app-surface' &&
678
+ value.producer !== 'browser-compositor-app-surface') ||
537
679
  typeof value.dataUri !== 'string' ||
538
680
  !value.dataUri.startsWith('data:image/png;base64,') ||
539
681
  !captureNumber(value.width) ||
package/src/config.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  // rnx.config.ts — per-app module resolution, turbo modules, env, state, and settings.
2
2
  //
3
3
  // usage:
4
- // import { defineConfig } from 'rnxsim/config'
4
+ // import { defineConfig } from 'peachsim/config'
5
5
  // export default defineConfig({ modules: { ... }, env: { ... } })
6
6
 
7
- import type { RevenueCatTestStoreConfig } from '@sootsim/compat/revenuecat-test-store'
7
+ import type { RevenueCatTestStoreConfig } from '@peach/compat/revenuecat-test-store'
8
8
 
9
9
  export type ModuleResolution =
10
10
  // an empty module under `modules`; under `nativeModules` / `turboModules` a
package/src/connect.ts CHANGED
@@ -1,4 +1,4 @@
1
- // `connect()` — the node entrypoint of the rnxsim SDK. resolves the running
1
+ // `connect()` — the node entrypoint of the peachsim SDK. resolves the running
2
2
  // bridge world the same way the CLI does (development bridge registry, then
3
3
  // daemon, then default port), picks a sim, and returns a connected SimClient
4
4
  // over the shared ws transport. node-only by design: sim-client.ts stays
@@ -67,7 +67,7 @@ export async function connect(options: ConnectOptions = {}): Promise<SimClient>
67
67
  const bridge = createCloudBridgeForParsed(target)
68
68
  if (!bridge)
69
69
  throw new SimConnectError('remote simulator session disappeared while connecting')
70
- return new SimClient(bridge, cloud.simId, {
70
+ return new SimClient(bridge, cloud.service === 'sim' ? cloud.simId : cloud.boxId, {
71
71
  commandTimeoutMs: options.timeoutMs ?? 15_000,
72
72
  })
73
73
  }
package/src/home-paths.ts CHANGED
@@ -56,10 +56,10 @@ export function rnxHomeDir(): string {
56
56
  // checkout, for exercising the
57
57
  // install path from this repo
58
58
  //
59
- // signal: realpath of process.argv[1] lands inside a `packages/sootsim/`
59
+ // signal: realpath of process.argv[1] lands inside a `packages/peachsim/`
60
60
  // directory. workspace bin symlinks (`node_modules/.bin/sootsim` →
61
- // `packages/sootsim/dist-cli/bin.js`) and bun-direct invocations
62
- // (`bun packages/sootsim/cli/bin.ts ...`) both match; published installs
61
+ // `packages/peachsim/dist-cli/bin.js`) and bun-direct invocations
62
+ // (`bun packages/peachsim/cli/bin.ts ...`) both match; published installs
63
63
  // resolve under `node_modules/sootsim/` instead.
64
64
  export function isSootsimDevCheckout(): boolean {
65
65
  if (process.env.SOOTSIM_FORCE_DAEMON_INSTALL === '1') return false
@@ -228,7 +228,7 @@ export interface BridgeHostOptions {
228
228
  port?: number
229
229
  openUrl?: (url: string, options?: OpenUrlOptions) => Promise<void> | void
230
230
  /** Contrast-specific excludes for the agent host's dev-server scan. passed
231
- * through to AgentHost; see packages/sootsim-engine/src/dev-scan-excludes.ts
231
+ * through to AgentHost; see packages/peach-engine/src/dev-scan-excludes.ts
232
232
  * for the canonical list. when omitted, AgentHost reads env vars directly. */
233
233
  agentScanExcludes?: AgentHostOptions['getExcludePorts']
234
234
  /** try preferred port, then port+1, port+2, …, up to this many attempts
@@ -1769,7 +1769,7 @@ export class SootSimBridgeHost {
1769
1769
 
1770
1770
  // /__server-scan — list local metro/expo/vxrn/one dev servers. tenant
1771
1771
  // worker ConnectRN polls this every few seconds to populate the device
1772
- // picker. mirrors packages/sootsim-shell/src/dev-middleware.ts so both
1772
+ // picker. mirrors packages/peach-shell/src/dev-middleware.ts so both
1773
1773
  // host environments (vite dev and the prod CLI daemon) return identical
1774
1774
  // JSON. icon URLs are rewritten through /__bundle-proxy so the browser
1775
1775
  // doesn't hit cross-origin loads against the discovered dev server.
@@ -1,6 +1,6 @@
1
1
  // shared fetch-proxy + app-api handlers for the sootsim dev/daemon HTTP
2
- // surface. both the vite dev middleware (packages/sootsim-shell/src/
3
- // dev-middleware.ts) and the daemon bridge (packages/sootsim/src/host/
2
+ // surface. both the vite dev middleware (packages/peach-shell/src/
3
+ // dev-middleware.ts) and the daemon bridge (packages/peachsim/src/host/
4
4
  // bridge-host.ts) mount these on their respective servers so guest bundle
5
5
  // fetches resolve the same way regardless of which surface the sim is
6
6
  // loaded through.
@@ -30,6 +30,7 @@ import { isLoopbackHost, normalizeHostname } from '../backend-origin.ts'
30
30
  import {
31
31
  FETCH_PROXY_BROWSER_USER_AGENT,
32
32
  getFetchProxyTargetHeaders,
33
+ resolveFetchProxyTargetUrl,
33
34
  } from './fetch-proxy-overrides.ts'
34
35
 
35
36
  const STRIP_FETCH_PROXY_HEADERS = new Set([
@@ -184,7 +185,7 @@ export async function handleFetchProxyRequest(
184
185
 
185
186
  let upstreamUrl: URL
186
187
  try {
187
- upstreamUrl = new URL(targetUrl)
188
+ upstreamUrl = resolveFetchProxyTargetUrl(new URL(targetUrl))
188
189
  } catch {
189
190
  applyFetchProxyCors(res)
190
191
  res.writeHead(400, { 'Content-Type': 'text/plain' })
@@ -39,6 +39,47 @@ function headerOverridesFor(hostname: string):
39
39
  return undefined
40
40
  }
41
41
 
42
+ // an app that builds its rpc provider hostname out of a per-tenant key cannot be
43
+ // pointed at another node by configuration alone. demo tooling injects this
44
+ // tenant name when a checkout carries no usable provider credentials, and the
45
+ // proxy resolves it to a public node for the chain. a real tenant never matches.
46
+ export const PUBLIC_RPC_TENANT = 'rnx-public-rpc'
47
+
48
+ const QUICKNODE_HOST_SUFFIX = '.quiknode.pro'
49
+
50
+ // quicknode encodes the chain in the subdomain and leaves mainnet off entirely.
51
+ const PUBLIC_RPC_BY_QUICKNODE_CHAIN: Record<string, string> = {
52
+ '': 'https://ethereum-rpc.publicnode.com',
53
+ 'arbitrum-mainnet': 'https://arbitrum-one-rpc.publicnode.com',
54
+ 'avalanche-mainnet': 'https://avalanche-c-chain-rpc.publicnode.com',
55
+ 'base-mainnet': 'https://base-rpc.publicnode.com',
56
+ 'blast-mainnet': 'https://blast-rpc.publicnode.com',
57
+ bsc: 'https://bsc-rpc.publicnode.com',
58
+ 'celo-mainnet': 'https://celo-rpc.publicnode.com',
59
+ matic: 'https://polygon-bor-rpc.publicnode.com',
60
+ optimism: 'https://optimism-rpc.publicnode.com',
61
+ 'soneium-mainnet': 'https://soneium-rpc.publicnode.com',
62
+ 'unichain-mainnet': 'https://unichain-rpc.publicnode.com',
63
+ }
64
+
65
+ // the url the proxy should actually fetch, which differs from the requested one
66
+ // only for the sentinel tenant above
67
+ export function resolveFetchProxyTargetUrl(requested: URL): URL {
68
+ const host = requested.hostname.toLowerCase()
69
+ if (!host.endsWith(QUICKNODE_HOST_SUFFIX)) return requested
70
+
71
+ const tenant = host.slice(0, -QUICKNODE_HOST_SUFFIX.length)
72
+ if (tenant !== PUBLIC_RPC_TENANT && !tenant.startsWith(`${PUBLIC_RPC_TENANT}.`)) {
73
+ return requested
74
+ }
75
+
76
+ const chain =
77
+ tenant === PUBLIC_RPC_TENANT ? '' : tenant.slice(PUBLIC_RPC_TENANT.length + 1)
78
+ const replacement = PUBLIC_RPC_BY_QUICKNODE_CHAIN[chain]
79
+ // the tenant token rides in the path, which a public node does not want
80
+ return replacement ? new URL(replacement) : requested
81
+ }
82
+
42
83
  export function getFetchProxyTargetHeaders(targetUrl: URL): Record<string, string> {
43
84
  const hostOverrides = headerOverridesFor(targetUrl.hostname)
44
85
  return {
@@ -17,9 +17,9 @@
17
17
 
18
18
  import fs from 'node:fs'
19
19
  import path from 'node:path'
20
- import { dispatchPlaneCommand, indexVersion } from '@rnx/box/plane'
20
+ import { dispatchPlaneCommand, indexVersion } from '@peach/box/plane'
21
21
  import { CheckoutFilePlane } from '../../cli/commands/box/checkout-plane.ts'
22
- import type { ProjectFilePlane } from '@rnx/box/plane'
22
+ import type { ProjectFilePlane } from '@peach/box/plane'
23
23
  import type { WebSocket } from 'ws'
24
24
 
25
25
  const WS_OPEN = 1
package/src/index.ts CHANGED
@@ -39,6 +39,7 @@ export {
39
39
  isRnxCaptureRegionAtlas,
40
40
  isRnxScreenCapture,
41
41
  rnxCaptureRasterRegions,
42
+ packRnxCaptureRegions,
42
43
  rnxRasterFrames,
43
44
  RNX_SCREEN_CAPTURE_VERSION,
44
45
  selectRnxRasterNodeIds,
@@ -54,6 +55,8 @@ export {
54
55
  type RnxCaptureSource,
55
56
  type RnxCaptureStyleTokenBinding,
56
57
  type RnxCaptureTextLine,
58
+ type RnxPackedCaptureAtlas,
59
+ type RnxPackedCaptureRegion,
57
60
  type RnxCaptureTextSegment,
58
61
  type RnxCaptureTree,
59
62
  type RnxCaptureVisualEvidence,
@@ -5,7 +5,7 @@
5
5
  // when active changes, so one loaded shell cannot mix runtime chunk trees.
6
6
  //
7
7
  // usage in metro.config.js:
8
- // const { withRNX } = require('rnxsim/metro')
8
+ // const { withRNX } = require('peachsim/metro')
9
9
  // module.exports = withRNX(getDefaultConfig(__dirname))
10
10
 
11
11
  import fs from 'fs'
@@ -89,7 +89,7 @@ function readCallableMetroExport(
89
89
  ? Reflect.get(moduleValue, 'default')
90
90
  : undefined
91
91
  if (typeof callable !== 'function') {
92
- throw new Error(`rnxsim: Metro ${label} module did not export a function`)
92
+ throw new Error(`peachsim: Metro ${label} module did not export a function`)
93
93
  }
94
94
  return (...args) => Reflect.apply(callable, undefined, args)
95
95
  }
@@ -115,7 +115,7 @@ function createMetroDefaultSerializer(projectRoot: string): MetroSerializer {
115
115
  ? Reflect.get(serialized, 'code')
116
116
  : undefined
117
117
  if (typeof code !== 'string') {
118
- throw new Error('rnxsim: Metro bundleToString did not return bundle code')
118
+ throw new Error('peachsim: Metro bundleToString did not return bundle code')
119
119
  }
120
120
  return code
121
121
  }
@@ -144,7 +144,7 @@ function appendRNXModulePaths(
144
144
  .replaceAll(path.sep, '/')
145
145
  }
146
146
  if (Object.keys(modulePaths).length !== dependencies.size) {
147
- throw new Error('rnxsim: bundle module IDs were not unique')
147
+ throw new Error('peachsim: bundle module IDs were not unique')
148
148
  }
149
149
 
150
150
  const specifiersByModuleId = new Map<string, Set<string>>()
@@ -30,7 +30,7 @@ export interface RNXMetroModuleIdentity extends RNXMetroModuleIdentityMetadata {
30
30
  // from the identity footer the plugin's serializer appends.
31
31
  export function toRNXProductionBundleUrl(bundleUrl: string): string {
32
32
  const relative = bundleUrl.startsWith('/')
33
- const url = new URL(bundleUrl, 'http://rnxsim.local')
33
+ const url = new URL(bundleUrl, 'http://peachsim.local')
34
34
  url.searchParams.set('dev', 'false')
35
35
  url.searchParams.set('hot', 'false')
36
36
  url.searchParams.set('minify', 'true')
@@ -44,7 +44,7 @@ export function toRNXProductionBundleUrl(bundleUrl: string): string {
44
44
  */
45
45
  export function toRNXDevelopmentBundleUrl(bundleUrl: string): string {
46
46
  const relative = bundleUrl.startsWith('/')
47
- const url = new URL(bundleUrl, 'http://rnxsim.local')
47
+ const url = new URL(bundleUrl, 'http://peachsim.local')
48
48
  url.searchParams.set('dev', 'true')
49
49
  url.searchParams.set('hot', 'false')
50
50
  url.searchParams.set('minify', 'false')
@@ -10,13 +10,13 @@
10
10
  // 1. identify upstream's native-module file (look for files calling
11
11
  // `requireNativeModule`, `TurboModuleRegistry.getEnforced`,
12
12
  // `requireNativeComponent`, or in `src/specs/Native*Module.ts`)
13
- // 2. write a thin stub at `packages/compat/src/stubs/native-seams/<pkg>.ts`
13
+ // 2. write a thin stub at `packages/peach-compat/src/stubs/native-seams/<pkg>.ts`
14
14
  // that exports the same default + named exports upstream's seam file
15
15
  // exports — except the implementation calls into sootsim's engine.
16
16
  // 3. append an entry below. the plugin handles the rest.
17
17
  //
18
18
  // canonical example: react-native-reanimated. see
19
- // - `packages/compat/src/stubs/react-native-reanimated.ts` (the seam stub)
19
+ // - `packages/peach-compat/src/stubs/react-native-reanimated.ts` (the seam stub)
20
20
  // - `reanimatedNativeSeamRedirect` in vite-plugin.ts (still hard-coded
21
21
  // because it has package-specific edge cases — fabricUtils,
22
22
  // validate-worklets-version. new entries should use this manifest path
@@ -41,7 +41,7 @@ export interface NativeSeamEntry {
41
41
  seamBasenames: string[]
42
42
  /**
43
43
  * absolute path to the seam stub. typically
44
- * `packages/compat/src/stubs/native-seams/<pkg>.ts` — but a single stub
44
+ * `packages/peach-compat/src/stubs/native-seams/<pkg>.ts` — but a single stub
45
45
  * file may export every native module the package exposes, so the same
46
46
  * `target` can appear in multiple entries if a package has multiple native
47
47
  * modules that share an implementation file.
@@ -61,9 +61,9 @@ import path from 'node:path'
61
61
  import { fileURLToPath } from 'node:url'
62
62
 
63
63
  const HERE = path.dirname(fileURLToPath(import.meta.url))
64
- const NATIVE_SEAMS_DIR = path.resolve(HERE, '../../compat/src/stubs/native-seams')
64
+ const NATIVE_SEAMS_DIR = path.resolve(HERE, '../../peach-compat/src/stubs/native-seams')
65
65
 
66
- /** resolve a stub path under `packages/compat/src/stubs/native-seams/`.
66
+ /** resolve a stub path under `packages/peach-compat/src/stubs/native-seams/`.
67
67
  * resolves to `.tsx` when only that exists (JSX-bearing seams like
68
68
  * expo-image, react-native-webview). preferred order: `.ts`, `.tsx`. */
69
69
  export function seamPath(basename: string): string {
@@ -217,7 +217,7 @@ export const NATIVE_SEAM_MANIFEST: NativeSeamEntry[] = [
217
217
  // onto the class-based seam, whose methods throw.
218
218
  //
219
219
  // registering by module NAME instead is what keeps old and new package
220
- // versions both working: ../../compat/src/stubs/expo-{calendar,contacts}-register.ts
220
+ // versions both working: ../../peach-compat/src/stubs/expo-{calendar,contacts}-register.ts
221
221
  // installs every id either version asks for, and upstream's own
222
222
  // `requireNativeModule` picks the right one per entry point. a file
223
223
  // redirect can only match one layout, so it breaks the versions it was
@@ -2,7 +2,7 @@ const name = 'rnx'
2
2
 
3
3
  export const rnxPublicBrand = Object.freeze({
4
4
  name,
5
- packageName: 'rnxsim',
5
+ packageName: 'peachsim',
6
6
  commandName: 'rnx',
7
7
  domain: 'rnxsim.com',
8
8
  origin: 'https://rnxsim.com',
@@ -39,6 +39,7 @@ import {
39
39
  enumerateBundleImages,
40
40
  enumerateBundleJson,
41
41
  } from '../../sootsim-swift/src/bundleAssets'
42
+ import { swiftEntryModuleEsm } from '../../sootsim-swift/src/entryModule'
42
43
  import { compileSwiftProject, httpSwiftCompileSubmit } from './swift-submit'
43
44
  import { entryRootId } from './vite-plugin-swift'
44
45
  import type { SwiftModuleAddress } from './swift-submit'
@@ -230,11 +231,13 @@ export function swiftRemotePlugin(options: SwiftRemotePluginOptions): Plugin {
230
231
  // every import mounts through the one entry: the artifact answers
231
232
  // whether it is a view or an app root at runtime, so the transform
232
233
  // never re-derives the conformance from source.
233
- return [
234
- `import { createSwiftEntry } from 'rnxsim/swift'`,
235
- `export default createSwiftEntry({ url: ${JSON.stringify(address.url)}, hash: ${JSON.stringify(address.hash)}, rootId: ${JSON.stringify(entryRootId(packagePath, file))}, resources: ${JSON.stringify(address.imageUrls ?? {})}, bundleFiles: ${JSON.stringify(pack.resources)} })`,
236
- '',
237
- ].join('\n')
234
+ return swiftEntryModuleEsm({
235
+ url: address.url,
236
+ hash: address.hash,
237
+ rootId: entryRootId(packagePath, file),
238
+ resources: address.imageUrls ?? {},
239
+ bundleFiles: pack.resources,
240
+ })
238
241
  },
239
242
  }
240
243
  }
@@ -34,11 +34,11 @@ let _monorepoRootCache: string | null | undefined
34
34
  function findMonorepoRoot(): string | null {
35
35
  if (_monorepoRootCache !== undefined) return _monorepoRootCache
36
36
  let dir = path.dirname(fileURLToPath(import.meta.url))
37
- // walk up until we find the Contrast monorepo: it has packages/sootsim-engine and
37
+ // walk up until we find the Contrast monorepo: it has packages/peach-engine and
38
38
  // a public/sootsim dev-build output slot. bounded by the filesystem root.
39
39
  for (let i = 0; i < 12 && dir !== path.dirname(dir); i++) {
40
40
  if (
41
- fs.existsSync(path.join(dir, 'packages/sootsim-engine/package.json')) &&
41
+ fs.existsSync(path.join(dir, 'packages/peach-engine/package.json')) &&
42
42
  fs.existsSync(path.join(dir, 'public/sootsim'))
43
43
  ) {
44
44
  _monorepoRootCache = dir
@@ -66,11 +66,12 @@ export function resolveDevCheckoutRuntimeRoot(): string | null {
66
66
  if (!root) return null
67
67
  const devBuild = path.join(root, 'public/sootsim')
68
68
  // require the runtime shape the installed runtime also has, so serving it is
69
- // a drop-in (index.html shell + engine/ + engine-tenant/worker.js tenant).
69
+ // a drop-in (index.html shell + engine/ + both engine-tenant workers).
70
70
  if (
71
71
  fs.existsSync(path.join(devBuild, 'index.html')) &&
72
72
  fs.existsSync(path.join(devBuild, 'engine/index.html')) &&
73
- fs.existsSync(path.join(devBuild, 'engine-tenant/worker.js'))
73
+ fs.existsSync(path.join(devBuild, 'engine-tenant/worker.js')) &&
74
+ fs.existsSync(path.join(devBuild, 'engine-tenant/worker.prod.js'))
74
75
  ) {
75
76
  return devBuild
76
77
  }
@@ -217,9 +218,10 @@ export function serveRuntimeFile(req: any, res: any, fullPath: string) {
217
218
  return
218
219
  }
219
220
  // these urls are STABLE across dev rebuilds and runtime version switches
220
- // (worker.js, canvaskit-graphite.wasm, the html shells are unhashed), so the
221
+ // (both worker entries, canvaskit-graphite.wasm, and the html shells are
222
+ // unhashed), so the
221
223
  // browser must revalidate every load — the old year-long immutable here made every
222
- // new tab run a stale cached engine-tenant worker.js against a fresh engine,
224
+ // new tab run a stale cached tenant entry against a fresh engine,
223
225
  // which also broke `do reload` ("bridge never reconnected"). no-cache +
224
226
  // last-modified keeps repeat loads cheap 304s on localhost.
225
227
  res.setHeader('cache-control', 'no-cache')