peachsim 0.1.527

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 (324) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +640 -0
  3. package/cli/app-config.ts +82 -0
  4. package/cli/app-fonts.ts +388 -0
  5. package/cli/app-project.ts +59 -0
  6. package/cli/app-splash.ts +171 -0
  7. package/cli/app-state-reset.ts +26 -0
  8. package/cli/auth.ts +184 -0
  9. package/cli/bin.ts +59 -0
  10. package/cli/bridge-diagnostics.ts +226 -0
  11. package/cli/bridge-flow-runner.ts +2806 -0
  12. package/cli/browser-backend.ts +44 -0
  13. package/cli/browser-evals.ts +96 -0
  14. package/cli/cli-update.ts +261 -0
  15. package/cli/cloud-client.ts +1530 -0
  16. package/cli/cloud-dispatch.ts +206 -0
  17. package/cli/cloud-session.ts +227 -0
  18. package/cli/command-contract.ts +221 -0
  19. package/cli/command-registry.ts +304 -0
  20. package/cli/commands/agent-wrapper.ts +1048 -0
  21. package/cli/commands/agent.ts +439 -0
  22. package/cli/commands/app-fonts.ts +99 -0
  23. package/cli/commands/assert.ts +542 -0
  24. package/cli/commands/auth.ts +71 -0
  25. package/cli/commands/box/bundle-plane.ts +71 -0
  26. package/cli/commands/box/checkout-plane.ts +256 -0
  27. package/cli/commands/box/plane-share.ts +208 -0
  28. package/cli/commands/box/rnx-command.ts +60 -0
  29. package/cli/commands/box/search-command.ts +157 -0
  30. package/cli/commands/box.ts +925 -0
  31. package/cli/commands/camera.ts +266 -0
  32. package/cli/commands/cleanup.ts +169 -0
  33. package/cli/commands/compat.ts +176 -0
  34. package/cli/commands/config.ts +34 -0
  35. package/cli/commands/control.ts +2154 -0
  36. package/cli/commands/cpu-profile.ts +941 -0
  37. package/cli/commands/daemon-mac-app.ts +165 -0
  38. package/cli/commands/daemon.ts +800 -0
  39. package/cli/commands/debug.ts +751 -0
  40. package/cli/commands/desktop.ts +39 -0
  41. package/cli/commands/detect.ts +201 -0
  42. package/cli/commands/detox.ts +481 -0
  43. package/cli/commands/device.ts +134 -0
  44. package/cli/commands/diagnose.ts +589 -0
  45. package/cli/commands/do-chain.ts +408 -0
  46. package/cli/commands/electron.ts +94 -0
  47. package/cli/commands/film.ts +379 -0
  48. package/cli/commands/flow.ts +1240 -0
  49. package/cli/commands/inspect/actions.ts +516 -0
  50. package/cli/commands/inspect/core.ts +2086 -0
  51. package/cli/commands/inspect/count.ts +17 -0
  52. package/cli/commands/inspect/describe.ts +119 -0
  53. package/cli/commands/inspect/env.ts +25 -0
  54. package/cli/commands/inspect/find.ts +169 -0
  55. package/cli/commands/inspect/get-layout.ts +53 -0
  56. package/cli/commands/inspect/keyboard.ts +54 -0
  57. package/cli/commands/inspect/list.ts +175 -0
  58. package/cli/commands/inspect/memory.ts +215 -0
  59. package/cli/commands/inspect/redaction.ts +39 -0
  60. package/cli/commands/inspect/resolve-target.ts +77 -0
  61. package/cli/commands/inspect/screens.ts +78 -0
  62. package/cli/commands/inspect/settle.ts +32 -0
  63. package/cli/commands/inspect/settling.ts +197 -0
  64. package/cli/commands/inspect/shared.ts +259 -0
  65. package/cli/commands/inspect/sleep.ts +15 -0
  66. package/cli/commands/inspect/tree.ts +23 -0
  67. package/cli/commands/inspect/url.ts +17 -0
  68. package/cli/commands/inspect/wait-event.ts +211 -0
  69. package/cli/commands/inspect/wait-idle.ts +25 -0
  70. package/cli/commands/inspect/wait-ready.ts +87 -0
  71. package/cli/commands/inspect/wait-selector.ts +59 -0
  72. package/cli/commands/inspect/wait.ts +32 -0
  73. package/cli/commands/inspect.ts +4738 -0
  74. package/cli/commands/install-cli.ts +275 -0
  75. package/cli/commands/install-desktop.ts +385 -0
  76. package/cli/commands/login.ts +357 -0
  77. package/cli/commands/logout.ts +24 -0
  78. package/cli/commands/maestro-generate.ts +375 -0
  79. package/cli/commands/maestro.ts +536 -0
  80. package/cli/commands/mode.ts +58 -0
  81. package/cli/commands/no-bridge-hint.ts +80 -0
  82. package/cli/commands/perf.ts +67 -0
  83. package/cli/commands/permissions.ts +203 -0
  84. package/cli/commands/platform.ts +410 -0
  85. package/cli/commands/preview.ts +103 -0
  86. package/cli/commands/profile.ts +106 -0
  87. package/cli/commands/react.ts +353 -0
  88. package/cli/commands/record.ts +1435 -0
  89. package/cli/commands/report-issue.ts +305 -0
  90. package/cli/commands/reset.ts +89 -0
  91. package/cli/commands/runtime.ts +348 -0
  92. package/cli/commands/screenshot-command.ts +106 -0
  93. package/cli/commands/screenshot-layers.ts +144 -0
  94. package/cli/commands/screenshot-mode.ts +38 -0
  95. package/cli/commands/screenshot.ts +492 -0
  96. package/cli/commands/screenshots-capture.ts +608 -0
  97. package/cli/commands/screenshots.ts +127 -0
  98. package/cli/commands/serve.ts +139 -0
  99. package/cli/commands/setup.ts +416 -0
  100. package/cli/commands/shell-boolean-mode.ts +82 -0
  101. package/cli/commands/skills.ts +468 -0
  102. package/cli/commands/slides.ts +357 -0
  103. package/cli/commands/state.ts +94 -0
  104. package/cli/commands/storage.ts +58 -0
  105. package/cli/commands/telemetry.ts +55 -0
  106. package/cli/commands/test.ts +535 -0
  107. package/cli/commands/three-mode.ts +771 -0
  108. package/cli/commands/timeline.ts +121 -0
  109. package/cli/commands/upgrade.ts +167 -0
  110. package/cli/commands/upload.ts +1203 -0
  111. package/cli/commands/version.ts +63 -0
  112. package/cli/commands/welcome.ts +17 -0
  113. package/cli/commands/what-happened.ts +328 -0
  114. package/cli/current-sim.ts +217 -0
  115. package/cli/desktop-companion.ts +315 -0
  116. package/cli/drivers/electron.ts +70 -0
  117. package/cli/drivers/index.ts +20 -0
  118. package/cli/drivers/playwright-provisioning.ts +184 -0
  119. package/cli/drivers/playwright-sim-host.ts +537 -0
  120. package/cli/drivers/playwright.ts +209 -0
  121. package/cli/drivers/registry.ts +65 -0
  122. package/cli/drivers/types.ts +102 -0
  123. package/cli/flow-export.ts +55 -0
  124. package/cli/flow-file.ts +142 -0
  125. package/cli/flow-live-status.ts +126 -0
  126. package/cli/flow-session.ts +140 -0
  127. package/cli/help-core.ts +143 -0
  128. package/cli/help.ts +44 -0
  129. package/cli/hidden-runtime-alias.ts +19 -0
  130. package/cli/hints.ts +214 -0
  131. package/cli/inspect-notice-state.ts +114 -0
  132. package/cli/internal-child.ts +176 -0
  133. package/cli/maestro-js.ts +323 -0
  134. package/cli/main.ts +704 -0
  135. package/cli/open-url.ts +8 -0
  136. package/cli/outbound-endpoints.ts +830 -0
  137. package/cli/parent-pid.ts +204 -0
  138. package/cli/parse-args.ts +189 -0
  139. package/cli/privacy.ts +53 -0
  140. package/cli/prompt.ts +56 -0
  141. package/cli/recording-access.ts +108 -0
  142. package/cli/registry.ts +2 -0
  143. package/cli/resolve-assets.ts +63 -0
  144. package/cli/rnx-in-process.ts +98 -0
  145. package/cli/run-registry.ts +226 -0
  146. package/cli/run-rnx-core.ts +148 -0
  147. package/cli/run-rnx.ts +137 -0
  148. package/cli/runtime-notes.ts +66 -0
  149. package/cli/runtime-summary.ts +25 -0
  150. package/cli/secure-file.ts +31 -0
  151. package/cli/self-invocation.ts +58 -0
  152. package/cli/send-to-box.ts +109 -0
  153. package/cli/setup-repository.ts +191 -0
  154. package/cli/shell-init.ts +98 -0
  155. package/cli/standalone.ts +4 -0
  156. package/cli/telemetry.ts +84 -0
  157. package/cli/ui.ts +68 -0
  158. package/cli/ws-bridge.ts +1020 -0
  159. package/detox/attempt-events.cjs +23 -0
  160. package/detox/colors.ts +54 -0
  161. package/detox/config-loader.ts +135 -0
  162. package/detox/element-types.ts +48 -0
  163. package/detox/expectations.ts +534 -0
  164. package/detox/gestures.ts +501 -0
  165. package/detox/index.ts +2694 -0
  166. package/detox/jest-preset.cjs +29 -0
  167. package/detox/jest-setup-after-env.cjs +22 -0
  168. package/detox/matchers.ts +30 -0
  169. package/detox/motion-change.cjs +109 -0
  170. package/detox/motion-change.d.cts +35 -0
  171. package/detox/navigation-lifecycle.ts +290 -0
  172. package/detox/navigation.ts +43 -0
  173. package/detox/proof-frame.cjs +114 -0
  174. package/detox/record-block-execution.cjs +78 -0
  175. package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
  176. package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
  177. package/detox/screenshots/color-test-rest-norngh.png +0 -0
  178. package/detox/screenshots/theme-blue-toggle.png +0 -0
  179. package/detox/screenshots/theme-blue.png +0 -0
  180. package/detox/screenshots/theme-red-toggle.png +0 -0
  181. package/detox/screenshots/theme-red.png +0 -0
  182. package/dist-lib/agent-daemon-client.cjs +502 -0
  183. package/dist-lib/agent-events.cjs +48 -0
  184. package/dist-lib/agent-identity.cjs +53 -0
  185. package/dist-lib/agent-sessions.cjs +759 -0
  186. package/dist-lib/attached-projects.cjs +526 -0
  187. package/dist-lib/auth/shared-session.cjs +208 -0
  188. package/dist-lib/backend-origin.cjs +88 -0
  189. package/dist-lib/beta.cjs +41 -0
  190. package/dist-lib/beta.mjs +13 -0
  191. package/dist-lib/bridge-constants.cjs +161 -0
  192. package/dist-lib/bridge-contract-input.cjs +451 -0
  193. package/dist-lib/bridge-contract-input.mjs +414 -0
  194. package/dist-lib/bridge-contract.cjs +130 -0
  195. package/dist-lib/bridge-contract.mjs +94 -0
  196. package/dist-lib/capture-contract.cjs +220 -0
  197. package/dist-lib/capture-contract.mjs +190 -0
  198. package/dist-lib/cli-constants.cjs +32 -0
  199. package/dist-lib/cloud-contract.cjs +194 -0
  200. package/dist-lib/cloud-contract.mjs +145 -0
  201. package/dist-lib/cloud.cjs +670 -0
  202. package/dist-lib/cloud.mjs +639 -0
  203. package/dist-lib/config.cjs +96 -0
  204. package/dist-lib/detox/index.cjs +3789 -0
  205. package/dist-lib/detox/jest-preset.cjs +29 -0
  206. package/dist-lib/dev-bundle-resolution.cjs +290 -0
  207. package/dist-lib/home-paths.cjs +638 -0
  208. package/dist-lib/host/bridge-host.cjs +10903 -0
  209. package/dist-lib/host/fetch-proxy-handler.cjs +421 -0
  210. package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
  211. package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
  212. package/dist-lib/host/replacement-module-handler.cjs +469 -0
  213. package/dist-lib/host/websocket-proxy.cjs +298 -0
  214. package/dist-lib/index.cjs +4268 -0
  215. package/dist-lib/jump-to-source-babel.cjs +93 -0
  216. package/dist-lib/jump-to-source-native.cjs +112 -0
  217. package/dist-lib/menu.cjs +646 -0
  218. package/dist-lib/menu.mjs +604 -0
  219. package/dist-lib/metro-fingerprint-registry.cjs +186 -0
  220. package/dist-lib/metro-fingerprint-registry.mjs +156 -0
  221. package/dist-lib/metro-production-bundle.cjs +329 -0
  222. package/dist-lib/metro-production-bundle.mjs +295 -0
  223. package/dist-lib/metro.cjs +904 -0
  224. package/dist-lib/profiles.cjs +246 -0
  225. package/dist-lib/public-brand.cjs +39 -0
  226. package/dist-lib/react-native-host-modules.cjs +51 -0
  227. package/dist-lib/react-native-host-modules.mjs +25 -0
  228. package/dist-lib/render-mode.cjs +54 -0
  229. package/dist-lib/scripts/dev-server-scanner.cjs +1232 -0
  230. package/dist-lib/sdk.cjs +2433 -0
  231. package/dist-lib/sdk.mjs +2336 -0
  232. package/dist-lib/skills.cjs +39132 -0
  233. package/dist-lib/swift-submit.cjs +92 -0
  234. package/dist-lib/swift-submit.mjs +65 -0
  235. package/dist-lib/swift.cjs +1646 -0
  236. package/dist-lib/vite.cjs +1591 -0
  237. package/package.json +290 -0
  238. package/scripts/dev-server-scanner.ts +1291 -0
  239. package/scripts/optional-demo-registry.ts +113 -0
  240. package/skills/contrast/SKILL.md +165 -0
  241. package/skills/rnx-debug/SKILL.md +433 -0
  242. package/skills/rnx-perf/SKILL.md +399 -0
  243. package/skills/rnx-setup/SKILL.md +172 -0
  244. package/skills/rnx-test/SKILL.md +321 -0
  245. package/skills/rnx-visual/SKILL.md +126 -0
  246. package/src/agent-daemon-client.ts +410 -0
  247. package/src/agent-events.ts +71 -0
  248. package/src/agent-identity.ts +23 -0
  249. package/src/agent-prompt.ts +71 -0
  250. package/src/agent-sessions.ts +580 -0
  251. package/src/app-url.ts +39 -0
  252. package/src/attached-projects.ts +539 -0
  253. package/src/auth/shared-session.ts +235 -0
  254. package/src/backend-origin.ts +70 -0
  255. package/src/beta.ts +19 -0
  256. package/src/bridge-constants.ts +175 -0
  257. package/src/bridge-contract-input.ts +502 -0
  258. package/src/bridge-contract.ts +558 -0
  259. package/src/browser-cache.ts +16 -0
  260. package/src/capture-contract.ts +587 -0
  261. package/src/cli-constants.ts +1 -0
  262. package/src/cli-version.ts +40 -0
  263. package/src/cloud-contract.ts +274 -0
  264. package/src/cloud.ts +762 -0
  265. package/src/codex-client.ts +234 -0
  266. package/src/config.ts +132 -0
  267. package/src/connect.ts +101 -0
  268. package/src/dev-bundle-resolution.ts +346 -0
  269. package/src/dev-server-open.ts +198 -0
  270. package/src/disk-cleanup.ts +540 -0
  271. package/src/flow-timeout-scale.ts +15 -0
  272. package/src/home-paths.ts +806 -0
  273. package/src/host/agent-host.ts +666 -0
  274. package/src/host/bridge-host.ts +2788 -0
  275. package/src/host/fetch-proxy-handler.ts +449 -0
  276. package/src/host/fetch-proxy-overrides.ts +51 -0
  277. package/src/host/open-url.ts +89 -0
  278. package/src/host/plane-host.ts +203 -0
  279. package/src/host/replacement-module-handler.ts +158 -0
  280. package/src/host/websocket-proxy.ts +315 -0
  281. package/src/index.ts +71 -0
  282. package/src/jump-to-source-babel.ts +135 -0
  283. package/src/jump-to-source-native.ts +102 -0
  284. package/src/menu.ts +777 -0
  285. package/src/metro-fingerprint-registry.ts +302 -0
  286. package/src/metro-plugin.ts +341 -0
  287. package/src/metro-production-bundle.ts +471 -0
  288. package/src/native-dev-bundle-url.ts +118 -0
  289. package/src/native-seam-manifest.ts +341 -0
  290. package/src/playwright-host-log.ts +16 -0
  291. package/src/plist.ts +27 -0
  292. package/src/poll-until.ts +123 -0
  293. package/src/profiles.ts +179 -0
  294. package/src/public-brand.ts +9 -0
  295. package/src/react-native-host-modules.ts +26 -0
  296. package/src/render-mode.ts +27 -0
  297. package/src/rolldown-plugin-swift.ts +240 -0
  298. package/src/runtime-assets.ts +233 -0
  299. package/src/runtime-delivery.ts +54 -0
  300. package/src/screenshots/compose.ts +422 -0
  301. package/src/screenshots/frame-compose.ts +514 -0
  302. package/src/screenshots/orchestrate.ts +256 -0
  303. package/src/screenshots/org-deck.ts +91 -0
  304. package/src/screenshots/registry.ts +58 -0
  305. package/src/screenshots/schema.ts +364 -0
  306. package/src/sdk.ts +15 -0
  307. package/src/settings-bundle.ts +418 -0
  308. package/src/shell-bridge-identity.ts +15 -0
  309. package/src/sim-client.ts +697 -0
  310. package/src/skills/builtin/a11y-review.ts +132 -0
  311. package/src/skills/builtin/compat-check.ts +71 -0
  312. package/src/skills/builtin/maestro-test.ts +121 -0
  313. package/src/skills/builtin/perf-profile.ts +90 -0
  314. package/src/skills/builtin/screenshot-all.ts +52 -0
  315. package/src/skills/builtin/visual-diff.ts +100 -0
  316. package/src/skills/registry.ts +107 -0
  317. package/src/skills/types.ts +41 -0
  318. package/src/swift-submit.ts +185 -0
  319. package/src/swift.ts +7 -0
  320. package/src/team-machine-prompt.ts +73 -0
  321. package/src/vite-plugin-one.ts +264 -0
  322. package/src/vite-plugin-swift.ts +620 -0
  323. package/src/vite-plugin.ts +1946 -0
  324. package/src/worklets-babel.ts +250 -0
