rnxsim 0.1.317 → 0.1.319

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 (183) hide show
  1. package/cli/bin.ts +1 -8
  2. package/cli/commands/debug.ts +1 -8
  3. package/cli/commands/detox.ts +16 -4
  4. package/cli/commands/flow.ts +1 -2
  5. package/cli/commands/inspect/wait-event.ts +1 -1
  6. package/cli/commands/inspect/wait-selector.ts +1 -1
  7. package/cli/commands/inspect.ts +56 -33
  8. package/cli/commands/maestro.ts +1 -1
  9. package/cli/commands/profile.ts +4 -6
  10. package/cli/commands/runtime.ts +0 -4
  11. package/cli/commands/storage.ts +1 -1
  12. package/cli/commands/telemetry.ts +0 -2
  13. package/cli/commands/timeline.ts +2 -4
  14. package/cli/parse-args.ts +0 -3
  15. package/detox/index.ts +155 -15
  16. package/detox/jest-preset.cjs +0 -1
  17. package/dist-cli/bin.js +5 -5
  18. package/dist-cli/chunks/{agent-EKGLRJEN.js → agent-Q4VAKGOL.js} +2 -2
  19. package/dist-cli/chunks/{agent-wrapper-FONNWB7J.js → agent-wrapper-Q2O4TR7H.js} +2 -2
  20. package/dist-cli/chunks/{app-fonts-XPM54HRY.js → app-fonts-B7UP3XTI.js} +2 -2
  21. package/dist-cli/chunks/{assert-PILHTZRZ.js → assert-NAFO4KMH.js} +2 -2
  22. package/dist-cli/chunks/{auth-Q2VUD5NQ.js → auth-CNUGYLCX.js} +2 -2
  23. package/dist-cli/chunks/{beta-P4XLZ3KL.js → beta-GN5BSED6.js} +2 -2
  24. package/dist-cli/chunks/{camera-O4PEVPI4.js → camera-EJRFUEXV.js} +2 -2
  25. package/dist-cli/chunks/{chunk-R6DDJVBB.js → chunk-2TNUPISH.js} +2 -2
  26. package/dist-cli/chunks/{chunk-BJ5CYKA7.js → chunk-3DEQXXSF.js} +4 -4
  27. package/dist-cli/chunks/{chunk-VB2ZZ4JI.js → chunk-3F3R7BM4.js} +2 -2
  28. package/dist-cli/chunks/{chunk-CUILVYWI.js → chunk-4UY4JSQ4.js} +1 -1
  29. package/dist-cli/chunks/chunk-522E7BRF.js +937 -0
  30. package/dist-cli/chunks/{chunk-NF63TOPI.js → chunk-5ABSWU6L.js} +2 -2
  31. package/dist-cli/chunks/{chunk-NA4AMR2E.js → chunk-5KXQT465.js} +1 -1
  32. package/dist-cli/chunks/{chunk-6WG4RQKM.js → chunk-6BGXKWGF.js} +2 -2
  33. package/dist-cli/chunks/{chunk-67ZR7PU7.js → chunk-72VB52IV.js} +1 -1
  34. package/dist-cli/chunks/{chunk-JQ4Y2XVT.js → chunk-76EVUPAL.js} +1 -1
  35. package/dist-cli/chunks/{chunk-OWADERTW.js → chunk-7DABL656.js} +2 -2
  36. package/dist-cli/chunks/{chunk-WWW5DC3G.js → chunk-7IW3PBRN.js} +2 -2
  37. package/dist-cli/chunks/{chunk-S23KHLTR.js → chunk-AX7PASOS.js} +2 -2
  38. package/dist-cli/chunks/{chunk-SZIPYT3D.js → chunk-AZ6S36CJ.js} +2 -2
  39. package/dist-cli/chunks/{chunk-T6W3B2KA.js → chunk-B27W3QWS.js} +1 -1
  40. package/dist-cli/chunks/{chunk-GQ3D2D4O.js → chunk-BQEWRZON.js} +1 -1
  41. package/dist-cli/chunks/{chunk-ADAIY3DX.js → chunk-BSCOA6LL.js} +2 -2
  42. package/dist-cli/chunks/{chunk-NUSBQSRQ.js → chunk-BVVUWEDG.js} +2 -2
  43. package/dist-cli/chunks/chunk-DD6F2WOA.js +4 -0
  44. package/dist-cli/chunks/{chunk-J4MCKLWE.js → chunk-DIADYUIH.js} +4 -4
  45. package/dist-cli/chunks/{chunk-KO7UG262.js → chunk-GHHVBATM.js} +2 -2
  46. package/dist-cli/chunks/{chunk-QCQQ2VDW.js → chunk-HCM7K4AU.js} +2 -2
  47. package/dist-cli/chunks/chunk-HPO2HNJX.js +124 -0
  48. package/dist-cli/chunks/{chunk-67OCUGZD.js → chunk-IHBAZ6ZJ.js} +2 -2
  49. package/dist-cli/chunks/{chunk-QGPNBTHT.js → chunk-IJYWC6QT.js} +2 -2
  50. package/dist-cli/chunks/{chunk-JZXCQEKO.js → chunk-JWAF5GXK.js} +2 -2
  51. package/dist-cli/chunks/{chunk-LSC2HFAB.js → chunk-JZIFPU64.js} +1 -1
  52. package/dist-cli/chunks/{chunk-RCPPPW7B.js → chunk-KMOLZN54.js} +1 -1
  53. package/dist-cli/chunks/{chunk-GMWPCQ63.js → chunk-L5JZXGYY.js} +1 -1
  54. package/dist-cli/chunks/{chunk-YRVJHPCU.js → chunk-LKO7E4VO.js} +2 -2
  55. package/dist-cli/chunks/{chunk-WJVNVBAI.js → chunk-M52VGRRN.js} +1 -1
  56. package/dist-cli/chunks/{chunk-7Q7IQ2SS.js → chunk-PFRV3TNY.js} +2 -2
  57. package/dist-cli/chunks/{chunk-3G7I72XV.js → chunk-PYDVLJOW.js} +2 -2
  58. package/dist-cli/chunks/{chunk-ATOUAYJP.js → chunk-QNNCKWZF.js} +1 -1
  59. package/dist-cli/chunks/{chunk-ODHQKXLB.js → chunk-R4JD7RQ5.js} +2 -2
  60. package/dist-cli/chunks/chunk-RIRZMX6H.js +4 -0
  61. package/dist-cli/chunks/{chunk-FIPCJ5OS.js → chunk-RVKU4F4H.js} +1 -1
  62. package/dist-cli/chunks/{chunk-A3GVXVSG.js → chunk-S5XIMHEZ.js} +2 -2
  63. package/dist-cli/chunks/{chunk-HJKQ6NEU.js → chunk-SPHYBQAZ.js} +2 -2
  64. package/dist-cli/chunks/chunk-TJSU45JD.js +4 -0
  65. package/dist-cli/chunks/{chunk-R6MEMDNB.js → chunk-UGYANOZR.js} +2 -2
  66. package/dist-cli/chunks/{chunk-W4YHMJOO.js → chunk-VB6FBQBW.js} +2 -2
  67. package/dist-cli/chunks/{chunk-5XVHBFAT.js → chunk-VLT3KCQT.js} +3 -3
  68. package/dist-cli/chunks/{chunk-OO5D7IJE.js → chunk-VQJMFDUN.js} +1 -1
  69. package/dist-cli/chunks/{chunk-EL23MRFL.js → chunk-WUZNWEBT.js} +1 -1
  70. package/dist-cli/chunks/{chunk-5BYFLVSZ.js → chunk-X2AHIPGY.js} +1 -1
  71. package/dist-cli/chunks/{chunk-OXSCF4UD.js → chunk-XJHAGKJD.js} +2 -2
  72. package/dist-cli/chunks/{chunk-RG3CME4Y.js → chunk-YPDFFAE7.js} +2 -2
  73. package/dist-cli/chunks/{chunk-HF4NZ5SD.js → chunk-YS6EWKGV.js} +2 -2
  74. package/dist-cli/chunks/{chunk-2SAOKV6V.js → chunk-YT6RDBLT.js} +1 -1
  75. package/dist-cli/chunks/{chunk-KZS5VM6Y.js → chunk-Z4LSUN4M.js} +1 -1
  76. package/dist-cli/chunks/{chunk-TUFL7CQK.js → chunk-Z72QDFQG.js} +2 -2
  77. package/dist-cli/chunks/{chunk-ARICQ4CG.js → chunk-ZH5X2H73.js} +2 -2
  78. package/dist-cli/chunks/{cleanup-O6IWLGDY.js → cleanup-GBLORG7C.js} +2 -2
  79. package/dist-cli/chunks/cli-version-6C4ZR7I7.js +4 -0
  80. package/dist-cli/chunks/{compat-562BT6JM.js → compat-OIWTQTTQ.js} +2 -2
  81. package/dist-cli/chunks/{config-7RO6O45J.js → config-DZCL6YFH.js} +2 -2
  82. package/dist-cli/chunks/{control-XYKZG442.js → control-MY5IZSCI.js} +2 -2
  83. package/dist-cli/chunks/daemon-42K3PILG.js +4 -0
  84. package/dist-cli/chunks/{debug-3VCEOVRM.js → debug-6UQUAZKP.js} +4 -4
  85. package/dist-cli/chunks/{desktop-K3PPS6VL.js → desktop-DFBFLSQ3.js} +3 -3
  86. package/dist-cli/chunks/detox-CIHUJOCI.js +52 -0
  87. package/dist-cli/chunks/{device-NZCR7JIN.js → device-7SOHSSJO.js} +2 -2
  88. package/dist-cli/chunks/{diagnose-VVJY6RF2.js → diagnose-P2FMX5HU.js} +2 -2
  89. package/dist-cli/chunks/{disk-cleanup-2VJMHUIY.js → disk-cleanup-6PP5CQVO.js} +2 -2
  90. package/dist-cli/chunks/drivers-CFJGYQFX.js +4 -0
  91. package/dist-cli/chunks/{film-CEMBA2DA.js → film-2WWHMPGU.js} +3 -3
  92. package/dist-cli/chunks/flow-KL7ZTVKL.js +4 -0
  93. package/dist-cli/chunks/help-T2OCMQPI.js +4 -0
  94. package/dist-cli/chunks/hidden-runtime-alias-ZZMC3E6R.js +4 -0
  95. package/dist-cli/chunks/{home-paths-TKPTU62O.js → home-paths-Y6X4LFSU.js} +2 -2
  96. package/dist-cli/chunks/inspect-PVMJQHMF.js +4 -0
  97. package/dist-cli/chunks/install-desktop-NHQZI4EH.js +4 -0
  98. package/dist-cli/chunks/{login-CIQT627K.js → login-BARKKDRL.js} +4 -4
  99. package/dist-cli/chunks/{logout-V2DJVY3U.js → logout-YVUMTGMP.js} +2 -2
  100. package/dist-cli/chunks/{maestro-YSZ6ZC5C.js → maestro-UPVELJ7G.js} +7 -7
  101. package/dist-cli/chunks/{maestro-generate-6JUELIXK.js → maestro-generate-EP76F4GB.js} +3 -3
  102. package/dist-cli/chunks/{mode-D4HQ6YN3.js → mode-ULOBJKDO.js} +2 -2
  103. package/dist-cli/chunks/{optional-demo-registry-AC6YJKT4.js → optional-demo-registry-O6RANNU3.js} +2 -2
  104. package/dist-cli/chunks/{perf-5GRZ4G5X.js → perf-ANPUHMRS.js} +2 -2
  105. package/dist-cli/chunks/{permissions-4V6UVWGK.js → permissions-NGMRAUGD.js} +2 -2
  106. package/dist-cli/chunks/{record-IDNLOQLR.js → record-CBO3QGRB.js} +3 -3
  107. package/dist-cli/chunks/{report-issue-TF6R77TT.js → report-issue-K7XKNJ2D.js} +2 -2
  108. package/dist-cli/chunks/reset-FXAX2W5A.js +4 -0
  109. package/dist-cli/chunks/runtime-GNCU66RR.js +4 -0
  110. package/dist-cli/chunks/{screenshot-command-CHJN7WUT.js → screenshot-command-KZZSYRX5.js} +3 -3
  111. package/dist-cli/chunks/{screenshot-layers-MSDCGYNE.js → screenshot-layers-Z3KSMJ3R.js} +2 -2
  112. package/dist-cli/chunks/{screenshots-capture-OGP3OQYR.js → screenshots-capture-QTB5GQNA.js} +2 -2
  113. package/dist-cli/chunks/{serve-GPBLCMXX.js → serve-JKTMCDG2.js} +2 -2
  114. package/dist-cli/chunks/{setup-S2KYWZZO.js → setup-GCAPEHZH.js} +2 -2
  115. package/dist-cli/chunks/{skills-AUS5DTUE.js → skills-Y4YDYEKA.js} +2 -2
  116. package/dist-cli/chunks/{state-QLYTMIKF.js → state-3HTG2CZU.js} +2 -2
  117. package/dist-cli/chunks/storage-XQE2XZJM.js +37 -0
  118. package/dist-cli/chunks/store-22QXHYUS.js +4 -0
  119. package/dist-cli/chunks/telemetry-VYGOHQWW.js +4 -0
  120. package/dist-cli/chunks/{timeline-EUHPXPFP.js → timeline-XYACO63Y.js} +3 -3
  121. package/dist-cli/chunks/{upgrade-JMMBM3ZN.js → upgrade-EVQTCUSG.js} +2 -2
  122. package/dist-cli/chunks/upload-XH3RQR4U.js +4 -0
  123. package/dist-cli/chunks/{version-QCF7RCA2.js → version-NLGYSRXS.js} +2 -2
  124. package/dist-cli/chunks/{web-WPYHMOVH.js → web-25OBFWDS.js} +2 -2
  125. package/dist-cli/chunks/{what-happened-3RNZP4MB.js → what-happened-MRN6CH5W.js} +2 -2
  126. package/dist-lib/agent-daemon-client.cjs +1 -1
  127. package/dist-lib/agent-events.cjs +1 -1
  128. package/dist-lib/agent-identity.cjs +1 -1
  129. package/dist-lib/agent-sessions.cjs +1 -1
  130. package/dist-lib/attached-projects.cjs +1 -1
  131. package/dist-lib/auth/shared-session.cjs +1 -1
  132. package/dist-lib/backend-origin.cjs +1 -1
  133. package/dist-lib/beta.cjs +1 -1
  134. package/dist-lib/beta.mjs +1 -1
  135. package/dist-lib/bridge-constants.cjs +1 -1
  136. package/dist-lib/bridge-contract.cjs +1 -1
  137. package/dist-lib/cli-constants.cjs +1 -1
  138. package/dist-lib/config.cjs +1 -1
  139. package/dist-lib/detox/index.cjs +93 -11
  140. package/dist-lib/detox/jest-preset.cjs +0 -1
  141. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  142. package/dist-lib/home-paths.cjs +1 -1
  143. package/dist-lib/host/bridge-host.cjs +1 -1
  144. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  145. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  146. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  147. package/dist-lib/host/websocket-proxy.cjs +1 -1
  148. package/dist-lib/index.cjs +1 -1
  149. package/dist-lib/jump-to-source-babel.cjs +1 -1
  150. package/dist-lib/menu.cjs +1 -1
  151. package/dist-lib/menu.mjs +1 -1
  152. package/dist-lib/metro.cjs +1 -1
  153. package/dist-lib/profiles.cjs +1 -1
  154. package/dist-lib/public-brand.cjs +1 -1
  155. package/dist-lib/render-mode.cjs +1 -1
  156. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  157. package/dist-lib/sdk.cjs +1 -1
  158. package/dist-lib/sdk.mjs +1 -1
  159. package/dist-lib/skills.cjs +62 -28
  160. package/dist-lib/vite.cjs +1 -1
  161. package/package.json +1 -1
  162. package/skills/contrast/SKILL.md +0 -1
  163. package/skills/rnx-perf/SKILL.md +17 -0
  164. package/src/vite-plugin.ts +74 -43
  165. package/dist-cli/chunks/chunk-744422UA.js +0 -4
  166. package/dist-cli/chunks/chunk-CHKTOWRH.js +0 -939
  167. package/dist-cli/chunks/chunk-O6OIXJ54.js +0 -4
  168. package/dist-cli/chunks/chunk-VUKB6JW3.js +0 -125
  169. package/dist-cli/chunks/cli-version-F2R7SUY7.js +0 -4
  170. package/dist-cli/chunks/daemon-WE37QLQC.js +0 -4
  171. package/dist-cli/chunks/detox-6ODNKASO.js +0 -52
  172. package/dist-cli/chunks/drivers-44ZJZ37B.js +0 -4
  173. package/dist-cli/chunks/flow-ORGIXYVS.js +0 -4
  174. package/dist-cli/chunks/help-UW52M7FZ.js +0 -4
  175. package/dist-cli/chunks/hidden-runtime-alias-R2QQLZZA.js +0 -4
  176. package/dist-cli/chunks/inspect-TNFI6C6G.js +0 -4
  177. package/dist-cli/chunks/install-desktop-NEJEPL4L.js +0 -4
  178. package/dist-cli/chunks/reset-D7WAFOZO.js +0 -4
  179. package/dist-cli/chunks/runtime-O5TNQBYH.js +0 -4
  180. package/dist-cli/chunks/storage-2MNS4TYO.js +0 -37
  181. package/dist-cli/chunks/store-UT52X7AA.js +0 -4
  182. package/dist-cli/chunks/telemetry-CAQVTTHY.js +0 -4
  183. package/dist-cli/chunks/upload-CEX4UBJL.js +0 -4
