rnxsim 0.1.393 → 0.1.395

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 (69) hide show
  1. package/README.md +12 -5
  2. package/cli/cloud-client.ts +74 -52
  3. package/cli/cloud-dispatch.ts +2 -1
  4. package/cli/commands/inspect/core.ts +33 -2
  5. package/cli/commands/platform.ts +34 -9
  6. package/dist-lib/agent-daemon-client.cjs +1 -1
  7. package/dist-lib/agent-events.cjs +1 -1
  8. package/dist-lib/agent-identity.cjs +1 -1
  9. package/dist-lib/agent-sessions.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 +12 -1
  17. package/dist-lib/bridge-contract-input.mjs +10 -1
  18. package/dist-lib/bridge-contract.cjs +7 -1
  19. package/dist-lib/bridge-contract.mjs +5 -1
  20. package/dist-lib/capture-contract.cjs +1 -1
  21. package/dist-lib/capture-contract.mjs +1 -1
  22. package/dist-lib/cli-constants.cjs +1 -1
  23. package/dist-lib/cloud-contract.cjs +29 -3
  24. package/dist-lib/cloud-contract.mjs +26 -2
  25. package/dist-lib/config.cjs +1 -1
  26. package/dist-lib/detox/index.cjs +1 -1
  27. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  28. package/dist-lib/home-paths.cjs +1 -1
  29. package/dist-lib/host/bridge-host.cjs +1 -1
  30. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  31. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  32. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  33. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  34. package/dist-lib/host/websocket-proxy.cjs +1 -1
  35. package/dist-lib/index.cjs +25 -55
  36. package/dist-lib/jump-to-source-babel.cjs +1 -1
  37. package/dist-lib/menu.cjs +1 -1
  38. package/dist-lib/menu.mjs +1 -1
  39. package/dist-lib/metro-production-bundle.cjs +132 -8
  40. package/dist-lib/metro-production-bundle.mjs +129 -6
  41. package/dist-lib/metro.cjs +3 -55
  42. package/dist-lib/profiles.cjs +1 -1
  43. package/dist-lib/public-brand.cjs +1 -1
  44. package/dist-lib/react-native-host-modules.cjs +1 -1
  45. package/dist-lib/react-native-host-modules.mjs +1 -1
  46. package/dist-lib/render-mode.cjs +1 -1
  47. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  48. package/dist-lib/sdk.cjs +24 -3
  49. package/dist-lib/sdk.mjs +24 -3
  50. package/dist-lib/skills.cjs +28 -25
  51. package/dist-lib/vite.cjs +1 -1
  52. package/package.json +1 -1
  53. package/src/bridge-contract-input.ts +12 -0
  54. package/src/bridge-contract.ts +7 -0
  55. package/src/cloud-contract.ts +29 -0
  56. package/src/metro-plugin.ts +17 -98
  57. package/src/metro-production-bundle.ts +204 -3
  58. package/dist-lib/metro-host-modules/react-dom.js +0 -6
  59. package/dist-lib/metro-host-modules/react-native-web.js +0 -6
  60. package/dist-lib/metro-host-modules/react-native.js +0 -6
  61. package/dist-lib/metro-host-modules/react.js +0 -6
  62. package/dist-lib/metro-host-modules/react__jsx-dev-runtime.js +0 -6
  63. package/dist-lib/metro-host-modules/react__jsx-runtime.js +0 -6
  64. package/src/metro-host-modules/react-dom.js +0 -6
  65. package/src/metro-host-modules/react-native-web.js +0 -6
  66. package/src/metro-host-modules/react-native.js +0 -6
  67. package/src/metro-host-modules/react.js +0 -6
  68. package/src/metro-host-modules/react__jsx-dev-runtime.js +0 -6
  69. package/src/metro-host-modules/react__jsx-runtime.js +0 -6
package/README.md CHANGED
@@ -386,9 +386,15 @@ development Electron build against the same user-data directory may ask for
386
386
  one-time Keychain approval. `RNX_NO_OPEN=1` keeps the server headless. The
387
387
  Metro wrapper also has two independent non-window opt-ins.
388
388
  `devServer` serves the RNX shell from the Metro process. `productionBundles`