@@ -0,0 +1,751 @@
1
+ // rnx debug — drive __sootsimDebug.* from the terminal
2
+ //
3
+ // connects to a running rnx instance via the WebSocket bridge and forwards
4
+ // debug channel management, tree snapshots, and on-demand inspectors without
5
+ // requiring a browser console.
6
+ //
7
+ // usage examples:
8
+ // rnx debug enable sheets,portals,onlayout
9
+ // rnx debug status
10
+ // rnx debug snapshot before
11
+ // # ... tap something in the UI ...
12
+ // rnx debug snapshot after
13
+ // rnx debug diff before after
14
+ // rnx debug find sheets
15
+ // rnx debug find portals
16
+ // rnx debug recent portals
17
+ // rnx debug flags
18
+ //
19
+ // all commands print JSON by default so they pipe cleanly into jq.
20
+
21
+ import { DEFAULT_SOOTSIM_BRIDGE_PORT } from '../../src/bridge-constants'
22
+ import { printBridgeFailureDiagnostics } from '../bridge-diagnostics'
23
+ import { rethrowIfExit, rnxExit } from '../run-rnx'
24
+ import {
25
+ createBridgeFromParsed,
26
+ evalInBridge,
27
+ parseBridgeCliArgs,
28
+ type WsBridge,
29
+ } from '../ws-bridge'
30
+ import {
31
+ callDebugBridge,
32
+ inspectDebugFind,
33
+ inspectDebugFlags,
34
+ inspectDebugRecent,
35
+ inspectDebugStatus,
36
+ setDebugChannels,
37
+ } from './inspect/core'
38
+ import { callTestBridge } from './inspect/shared'
39
+
40
+ interface DebugOptions {
41
+ port?: number
42
+ verbose?: boolean
43
+ }
44
+
45
+ const KNOWN_CHANNELS = [
46
+ 'portals',
47
+ 'sheets',
48
+ 'layout',
49
+ 'onlayout',
50
+ 'animated',
51
+ 'render',
52
+ 'touch',
53
+ 'yoga',
54
+ 'all',
55
+ ] as const
56
+
57
+ const SHELL_TRACE_STORE_GLOBAL = '__sootsimShellAnimationTrace'
58
+ const SHELL_TRACE_HOOK_GLOBAL = '__sootsimDebugAnimation'
59
+
60
+ function printHelp() {
61
+ console.log(`
62
+ rnx debug — drive __sootsimDebug from the terminal
63
+
64
+ usage:
65
+ rnx debug <subcommand> [args]
66
+
67
+ record, recent, and clear-events target the tenant; --host selects the page buffer.
68
+ enable/disable/toggle also update the tenant unless --host is passed.
69
+ shell event buffers are separate; shell console logs are forwarded to the host.
70
+
71
+ subcommands:
72
+ enable <channels> turn on host, shell/compositor, and tenant channels
73
+ channels: ${KNOWN_CHANNELS.slice(0, -1).join(', ')}
74
+ or 'all' for every channel
75
+ disable <channels> turn off one or more debug channels (or 'all')
76
+ toggle <channel> flip a single channel
77
+ status list host channels forwarded to shell/compositor
78
+ channels list every known channel name
79
+ flags print the host DEBUG flag object
80
+
81
+ state <kind> ... dump raw runtime state (diagnostic — not a getter)
82
+ kinds: shell, worker, keyboard, node <id>,
83
+ scroll <id>, scroll-hit <x> <y>,
84
+ hit <x> <y>, gesture <x> <y>
85
+
86
+ js <code> execute javascript in the running app. rnx
87
+ is the canonical state object — reach into its
88
+ bridges / state / chrome / debug slots directly.
89
+ e.g. SootSim.bridges.test.findByText("Sign in")
90
+ eval <code> alias for js
91
+ perf <sub> shell frame profiling: start, stop, transition
92
+ memory [--watch [s]] per-worker object counts + heaps; --watch samples
93
+ every --interval seconds and reports slopes
94
+ sample-color <...> sample averaged color from canvas (same flags as
95
+ screenshot)
96
+
97
+ snapshot [label] capture a tree snapshot (layout + transform + opacity)
98
+ snapshots list taken snapshot labels
99
+ diff <a> <b> compare two snapshots by label
100
+ clear-snapshots [label] drop a snapshot (or all if no label given)
101
+
102
+ image-audit for every image the compositor actually drew, the
103
+ source pixels it landed on each device pixel. a row
104
+ whose shortfall is well under 1.0 is an image the
105
+ pipeline under-resolved; 0.5 is half resolution.
106
+ flags: --min <ratio> (default 0.98), --all, --json
107
+ exits 1 when any image is under the ratio, and 2 when
108
+ the audit observed no image draws at all
109
+
110
+ find sheets dump Sheet.Frame-shaped nodes currently in the tree
111
+ find portals dump portal-host / portal-view nodes
112
+ find boundaries dump live paint boundaries (+ why scroll rows were
113
+ rejected from the auto-retained raster tier)
114
+
115
+ trace shell [cmd] opt-in shell animation trace
116
+ cmds: on [limit], off, status, clear, [recentLimit]
117
+ trace anim <sub> opt-in per-tick animation value trace
118
+ subs: on <id|all> [limit], off [id|all],
119
+ status, clear, <id> [limit]
120
+ record [on|off] toggle event ring buffer recording
121
+ recent [channel] [n] last N events (default 50), optionally filtered
122
+ clear-events clear the event ring buffer
123
+
124
+ options:
125
+ --port <port> WS bridge port (default: ${DEFAULT_SOOTSIM_BRIDGE_PORT})
126
+ --sim <sim> target a specific connected sim
127
+ --pretty format JSON output for humans (default)
128
+ --json compact JSON output for pipelines
129
+
130
+ examples:
131
+ rnx debug enable sheets,portals
132
+ rnx debug snapshot before
133
+ rnx debug snapshot after
134
+ rnx debug diff before after
135
+ rnx debug image-audit per-image source pixels per device pixel
136
+ rnx debug find sheets | jq '.[] | select(.translateY != null)'
137
+ rnx debug trace shell on 240
138
+ rnx debug trace shell 120
139
+ rnx debug trace anim on 3 240
140
+ rnx debug trace anim 3 60
141
+ `)
142
+ }
143
+
144
+ function parseChannelList(raw: string | undefined): string[] {
145
+ if (!raw) return []
146
+ return raw
147
+ .split(',')
148
+ .map((s) => s.trim())
149
+ .filter(Boolean)
150
+ }
151
+
152
+ function fmt(value: unknown, pretty: boolean): string {
153
+ if (value === undefined) return ''
154
+ return JSON.stringify(value, null, pretty ? 2 : 0)
155
+ }
156
+
157
+ function buildShellTraceControlCode(
158
+ action: 'on' | 'off' | 'clear' | 'status',
159
+ limit = 240,
160
+ ): string {
161
+ const normalizedLimit =
162
+ Number.isFinite(limit) && limit > 0 ? Math.max(1, Math.round(limit)) : 240
163
+ const storeKey = JSON.stringify(SHELL_TRACE_STORE_GLOBAL)
164
+ const hookKey = JSON.stringify(SHELL_TRACE_HOOK_GLOBAL)
165
+ if (action === 'on') {
166
+ return `(() => {
167
+ const root = window
168
+ const store = root[${storeKey}] || (root[${storeKey}] = {
169
+ enabled: false,
170
+ limit: ${normalizedLimit},
171
+ seq: 0,
172
+ samples: [],
173
+ prevHook: undefined,
174
+ hook: undefined,
175
+ })
176
+ store.limit = ${normalizedLimit}
177
+ store.enabled = true
178
+ const currentHook = root[${hookKey}]
179
+ if (store.hook !== currentHook) {
180
+ store.prevHook = typeof currentHook === 'function' ? currentHook : undefined
181
+ }
182
+ if (typeof store.hook !== 'function') {
183
+ store.hook = (sample) => {
184
+ const target = root[${storeKey}]
185
+ if (!target || target.enabled !== true) return
186
+ const samples = Array.isArray(target.samples) ? target.samples : (target.samples = [])
187
+ target.seq = typeof target.seq === 'number' ? target.seq + 1 : 1
188
+ samples.push({ seq: target.seq, ...sample })
189
+ if (samples.length > target.limit) {
190
+ samples.splice(0, samples.length - target.limit)
191
+ }
192
+ if (typeof target.prevHook === 'function') {
193
+ try {
194
+ target.prevHook(sample)
195
+ } catch {}
196
+ }
197
+ }
198
+ }
199
+ root[${hookKey}] = store.hook
200
+ return {
201
+ enabled: true,
202
+ limit: store.limit,
203
+ count: Array.isArray(store.samples) ? store.samples.length : 0,
204
+ }
205
+ })()`
206
+ }
207
+ if (action === 'off') {
208
+ return `(() => {
209
+ const root = window
210
+ const store = root[${storeKey}]
211
+ if (!store) return { enabled: false, limit: 0, count: 0 }
212
+ store.enabled = false
213
+ if (root[${hookKey}] === store.hook) {
214
+ root[${hookKey}] = typeof store.prevHook === 'function' ? store.prevHook : undefined
215
+ }
216
+ return {
217
+ enabled: false,
218
+ limit: typeof store.limit === 'number' ? store.limit : 0,
219
+ count: Array.isArray(store.samples) ? store.samples.length : 0,
220
+ }
221
+ })()`
222
+ }
223
+ if (action === 'clear') {
224
+ return `(() => {
225
+ const store = window[${storeKey}]
226
+ if (!store) return { cleared: true, count: 0 }
227
+ store.samples = []
228
+ store.seq = 0
229
+ return {
230
+ cleared: true,
231
+ enabled: store.enabled === true,
232
+ limit: typeof store.limit === 'number' ? store.limit : 0,
233
+ count: 0,
234
+ }
235
+ })()`
236
+ }
237
+ return `(() => {
238
+ const store = window[${storeKey}]
239
+ return {
240
+ enabled: !!store?.enabled,
241
+ limit: typeof store?.limit === 'number' ? store.limit : 0,
242
+ count: Array.isArray(store?.samples) ? store.samples.length : 0,
243
+ }
244
+ })()`
245
+ }
246
+
247
+ function buildShellTraceRecentCode(limit = 50): string {
248
+ const normalizedLimit =
249
+ Number.isFinite(limit) && limit > 0 ? Math.max(1, Math.round(limit)) : 50
250
+ const storeKey = JSON.stringify(SHELL_TRACE_STORE_GLOBAL)
251
+ return `(() => {
252
+ const store = window[${storeKey}]
253
+ const all = Array.isArray(store?.samples) ? store.samples : []
254
+ const samples = all.slice(-${normalizedLimit})
255
+ const first = samples[0] ?? null
256
+ const last = samples[samples.length - 1] ?? null
257
+ const uniquePhases = [...new Set(samples.map((sample) => sample?.phase).filter(Boolean))]
258
+ const metricRange = (key) => {
259
+ const values = samples
260
+ .map((sample) => (typeof sample?.[key] === 'number' ? sample[key] : null))
261
+ .filter((value) => value != null)
262
+ if (values.length === 0) return null
263
+ return {
264
+ min: Math.min(...values),
265
+ max: Math.max(...values),
266
+ }
267
+ }
268
+ return {
269
+ enabled: !!store?.enabled,
270
+ limit: typeof store?.limit === 'number' ? store.limit : 0,
271
+ count: samples.length,
272
+ total: all.length,
273
+ summary: first && last ? {
274
+ durationMs: last.ts - first.ts,
275
+ phases: uniquePhases,
276
+ start: first,
277
+ end: last,
278
+ zoomLevel: metricRange('zoomLevel'),
279
+ horizontalZoom: metricRange('horizontalZoom'),
280
+ launchProgress: metricRange('launchProgress'),
281
+ dismissOffsetY: metricRange('dismissOffsetY'),
282
+ } : null,
283
+ samples,
284
+ }
285
+ })()`
286
+ }
287
+
288
+ async function call<T = unknown>(bridge: WsBridge, code: string): Promise<T> {
289
+ return evalInBridge<T>(bridge, code)
290
+ }
291
+
292
+ interface ImageAuditRowView {
293
+ nodeId: number
294
+ type: string
295
+ testID: string | null
296
+ sourceUri: string | null
297
+ resizeMode: string | null
298
+ sampling: string | null
299
+ vector: boolean
300
+ intrinsic: { width: number; height: number } | null
301
+ decoded: { width: number; height: number }
302
+ sampled: { width: number; height: number }
303
+ destination: { x: number; y: number; width: number; height: number }
304
+ decision: { gpuBacked: boolean; hasMipmaps: boolean }
305
+ deviceScale: number
306
+ dpr: number
307
+ needed: { width: number; height: number }
308
+ effective: { width: number; height: number }
309
+ ceiling: { width: number; height: number } | null
310
+ shortfall: number | null
311
+ }
312
+
313
+ interface ImageAuditReportView {
314
+ draws: number
315
+ samplingCounts: { mipmapped: number; cubic: number; repeat: number; none: number }
316
+ rows: ImageAuditRowView[]
317
+ }
318
+
319
+ function readNumberFlag(args: string[], flag: string): number | null {
320
+ const index = args.indexOf(flag)
321
+ if (index < 0) return null
322
+ const parsed = Number(args[index + 1])
323
+ return Number.isFinite(parsed) ? parsed : null
324
+ }
325
+
326
+ // enable the audit, force one real frame, then read and stop. the screenshot
327
+ // request is what forces the frame: it runs the same publish-and-paint pass
328
+ // `rnx screenshot` does, so the rows describe pixels that were actually
329
+ // produced rather than a tree walk's guess about them.
330
+ async function runImageAudit(bridge: WsBridge): Promise<ImageAuditReportView> {
331
+ await call(
332
+ bridge,
333
+ "window.__sootsimCompositor.getStats(false, false, undefined, 'enable').then(() => 'enabled')",
334
+ )
335
+ await bridge.send({ type: 'screenshot' })
336
+ const stats = await call<{ imageAudit?: ImageAuditReportView | null }>(
337
+ bridge,
338
+ "window.__sootsimCompositor.getStats(false, false, undefined, 'disable').then((s) => ({ imageAudit: s.imageAudit }))",
339
+ )
340
+ const report = stats?.imageAudit
341
+ if (!report) throw new Error('compositor returned no image audit')
342
+ return report
343
+ }
344
+
345
+ function ratioText(value: number | null): string {
346
+ return value === null ? ' - ' : value.toFixed(3).padStart(6)
347
+ }
348
+
349
+ function sourceText(row: ImageAuditRowView): string {
350
+ const uri = row.sourceUri ?? row.testID ?? `node ${row.nodeId}`
351
+ if (uri.length <= 58) return uri
352
+ return `...${uri.slice(-55)}`
353
+ }
354
+
355
+ function printImageAudit(
356
+ report: ImageAuditReportView,
357
+ rows: ImageAuditRowView[],
358
+ minimum: number,
359
+ ): void {
360
+ const withCeiling = report.rows.filter((row) => row.shortfall !== null)
361
+ const under = withCeiling.filter((row) => row.shortfall! < minimum)
362
+ console.log(
363
+ ` ${report.rows.length} images drawn (${report.draws} draws), ` +
364
+ `${under.length} below ${minimum} of what their source could deliver`,
365
+ )
366
+ console.log(
367
+ ` sampling: mipmapped ${report.samplingCounts.mipmapped} · cubic ${report.samplingCounts.cubic} · ` +
368
+ `repeat ${report.samplingCounts.repeat} · none ${report.samplingCounts.none}`,
369
+ )
370
+ if (rows.length === 0) {
371
+ console.log(' every image delivered its source resolution')
372
+ return
373
+ }
374
+ console.log('')
375
+ console.log(' short eff decoded source needed(dev) dest(pt) dpr image')
376
+ for (const row of rows) {
377
+ const decoded = `${row.decoded.width}x${row.decoded.height}`.padEnd(11)
378
+ const intrinsic = (
379
+ row.vector
380
+ ? 'vector'
381
+ : row.intrinsic
382
+ ? `${row.intrinsic.width}x${row.intrinsic.height}`
383
+ : '?'
384
+ ).padEnd(11)
385
+ const needed =
386
+ `${Math.round(row.needed.width)}x${Math.round(row.needed.height)}`.padEnd(12)
387
+ const dest =
388
+ `${Math.round(row.destination.width)}x${Math.round(row.destination.height)}`.padEnd(
389
+ 9,
390
+ )
391
+ console.log(
392
+ ` ${ratioText(row.shortfall)} ${ratioText(row.effective.width)} ` +
393
+ `${decoded} ${intrinsic} ${needed} ${dest} ${row.dpr.toFixed(1)} ${sourceText(row)}`,
394
+ )
395
+ }
396
+ }
397
+
398
+ export async function runDebug(args: string[], opts: DebugOptions) {
399
+ const parsed = parseBridgeCliArgs(args, {
400
+ port: opts.port,
401
+ stripBooleanFlags: ['--pretty', '--json', '--help', '-h', '--host'],
402
+ })
403
+ const positional = parsed.positional
404
+ const subcommand = positional[0]
405
+
406
+ if (!subcommand || args.includes('--help') || args.includes('-h')) {
407
+ printHelp()
408
+ rnxExit(0)
409
+ }
410
+
411
+ const wsPort = parsed.wsPort
412
+ const simId = parsed.simId
413
+ const pretty = !args.includes('--json')
414
+ const simHint = simId ? ` --sim ${simId}` : ''
415
+
416
+ const sub = positional[0]
417
+ const rest = positional.slice(1)
418
+
419
+ // these subcommands are organized under `debug` for the user but their
420
+ // implementation lives in inspect.ts. forward to runInspect with the raw
421
+ // `debug` prefix intact — runInspect strips it. done before we open a
422
+ // debug-side bridge so we don't hold two bridges at once.
423
+ const INSPECT_FORWARDED = new Set(['state', 'js', 'perf', 'memory', 'sample-color'])
424
+ if (INSPECT_FORWARDED.has(sub)) {
425
+ const { runInspect } = await import('./inspect')
426
+ // pass the raw arg list through so every flag (--sim, --json,
427
+ // --verbose, per-subcommand flags, etc.) reaches the inspect parser
428
+ // untouched. the leading 'debug' is recognized as a verb prefix by
429
+ // runInspect and stripped.
430
+ await runInspect(['debug', ...args], {
431
+ port: opts.port,
432
+ verbose: opts.verbose,
433
+ })
434
+ return
435
+ }
436
+
437
+ const bridge = createBridgeFromParsed(parsed)
438
+
439
+ try {
440
+ switch (sub) {
441
+ case 'enable': {
442
+ const channels = parseChannelList(rest[0])
443
+ if (channels.length === 0) {
444
+ console.error(
445
+ ` usage: rnx debug enable <channel[,channel,...]>\n known: ${KNOWN_CHANNELS.join(', ')}`,
446
+ )
447
+ rnxExit(1)
448
+ }
449
+ const active = await setDebugChannels(
450
+ bridge,
451
+ 'enable',
452
+ channels,
453
+ args.includes('--host'),
454
+ )
455
+ console.log(fmt({ active }, pretty))
456
+ break
457
+ }
458
+
459
+ case 'disable': {
460
+ const channels = parseChannelList(rest[0])
461
+ const active = await setDebugChannels(
462
+ bridge,
463
+ 'disable',
464
+ channels,
465
+ args.includes('--host'),
466
+ )
467
+ console.log(fmt({ active }, pretty))
468
+ break
469
+ }
470
+
471
+ case 'toggle': {
472
+ const channel = rest[0]
473
+ if (!channel) {
474
+ console.error(' usage: rnx debug toggle <channel>')
475
+ rnxExit(1)
476
+ }
477
+ const result = await callDebugBridge(
478
+ bridge,
479
+ `toggle(${JSON.stringify(channel)})`,
480
+ true,
481
+ )
482
+ if (!args.includes('--host')) {
483
+ await callDebugBridge(
484
+ bridge,
485
+ `${result ? 'enable' : 'disable'}(${JSON.stringify(channel)})`,
486
+ )
487
+ }
488
+ console.log(fmt({ [channel]: result }, pretty))
489
+ break
490
+ }
491
+
492
+ case 'status': {
493
+ const result = await inspectDebugStatus(bridge)
494
+ console.log(fmt(result, pretty))
495
+ break
496
+ }
497
+
498
+ case 'channels': {
499
+ const result = await call(bridge, 'window.__sootsimDebug.channels()')
500
+ console.log(fmt(result, pretty))
501
+ break
502
+ }
503
+
504
+ case 'flags': {
505
+ const result = await inspectDebugFlags(bridge)
506
+ console.log(fmt(result, pretty))
507
+ break
508
+ }
509
+
510
+ case 'snapshot': {
511
+ const label = rest[0]
512
+ const code = label
513
+ ? `window.__sootsimDebug.snapshot(${JSON.stringify(label)})`
514
+ : 'window.__sootsimDebug.snapshot()'
515
+ // the tree snapshot contains a Map, which doesn't serialize cleanly.
516
+ // project to the same shape the diff command will accept: label +
517
+ // size. to inspect contents, use `find sheets` / `find portals` or
518
+ // take a diff.
519
+ const result = await call<{
520
+ label: string
521
+ at: number
522
+ nodes: unknown
523
+ }>(
524
+ bridge,
525
+ `(() => { const s = ${code}; if (!s) return null; return { label: s.label, at: s.at, size: s.nodes.size }; })()`,
526
+ )
527
+ console.log(fmt(result, pretty))
528
+ break
529
+ }
530
+
531
+ case 'snapshots': {
532
+ const result = await call(bridge, 'window.__sootsimDebug.snapshots()')
533
+ console.log(fmt(result, pretty))
534
+ break
535
+ }
536
+
537
+ case 'diff': {
538
+ const a = rest[0]
539
+ const b = rest[1]
540
+ if (!a || !b) {
541
+ console.error(' usage: rnx debug diff <labelA> <labelB>')
542
+ rnxExit(1)
543
+ }
544
+ // Maps don't serialize — project added/removed/changed arrays only
545
+ const code = `(() => {
546
+ const d = window.__sootsimDebug.diff(${JSON.stringify(a)}, ${JSON.stringify(b)});
547
+ if (!d) return null;
548
+ return {
549
+ a: d.a,
550
+ b: d.b,
551
+ counts: { added: d.added.length, removed: d.removed.length, changed: d.changed.length },
552
+ added: d.added,
553
+ removed: d.removed,
554
+ changed: d.changed,
555
+ };
556
+ })()`
557
+ const result = await call(bridge, code)
558
+ console.log(fmt(result, pretty))
559
+ break
560
+ }
561
+
562
+ case 'clear-snapshots': {
563
+ const label = rest[0]
564
+ const code = label
565
+ ? `window.__sootsimDebug.clearSnapshots(${JSON.stringify(label)})`
566
+ : 'window.__sootsimDebug.clearSnapshots()'
567
+ await call(bridge, code)
568
+ console.log(fmt({ cleared: label || 'all' }, pretty))
569
+ break
570
+ }
571
+
572
+ case 'image-audit': {
573
+ const minimum = readNumberFlag(args, '--min') ?? 0.98
574
+ const report = await runImageAudit(bridge)
575
+ if (report.draws === 0) {
576
+ console.error(
577
+ ' the audit recorded zero image draws, so an empty result proves nothing.\n' +
578
+ ' while it records, paint boundaries rebuild and raster blits are\n' +
579
+ ' bypassed, so every image on screen should reach the draw path.\n' +
580
+ ' zero draws means either nothing on screen is an image, or the audit\n' +
581
+ ' is not attached to the surface that paints.',
582
+ )
583
+ rnxExit(2)
584
+ }
585
+ const under = report.rows.filter(
586
+ (row) => row.shortfall !== null && row.shortfall < minimum,
587
+ )
588
+ if (!pretty) {
589
+ console.log(fmt({ ...report, minimum, under: under.length }, false))
590
+ } else {
591
+ printImageAudit(report, args.includes('--all') ? report.rows : under, minimum)
592
+ }
593
+ if (under.length > 0) rnxExit(1)
594
+ break
595
+ }
596
+
597
+ case 'find': {
598
+ const target = rest[0]
599
+ if (target === 'sheets' || target === 'portals' || target === 'boundaries') {
600
+ const result = await inspectDebugFind(bridge, target)
601
+ console.log(fmt(result, pretty))
602
+ } else {
603
+ console.error(' usage: rnx debug find <sheets|portals|boundaries>')
604
+ rnxExit(1)
605
+ }
606
+ break
607
+ }
608
+
609
+ case 'trace': {
610
+ const traceTarget = rest[0]
611
+ const traceCommand = rest[1]
612
+ if (traceTarget === 'anim') {
613
+ // `debug trace anim on <id|all> [limit]`
614
+ // `debug trace anim off [id|all]`
615
+ // `debug trace anim <id> [limit]` → read samples
616
+ // `debug trace anim status` | `clear`
617
+ const sub = rest[1]
618
+ if (!sub || sub === '--help' || sub === '-h') {
619
+ console.error(
620
+ ' usage: rnx debug trace anim <on|off|status|clear|<id>> [id|limit]',
621
+ )
622
+ rnxExit(1)
623
+ }
624
+ if (sub === 'on') {
625
+ const raw = rest[2] ?? 'all'
626
+ const limit = rest[3] ? Number(rest[3]) : undefined
627
+ const target: number | 'all' = raw === 'all' ? 'all' : Number(raw)
628
+ if (target !== 'all' && !Number.isFinite(target)) {
629
+ console.error(` invalid target: ${raw}`)
630
+ rnxExit(1)
631
+ }
632
+ await callTestBridge(bridge, 'enableAnimationTrace', target, limit)
633
+ const listed = await callTestBridge(bridge, 'listAnimationTraces')
634
+ console.log(fmt({ enabled: target, traces: listed }, pretty))
635
+ break
636
+ }
637
+ if (sub === 'off') {
638
+ const raw = rest[2] ?? 'all'
639
+ const target: number | 'all' = raw === 'all' ? 'all' : Number(raw)
640
+ if (target !== 'all' && !Number.isFinite(target)) {
641
+ console.error(` invalid target: ${raw}`)
642
+ rnxExit(1)
643
+ }
644
+ await callTestBridge(bridge, 'disableAnimationTrace', target)
645
+ console.log(fmt({ disabled: target }, pretty))
646
+ break
647
+ }
648
+ if (sub === 'status' || sub === 'clear') {
649
+ if (sub === 'clear') {
650
+ await callTestBridge(bridge, 'disableAnimationTrace', 'all')
651
+ }
652
+ const listed = await callTestBridge(bridge, 'listAnimationTraces')
653
+ console.log(fmt({ traces: listed }, pretty))
654
+ break
655
+ }
656
+ const id = Number(sub)
657
+ if (!Number.isFinite(id)) {
658
+ console.error(` invalid id: ${sub}`)
659
+ rnxExit(1)
660
+ }
661
+ const limit = rest[2] ? Number(rest[2]) : undefined
662
+ const samples = await callTestBridge(bridge, 'getAnimationTrace', id, limit)
663
+ console.log(fmt({ id, samples }, pretty))
664
+ break
665
+ }
666
+ if (traceTarget !== 'shell') {
667
+ console.error(' usage: rnx debug trace <shell|anim> [args]')
668
+ rnxExit(1)
669
+ }
670
+ if (!traceCommand || /^[0-9]+$/.test(traceCommand)) {
671
+ const recentLimit = traceCommand ? Number(traceCommand) : 50
672
+ const result = await call(bridge, buildShellTraceRecentCode(recentLimit))
673
+ console.log(fmt(result, pretty))
674
+ break
675
+ }
676
+ if (traceCommand === 'on') {
677
+ const limit = rest[2] ? Number(rest[2]) : 240
678
+ const result = await call(bridge, buildShellTraceControlCode('on', limit))
679
+ console.log(fmt(result, pretty))
680
+ break
681
+ }
682
+ if (traceCommand === 'off') {
683
+ const result = await call(bridge, buildShellTraceControlCode('off'))
684
+ console.log(fmt(result, pretty))
685
+ break
686
+ }
687
+ if (traceCommand === 'clear') {
688
+ const result = await call(bridge, buildShellTraceControlCode('clear'))
689
+ console.log(fmt(result, pretty))
690
+ break
691
+ }
692
+ if (traceCommand === 'status') {
693
+ const result = await call(bridge, buildShellTraceControlCode('status'))
694
+ console.log(fmt(result, pretty))
695
+ break
696
+ }
697
+ console.error(
698
+ ' usage: rnx debug trace shell [on [limit]|off|status|clear|recentLimit]',
699
+ )
700
+ rnxExit(1)
701
+ }
702
+
703
+ case 'record': {
704
+ const target = rest[0]
705
+ const on = target === 'on' ? 'true' : target === 'off' ? 'false' : 'undefined'
706
+ const result = await callDebugBridge(
707
+ bridge,
708
+ `record(${on})`,
709
+ args.includes('--host'),
710
+ )
711
+ console.log(fmt({ recording: result }, pretty))
712
+ break
713
+ }
714
+
715
+ case 'recent': {
716
+ const channel = rest[0]
717
+ const limit = rest[1] ? Number(rest[1]) : 50
718
+ const result = await inspectDebugRecent(
719
+ bridge,
720
+ channel,
721
+ limit,
722
+ args.includes('--host'),
723
+ )
724
+ console.log(fmt(result, pretty))
725
+ break
726
+ }
727
+
728
+ case 'clear-events': {
729
+ await callDebugBridge(bridge, 'clearEvents()', args.includes('--host'))
730
+ console.log(fmt({ cleared: true }, pretty))
731
+ break
732
+ }
733
+
734
+ default:
735
+ console.error(` unknown subcommand: ${sub}`)
736
+ printHelp()
737
+ rnxExit(1)
738
+ }
739
+ } catch (err: any) {
740
+ rethrowIfExit(err)
741
+ console.error(` debug failed: ${err.message}`)
742
+ await printBridgeFailureDiagnostics(bridge, {
743
+ errorsCommand: `rnx get errors 5${simHint}`,
744
+ warningsCommand: `rnx get warnings 5${simHint}`,
745
+ requestsCommand: `rnx get requests 5${simHint}`,
746
+ })
747
+ rnxExit(1)
748
+ } finally {
749
+ bridge.close()
750
+ }
751
+ }