package/cli/bin.ts CHANGED
@@ -420,12 +420,6 @@ if (TOP_LEVEL_RUNTIME_COMMANDS.has(command)) {
420
420
  await exitWithFlush(code)
421
421
  }
422
422
 
423
- case 'inspect': {
424
- const { runInspect } = await import('./commands/inspect')
425
- await runInspect(commandArgsWithSim, { port, verbose })
426
- break
427
- }
428
-
429
423
  case 'debug': {
430
424
  const { runDebug } = await import('./commands/debug')
431
425
  await runDebug(commandArgsWithSim, { port, verbose })
@@ -534,8 +528,7 @@ if (TOP_LEVEL_RUNTIME_COMMANDS.has(command)) {
534
528
  break
535
529
  }
536
530
 
537
- case 'upgrade':
538
- case 'update': {
531
+ case 'upgrade': {
539
532
  const { runUpgrade } = await import('./commands/upgrade')
540
533
  await runUpgrade(parsed.commandArgs)
541
534
  break
@@ -408,14 +408,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
408
408
  // implementation lives in inspect.ts. forward to runInspect with the raw
409
409
  // `debug` prefix intact — runInspect strips it. done before we open a
410
410
  // debug-side bridge so we don't hold two bridges at once.
411
- const INSPECT_FORWARDED = new Set([
412
- 'state',
413
- 'js',
414
- 'eval',
415
- 'perf',
416
- 'memory',
417
- 'sample-color',
418
- ])
411
+ const INSPECT_FORWARDED = new Set(['state', 'js', 'perf', 'memory', 'sample-color'])
419
412
  if (INSPECT_FORWARDED.has(sub)) {
420
413
  const { runInspect } = await import('./inspect')
421
414
  // pass the raw arg list through so every flag (--sim, --json,
@@ -83,14 +83,26 @@ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise
83
83
  prev === '--config' ||
84
84
  prev === '-t' ||
85
85
  prev === '--testNamePattern' ||
86
- prev === '--grep'
86
+ prev === '--grep' ||
87
+ prev === '--maxWorkers' ||
88
+ prev === '--shard'
87
89
  )
88
90
  return false
89
91
  return true
90
92
  })
91
- const passthroughJestFlags = args.filter(
92
- (a) => a === '--runInBand' || a.startsWith('--maxWorkers='),
93
- )
93
+ const passthroughJestFlags = args.flatMap((arg, index) => {
94
+ if (
95
+ arg === '--runInBand' ||
96
+ arg.startsWith('--maxWorkers=') ||
97
+ arg.startsWith('--shard=')
98
+ ) {
99
+ return [arg]
100
+ }
101
+ if ((arg === '--maxWorkers' || arg === '--shard') && args[index + 1]) {
102
+ return [arg, args[index + 1]]
103
+ }
104
+ return []
105
+ })
94
106
 
95
107
  // test dir discovery. external test suites set SOOTSIM_TEST_DIR; otherwise
96
108
  // we auto-detect the conventional locations.
@@ -966,8 +966,7 @@ async function runFlowSession(args: string[]) {
966
966
  return
967
967
  }
968
968
 
969
- case 'keep':
970
- case 'good': {
969
+ case 'keep': {
971
970
  const result = keepFlowCandidate()
972
971
  if (!result.active) {
973
972
  console.error(' no active flow draft — run `sootsim maestro start` first')
@@ -116,7 +116,7 @@ export async function runWaitEventSubcommand(opts: WaitEventOptions): Promise<vo
116
116
  if (!kind) {
117
117
  console.error(
118
118
  inspectUsage(
119
- 'wait event',
119
+ 'event',
120
120
  '<kind> [--max-ms 5000] [--filter <substring>] [--equals <exact>] [--since now|cursor]',
121
121
  ),
122
122
  )
@@ -13,7 +13,7 @@ export async function runWaitSelectorSubcommand(opts: {
13
13
  const { bridge, args, positional, inspectUsage } = opts
14
14
  const testId = positional[1]
15
15
  if (!testId) {
16
- console.error(inspectUsage('wait selector', '<testid> [--max-ms 5000] [--gone]'))
16
+ console.error(inspectUsage('selector', '<testid> [--max-ms 5000] [--gone]'))
17
17
  process.exit(1)
18
18
  }
19
19
  const maxMsIdx = args.indexOf('--max-ms')
@@ -1,6 +1,7 @@
1
1
  // sootsim inspect — dump node tree, accessibility info, query nodes
2
2
  // connects to the sootsim WS bridge (no Playwright)
3
3
 
4
+ import { getCliInspectVerbMeta } from '@sootsim/skills/cli'
4
5
  import { renderCliCommandHelp, renderCliGroupHelp } from '@sootsim/skills/cli/renderers'
5
6
  import { DEFAULT_SOOTSIM_BRIDGE_PORT } from '../../src/bridge-constants'
6
7
  import { DEFAULT_SOOTSIM_SHELL_URL } from '../../src/cli-constants'
@@ -615,6 +616,41 @@ export function printShellPerfReport(result: Record<string, any>) {
615
616
  printPhase('CanvasKit submission', compositorCadence.canvaskitSubmission)
616
617
  }
617
618
  const surfaces = Array.isArray(result.auxSurfaces) ? result.auxSurfaces : []
619
+ const graphiteCaches = result.graphiteCaches
620
+ const decodedImageCache = result.decodedImageCache
621
+ if (graphiteCaches || decodedImageCache) {
622
+ const printCache = (
623
+ label: string,
624
+ cache: { bytes?: number; limitBytes?: number; purgeableBytes?: number } | null,
625
+ ) => {
626
+ if (typeof cache?.bytes !== 'number' || typeof cache.limitBytes !== 'number') {
627
+ return
628
+ }
629
+ const purgeable =
630
+ typeof cache.purgeableBytes === 'number'
631
+ ? ` · ${(cache.purgeableBytes / 1024 / 1024).toFixed(1)}MB purgeable`
632
+ : ''
633
+ console.log(
634
+ ` ${label.padEnd(29)} ${(cache.bytes / 1024 / 1024).toFixed(1)}MB/${(cache.limitBytes / 1024 / 1024).toFixed(1)}MB${purgeable}`,
635
+ )
636
+ }
637
+ console.log(``)
638
+ console.log(` device-wide caches:`)
639
+ printCache('Graphite context resources', graphiteCaches?.contextResources ?? null)
640
+ printCache('Graphite recorder resources', graphiteCaches?.recorderResources ?? null)
641
+ printCache('Graphite image provider', graphiteCaches?.imageProvider ?? null)
642
+ printCache('decoded image pixels', decodedImageCache ?? null)
643
+ if (graphiteCaches?.imageProvider) {
644
+ console.log(
645
+ ` ${''.padEnd(29)} ${graphiteCaches.imageProvider.entries} entries · ${graphiteCaches.imageProvider.uploads} uploads · ${graphiteCaches.imageProvider.evictions} evictions`,
646
+ )
647
+ }
648
+ if (decodedImageCache) {
649
+ console.log(
650
+ ` ${''.padEnd(29)} ${decodedImageCache.entries} entries/${decodedImageCache.maxEntries} max`,
651
+ )
652
+ }
653
+ }
618
654
  if (surfaces.length > 0) {
619
655
  console.log(``)
620
656
  console.log(` surfaces (last 100 frames):`)
@@ -622,14 +658,6 @@ export function printShellPerfReport(result: Record<string, any>) {
622
658
  console.log(
623
659
  ` ${String(s.surfaceId).padEnd(10)} ${String(s.frames).padStart(4)} paints avg ${s.avgMs}ms (layout ${s.avgLayoutMs}, render ${s.avgRenderMs}, flush ${s.avgFlushMs}) max ${s.maxMs}ms`,
624
660
  )
625
- if (
626
- typeof s.gpuCacheBytes === 'number' &&
627
- typeof s.gpuCacheLimitBytes === 'number'
628
- ) {
629
- console.log(
630
- ` ${''.padEnd(10)} gpu cache ${(s.gpuCacheBytes / 1024 / 1024).toFixed(1)}MB/${(s.gpuCacheLimitBytes / 1024 / 1024).toFixed(1)}MB`,
631
- )
632
- }
633
661
  }
634
662
  }
635
663
  // two profiles, two workers: the shell paints chrome/overlays, the
@@ -875,9 +903,9 @@ function keyCodeToVisualKey(code: string) {
875
903
  }
876
904
 
877
905
  function getInspectCommandName(subcommand: string, directSubcommand: string | null) {
878
- return directSubcommand === subcommand
879
- ? `rnxsim ${subcommand}`
880
- : `rnxsim inspect ${subcommand}`
906
+ if (directSubcommand === subcommand) return `rnxsim ${subcommand}`
907
+ const group = getCliInspectVerbMeta(subcommand)?.group
908
+ return group ? `rnxsim ${group} ${subcommand}` : `rnxsim ${subcommand}`
881
909
  }
882
910
 
883
911
  function getInspectUsage(
@@ -1062,25 +1090,24 @@ export async function runInspect(args: string[], opts: InspectOptions) {
1062
1090
  const positional = parsed.positional
1063
1091
  const subcommand = positional[0]
1064
1092
  // used by inspectCommand() below to format usage/error strings with the
1065
- // same prefix the user actually typed. when `inspect` is the entrypoint
1066
- // (no prefix), we prefix with `inspect` so the instructions are valid.
1067
- const invocationPrefix: 'get' | 'do' | 'debug' | 'wait' | 'inspect' =
1068
- verbPrefix === 'get' ||
1069
- verbPrefix === 'do' ||
1070
- verbPrefix === 'debug' ||
1071
- verbPrefix === 'wait'
1072
- ? verbPrefix
1073
- : 'inspect'
1093
+ // same prefix the user actually typed. null when they typed a top-level
1094
+ // command, and inspectCommand asks the registry for the group instead.
1095
+ const invocationPrefix: 'get' | 'do' | 'debug' | 'wait' | null = verbPrefix
1074
1096
  const isTopLevel =
1075
1097
  typeof effectiveArgs[0] === 'string' &&
1076
1098
  TOP_LEVEL_RUNTIME_COMMANDS.has(effectiveArgs[0])
1077
1099
  const topLevelCommand = isTopLevel ? effectiveArgs[0] : null
1078
- const inspectCommand = (name: string) =>
1079
- opts.internalPerfCommand && name === 'perf'
1080
- ? `rnxsim perf ${opts.internalPerfCommand}`
1081
- : isTopLevel && name === effectiveArgs[0]
1082
- ? `rnxsim ${name}`
1083
- : `rnxsim ${invocationPrefix} ${name}`
1100
+ // a usage string has to name something the user can actually type. the
1101
+ // registry knows which group each verb belongs to, so an unprefixed
1102
+ // invocation still renders `rnxsim get tree` rather than a bare verb.
1103
+ const inspectCommand = (name: string) => {
1104
+ if (opts.internalPerfCommand && name === 'perf') {
1105
+ return `rnxsim perf ${opts.internalPerfCommand}`
1106
+ }
1107
+ if (isTopLevel && name === effectiveArgs[0]) return `rnxsim ${name}`
1108
+ const group = invocationPrefix ?? getCliInspectVerbMeta(name)?.group
1109
+ return group ? `rnxsim ${group} ${name}` : `rnxsim ${name}`
1110
+ }
1084
1111
  const inspectUsage = (name: string, tail: string) =>
1085
1112
  ` usage: ${inspectCommand(name)}${tail ? ` ${tail}` : ''}`
1086
1113
 
@@ -1118,14 +1145,12 @@ export async function runInspect(args: string[], opts: InspectOptions) {
1118
1145
  }
1119
1146
  }
1120
1147
 
1121
- // catch-all `sootsim inspect --help` — the inspect command meta +
1122
- // each group's listing stitched together.
1123
- const inspectMeta = renderCliCommandHelp('inspect', docContext)
1148
+ // catch-all help — every group's listing stitched together.
1124
1149
  const groupSections = ['do', 'get', 'debug', 'wait']
1125
1150
  .map((g) => renderCliGroupHelp(g, docContext))
1126
1151
  .filter((s): s is string => s != null)
1127
1152
  .join('\n\n')
1128
- console.log(`${inspectMeta ?? ''}\n\n${groupSections}\n`)
1153
+ console.log(`${groupSections}\n`)
1129
1154
  process.exit(0)
1130
1155
  }
1131
1156
 
@@ -1190,7 +1215,6 @@ export async function runInspect(args: string[], opts: InspectOptions) {
1190
1215
  'warnings',
1191
1216
  'requests',
1192
1217
  'js',
1193
- 'eval',
1194
1218
  'reload',
1195
1219
  'globals',
1196
1220
  'perf',
@@ -3571,7 +3595,6 @@ export async function runInspect(args: string[], opts: InspectOptions) {
3571
3595
  break
3572
3596
  }
3573
3597
 
3574
- case 'eval':
3575
3598
  case 'js': {
3576
3599
  const code = positional.slice(1).join(' ')
3577
3600
  if (!code) {
@@ -4501,7 +4524,7 @@ export async function runInspect(args: string[], opts: InspectOptions) {
4501
4524
  }
4502
4525
  } catch (err: any) {
4503
4526
  const message = err instanceof Error ? err.message : String(err)
4504
- console.error(` ${subcommand ?? 'inspect'} failed: ${message}`)
4527
+ console.error(` ${subcommand ?? 'rnxsim'} failed: ${message}`)
4505
4528
  const unknownSim = /^no sim connected with id ([^;]+)(?:; connected sims: .+)?$/.exec(
4506
4529
  message,
4507
4530
  )
@@ -94,7 +94,7 @@ workspace mode (directory runs):
94
94
 
95
95
  partial:
96
96
  clearKeychain — warn-only, rnx has no keychain surface.
97
- when.platform — rnx reports iOS.
97
+ when.platform — rnx reports iOS for this runner.
98
98
  openLink — dispatched into the app via the Linking emulation
99
99
  (react-navigation deep links work); no OS-level Safari fallback.
100
100
  launchApp.arguments — ignored; rnx has no native process.
@@ -18,7 +18,7 @@ function printHelp() {
18
18
  rnxsim storage profile — manage isolated storage profiles
19
19
 
20
20
  usage:
21
- rnxsim storage profile ls
21
+ rnxsim storage profile list
22
22
  rnxsim storage profile create <id>
23
23
  rnxsim storage profile clear <id>
24
24
  rnxsim storage profile delete <id>
@@ -28,7 +28,7 @@ service workers in Electron and Playwright launches. Plain browser-tab launches
28
28
  cannot honor profiles and will fail clearly if a profile is requested.
29
29
 
30
30
  examples:
31
- rnxsim storage profile ls
31
+ rnxsim storage profile list
32
32
  rnxsim storage profile create qa
33
33
  rnxsim open 8081 --profile qa
34
34
  rnxsim open 8081 --profile qa --driver playwright
@@ -43,10 +43,9 @@ export async function runProfile(args: string[], _opts: ProfileOptions): Promise
43
43
  return 0
44
44
  }
45
45
 
46
- const subcommand = args[0] ?? 'ls'
46
+ const subcommand = args[0] ?? 'list'
47
47
  try {
48
48
  switch (subcommand) {
49
- case 'ls':
50
49
  case 'list': {
51
50
  const profiles = listProfiles()
52
51
  for (const profile of profiles) {
@@ -85,8 +84,7 @@ export async function runProfile(args: string[], _opts: ProfileOptions): Promise
85
84
  console.log(` cleared profile: ${id}`)
86
85
  return 0
87
86
  }
88
- case 'delete':
89
- case 'rm': {
87
+ case 'delete': {
90
88
  const id = args[1]
91
89
  if (!id) {
92
90
  console.error(' profile delete expects <id>')
@@ -44,18 +44,14 @@ export async function runRuntime(args: string[], opts: RuntimeOptions = {}) {
44
44
  case 'install':
45
45
  return runtimeInstall(rest, opts)
46
46
  case 'list':
47
- case 'ls':
48
47
  return runtimeList(rest)
49
48
  case 'use':
50
49
  return runtimeUse(rest)
51
50
  case 'remove':
52
- case 'rm':
53
51
  return runtimeRemove(rest)
54
52
  case 'which':
55
- case 'active':
56
53
  return runtimeWhich()
57
54
  case 'notes':
58
- case 'changelog':
59
55
  return runtimeNotes(rest)
60
56
  default:
61
57
  console.error(` unknown runtime subcommand: ${sub}`)
@@ -30,7 +30,7 @@ usage:
30
30
  rnxsim storage clear [--full] [--sim <id>] [--json]
31
31
 
32
32
  subcommands:
33
- profile ls
33
+ profile list
34
34
  profile create <id>
35
35
  profile clear <id>
36
36
  profile delete <id>
@@ -33,14 +33,12 @@ export async function runTelemetry(args: string[]): Promise<void> {
33
33
  return
34
34
 
35
35
  case 'on':
36
- case 'enable':
37
36
  writePrivacyPreferences({ ...readPrivacyPreferences(), productAnalytics: true })
38
37
  console.log(' product analytics ON')
39
38
  console.log(` config: ${configFilePath()}`)
40
39
  return
41
40
 
42
41
  case 'off':
43
- case 'disable':
44
42
  writePrivacyPreferences({ ...readPrivacyPreferences(), productAnalytics: false })
45
43
  console.log(' product analytics OFF')
46
44
  console.log(` config: ${configFilePath()}`)
@@ -74,16 +74,14 @@ export async function runTimeline(args: string[], opts: TimelineOptions) {
74
74
  console.log(JSON.stringify(status, null, json ? 0 : 2))
75
75
  break
76
76
  }
77
- case 'start':
78
- case 'enable': {
77
+ case 'start': {
79
78
  if (kinds.length === 0) kinds.push('all')
80
79
  await callInBridge(bridge, 'SootSim.bridges.timeline.enable', ...kinds)
81
80
  const status = await callInBridge(bridge, 'SootSim.bridges.timeline.status')
82
81
  console.log(JSON.stringify(status, null, json ? 0 : 2))
83
82
  break
84
83
  }
85
- case 'stop':
86
- case 'disable': {
84
+ case 'stop': {
87
85
  if (kinds.length === 0) kinds.push('all')
88
86
  await callInBridge(bridge, 'SootSim.bridges.timeline.disable', ...kinds)
89
87
  const status = await callInBridge(bridge, 'SootSim.bridges.timeline.status')
package/cli/parse-args.ts CHANGED
@@ -33,7 +33,6 @@ const GLOBAL_FLAGS: Record<
33
33
  // - `sootsim get <noun>` reads
34
34
  // - `sootsim do <action>` writes
35
35
  // - `sootsim debug <tool>` instrumentation (handled by runDebug)
36
- // - `sootsim inspect <sub>` full legacy surface, all subcommands
37
36
  // `describe` and `find` stay at root as the most common entrypoints.
38
37
  // `list` stays at root because it's a sim-management read.
39
38
  export const TOP_LEVEL_RUNTIME_COMMANDS = new Set([
@@ -70,7 +69,6 @@ const COMMANDS = new Set([
70
69
  'reset',
71
70
  'camera',
72
71
  'mode',
73
- 'inspect',
74
72
  'debug',
75
73
  'timeline',
76
74
  'what-happened',
@@ -93,7 +91,6 @@ const COMMANDS = new Set([
93
91
  'daemon',
94
92
  'runtime',
95
93
  'upgrade',
96
- 'update',
97
94
  'version',
98
95
  'skill',
99
96
  'config',
package/detox/index.ts CHANGED
@@ -608,6 +608,7 @@ type FastScreenshotData = {
608
608
  label: string
609
609
  offsetMs: number
610
610
  actualMs: number
611
+ captureRequestedAtMs?: number
611
612
  dataUrl: string
612
613
  frameIndex?: number
613
614
  virtualTimestampMs?: number
@@ -1854,17 +1855,44 @@ export const device = {
1854
1855
  async takeScreenshotStagesFast(
1855
1856
  namePrefix: string,
1856
1857
  stages: Array<{ label: string; offsetMs: number }>,
1857
- options?: { skipWarmup?: boolean },
1858
- ): Promise<Array<{ label: string; offsetMs: number; actualMs: number; path: string }>> {
1859
- const page = getPage()
1860
- const frames: Array<{
1858
+ options: {
1859
+ anchorAfterTrigger?: boolean
1860
+ skipWarmup?: boolean
1861
+ trigger: () => unknown
1862
+ },
1863
+ ): Promise<{
1864
+ captureAnchorMs: number
1865
+ frames: Array<{
1861
1866
  label: string
1862
1867
  offsetMs: number
1863
1868
  actualMs: number
1864
- dataUrl: string
1865
- }> = await page.evaluate(
1869
+ captureRequestedAtMs: number
1870
+ path: string
1871
+ }>
1872
+ triggerCompletedMs: number
1873
+ triggerStartMs: number
1874
+ }> {
1875
+ const page = getPage()
1876
+ const token = `timed-${process.pid}-${Date.now()}-${Math.random()}`
1877
+ const capturePromise: Promise<{
1878
+ captureAnchorMs: number
1879
+ frames: Array<{
1880
+ label: string
1881
+ offsetMs: number
1882
+ actualMs: number
1883
+ captureRequestedAtMs: number
1884
+ dataUrl: string
1885
+ }>
1886
+ }> = page.evaluate(
1866
1887
  async (input) => {
1867
- const sim = window as {
1888
+ type CaptureState = {
1889
+ anchorPerformanceMs: number | null
1890
+ anchorWallMs: number | null
1891
+ cancelled: boolean
1892
+ ready: boolean
1893
+ }
1894
+ type CaptureWindow = Window & {
1895
+ __sootsimTimedCaptures?: Record<string, CaptureState>
1868
1896
  SootSim?: {
1869
1897
  bridges?: {
1870
1898
  screenshot?: (opts?: unknown) => Promise<string>
@@ -1877,11 +1905,22 @@ export const device = {
1877
1905
  }
1878
1906
  }
1879
1907
  }
1880
- const screenshot = sim.SootSim?.bridges?.screenshot
1881
- const live = sim.SootSim?.bridges?.liveComposite
1908
+ const captureWindow = window as unknown as CaptureWindow
1909
+ const screenshot = captureWindow.SootSim?.bridges?.screenshot
1910
+ const live = captureWindow.SootSim?.bridges?.liveComposite
1882
1911
  if (typeof screenshot !== 'function') {
1883
1912
  throw new Error('rnx screenshot bridge is not installed')
1884
1913
  }
1914
+ const registry =
1915
+ captureWindow.__sootsimTimedCaptures ??
1916
+ (captureWindow.__sootsimTimedCaptures = {})
1917
+ const state: CaptureState = {
1918
+ anchorPerformanceMs: null,
1919
+ anchorWallMs: null,
1920
+ cancelled: false,
1921
+ ready: false,
1922
+ }
1923
+ registry[input.token] = state
1885
1924
  // initial warm-up: ensure the worker has produced at least one
1886
1925
  // frame after the menu's portal mount before we start sampling.
1887
1926
  // (a pump at higher cadence was tried and starved tap propagation —
@@ -1890,11 +1929,18 @@ export const device = {
1890
1929
  if (!input.skipWarmup) {
1891
1930
  await screenshot({ format: 'png' })
1892
1931
  }
1893
- const start = performance.now()
1932
+ state.ready = true
1933
+ while (state.anchorPerformanceMs === null || state.anchorWallMs === null) {
1934
+ if (state.cancelled) throw new Error('rnx timed capture was cancelled')
1935
+ await new Promise((resolve) => setTimeout(resolve, 0))
1936
+ }
1937
+ const captureAnchorMs = state.anchorWallMs
1938
+ const captureAnchorPerformanceMs = state.anchorPerformanceMs
1894
1939
  type Snapshot = {
1895
1940
  label: string
1896
1941
  offsetMs: number
1897
1942
  actualMs: number
1943
+ captureRequestedAtMs: number
1898
1944
  dataUrl: string | null
1899
1945
  }
1900
1946
  // each stage fires at its requested offset and SYNCHRONOUSLY
@@ -1907,7 +1953,8 @@ export const device = {
1907
1953
  (stage) =>
1908
1954
  new Promise<Snapshot>((resolve) => {
1909
1955
  const fire = async () => {
1910
- const actualMs = performance.now() - start
1956
+ const actualMs = performance.now() - captureAnchorPerformanceMs
1957
+ const captureRequestedAtMs = Date.now()
1911
1958
  try {
1912
1959
  let dataUrl: string | null = null
1913
1960
  if (live && typeof live.captureFresh === 'function') {
@@ -1935,6 +1982,7 @@ export const device = {
1935
1982
  label: stage.label,
1936
1983
  offsetMs: stage.offsetMs,
1937
1984
  actualMs,
1985
+ captureRequestedAtMs,
1938
1986
  dataUrl,
1939
1987
  })
1940
1988
  } catch {
@@ -1942,6 +1990,7 @@ export const device = {
1942
1990
  label: stage.label,
1943
1991
  offsetMs: stage.offsetMs,
1944
1992
  actualMs,
1993
+ captureRequestedAtMs,
1945
1994
  dataUrl: null,
1946
1995
  })
1947
1996
  }
@@ -1950,7 +1999,10 @@ export const device = {
1950
1999
  () => {
1951
2000
  void fire()
1952
2001
  },
1953
- Math.max(0, stage.offsetMs),
2002
+ Math.max(
2003
+ 0,
2004
+ captureAnchorPerformanceMs + stage.offsetMs - performance.now(),
2005
+ ),
1954
2006
  )
1955
2007
  }),
1956
2008
  ),
@@ -1959,6 +2011,7 @@ export const device = {
1959
2011
  label: string
1960
2012
  offsetMs: number
1961
2013
  actualMs: number
2014
+ captureRequestedAtMs: number
1962
2015
  dataUrl: string
1963
2016
  }> = []
1964
2017
  for (const snap of snaps) {
@@ -1967,14 +2020,101 @@ export const device = {
1967
2020
  label: snap.label,
1968
2021
  offsetMs: snap.offsetMs,
1969
2022
  actualMs: snap.actualMs,
2023
+ captureRequestedAtMs: snap.captureRequestedAtMs,
1970
2024
  dataUrl: snap.dataUrl,
1971
2025
  })
1972
2026
  }
1973
- return out
2027
+ delete registry[input.token]
2028
+ return { captureAnchorMs, frames: out }
1974
2029
  },
1975
- { stages, skipWarmup: options?.skipWarmup === true },
2030
+ { stages, skipWarmup: options.skipWarmup === true, token },
1976
2031
  )
1977
- return writeFastScreenshotFrames(namePrefix, frames)
2032
+
2033
+ await page.waitForFunction(
2034
+ (captureToken) => {
2035
+ const registry = (
2036
+ window as Window & {
2037
+ __sootsimTimedCaptures?: Record<string, { ready: boolean }>
2038
+ }
2039
+ ).__sootsimTimedCaptures
2040
+ return registry?.[captureToken]?.ready === true
2041
+ },
2042
+ token,
2043
+ { timeout: 5000 },
2044
+ )
2045
+
2046
+ const armCapture = (anchorMs?: number) =>
2047
+ page.evaluate(
2048
+ ({ anchorMs, captureToken }) => {
2049
+ const registry = (
2050
+ window as Window & {
2051
+ __sootsimTimedCaptures?: Record<
2052
+ string,
2053
+ {
2054
+ anchorPerformanceMs: number | null
2055
+ anchorWallMs: number | null
2056
+ ready: boolean
2057
+ }
2058
+ >
2059
+ }
2060
+ ).__sootsimTimedCaptures
2061
+ const capture = registry?.[captureToken]
2062
+ if (!capture?.ready) throw new Error('rnx timed capture was not armed')
2063
+ const nowMs = Date.now()
2064
+ capture.anchorWallMs = anchorMs ?? nowMs
2065
+ capture.anchorPerformanceMs = performance.now() - (nowMs - capture.anchorWallMs)
2066
+ return capture.anchorWallMs
2067
+ },
2068
+ { anchorMs, captureToken: token },
2069
+ )
2070
+
2071
+ let triggerStartMs = 0
2072
+ let triggerCompletedMs = 0
2073
+ try {
2074
+ let captureAnchorMs: number
2075
+ if (options.anchorAfterTrigger === false) {
2076
+ captureAnchorMs = await armCapture()
2077
+ triggerStartMs = captureAnchorMs
2078
+ const triggerPromise = Promise.resolve()
2079
+ .then(options.trigger)
2080
+ .then(() => {
2081
+ triggerCompletedMs = Date.now()
2082
+ })
2083
+ const [capture] = await Promise.all([capturePromise, triggerPromise])
2084
+ return {
2085
+ captureAnchorMs,
2086
+ frames: writeFastScreenshotFrames(namePrefix, capture.frames),
2087
+ triggerCompletedMs,
2088
+ triggerStartMs,
2089
+ }
2090
+ }
2091
+
2092
+ triggerStartMs = Date.now()
2093
+ await Promise.resolve(options.trigger())
2094
+ triggerCompletedMs = Date.now()
2095
+ captureAnchorMs = await armCapture(triggerCompletedMs)
2096
+ const capture = await capturePromise
2097
+ return {
2098
+ captureAnchorMs,
2099
+ frames: writeFastScreenshotFrames(namePrefix, capture.frames),
2100
+ triggerCompletedMs,
2101
+ triggerStartMs,
2102
+ }
2103
+ } catch (error) {
2104
+ await page
2105
+ .evaluate((captureToken) => {
2106
+ const registry = (
2107
+ window as Window & {
2108
+ __sootsimTimedCaptures?: Record<string, { cancelled: boolean }>
2109
+ }
2110
+ ).__sootsimTimedCaptures
2111
+ const capture = registry?.[captureToken]
2112
+ if (capture) capture.cancelled = true
2113
+ }, token)
2114
+ .catch(() => {})
2115
+ await capturePromise.catch(() => {})
2116
+ throw error
2117
+ }
1978
2118
  },
1979
2119
 
1980
2120
  // batched multi-stage capture for animation diffs. all N captures happen
@@ -17,7 +17,6 @@ module.exports = {
17
17
  testTimeout: 180000,
18
18
  maxWorkers: 1,
19
19
  watchman: false,
20
- forceExit: true,
21
20
  verbose: true,
22
21
  testEnvironment: 'node',
23
22
  setupFilesAfterEnv: [path.join(PACKAGE_ROOT, 'detox', 'jest-setup-after-env.cjs')],