389
- lets explicitly marked production bundle requests use the RNX host
390
- implementations of React and React Native while the rest of the app receives
391
- production transforms.
389
+ appends Metro's exact module-ID-to-source-path map to every production bundle,
390
+ as a trailing `globalThis.__sootsimModulePaths = { ... }` assignment.
391
+
392
+ That footer is the only change. Metro drops the fourth `__d` argument when
393
+ `dev` is false, so a production bundle names none of its modules, and RNX needs
394
+ those names to recognize React, React Native, and native compatibility seams at
395
+ load time. Resolution, transforms, minification, and the source map stay the
396
+ app's own, so the annotated bundle is the bundle you ship: the footer assigns
397
+ one unused global on a device, and Hermes drops it during bytecode compilation.
392
398
 
393
399
  ```js
394
400
  module.exports = withRNX(config, { productionBundles: true })
@@ -400,8 +406,9 @@ module.exports = withRNX(config, {
400
406
  })
401
407
  ```
402
408
 
403
- Calling `withRNX(config)` changes nothing. Production requests must run in a
404
- Metro process started with `NODE_ENV=production` and use the URL helper:
409
+ Calling `withRNX(config)` changes nothing. To load a production bundle from a
410
+ running Metro dev server, use the URL helper, which sets Metro's ordinary
411
+ `dev=false&minify=true` options:
405
412
 
