peachsim 0.1.1641 → 0.1.1644

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 (96) hide show
  1. package/cli/app-bundle-resources.ts +57 -0
  2. package/cli/commands/control.ts +12 -0
  3. package/cli/drivers/playwright.ts +39 -0
  4. package/cli/outbound-endpoints.ts +4 -2
  5. package/detox/motion-change.cjs +7 -5
  6. package/detox/motion-change.d.cts +1 -0
  7. package/detox/motion-leg-starts.cjs +20 -0
  8. package/detox/motion-leg-starts.d.cts +8 -0
  9. package/dist-lib/agent-daemon-client.cjs +1 -1
  10. package/dist-lib/agent-events.cjs +1 -1
  11. package/dist-lib/attached-projects.cjs +1 -1
  12. package/dist-lib/auth/shared-session.cjs +1 -1
  13. package/dist-lib/backend-origin.cjs +3 -3
  14. package/dist-lib/beta.cjs +1 -1
  15. package/dist-lib/beta.mjs +1 -1
  16. package/dist-lib/bridge-constants.cjs +1 -1
  17. package/dist-lib/bridge-contract-input.cjs +1 -1
  18. package/dist-lib/bridge-contract-input.mjs +1 -1
  19. package/dist-lib/bridge-contract.cjs +1 -1
  20. package/dist-lib/bridge-contract.mjs +1 -1
  21. package/dist-lib/bundle-channel.cjs +719 -572
  22. package/dist-lib/capture-contract.cjs +1 -1
  23. package/dist-lib/capture-contract.mjs +1 -1
  24. package/dist-lib/cloud-contract.cjs +1 -1
  25. package/dist-lib/cloud-contract.mjs +1 -1
  26. package/dist-lib/cloud-test-args.cjs +1 -1
  27. package/dist-lib/cloud-test-args.mjs +1 -1
  28. package/dist-lib/cloud.cjs +1 -1
  29. package/dist-lib/cloud.mjs +1 -1
  30. package/dist-lib/config.cjs +1 -1
  31. package/dist-lib/config.mjs +1 -1
  32. package/dist-lib/detox/index.cjs +7 -6
  33. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  34. package/dist-lib/expo-swift-bundle.cjs +656 -509
  35. package/dist-lib/expo-swift-metadata.cjs +1 -1
  36. package/dist-lib/expo-swift-metadata.mjs +1 -1
  37. package/dist-lib/home-paths.cjs +1 -1
  38. package/dist-lib/host/bridge-host.cjs +811 -658
  39. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  40. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  41. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  42. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  43. package/dist-lib/host/websocket-proxy.cjs +1 -1
  44. package/dist-lib/index.cjs +89 -41
  45. package/dist-lib/jump-to-source-babel.cjs +1 -1
  46. package/dist-lib/jump-to-source-native.cjs +1 -1
  47. package/dist-lib/kotlin-host.cjs +1 -1
  48. package/dist-lib/menu.cjs +14 -6
  49. package/dist-lib/menu.mjs +14 -6
  50. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  51. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  52. package/dist-lib/metro-production-bundle.cjs +1 -1
  53. package/dist-lib/metro-production-bundle.mjs +1 -1
  54. package/dist-lib/metro.cjs +88 -41
  55. package/dist-lib/native-app-identity-project.cjs +90 -41
  56. package/dist-lib/native-app-identity-project.mjs +89 -41
  57. package/dist-lib/native-app-identity.cjs +1 -1
  58. package/dist-lib/native-app-identity.mjs +1 -1
  59. package/dist-lib/native-firebase-metadata.cjs +1 -1
  60. package/dist-lib/native-firebase-metadata.mjs +1 -1
  61. package/dist-lib/plist.cjs +1 -1
  62. package/dist-lib/plist.mjs +1 -1
  63. package/dist-lib/profiles.cjs +1 -1
  64. package/dist-lib/public-brand.cjs +1 -1
  65. package/dist-lib/react-native-host-modules.cjs +1 -1
  66. package/dist-lib/react-native-host-modules.mjs +1 -1
  67. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  68. package/dist-lib/sdk.cjs +2 -1
  69. package/dist-lib/sdk.mjs +2 -1
  70. package/dist-lib/skills.cjs +1716 -1366
  71. package/dist-lib/swift-compile.cjs +297 -215
  72. package/dist-lib/swift-compile.mjs +315 -227
  73. package/dist-lib/swift-package.cjs +1 -1
  74. package/dist-lib/swift-package.mjs +1 -1
  75. package/dist-lib/swift-submit.cjs +1 -1
  76. package/dist-lib/swift-submit.mjs +1 -1
  77. package/dist-lib/swift-toolchain.cjs +1 -1
  78. package/dist-lib/swift.cjs +1 -1
  79. package/dist-lib/vite-swift.cjs +620 -473
  80. package/dist-lib/vite.cjs +1 -1
  81. package/dist-lib/worklet-executable.cjs +1 -1
  82. package/dist-lib/worklet-executable.mjs +1 -1
  83. package/package.json +1 -1
  84. package/src/appBundleResourcesContract.ts +51 -0
  85. package/src/backend-origin.ts +3 -1
  86. package/src/host/bridge-host.ts +17 -11
  87. package/src/host/staged-host-files.ts +28 -4
  88. package/src/launchMetadataContract.ts +1 -0
  89. package/src/menu.ts +12 -4
  90. package/src/native-app-identity-project.ts +47 -38
  91. package/src/runtime-delivery.ts +6 -7
  92. package/src/swift-compile.ts +18 -7
  93. package/src/swift-compiler-digest.ts +5 -3
  94. package/src/swift-objc-compile.ts +34 -4
  95. package/src/swift-root-lock.ts +241 -92
  96. package/src/xcode-project.ts +67 -4
