peachsim 0.1.900 → 0.1.908

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 (98) hide show
  1. package/README.md +8 -0
  2. package/cli/commands/control.ts +29 -16
  3. package/cli/commands/flow.ts +8 -1
  4. package/cli/commands/upload.ts +53 -39
  5. package/cli/flow-live-status.ts +11 -6
  6. package/detox/expectations.ts +22 -15
  7. package/detox/index.ts +4 -19
  8. package/dist-lib/agent-daemon-client.cjs +1 -1
  9. package/dist-lib/agent-events.cjs +1 -1
  10. package/dist-lib/agent-identity.cjs +1 -1
  11. package/dist-lib/agent-sessions.cjs +1 -1
  12. package/dist-lib/attached-projects.cjs +1 -1
  13. package/dist-lib/auth/shared-session.cjs +1 -1
  14. package/dist-lib/backend-origin.cjs +1 -1
  15. package/dist-lib/beta.cjs +1 -1
  16. package/dist-lib/beta.mjs +1 -1
  17. package/dist-lib/bridge-constants.cjs +1 -1
  18. package/dist-lib/bridge-contract-input.cjs +1 -1
  19. package/dist-lib/bridge-contract-input.mjs +1 -1
  20. package/dist-lib/bridge-contract.cjs +1 -1
  21. package/dist-lib/bridge-contract.mjs +1 -1
  22. package/dist-lib/bundle-channel.cjs +6 -3
  23. package/dist-lib/capture-contract.cjs +1 -1
  24. package/dist-lib/capture-contract.mjs +1 -1
  25. package/dist-lib/cli-constants.cjs +1 -1
  26. package/dist-lib/cloud-contract.cjs +1 -1
  27. package/dist-lib/cloud-contract.mjs +1 -1
  28. package/dist-lib/cloud-test-args.cjs +1 -1
  29. package/dist-lib/cloud-test-args.mjs +1 -1
  30. package/dist-lib/cloud.cjs +1 -1
  31. package/dist-lib/cloud.mjs +1 -1
  32. package/dist-lib/config.cjs +1 -1
  33. package/dist-lib/detox/index.cjs +15 -18
  34. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  35. package/dist-lib/expo-swift-bundle.cjs +6 -3
  36. package/dist-lib/expo-swift-metadata.cjs +1 -1
  37. package/dist-lib/expo-swift-metadata.mjs +1 -1
  38. package/dist-lib/home-paths.cjs +1 -1
  39. package/dist-lib/host/bridge-host.cjs +6 -3
  40. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  41. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  42. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  43. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  44. package/dist-lib/host/websocket-proxy.cjs +1 -1
  45. package/dist-lib/index.cjs +1 -1
  46. package/dist-lib/jump-to-source-babel.cjs +1 -1
  47. package/dist-lib/jump-to-source-native.cjs +1 -1
  48. package/dist-lib/kotlin-host.cjs +1 -1
  49. package/dist-lib/menu.cjs +1 -1
  50. package/dist-lib/menu.mjs +1 -1
  51. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  52. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  53. package/dist-lib/metro-production-bundle.cjs +1 -1
  54. package/dist-lib/metro-production-bundle.mjs +1 -1
  55. package/dist-lib/metro.cjs +1 -1
  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/plist.cjs +1 -1
  61. package/dist-lib/plist.mjs +1 -1
  62. package/dist-lib/profiles.cjs +1 -1
  63. package/dist-lib/public-brand.cjs +1 -1
  64. package/dist-lib/react-native-host-modules.cjs +1 -1
  65. package/dist-lib/react-native-host-modules.mjs +1 -1
  66. package/dist-lib/render-mode.cjs +1 -1
  67. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  68. package/dist-lib/sdk.cjs +1 -1
  69. package/dist-lib/sdk.mjs +1 -1
  70. package/dist-lib/skills.cjs +39 -20
  71. package/dist-lib/swift-compile.cjs +1 -1
  72. package/dist-lib/swift-compile.mjs +1 -1
  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 +1 -1
  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/kotlin/fork/NOTICE +3 -0
  84. package/kotlin/fork/README.md +10 -2
  85. package/kotlin/fork/build.sh +14 -14
  86. package/kotlin/fork/compose-worker-globals.patch +105 -0
  87. package/kotlin/fork/fork.json +3 -3
  88. package/kotlin/fork/publish.ts +8 -1
  89. package/kotlin/fork/skia-android-shaping.patch +30 -0
  90. package/kotlin/fork/skiko-local.init.gradle +10 -0
  91. package/kotlin/fork/skiko-worker-clock.patch +17 -0
  92. package/kotlin/toolchain.json +9 -9
  93. package/package.json +1 -1
  94. package/skills/peach-debug/SKILL.md +15 -153
  95. package/skills/peach-debug/references/accessibility.md +102 -0
  96. package/skills/peach-debug/references/commands.md +56 -0
  97. package/src/expo-swift-host.ts +5 -2
  98. package/src/vite-plugin.ts +3 -3