406
413
  ```js
407
414
  const { toRNXProductionBundleUrl } = require('rnxsim/metro')
@@ -1,8 +1,16 @@
1
1
  import { createHash, randomUUID } from 'node:crypto'
2
2
  import { closeSync, fstatSync, openSync, readFileSync } from 'node:fs'
3
- import { extname, resolve } from 'node:path'
4
- import { assertSelfContainedMetroArtifactAssets } from '../../contrast-bundler/src/metroAssetArtifact'
3
+ import { dirname, extname, join, resolve } from 'node:path'
5
4
  import {
5
+ embedMetroAssetFiles,
6
+ type MetroAssetVariant,
7
+ } from '../../contrast-bundler/src/metroAssetArtifact'
8
+ import {
9
+ buildRnxCloudArtifact,
10
+ type RnxCloudArtifactReceipt,
11
+ } from '../../contrast-bundler/src/metroCloudArtifact'
12
+ import {
13
+ isRnxCloudCommandType,
6
14
  RNX_CLOUD_MAX_ARTIFACT_BYTES,
7
15
  type RnxCloudCreateReceipt,
8
16
  } from '../src/cloud-contract'
@@ -27,6 +35,8 @@ interface CreateCloudSimulatorOptions {
27
35
  bundlePath: string
28
36
  device: string
29
37
  authorization: string
38
+ /** the `--assets-dest` directory Metro wrote beside the bundle. */
39
+ assetsPath?: string
30
40
  apiOrigin?: string
31
41
  }
32
42
 
@@ -61,9 +71,7 @@ function resolveCloudOrigin(value?: string): string {
61
71
  return normalizeCloudOrigin(source)
62
72
  }
63
73
 
64
- async function readImmutableBundle(
65
- bundlePath: string,
66
- ): Promise<{ bytes: Buffer; sha256: string }> {
74
+ function readImmutableBundleSource(bundlePath: string): string {
67
75
  const filepath = resolve(bundlePath)
68
76
  if (extname(filepath) !== '.js') {
69
77
  throw new Error('rnx ios --cloud requires one local Metro .js bundle')
@@ -77,11 +85,6 @@ async function readImmutableBundle(
77
85
  if (before.size <= 0n) {
78
86
  throw new Error('rnx ios --cloud bundle is empty')
79
87
  }
80
- if (before.size > BigInt(MAX_RNX_CLOUD_BUNDLE_BYTES)) {
81
- throw new Error(
82
- `rnx ios --cloud bundle exceeds the ${MAX_RNX_CLOUD_BUNDLE_BYTES}-byte limit`,
83
- )
84
- }
85
88
  const bytes = readFileSync(descriptor)
86
89
  const after = fstatSync(descriptor, { bigint: true })
87
90
  if (
@@ -102,16 +105,61 @@ async function readImmutableBundle(
102
105
  if (source.includes('\0') || !source.includes('__d(') || !source.includes('__r(')) {
103
106
  throw new Error('rnx ios --cloud requires a Metro JavaScript bundle')
104
107
  }
105
- await assertSelfContainedMetroArtifactAssets(source)
106
- return {
107
- bytes,
108
- sha256: createHash('sha256').update(bytes).digest('hex'),
109
- }
108
+ return source
110
109
  } finally {
111
110
  closeSync(descriptor)
112
111
  }
113
112
  }
114
113
 
114
+ // `react-native bundle --assets-dest <dir>` and `expo export:embed` write every
115
+ // scale variant with getAssetDestPathIOS: the descriptor's httpServerLocation
116
+ // without its leading slash, each `../` collapsed to `_`, then the asset name
117
+ // with its scale suffix. reading that layout is what lets an ordinary Metro
118
+ // build reach the cloud with no per-app script.
119
+ function metroAssetDestPath(assetsPath: string, variant: MetroAssetVariant): string {
120
+ const { descriptor, scale } = variant
121
+ const suffix = scale === 1 ? '' : `@${scale}x`
122
+ return join(
123
+ assetsPath,
124
+ descriptor.httpServerLocation.slice(1).replace(/\.\.\//g, '_'),
125
+ `${descriptor.name}${suffix}.${descriptor.type}`,
126
+ )
127
+ }
128
+
129
+ /**
130
+ * turns the Metro production bundle a caller built into the immutable artifact
131
+ * the runner executes: every asset scale is read from the asset graph and
132
+ * embedded, module identity is normalized, and the JavaScript is minified.
133
+ */
134
+ async function produceCloudArtifact(
135
+ bundlePath: string,
136
+ assetsPath: string,
137
+ ): Promise<{ bytes: Buffer; sha256: string; receipt: RnxCloudArtifactReceipt }> {
138
+ const source = readImmutableBundleSource(bundlePath)
139
+ const embedded = await embedMetroAssetFiles(source, async (variant) => {
140
+ const file = metroAssetDestPath(assetsPath, variant)
141
+ try {
142
+ return readFileSync(file)
143
+ } catch {
144
+ throw new Error(
145
+ `rnx ios --cloud could not read ${file} for ${variant.descriptor.name}.${variant.descriptor.type} at scale ${variant.scale}. Build the bundle with --assets-dest, or pass --assets <dir>.`,
146
+ )
147
+ }
148
+ })
149
+ const artifact = await buildRnxCloudArtifact(embedded.code)
150
+ const bytes = Buffer.from(artifact.code, 'utf8')
151
+ if (bytes.length > MAX_RNX_CLOUD_BUNDLE_BYTES) {
152
+ throw new Error(
153
+ `rnx ios --cloud artifact is ${bytes.length} bytes and exceeds the ${MAX_RNX_CLOUD_BUNDLE_BYTES}-byte limit`,
154
+ )
155
+ }
156
+ return {
157
+ bytes,
158
+ sha256: createHash('sha256').update(bytes).digest('hex'),
159
+ receipt: artifact.receipt,
160
+ }
161
+ }
162
+
115
163
  function readCreateResponse(
116
164
  value: unknown,
117
165
  expectedBytes: number,
@@ -155,9 +203,16 @@ function readCreateResponse(
155
203
 
156
204
  export async function createCloudSimulator(
157
205
  options: CreateCloudSimulatorOptions,
158
- ): Promise<{ receipt: RnxCloudCreateReceipt; session: CloudSession }> {
206
+ ): Promise<{
207
+ receipt: RnxCloudCreateReceipt
208
+ session: CloudSession
209
+ artifact: RnxCloudArtifactReceipt
210
+ }> {
159
211
  const apiOrigin = resolveCloudOrigin(options.apiOrigin)
160
- const artifact = await readImmutableBundle(options.bundlePath)
212
+ const artifact = await produceCloudArtifact(
213
+ options.bundlePath,
214
+ resolve(options.assetsPath ?? dirname(options.bundlePath)),
215
+ )
161
216
  let response: Response
162
217
  try {
163
218
  response = await fetch(`${apiOrigin}/v1/sims`, {
@@ -193,7 +248,7 @@ export async function createCloudSimulator(
193
248
  claimId: created.receipt.claim.id,
194
249
  token: created.token,
195
250
  })
196
- return { receipt: created.receipt, session }
251
+ return { receipt: created.receipt, session, artifact: artifact.receipt }
197
252
  }
198
253
 
199
254
  export function resolveCloudSessionForParsed(
@@ -219,40 +274,7 @@ function cloudCommand(command: {
219
274
  for (const [key, value] of Object.entries(command)) {
220
275
  if (key !== 'id' && key !== 'simId') clean[key] = value
221
276
  }
222
- if (command.type === 'query') {
223
- const query = command.query
224
- if (
225
- !isRecord(query) ||
226
- (query.kind !== 'tree' && query.kind !== 'find' && query.kind !== 'count')
227
- ) {
228
- throw new Error('RNX Cloud supports only structured tree, node, and count queries')
229
- }
230
- return clean
231
- }
232
- if (command.type === 'waitFor') {
233
- const options = command.waitForOptions
234
- const condition = isRecord(options) ? options.condition : null
235
- if (
236
- !isRecord(condition) ||
237
- (condition.type !== 'ready' &&
238
- condition.type !== 'present' &&
239
- condition.type !== 'absent')
240
- ) {
241
- throw new Error('RNX Cloud supports only ready and selector waits')
242
- }
243
- return clean
244
- }
245
- if (command.type === 'state') return clean
246
- if (command.type === 'capture' || command.type === 'captureRegions') return clean
247
- if (command.type === 'setAppearance' || command.type === 'openUrl') return clean
248
- if (
249
- command.type === 'screenshot' &&
250
- command.crop === undefined &&
251
- command.captureFilter === undefined &&
252
- (command.layers === undefined || command.layers === 'tenant')
253
- ) {
254
- return clean
255
- }
277
+ if (isRnxCloudCommandType(command.type)) return clean
256
278
  throw new Error(`RNX Cloud does not support bridge command type ${command.type}`)
257
279
  }
258
280
 
@@ -47,7 +47,7 @@ function supportsCloudRead(
47
47
  if (command === 'state') return firstVerb(positional) === null
48
48
  if (command === 'get') {
49
49
  const verb = firstVerb(positional)
50
- return verb === 'tree' || verb === 'node' || verb === 'count'
50
+ return verb === 'tree' || verb === 'node' || verb === 'count' || verb === 'memory'
51
51
  }
52
52
  if (command === 'wait') {
53
53
  const verb = firstVerb(positional)
@@ -77,6 +77,7 @@ function supportsCloudRead(
77
77
  ].includes(arg),
78
78
  )
79
79
  }
80
+ if (command === 'do' || command === 'reset') return true
80
81
  return false
81
82
  }
82
83
 
@@ -1095,8 +1095,16 @@ export async function inspectWaitReady(
1095
1095
  }
1096
1096
  }
1097
1097
  const probe = result.probe
1098
+ // a guest app that redboxes still paints: its ErrorBoundary is a real tree
1099
+ // with real nodes, so the engine's own readiness condition matches and every
1100
+ // caller reads exit 0 as "the app is up". an app that has errored is not
1101
+ // ready, so a guest error is disqualifying here rather than merely printed
1102
+ // on the failure path. `errors` (a console.error count) is deliberately not
1103
+ // used because apps can log errors benignly. `suppressedEntryError` remains
1104
+ // excluded because it never gates readiness by contract.
1105
+ const externalError = probe?.externalError ?? ''
1098
1106
  const status: WaitReadyStatus = {
1099
- ready: result.matched,
1107
+ ready: result.matched && !externalError,
1100
1108
  elapsedMs: result.elapsedMs,
1101
1109
  nodes: probe?.nodes ?? 0,
1102
1110
  targets: probe?.targets ?? 0,
@@ -1107,7 +1115,7 @@ export async function inspectWaitReady(
1107
1115
  loadingText: probe?.loadingText ?? '',
1108
1116
  externalReady: probe?.externalReady ?? null,
1109
1117
  externalStatus: probe?.externalStatus ?? '',
1110
- externalError: probe?.externalError ?? '',
1118
+ externalError,
1111
1119
  suppressedEntryError: probe?.suppressedEntryError ?? '',
1112
1120
  errors: probe?.errors ?? 0,
1113
1121
  bridgeError: result.error ?? '',
@@ -1835,6 +1843,29 @@ export interface MemoryReport {
1835
1843
  // picture and raster image — answers through its stats channel.
1836
1844
  // performance.memory is chrome-only, null on webkit.
1837
1845
  export async function inspectMemory(bridge: InspectBridge): Promise<MemoryReport> {
1846
+ if (bridge.plane === 'cloud') {
1847
+ const value: unknown = await bridge.send({ type: 'memory' })
1848
+ const rawWasmHeapBytes =
1849
+ typeof value === 'object' && value !== null
1850
+ ? Reflect.get(value, 'wasmHeapBytes')
1851
+ : null
1852
+ const wasmHeapBytes = typeof rawWasmHeapBytes === 'number' ? rawWasmHeapBytes : null
1853
+ return {
1854
+ tenant: {
1855
+ nodesRegistered: null,
1856
+ nodesDetached: null,
1857
+ detachedTypes: null,
1858
+ objects: null,
1859
+ activeNativeAnimations: null,
1860
+ imageLoader: null,
1861
+ workerHeap: null,
1862
+ wasmHeapBytes,
1863
+ },
1864
+ shell: null,
1865
+ compositor: null,
1866
+ hostHeap: null,
1867
+ }
1868
+ }
1838
1869
  const result = (await bridge.send({
1839
1870
  type: 'evaluate',
1840
1871
  code: `(async () => {
@@ -26,8 +26,15 @@ function isDeviceModel(value: string): value is DeviceModel {
26
26
  return value in devices
27
27
  }
28
28
 
29
- function cloudBundleFromArgs(args: string[]): string | null {
29
+ const CLOUD_USAGE =
30
+ 'usage: rnx ios --cloud <local bundle.js> [--assets <dir>] [--device <model>]'
31
+
32
+ function cloudInputFromArgs(args: string[]): {
33
+ bundlePath: string
34
+ assetsPath: string | null
35
+ } {
30
36
  const positional: string[] = []
37
+ let assetsPath: string | null = null
31
38
  for (let index = 0; index < args.length; index++) {
32
39
  const arg = args[index]
33
40
  if (arg === '--cloud') continue
@@ -35,6 +42,16 @@ function cloudBundleFromArgs(args: string[]): string | null {
35
42
  index++
36
43
  continue
37
44
  }
45
+ if (arg === '--assets') {
46
+ assetsPath = args[++index] ?? null
47
+ if (!assetsPath) throw new Error('rnx ios --cloud --assets requires a directory')
48
+ continue
49
+ }
50
+ if (arg.startsWith('--assets=')) {
51
+ assetsPath = arg.slice('--assets='.length) || null
52
+ if (!assetsPath) throw new Error('rnx ios --cloud --assets requires a directory')
53
+ continue
54
+ }
38
55
  if (
39
56
  arg === '--sim' ||
40
57
  arg === '--session' ||
@@ -50,10 +67,8 @@ function cloudBundleFromArgs(args: string[]): string | null {
50
67
  }
51
68
  positional.push(arg)
52
69
  }
53
- if (positional.length !== 1) {
54
- throw new Error('usage: rnx ios --cloud <local bundle.js> [--device <model>]')
55
- }
56
- return positional[0] ?? null
70
+ if (positional.length !== 1 || !positional[0]) throw new Error(CLOUD_USAGE)
71
+ return { bundlePath: positional[0], assetsPath }
57
72
  }
58
73
 
59
74
  export async function runPlatformCommand(
@@ -107,8 +122,7 @@ export async function runPlatformCommand(
107
122
  return 1
108
123
  }
109
124
  try {
110
- const bundlePath = cloudBundleFromArgs(args)
111
- if (!bundlePath) throw new Error('rnx ios --cloud requires a local bundle.js')
125
+ const input = cloudInputFromArgs(args)
112
126
  const {
113
127
  publishCloudSessionToShell,
114
128
  readCloudSession,
@@ -125,7 +139,8 @@ export async function runPlatformCommand(
125
139
  await import('../cloud-client')
126
140
  const { header } = authHeaderOrExit('ios --cloud')
127
141
  const created = await createCloudSimulator({
128
- bundlePath,
142
+ bundlePath: input.bundlePath,
143
+ assetsPath: input.assetsPath ?? undefined,
129
144
  device: selectedDevice,
130
145
  authorization: header,
131
146
  })
@@ -136,11 +151,21 @@ export async function runPlatformCommand(
136
151
  throw error
137
152
  }
138
153
  const receipt = created.receipt
154
+ const produced = created.artifact
139
155
  console.log(` cloud sim: ${receipt.simId}`)
140
156
  console.log(` artifact: ${receipt.artifact.id} (${receipt.artifact.bytes} bytes)`)
157
+ console.log(
158
+ ` source bundle: sha256:${produced.source.sha256} (${produced.source.bytes} bytes)`,
159
+ )
160
+ console.log(
161
+ ` normalized modules: ${produced.modules}; javascript ${produced.javascript.sourceBytes} -> ${produced.javascript.minifiedBytes} bytes`,
162
+ )
163
+ console.log(
164
+ ` embedded assets: ${produced.assets.descriptors} descriptors, ${produced.assets.variants} scales, ${produced.assets.bytes} bytes (${produced.assets.encodedBytes} encoded)`,
165
+ )
141
166
  console.log(` claim expires: ${receipt.claim.expiresAt}`)
142
167
  console.log(
143
- ' reads: describe, find, get tree|node|count, wait ready|selector, logs, state, screenshot',
168
+ ' commands: describe, find, get tree|node|count|memory, wait ready|selector, do, reset, logs, state, screenshot',
144
169
  )
145
170
  return 0
146
171
  } catch (error) {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/beta.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/beta.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (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
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -33,6 +33,11 @@ __export(bridge_contract_input_exports, {
33
33
  parseStateOptions: () => parseStateOptions
34
34
  });
35
35
  module.exports = __toCommonJS(bridge_contract_input_exports);
36
+
37
+ // src/bridge-contract.ts
38
+ var SIM_LONG_PRESS_MAX_MS = 5e3;
39
+
40
+ // src/bridge-contract-input.ts
36
41
  var BridgeInputError = class extends Error {
37
42
  constructor(message) {
38
43
  super(message);
@@ -123,6 +128,12 @@ function parsePerformStep(value, index) {
123
128
  }
124
129
  case "longPress": {
125
130
  const durationMs = optionalNumber(step, "durationMs", path);
131
+ if (durationMs !== void 0 && (!Number.isInteger(durationMs) || durationMs < 1 || durationMs > SIM_LONG_PRESS_MAX_MS)) {
132
+ validationFailure(
133
+ `${path}.durationMs`,
134
+ `must be an integer between 1 and ${SIM_LONG_PRESS_MAX_MS}`
135
+ );
136
+ }
126
137
  return {
127
138
  type,
128
139
  x: numberValue(step.x, `${path}.x`),
@@ -1,4 +1,7 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
+
3
+ // src/bridge-contract.ts
4
+ var SIM_LONG_PRESS_MAX_MS = 5e3;
2
5
 
3
6
  // src/bridge-contract-input.ts
4
7
  var BridgeInputError = class extends Error {
@@ -91,6 +94,12 @@ function parsePerformStep(value, index) {
91
94
  }
92
95
  case "longPress": {
93
96
  const durationMs = optionalNumber(step, "durationMs", path);
97
+ if (durationMs !== void 0 && (!Number.isInteger(durationMs) || durationMs < 1 || durationMs > SIM_LONG_PRESS_MAX_MS)) {
98
+ validationFailure(
99
+ `${path}.durationMs`,
100
+ `must be an integer between 1 and ${SIM_LONG_PRESS_MAX_MS}`
101
+ );
102
+ }
94
103
  return {
95
104
  type,
96
105
  x: numberValue(step.x, `${path}.x`),
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -25,10 +25,14 @@ __export(bridge_contract_exports, {
25
25
  SEMANTIC_READY_CONTENT_NODE_FLOOR: () => SEMANTIC_READY_CONTENT_NODE_FLOOR,
26
26
  SEMANTIC_READY_NODE_STABLE_MS: () => SEMANTIC_READY_NODE_STABLE_MS,
27
27
  SEMANTIC_READY_POLL_MS: () => SEMANTIC_READY_POLL_MS,
28
+ SIM_LONG_PRESS_DEFAULT_MS: () => SIM_LONG_PRESS_DEFAULT_MS,
29
+ SIM_LONG_PRESS_MAX_MS: () => SIM_LONG_PRESS_MAX_MS,
28
30
  readyProbeHasContent: () => readyProbeHasContent,
29
31
  readyProbeHasTargetContent: () => readyProbeHasTargetContent
30
32
  });
31
33
  module.exports = __toCommonJS(bridge_contract_exports);
34
+ var SIM_LONG_PRESS_DEFAULT_MS = 500;
35
+ var SIM_LONG_PRESS_MAX_MS = 5e3;
32
36
  var SEMANTIC_READY_CONTENT_NODE_FLOOR = 100;
33
37
  var SEMANTIC_READY_NODE_STABLE_MS = 750;
34
38
  var SEMANTIC_READY_POLL_MS = 150;
@@ -43,6 +47,8 @@ function readyProbeHasTargetContent(probe) {
43
47
  SEMANTIC_READY_CONTENT_NODE_FLOOR,
44
48
  SEMANTIC_READY_NODE_STABLE_MS,
45
49
  SEMANTIC_READY_POLL_MS,
50
+ SIM_LONG_PRESS_DEFAULT_MS,
51
+ SIM_LONG_PRESS_MAX_MS,
46
52
  readyProbeHasContent,
47
53
  readyProbeHasTargetContent
48
54
  });
@@ -1,6 +1,8 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
+ var SIM_LONG_PRESS_DEFAULT_MS = 500;
5
+ var SIM_LONG_PRESS_MAX_MS = 5e3;
4
6
  var SEMANTIC_READY_CONTENT_NODE_FLOOR = 100;
5
7
  var SEMANTIC_READY_NODE_STABLE_MS = 750;
6
8
  var SEMANTIC_READY_POLL_MS = 150;
@@ -14,6 +16,8 @@ export {
14
16
  SEMANTIC_READY_CONTENT_NODE_FLOOR,
15
17
  SEMANTIC_READY_NODE_STABLE_MS,
16
18
  SEMANTIC_READY_POLL_MS,
19
+ SIM_LONG_PRESS_DEFAULT_MS,
20
+ SIM_LONG_PRESS_MAX_MS,
17
21
  readyProbeHasContent,
18
22
  readyProbeHasTargetContent
19
23
  };
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/capture-contract.ts
4
4
  var RNX_SCREEN_CAPTURE_VERSION = 1;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.395 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -22,20 +22,46 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  // src/cloud-contract.ts
23
23
  var cloud_contract_exports = {};
24
24
  __export(cloud_contract_exports, {
25
+ RNX_CLOUD_COMMAND_TYPES: () => RNX_CLOUD_COMMAND_TYPES,
25
26
  RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES: () => RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES,
26
27
  RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES: () => RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES,
27
28
  RNX_CLOUD_DESIGN_PARALLEL_JOB_LIMIT: () => RNX_CLOUD_DESIGN_PARALLEL_JOB_LIMIT,
28
- RNX_CLOUD_MAX_ARTIFACT_BYTES: () => RNX_CLOUD_MAX_ARTIFACT_BYTES
29
+ RNX_CLOUD_MAX_ARTIFACT_BYTES: () => RNX_CLOUD_MAX_ARTIFACT_BYTES,
30
+ isRnxCloudCommandType: () => isRnxCloudCommandType
29
31
  });
30
32
  module.exports = __toCommonJS(cloud_contract_exports);
31
33
  var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
32
34
  var RNX_CLOUD_DESIGN_PARALLEL_JOB_LIMIT = 16;
33
35
  var RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES = 256;
34
36
  var RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024;
37
+ var RNX_CLOUD_COMMAND_TYPES = Object.freeze([
38
+ "state",
39
+ "query",
40
+ "resolve",
41
+ "perform",
42
+ "waitFor",
43
+ "reset",
44
+ "screenshot",
45
+ "capture",
46
+ "captureRegions",
47
+ "settle",
48
+ "setAppearance",
49
+ "openUrl",
50
+ "tree",
51
+ "tap",
52
+ "longPress",
53
+ "memory"
54
+ ]);
55
+ var RNX_CLOUD_COMMAND_TYPE_SET = new Set(RNX_CLOUD_COMMAND_TYPES);
56
+ function isRnxCloudCommandType(value) {
57
+ return RNX_CLOUD_COMMAND_TYPE_SET.has(value);
58
+ }
35
59
  // Annotate the CommonJS export names for ESM import in node:
36
60
  0 && (module.exports = {
61
+ RNX_CLOUD_COMMAND_TYPES,
37
62
  RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES,
38
63
  RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES,
39
64
  RNX_CLOUD_DESIGN_PARALLEL_JOB_LIMIT,
40
- RNX_CLOUD_MAX_ARTIFACT_BYTES
65
+ RNX_CLOUD_MAX_ARTIFACT_BYTES,
66
+ isRnxCloudCommandType
41
67
  });