@@ -0,0 +1,57 @@
1
+ import { createHash } from 'node:crypto'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { preferredMimeType } from '../../peach-compat/src/stubs/uttype-mime'
5
+ import { enumerateBundleResources } from '../../sootsim-swift/src/bundleAssets'
6
+ import {
7
+ isAppBundleResourcePath,
8
+ type AppBundleResources,
9
+ } from '../src/appBundleResourcesContract'
10
+ import { stageHostByteFiles } from '../src/host/staged-host-files'
11
+ import { readIosAppTarget } from '../src/native-app-identity-project'
12
+ import { readPackageFile } from '../src/swift-source-file'
13
+
14
+ export function stageAppBundleResources(
15
+ appDir: string,
16
+ origin: string,
17
+ iosTarget?: string,
18
+ ): AppBundleResources {
19
+ const app = readIosAppTarget(appDir, iosTarget)
20
+ if (!app) return {}
21
+ const root = fs.realpathSync(appDir)
22
+ const members = new Map<string, string>()
23
+ for (const file of app.resources) {
24
+ const real = fs.realpathSync(file)
25
+ const relative = path.relative(root, real)
26
+ if (relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
27
+ throw new Error('native app resource escapes the Xcode input root')
28
+ const lexical = path.relative(root, file)
29
+ if (
30
+ lexical === '..' ||
31
+ lexical.startsWith(`..${path.sep}`) ||
32
+ path.isAbsolute(lexical)
33
+ )
34
+ throw new Error('native app resource escapes the selected project')
35
+ const name = `Resources/${lexical.split(path.sep).join('/')}`
36
+ members.set(name, file)
37
+ }
38
+ const resources = enumerateBundleResources([...members.keys()])
39
+ const bytes = resources.map(({ base, file }) => {
40
+ if (!isAppBundleResourcePath(base)) throw new Error('invalid native resource path')
41
+ const source = members.get(file)
42
+ if (!source) throw new Error('native resource is not an Xcode target member')
43
+ return readPackageFile(root, source, Infinity)
44
+ })
45
+ const urls = stageHostByteFiles(bytes, origin)
46
+ const result: AppBundleResources = Object.create(null)
47
+ resources.forEach(({ base }, index) => {
48
+ const content = bytes[index]!
49
+ result[base] = {
50
+ uri: urls[index]!,
51
+ sha256: createHash('sha256').update(content).digest('hex'),
52
+ contentType: preferredMimeType(base) ?? 'application/octet-stream',
53
+ byteLength: content.byteLength,
54
+ }
55
+ })
56
+ return result
57
+ }
@@ -89,6 +89,7 @@ import {
89
89
  isPeachSimulatorDocumentPath,
90
90
  } from '../../src/simulator-route'
91
91
  import { sleep } from '../../src/sleep'
92
+ import { stageAppBundleResources } from '../app-bundle-resources'
92
93
  import { discoverAppCatalog } from '../app-catalog'
93
94
  import { loadRNXAppConfig } from '../app-config'
94
95
  import { discoverNativeLinkedAppFonts, resolveMetroProjectRoot } from '../app-fonts'
@@ -1378,6 +1379,7 @@ export async function buildShellUrl(
1378
1379
  )
1379
1380
  const expoStamped = new URL(stamped)
1380
1381
  expoStamped.searchParams.delete('nativeAppIdentity')
1382
+ expoStamped.searchParams.delete('appBundleResources')
1381
1383
  if (project) {
1382
1384
  const nativeRenderer = readNativeProjectRenderer(project.appDir, platform)
1383
1385
  if (nativeRenderer !== undefined) {
@@ -1397,6 +1399,15 @@ export async function buildShellUrl(
1397
1399
  : null
1398
1400
  if (nativeAppIdentity)
1399
1401
  expoStamped.searchParams.set('nativeAppIdentity', nativeAppIdentity)
1402
+ if (platform === 'ios' && nativeAppIdentity) {
1403
+ const resources = stageAppBundleResources(
1404
+ project.appDir,
1405
+ `http://localhost:${port.port}`,
1406
+ opts.iosTarget,
1407
+ )
1408
+ if (Object.keys(resources).length > 0)
1409
+ expoStamped.searchParams.set('appBundleResources', JSON.stringify(resources))
1410
+ }
1400
1411
  }
1401
1412
  // a plain Metro has no native Firebase config endpoint, so the CLI reads it
1402
1413
  // from the project whose Expo config it can evaluate. a project without its
@@ -1498,6 +1509,7 @@ function deriveCurrentSimBaseUrl(
1498
1509
  // bundle.
1499
1510
  url.searchParams.delete('appSplash')
1500
1511
  url.searchParams.delete('appCatalog')
1512
+ url.searchParams.delete('appBundleResources')
1501
1513
  url.searchParams.delete('nativeFirebase')
1502
1514
  url.searchParams.delete('packageVersions')
1503
1515
  url.searchParams.delete('nativeAppIdentity')
@@ -18,6 +18,7 @@ import { createServer } from 'net'
18
18
  import { tmpdir } from 'os'
19
19
  import { join } from 'path'
20
20
  import { AUTOMATION_SIM_IDLE_REAP_TTL_MS } from '../../src/bridge-constants'
21
+ import { getCliVersion } from '../../src/cli-version'
21
22
  import {
22
23
  desktopHostDelivery,
23
24
  desktopHostPlatform,
@@ -54,6 +55,38 @@ function probe(): DriverAvailability {
54
55
  }
55
56
  }
56
57
 
58
+ // every bridge since the fetch transport landed serves its route, so a 404
59
+ // from a server whose /healthz identifies a peach bridge means that bridge
60
+ // predates this cli. without that identity the port may hold something else,
61
+ // and the message says only what was observed. restarting closes every sim
62
+ // the bridge hosts, so the cli never does it.
63
+ async function olderBridgeMessage(bridgePort: number): Promise<string> {
64
+ const restart = `run \`peach daemon restart\` to load the matching bridge; it closes every sim that bridge hosts, so it was not restarted.`
65
+ let health: unknown = null
66
+ try {
67
+ const response = await fetch(`http://127.0.0.1:${bridgePort}/healthz`, {
68
+ signal: AbortSignal.timeout(10_000),
69
+ })
70
+ health = await response.json()
71
+ } catch {
72
+ // an unreadable /healthz leaves the server unidentified
73
+ }
74
+ const field = (key: string) =>
75
+ health && typeof health === 'object' ? Reflect.get(health, key) : null
76
+ const pid = field('pid')
77
+ if (field('ok') !== true || typeof pid !== 'number') {
78
+ return `port ${bridgePort} has no fetch transport route and its /healthz did not identify a peach bridge, so this peach cli ${getCliVersion()} cannot use it. if an older peach bridge holds that port, ${restart}`
79
+ }
80
+ const runtime = field('activeRuntime')
81
+ const details = [
82
+ `pid ${pid}`,
83
+ typeof runtime === 'string' ? `runtime ${runtime}` : null,
84
+ ]
85
+ .filter((part) => part !== null)
86
+ .join(', ')
87
+ return `the peach bridge on port ${bridgePort} (${details}) has no fetch transport route, so it predates this peach cli ${getCliVersion()}. ${restart}`
88
+ }
89
+
57
90
  // resolve the Chrome user-data dir for a profiled launch. a non-empty
58
91
  // return means the host should use a persistent context; '' means a plain
59
92
  // (in-memory) browser launch.
@@ -111,6 +144,12 @@ async function launch(opts: DriverLaunchOptions): Promise<DriverLaunchResult> {
111
144
  `http://127.0.0.1:${opts.bridgePort}/__peach/fetch-transport`,
112
145
  { signal: AbortSignal.timeout(10_000) },
113
146
  )
147
+ if (response.status === 404) {
148
+ return {
149
+ launched: false,
150
+ message: await olderBridgeMessage(opts.bridgePort),
151
+ }
152
+ }
114
153
  if (!response.ok) throw new Error(`transport discovery returned ${response.status}`)
115
154
  const descriptor: unknown = await response.json()
116
155
  if (!descriptor || typeof descriptor !== 'object')
@@ -38,7 +38,7 @@ export const OUTBOUND_ENDPOINTS: Record<string, OutboundEndpointDeclaration> = {
38
38
  runtime_delivery: {
39
39
  category: 'runtime-delivery',
40
40
  destination:
41
- 'https://contrast.dev/runtimes/manifest.json and selected runtime tarball; with `peach desktop install --native`, https://r2.rnxsim.com/releases/native/<channel>/ manifest and versioned host zip',
41
+ 'https://peachsim.com/runtimes/manifest.json and selected runtime tarball; with `peach desktop install --native`, https://r2.rnxsim.com/releases/native/<channel>/ manifest and versioned host zip',
42
42
  preConsent: true,
43
43
  payload: 'HTTP runtime or desktop host version request; no project or app data',
44
44
  },
@@ -107,7 +107,7 @@ export const OUTBOUND_ENDPOINTS: Record<string, OutboundEndpointDeclaration> = {
107
107
  },
108
108
  release_notes: {
109
109
  category: 'explicit-user-action',
110
- destination: 'https://contrast.dev/api/changelog/sootsim',
110
+ destination: 'https://peachsim.com/api/changelog/sootsim',
111
111
  preConsent: false,
112
112
  payload: 'requested runtime version only',
113
113
  },
@@ -639,6 +639,8 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
639
639
  { category: 'local_development', count: 1 },
640
640
  'packages/peachsim/cli/drivers/playwright.ts :: fetch :: `http://127.0.0.1:${opts.bridgePort}/__peach/fetch-transport`':
641
641
  { category: 'local_development', count: 1 },
642
+ 'packages/peachsim/cli/drivers/playwright.ts :: fetch :: `http://127.0.0.1:${bridgePort}/healthz`':
643
+ { category: 'local_development', count: 1 },
642
644
  "packages/peach-engine/src/screenshots/capture-store.ts :: fetch :: captureApiUrl(ref, 'grid')":
643
645
  { category: 'contrast_user_action', count: 1 },
644
646
  'packages/peach-engine/src/screenshots/capture-store.ts :: fetch :: captureApiUrl(ref, variant)':
@@ -112,8 +112,10 @@ const PAIRED_MOTION_MINIMUM_CHANGE_PERCENT = 0.3
112
112
  const SETTLE_BLIP_MAXIMUM_CHANGE_PERCENT = 0.6
113
113
 
114
114
  // a present or dismiss records one second (60 transitions); a gesture ladder
115
- // records four. a gesture jumps at its touch cadence and each capture phases
116
- // that cadence differently, so a gesture is graded by total travel instead of
115
+ // records four. repeated animation ladders can also exceed that length, but
116
+ // their declared cycles retain frame pairing. a gesture jumps at its touch
117
+ // cadence and each capture phases that cadence differently, so it is graded by
118
+ // total travel instead of
117
119
  // by frame. travel is judged over the whole gesture and again over each quarter
118
120
  // so one jump or a lagging release cannot pass on its total.
119
121
  const GESTURE_MOTION_MIN_TRANSITIONS = 120
@@ -127,7 +129,7 @@ const GESTURE_QUARTER_TRAVEL_RATIO_RANGE = Object.freeze({ min: 0.5, max: 2 })
127
129
  // against a moving oracle fails. a gesture needs sootsim's total travel within
128
130
  // a range of the oracle's. both lanes hold the same unique stage-label
129
131
  // transitions in the same order; every measurement must exist.
130
- function evaluateMotionTransitions(transitions, oracleTarget) {
132
+ function evaluateMotionTransitions(transitions, oracleTarget, repeating) {
131
133
  const key = (transition) => `${transition.fromLabel}\u0000${transition.toLabel}`
132
134
  const oracle = transitions.filter((transition) => transition.target === oracleTarget)
133
135
  const sootsim = transitions.filter((transition) => transition.target === 'sootsim')
@@ -175,7 +177,7 @@ function evaluateMotionTransitions(transitions, oracleTarget) {
175
177
  Math.floor((list.length * (index + 1)) / 4),
176
178
  )
177
179
  const follows =
178
- oracle.length >= GESTURE_MOTION_MIN_TRANSITIONS
180
+ !repeating && oracle.length >= GESTURE_MOTION_MIN_TRANSITIONS
179
181
  ? within(travel(sootsim), travel(oracle), GESTURE_TRAVEL_RATIO_RANGE) &&
180
182
  [0, 1, 2, 3].every((index) =>
181
183
  within(
@@ -568,7 +570,7 @@ function evaluateMotionPublication(stages, transitions, oracleTarget, cycleOnset
568
570
  last.passedThreshold &&
569
571
  (!needsIntermediates || movingPassed >= PUBLICATION_MIN_INTERMEDIATE_PASSES) &&
570
572
  cyclePeriods.every((period) => period.passed) &&
571
- evaluateMotionTransitions(transitions, oracleTarget).passed,
573
+ evaluateMotionTransitions(transitions, oracleTarget, cyclePeriods.length > 0).passed,
572
574
  cyclePeriods,
573
575
  intermediateCount: intermediates.length,
574
576
  intermediatePassed,
@@ -50,6 +50,7 @@ export type MotionTransitionSample = {
50
50
  export function evaluateMotionTransitions(
51
51
  transitions: MotionTransitionSample[],
52
52
  oracleTarget: string,
53
+ repeating: boolean,
53
54
  ): {
54
55
  moving: number
55
56
  passed: boolean
@@ -0,0 +1,20 @@
1
+ // animation start witnesses are diagnostics; they never replace pixel or cycle grading.
2
+ function animationLegStarts(value) {
3
+ if (!Array.isArray(value) || value.length === 0)
4
+ throw new Error('animation leg starts must be a non-empty array')
5
+ return value.map((row) => {
6
+ if (
7
+ !row ||
8
+ typeof row.track !== 'string' ||
9
+ !row.track ||
10
+ typeof row.leg !== 'string' ||
11
+ !row.leg ||
12
+ !Number.isFinite(row.atMs) ||
13
+ !Number.isFinite(row.startValue)
14
+ )
15
+ throw new Error('invalid animation leg start witness')
16
+ return { track: row.track, leg: row.leg, atMs: row.atMs, startValue: row.startValue }
17
+ })
18
+ }
19
+
20
+ module.exports = { animationLegStarts }
@@ -0,0 +1,8 @@
1
+ export type AnimationLegStart = {
2
+ track: string
3
+ leg: string
4
+ atMs: number
5
+ startValue: number
6
+ }
7
+
8
+ export function animationLegStarts(value: unknown): AnimationLegStart[]
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -39,7 +39,7 @@ __export(backend_origin_exports, {
39
39
  resolveContrastBackendOrigin: () => resolveContrastBackendOrigin
40
40
  });
41
41
  module.exports = __toCommonJS(backend_origin_exports);
42
- var DEFAULT_CONTRAST_BACKEND_ORIGIN = "https://contrast.dev";
42
+ var DEFAULT_CONTRAST_BACKEND_ORIGIN = "https://peachsim.com";
43
43
  var LOOPBACK_PROBE_HOSTNAMES = ["127.0.0.1", "::1"];
44
44
  function normalizeHostname(hostname) {
45
45
  return hostname.replace(/^\[|\]$/g, "").toLowerCase();
@@ -100,7 +100,7 @@ function isBrowserAssetProxyUrl(value) {
100
100
  var PEACHSIM_NATIVE_HOST = "native.peachsim.com";
101
101
  function isContrastBackendHost(hostname) {
102
102
  const normalized = normalizeHostname(hostname);
103
- return isLoopbackHost(normalized) || normalized === "contrast.dev" || normalized.endsWith(".contrast.dev") || normalized === PEACHSIM_NATIVE_HOST;
103
+ return isLoopbackHost(normalized) || normalized === "contrast.dev" || normalized.endsWith(".contrast.dev") || normalized === "peachsim.com" || normalized === "www.peachsim.com" || normalized === PEACHSIM_NATIVE_HOST;
104
104
  }
105
105
  function isContrastBackendOrigin(origin) {
106
106
  try {
package/dist-lib/beta.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/beta.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (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.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (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.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! peach v0.1.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
1
+ /*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var REFUSED_PERFORM_STEP_TYPES = [