package/README.md CHANGED
@@ -215,6 +215,14 @@ bridge always addresses the **shell worker**, never an undifferentiated sim.
215
215
  own dev server. It is used to build the engine/shell and to load external RN
216
216
  apps — it is **not** the published `peachsim/vite` export.
217
217
 
218
+ Preview uploads retain one response snapshot in the simulator and transfer
219
+ its base64 bodies in bounded slices, including authenticated API responses.
220
+ The CLI removes that temporary snapshot after transfer. A completed upload
221
+ publishes its trace URL through a fresh bridge connection because compression
222
+ and HTTP upload can outlive the interactive socket's idle lifetime.
223
+ The finished test duration uses the last execution status timestamp; publishing
224
+ the later trace URL notifies viewers without extending that duration.
225
+
218
226
  ## Key components
219
227
 
220
228
  | component | role | key files |
@@ -241,19 +241,30 @@ export async function ensureBridgeForOpen(
241
241
  }
242
242
 
243
243
  const { executable, prefixArgs } = rnxSelfInvocation()
244
- const child = spawn(
245
- executable,
246
- [...prefixArgs, 'serve', '--quiet', '--port', String(preferredPort)],
247
- {
248
- detached: true,
249
- stdio: 'ignore',
250
- // this bridge does not supply the shell, so it needs no runtime updates.
251
- env:
252
- runtimeSource === 'external'
253
- ? { ...process.env, PEACH_UPDATE_CHECK: 'off' }
254
- : process.env,
255
- },
244
+ // the bridge outlives open, so it must not hold the command's captured stderr.
245
+ const bridgeLogPath = path.join(
246
+ os.tmpdir(),
247
+ `peach-bridge-${Date.now()}-${process.pid}.log`,
256
248
  )
249
+ const bridgeLogFd = fs.openSync(bridgeLogPath, 'a')
250
+ let child: ReturnType<typeof spawn>
251
+ try {
252
+ child = spawn(
253
+ executable,
254
+ [...prefixArgs, 'serve', '--quiet', '--port', String(preferredPort)],
255
+ {
256
+ detached: true,
257
+ stdio: ['ignore', 'ignore', bridgeLogFd],
258
+ // this bridge does not supply the shell, so it needs no runtime updates.
259
+ env:
260
+ runtimeSource === 'external'
261
+ ? { ...process.env, PEACH_UPDATE_CHECK: 'off' }
262
+ : process.env,
263
+ },
264
+ )
265
+ } finally {
266
+ fs.closeSync(bridgeLogFd)
267
+ }
257
268
  if (resources) {
258
269
  resources.bridge = child
259
270
  resources.port = preferredPort
@@ -298,14 +309,16 @@ export async function ensureBridgeForOpen(
298
309
  }
299
310
  return selectedPort
300
311
  }
301
- if (childExit) {
302
- throw new Error(`peach bridge exited before becoming ready (exit ${childExit})`)
303
- }
312
+ if (childExit) break
304
313
  await sleep(150)
305
314
  }
306
315
 
316
+ const reason = childExit
317
+ ? `peach bridge exited before becoming ready (exit ${childExit})`
318
+ : `peach bridge did not start within ${Math.round(OPEN_BRIDGE_SPAWN_TIMEOUT_MS / 1000)}s`
319
+ const bridgeLog = fs.readFileSync(bridgeLogPath, 'utf8').trim().slice(-2000)
307
320
  throw new Error(
308
- `peach bridge did not start within ${Math.round(OPEN_BRIDGE_SPAWN_TIMEOUT_MS / 1000)}s`,
321
+ `${reason}${bridgeLog ? `\n${bridgeLog}` : ''}\nbridge log: ${bridgeLogPath}`,
309
322
  )
310
323
  }
311
324
 
@@ -955,7 +955,14 @@ flow extension:
955
955
  }
956
956
  const { runUpload } = await import('./upload')
957
957
  lastPreviewUploadResult = await runUpload(uploadArgs, {})
958
- await driver.liveStatus?.preview(lastPreviewUploadResult.previewUrl)
958
+ // upload can outlive the command socket's idle lifetime. publish its
959
+ // completed trace over a fresh connection to the same bridge world.
960
+ const resultBridge = createBridgeFromParsed(parsedBridgeArgs)
961
+ try {
962
+ await driver.liveStatus?.preview(lastPreviewUploadResult.previewUrl, resultBridge)
963
+ } finally {
964
+ resultBridge.close()
965
+ }
959
966
  } catch (err: any) {
960
967
  lastPreviewUploadError =
961
968
  err instanceof PeachExit && err.reason ? err.reason : String(err?.message || err)
@@ -5,7 +5,7 @@
5
5
  // uploads are unlisted-public (unguessable id) but require a signed-in
6
6
  // desktop session so the share is associated with the user's account.
7
7
 
8
- import { createHash } from 'crypto'
8
+ import { createHash, randomUUID } from 'crypto'
9
9
  import { readFileSync } from 'fs'
10
10
  import path from 'path'
11
11
  import { gzipSync, gunzipSync } from 'zlib'
@@ -609,45 +609,59 @@ export async function runUpload(
609
609
  ` ${crossOriginStats.count} recorded cross-origin responses ` +
610
610
  `(${(crossOriginStats.totalBytes / 1024).toFixed(1)} KiB)`,
611
611
  )
612
- // fetch the full records (with bodies). limit to ~20 MiB total —
613
- // if we're over, warn and truncate. most dev sessions are well
614
- // under this.
615
- // also pull bundle-origin-keyed api responses with bodies so the
616
- // upload can use them instead of re-fetching from the dev server
617
- // (which fails with 401 because the cli has no auth context). main
618
- // bundle + static assets stay on the manifest re-fetch path.
619
- const dumped = await evalInBridge<{
620
- main: RecordedFetch[]
621
- worker: RecordedFetch[]
622
- }>(
623
- bridge,
624
- `(async () => {
625
- const rec = window.__sootsimPreviewRecorder
626
- const workerDump = window.__sootsimDumpWorkerFetchRecorder
627
- const bundleUrl = ${JSON.stringify(snapshot.bundleUrl)}
628
- await rec?.flush?.()
629
- const keep = (r) => {
630
- try {
631
- const u = new URL(r.url)
632
- if (u.origin !== ${JSON.stringify(bundleOrigin)}) return true
633
- // bundle-origin api responses came from the rewritten
634
- // /__app-api proxy at record time (re-keyed by the
635
- // recorder's unwrap step). they need bodies attached the
636
- // same way as cross-origin records — direct re-fetch from
637
- // the dev server lacks the bundle's session cookies.
638
- if (r.url !== bundleUrl && u.pathname.startsWith('/api/')) return true
639
- return false
640
- } catch { return false }
641
- }
642
- return {
643
- main: rec?.dump ? rec.dump().filter(keep) : [],
644
- worker: typeof workerDump === 'function'
645
- ? (await workerDump()).filter(keep)
646
- : [],
612
+ // retain the snapshot in the page and pull bodies in bounded slices.
613
+ // a complete base64 dump can exceed the bridge's 100 mib frame cap.
614
+ const cacheKey = `__peachCliUploadFetches_${randomUUID()}`
615
+ try {
616
+ const entries = await evalInBridge<
617
+ Array<RecordedEntry & { index: number; bodyBase64Length: number }>
618
+ >(
619
+ bridge,
620
+ `(async () => {
621
+ const rec = window.__sootsimPreviewRecorder
622
+ const workerDump = window.__sootsimDumpWorkerFetchRecorder
623
+ const bundleUrl = ${JSON.stringify(snapshot.bundleUrl)}
624
+ await rec?.flush?.()
625
+ const keep = (r) => {
626
+ try {
627
+ const u = new URL(r.url)
628
+ if (u.origin !== ${JSON.stringify(bundleOrigin)}) return true
629
+ return r.url !== bundleUrl && u.pathname.startsWith('/api/')
630
+ } catch { return false }
631
+ }
632
+ const records = [
633
+ ...(rec?.dump ? rec.dump().filter(keep) : []),
634
+ ...(typeof workerDump === 'function'
635
+ ? (await workerDump()).filter(keep)
636
+ : []),
637
+ ]
638
+ window[${JSON.stringify(cacheKey)}] = records
639
+ return records.map(({ bodyBase64, ...entry }, index) => ({
640
+ ...entry, index, bodyBase64Length: bodyBase64.length,
641
+ }))
642
+ })()`,
643
+ )
644
+ const dumped: RecordedFetch[] = []
645
+ const chunkChars = 1_000_000
646
+ for (const { index, bodyBase64Length, ...entry } of entries) {
647
+ let bodyBase64 = ''
648
+ for (let offset = 0; offset < bodyBase64Length; offset += chunkChars) {
649
+ const end = Math.min(offset + chunkChars, bodyBase64Length)
650
+ const chunk = await evalInBridge(
651
+ bridge,
652
+ `window[${JSON.stringify(cacheKey)}]?.[${index}]?.bodyBase64.slice(${offset}, ${end})`,
653
+ )
654
+ if (typeof chunk !== 'string' || chunk.length !== end - offset) {
655
+ throw new Error('recorded response snapshot changed during upload')
656
+ }
657
+ bodyBase64 += chunk
647
658
  }
648
- })()`,
649
- )
650
- recordedFetches = mergeRecordedEntries([...dumped.main, ...dumped.worker])
659
+ dumped.push({ ...entry, bodyBase64 })
660
+ }
661
+ recordedFetches = mergeRecordedEntries(dumped)
662
+ } finally {
663
+ await evalInBridge(bridge, `delete window[${JSON.stringify(cacheKey)}]`)
664
+ }
651
665
  if (assetsOnly) {
652
666
  const before = recordedFetches.length
653
667
  recordedFetches = recordedFetches.filter((r) =>
@@ -47,8 +47,9 @@ export class FlowLiveStatusReporter {
47
47
  private async send(
48
48
  payload: Record<string, unknown>,
49
49
  simId: string | undefined,
50
+ bridge: WsBridge = this.bridge,
50
51
  ): Promise<unknown> {
51
- const reply: unknown = await this.bridge.send({
52
+ const reply: unknown = await bridge.send({
52
53
  type: 'flowStatus',
53
54
  simId,
54
55
  status: { runId: this.runId, flowName: this.flowName, ...payload },
@@ -57,7 +58,10 @@ export class FlowLiveStatusReporter {
57
58
  return reply
58
59
  }
59
60
 
60
- private async push(payload: Record<string, unknown>): Promise<unknown> {
61
+ private async push(
62
+ payload: Record<string, unknown>,
63
+ bridge: WsBridge = this.bridge,
64
+ ): Promise<unknown> {
61
65
  if (this.disabled) return undefined
62
66
  try {
63
67
  const simId = this.getSimId?.()
@@ -75,13 +79,14 @@ export class FlowLiveStatusReporter {
75
79
  source: this.flowSource,
76
80
  },
77
81
  simId,
82
+ bridge,
78
83
  )
79
84
  for (const step of this.completedSteps.values()) {
80
- await this.send({ phase: 'step', state: 'running', step }, simId)
85
+ await this.send({ phase: 'step', state: 'running', step }, simId, bridge)
81
86
  }
82
87
  }
83
88
  this.lastSimId = simId
84
- return await this.send(payload, simId)
89
+ return await this.send(payload, simId, bridge)
85
90
  } catch (error) {
86
91
  this.disabled = true
87
92
  console.log(
@@ -165,8 +170,8 @@ export class FlowLiveStatusReporter {
165
170
 
166
171
  // the finished run's cloud trace url, once --preview has uploaded it. the
167
172
  // devtools test pane's Open Trace button opens it.
168
- async preview(previewUrl: string): Promise<void> {
169
- await this.push({ phase: 'preview', previewUrl })
173
+ async preview(previewUrl: string, bridge: WsBridge): Promise<void> {
174
+ await this.push({ phase: 'preview', previewUrl }, bridge)
170
175
  }
171
176
 
172
177
  // park while the devtools pause toggle is on. checked between top-level
@@ -5,7 +5,7 @@
5
5
  import { dragScrollNode } from './gestures'
6
6
  import type { SootSimElement } from './element-types'
7
7
  import type { Matcher } from './matchers'
8
- import type { SootSimNodeInfo } from '@peach/globals'
8
+ import type { SootSimFocusedNodeInfo, SootSimNodeInfo } from '@peach/globals'
9
9
  import type { Page } from 'playwright-core'
10
10
 
11
11
  type FindFn = (matcher: Matcher) => Promise<any>
@@ -52,20 +52,27 @@ export function isNodeVisible(node: SootSimNodeInfo | null | undefined): boolean
52
52
  return visibleFraction(node) >= DEFAULT_VISIBILITY_THRESHOLD
53
53
  }
54
54
 
55
- // the engine tags each rendered node with its id, so DOM focus is the source of
56
- // truth for both the `expect` and `waitFor` forms of toBeFocused.
57
- async function isNodeFocused(node: any, page: Page): Promise<boolean> {
58
- const nodeId =
59
- typeof node?.nodeId === 'number'
60
- ? node.nodeId
61
- : typeof node?.id === 'number'
62
- ? node.id
63
- : null
64
- if (nodeId === null) return false
65
- return page.evaluate((id) => {
66
- const active = document.activeElement
67
- return active?.getAttribute('data-sootsim-id') === String(id)
68
- }, nodeId)
55
+ // a native host is focused when its editor child holds focus.
56
+ export function focusIsWithin(focused: SootSimFocusedNodeInfo, nodeId: number): boolean {
57
+ return focused.nodeId === nodeId || focused.ancestorNodeIds.includes(nodeId)
58
+ }
59
+
60
+ // the engine owns focus for canvas inputs and hosted native editors.
61
+ export async function isTextInputNodeFocused(
62
+ node: SootSimNodeInfo,
63
+ page: Page,
64
+ ): Promise<boolean> {
65
+ const focused = await page.evaluate(async () => window.__sootsimTest!.getFocusedNode())
66
+ return focused !== null && focusIsWithin(focused, node.nodeId)
67
+ }
68
+
69
+ async function isNodeFocused(node: SootSimNodeInfo, page: Page): Promise<boolean> {
70
+ if (node.isTextInput) return isTextInputNodeFocused(node, page)
71
+ return page.evaluate(
72
+ (nodeId) =>
73
+ document.activeElement?.getAttribute('data-sootsim-id') === String(nodeId),
74
+ node.nodeId,
75
+ )
69
76
  }
70
77
 
71
78
  export function describeNodeVisibility(node: SootSimNodeInfo | null | undefined): string {
package/detox/index.ts CHANGED
@@ -29,6 +29,8 @@ import {
29
29
  createExpect,
30
30
  createWaitFor,
31
31
  describeNodeVisibility,
32
+ focusIsWithin,
33
+ isTextInputNodeFocused,
32
34
  isNodeVisible,
33
35
  } from './expectations'
34
36
  import {
@@ -776,35 +778,18 @@ async function dispatchTapAtSootsimPoint(
776
778
  )
777
779
  }
778
780
 
779
- async function isFocusedTextInputNode(page: Page, nodeInfo: any): Promise<boolean> {
780
- const focused = await page.evaluate(
781
- async () => (await window.__sootsimTest!.getFocusedNode?.()) ?? null,
782
- )
783
- return !!focused && focusIsWithin(focused, nodeInfo?.nodeId)
784
- }
785
-
786
- // espresso's typeText taps the matched view and types into whatever took
787
- // focus, so a native host whose editor child holds focus takes the text.
788
- function focusIsWithin(
789
- focused: { nodeId: number; ancestorNodeIds: number[] },
790
- nodeId: number,
791
- ): boolean {
792
- return focused.nodeId === nodeId || focused.ancestorNodeIds.includes(nodeId)
793
- }
794
-
795
781
  async function focusElementForTextEntry(el: SootSimElement): Promise<void> {
796
782
  const page = getPage()
797
783
  const node = await findNodeByMatcher(el._matcher)
798
784
  if (!node) {
799
785
  throw new Error(`element not found for text entry: ${JSON.stringify(el._matcher)}`)
800
786
  }
801
- if (await isFocusedTextInputNode(page, node)) return
787
+ if (await isTextInputNodeFocused(node, page)) return
802
788
  await el.tap()
803
789
  // the tap reaches the worker on its own channel. keys pressed before it lands
804
790
  // would edit around a caret the tap then moves, so wait for its focus.
805
791
  await pollUntil(
806
- () =>
807
- page.evaluate(async () => (await window.__sootsimTest!.getFocusedNode?.()) ?? null),
792
+ () => page.evaluate(async () => window.__sootsimTest!.getFocusedNode()),
808
793
  (focused) => focused !== null && focusIsWithin(focused, node.nodeId),
809
794
  {
810
795
  timeoutMs: 5000,
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var REFUSED_PERFORM_STEP_TYPES = [
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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;
@@ -8608,7 +8608,7 @@ async function collectExpoSwiftSources(appRoot) {
8608
8608
  const namespacesSeen = /* @__PURE__ */ new Set();
8609
8609
  let bytes = 0;
8610
8610
  let fileCount = 0;
8611
- for (const entry of Object.values(search)) {
8611
+ for (const [, entry] of Object.entries(search).sort(([a], [b]) => a.localeCompare(b))) {
8612
8612
  if (!entry || typeof entry !== "object")
8613
8613
  throw new Error("invalid Expo autolinking entry");
8614
8614
  const sourceRoot = Reflect.get(entry, "path");
@@ -8660,7 +8660,10 @@ async function collectExpoSwiftSources(appRoot) {
8660
8660
  fileCount++;
8661
8661
  if (fileCount > MAX_SWIFT_COMPILE_FILES - 2 || bytes > MAX_SWIFT_COMPILE_SOURCE_BYTES)
8662
8662
  throw new Error("Expo Swift sources exceed compile service caps");
8663
- files2[`Sources/${namespace}/${import_node_path16.default.relative(ios, file).split(import_node_path16.default.sep).join("/")}`] = `#sourceLocation(file: ${JSON.stringify(file)}, line: 1)
8663
+ files2[`Sources/${namespace}/${import_node_path16.default.relative(ios, file).split(import_node_path16.default.sep).join("/")}`] = // CocoaPods' iOS umbrella header imports UIKit into every Swift file
8664
+ // through the underlying module; these source units have no clang pod.
8665
+ `import UIKit
8666
+ #sourceLocation(file: ${JSON.stringify(file)}, line: 1)
8664
8667
  ${import_node_fs15.default.readFileSync(file, "utf8")}`;
8665
8668
  }
8666
8669
  };
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/cloud-contract.ts
4
4
  var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/cloud-test-args.ts
4
4
  var CLOUD_TEST_ARGS_MAX_COUNT = 32;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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;
@@ -7387,13 +7387,19 @@ function visibleFraction(node) {
7387
7387
  function isNodeVisible(node) {
7388
7388
  return visibleFraction(node) >= DEFAULT_VISIBILITY_THRESHOLD;
7389
7389
  }
7390
+ function focusIsWithin(focused, nodeId) {
7391
+ return focused.nodeId === nodeId || focused.ancestorNodeIds.includes(nodeId);
7392
+ }
7393
+ async function isTextInputNodeFocused(node, page) {
7394
+ const focused = await page.evaluate(async () => window.__sootsimTest.getFocusedNode());
7395
+ return focused !== null && focusIsWithin(focused, node.nodeId);
7396
+ }
7390
7397
  async function isNodeFocused(node, page) {
7391
- const nodeId = typeof node?.nodeId === "number" ? node.nodeId : typeof node?.id === "number" ? node.id : null;
7392
- if (nodeId === null) return false;
7393
- return page.evaluate((id) => {
7394
- const active = document.activeElement;
7395
- return active?.getAttribute("data-sootsim-id") === String(id);
7396
- }, nodeId);
7398
+ if (node.isTextInput) return isTextInputNodeFocused(node, page);
7399
+ return page.evaluate(
7400
+ (nodeId) => document.activeElement?.getAttribute("data-sootsim-id") === String(nodeId),
7401
+ node.nodeId
7402
+ );
7397
7403
  }
7398
7404
  function describeNodeVisibility(node) {
7399
7405
  if (!node) return "not found";
@@ -8510,25 +8516,16 @@ async function dispatchTapAtSootsimPoint(page, x, y, token) {
8510
8516
  { x, y, token }
8511
8517
  );
8512
8518
  }
8513
- async function isFocusedTextInputNode(page, nodeInfo) {
8514
- const focused = await page.evaluate(
8515
- async () => await window.__sootsimTest.getFocusedNode?.() ?? null
8516
- );
8517
- return !!focused && focusIsWithin(focused, nodeInfo?.nodeId);
8518
- }
8519
- function focusIsWithin(focused, nodeId) {
8520
- return focused.nodeId === nodeId || focused.ancestorNodeIds.includes(nodeId);
8521
- }
8522
8519
  async function focusElementForTextEntry(el) {
8523
8520
  const page = getPage();
8524
8521
  const node = await findNodeByMatcher(el._matcher);
8525
8522
  if (!node) {
8526
8523
  throw new Error(`element not found for text entry: ${JSON.stringify(el._matcher)}`);
8527
8524
  }
8528
- if (await isFocusedTextInputNode(page, node)) return;
8525
+ if (await isTextInputNodeFocused(node, page)) return;
8529
8526
  await el.tap();
8530
8527
  await pollUntil(
8531
- () => page.evaluate(async () => await window.__sootsimTest.getFocusedNode?.() ?? null),
8528
+ () => page.evaluate(async () => window.__sootsimTest.getFocusedNode()),
8532
8529
  (focused) => focused !== null && focusIsWithin(focused, node.nodeId),
8533
8530
  {
8534
8531
  timeoutMs: 5e3,
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.900 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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;