rnxsim 0.0.0 → 0.1.313

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 (228) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +512 -0
  3. package/detox/colors.ts +54 -0
  4. package/detox/config-loader.ts +135 -0
  5. package/detox/element-types.ts +39 -0
  6. package/detox/expectations.ts +537 -0
  7. package/detox/gestures.ts +480 -0
  8. package/detox/index.ts +2118 -0
  9. package/detox/jest-preset.cjs +51 -0
  10. package/detox/jest-setup-after-env.cjs +18 -0
  11. package/detox/matchers.ts +30 -0
  12. package/detox/motion-change.cjs +109 -0
  13. package/detox/motion-change.d.cts +35 -0
  14. package/detox/navigation.ts +43 -0
  15. package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
  16. package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
  17. package/detox/screenshots/color-test-rest-norngh.png +0 -0
  18. package/detox/screenshots/theme-blue-toggle.png +0 -0
  19. package/detox/screenshots/theme-blue.png +0 -0
  20. package/detox/screenshots/theme-red-toggle.png +0 -0
  21. package/detox/screenshots/theme-red.png +0 -0
  22. package/dist-cli/bin.js +15 -0
  23. package/dist-cli/chunks/agent-XZ2KTPCU.js +48 -0
  24. package/dist-cli/chunks/agent-wrapper-JJYYW2WH.js +17 -0
  25. package/dist-cli/chunks/app-fonts-IXRNQG6B.js +4 -0
  26. package/dist-cli/chunks/assert-54T5SK5F.js +49 -0
  27. package/dist-cli/chunks/auth-FI5UDI45.js +12 -0
  28. package/dist-cli/chunks/beta-JV6UKADW.js +4 -0
  29. package/dist-cli/chunks/camera-VL73YIKP.js +22 -0
  30. package/dist-cli/chunks/chunk-2D2UPBBR.js +4 -0
  31. package/dist-cli/chunks/chunk-2YR5BGA5.js +5 -0
  32. package/dist-cli/chunks/chunk-3NV2NCNX.js +4 -0
  33. package/dist-cli/chunks/chunk-46EUUFJ5.js +4 -0
  34. package/dist-cli/chunks/chunk-4NPPOV2N.js +5 -0
  35. package/dist-cli/chunks/chunk-5DHC6KHQ.js +7 -0
  36. package/dist-cli/chunks/chunk-5TEF3ET3.js +74 -0
  37. package/dist-cli/chunks/chunk-5TPRP5QT.js +4 -0
  38. package/dist-cli/chunks/chunk-5YJCOWCH.js +4 -0
  39. package/dist-cli/chunks/chunk-7GN3LVWB.js +4 -0
  40. package/dist-cli/chunks/chunk-BBULZ7CG.js +568 -0
  41. package/dist-cli/chunks/chunk-BTWORNNG.js +71 -0
  42. package/dist-cli/chunks/chunk-D4FFVGI5.js +5 -0
  43. package/dist-cli/chunks/chunk-DCEMHR2Y.js +4 -0
  44. package/dist-cli/chunks/chunk-DZS6WPUI.js +4 -0
  45. package/dist-cli/chunks/chunk-F5ZRSS3C.js +3 -0
  46. package/dist-cli/chunks/chunk-FSUYIVJ6.js +9 -0
  47. package/dist-cli/chunks/chunk-G2WW6L2C.js +23 -0
  48. package/dist-cli/chunks/chunk-GASE6UBA.js +4 -0
  49. package/dist-cli/chunks/chunk-GGRX24GF.js +142 -0
  50. package/dist-cli/chunks/chunk-HI5TFJWN.js +5 -0
  51. package/dist-cli/chunks/chunk-IJ5CAZZC.js +5 -0
  52. package/dist-cli/chunks/chunk-IJO63TDP.js +4 -0
  53. package/dist-cli/chunks/chunk-KTHV3RUS.js +26 -0
  54. package/dist-cli/chunks/chunk-LF2ZVT7O.js +6 -0
  55. package/dist-cli/chunks/chunk-MJRLLB4R.js +38 -0
  56. package/dist-cli/chunks/chunk-NFK7T35W.js +4 -0
  57. package/dist-cli/chunks/chunk-NMF2ZMZQ.js +15 -0
  58. package/dist-cli/chunks/chunk-OHAZNXLK.js +4 -0
  59. package/dist-cli/chunks/chunk-OVFJFXUD.js +101 -0
  60. package/dist-cli/chunks/chunk-OZSSI4WN.js +28 -0
  61. package/dist-cli/chunks/chunk-QKDWYITG.js +25 -0
  62. package/dist-cli/chunks/chunk-RSZWCKNT.js +30 -0
  63. package/dist-cli/chunks/chunk-RTN5C5RL.js +4 -0
  64. package/dist-cli/chunks/chunk-TIVZIMMW.js +4 -0
  65. package/dist-cli/chunks/chunk-TZFFR3SD.js +7 -0
  66. package/dist-cli/chunks/chunk-UC6U3MML.js +5 -0
  67. package/dist-cli/chunks/chunk-VFCMSYZK.js +6 -0
  68. package/dist-cli/chunks/chunk-VNQEB4L7.js +5 -0
  69. package/dist-cli/chunks/chunk-VZXWHRUZ.js +983 -0
  70. package/dist-cli/chunks/chunk-W6K4EFPH.js +107 -0
  71. package/dist-cli/chunks/chunk-WEXDAC74.js +4 -0
  72. package/dist-cli/chunks/chunk-WF3T4SVI.js +14 -0
  73. package/dist-cli/chunks/chunk-WINYQ44O.js +4 -0
  74. package/dist-cli/chunks/chunk-WMIIKMGK.js +10 -0
  75. package/dist-cli/chunks/chunk-WUSWBCWA.js +507 -0
  76. package/dist-cli/chunks/chunk-WWZIXIRD.js +4 -0
  77. package/dist-cli/chunks/chunk-XEVZYVIW.js +766 -0
  78. package/dist-cli/chunks/chunk-YDGQTMQL.js +4 -0
  79. package/dist-cli/chunks/chunk-YIFT42WN.js +4 -0
  80. package/dist-cli/chunks/chunk-ZMJD5GEC.js +125 -0
  81. package/dist-cli/chunks/cleanup-P27PA6JI.js +25 -0
  82. package/dist-cli/chunks/cli-version-WWLPBDQ7.js +4 -0
  83. package/dist-cli/chunks/compat-ZD65FED3.js +20 -0
  84. package/dist-cli/chunks/config-XMJRNM2A.js +13 -0
  85. package/dist-cli/chunks/control-KMIQT3QP.js +4 -0
  86. package/dist-cli/chunks/daemon-G2ME7NLB.js +4 -0
  87. package/dist-cli/chunks/debug-PT4HOP7N.js +200 -0
  88. package/dist-cli/chunks/desktop-S3FG72AK.js +31 -0
  89. package/dist-cli/chunks/detox-B3D4IFCN.js +52 -0
  90. package/dist-cli/chunks/device-XBNDSB2R.js +18 -0
  91. package/dist-cli/chunks/diagnose-HMQXJE5N.js +43 -0
  92. package/dist-cli/chunks/disk-cleanup-BLCZ5BSZ.js +4 -0
  93. package/dist-cli/chunks/drivers-LDECZGP2.js +4 -0
  94. package/dist-cli/chunks/film-BJGTBYZB.js +74 -0
  95. package/dist-cli/chunks/flow-UEQNVTU7.js +4 -0
  96. package/dist-cli/chunks/help-T5FYSVGB.js +4 -0
  97. package/dist-cli/chunks/hidden-runtime-alias-ANOYADHM.js +4 -0
  98. package/dist-cli/chunks/home-paths-GT3LFNOR.js +4 -0
  99. package/dist-cli/chunks/inspect-ZA6XF5LD.js +4 -0
  100. package/dist-cli/chunks/install-desktop-TIMUDHPL.js +4 -0
  101. package/dist-cli/chunks/login-FJ737MWG.js +28 -0
  102. package/dist-cli/chunks/logout-ZCNMMHMY.js +4 -0
  103. package/dist-cli/chunks/maestro-SZTNKLDF.js +104 -0
  104. package/dist-cli/chunks/maestro-generate-DCFAIZ4H.js +36 -0
  105. package/dist-cli/chunks/mode-GRMQCRXR.js +26 -0
  106. package/dist-cli/chunks/optional-demo-registry-W36EWFFB.js +4 -0
  107. package/dist-cli/chunks/perf-QYBAAUZG.js +48 -0
  108. package/dist-cli/chunks/permissions-3QCQ6VF4.js +13 -0
  109. package/dist-cli/chunks/record-QPPC2S4E.js +72 -0
  110. package/dist-cli/chunks/report-issue-7NMFP4HK.js +29 -0
  111. package/dist-cli/chunks/runtime-XOAXMSTU.js +4 -0
  112. package/dist-cli/chunks/screenshot-command-67AECJFB.js +159 -0
  113. package/dist-cli/chunks/screenshot-layers-ASWBYPJL.js +20 -0
  114. package/dist-cli/chunks/screenshots-capture-PXA3HFQK.js +42 -0
  115. package/dist-cli/chunks/serve-BI2NBAXG.js +44 -0
  116. package/dist-cli/chunks/setup-7DWPMRSB.js +32 -0
  117. package/dist-cli/chunks/skills-S3Y22TUA.js +50 -0
  118. package/dist-cli/chunks/storage-XUIMJWAJ.js +37 -0
  119. package/dist-cli/chunks/store-JTHEJLAZ.js +4 -0
  120. package/dist-cli/chunks/telemetry-ZYJGD2DB.js +4 -0
  121. package/dist-cli/chunks/timeline-TMPLQPSP.js +24 -0
  122. package/dist-cli/chunks/upgrade-7HDSIM7K.js +7 -0
  123. package/dist-cli/chunks/upload-GMSZPWM6.js +4 -0
  124. package/dist-cli/chunks/version-HOCHZ37L.js +6 -0
  125. package/dist-cli/chunks/web-DG3WBYD3.js +4 -0
  126. package/dist-cli/chunks/what-happened-XFVUTZR7.js +25 -0
  127. package/dist-lib/agent-daemon-client.cjs +468 -0
  128. package/dist-lib/agent-events.cjs +48 -0
  129. package/dist-lib/agent-identity.cjs +53 -0
  130. package/dist-lib/agent-sessions.cjs +725 -0
  131. package/dist-lib/attached-projects.cjs +526 -0
  132. package/dist-lib/auth/shared-session.cjs +195 -0
  133. package/dist-lib/backend-origin.cjs +70 -0
  134. package/dist-lib/beta.cjs +41 -0
  135. package/dist-lib/beta.mjs +13 -0
  136. package/dist-lib/bridge-constants.cjs +161 -0
  137. package/dist-lib/cli-constants.cjs +32 -0
  138. package/dist-lib/config.cjs +96 -0
  139. package/dist-lib/detox/index.cjs +2863 -0
  140. package/dist-lib/detox/jest-preset.cjs +51 -0
  141. package/dist-lib/dev-bundle-resolution.cjs +223 -0
  142. package/dist-lib/home-paths.cjs +421 -0
  143. package/dist-lib/host/bridge-host.cjs +5600 -0
  144. package/dist-lib/host/fetch-proxy-handler.cjs +373 -0
  145. package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
  146. package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
  147. package/dist-lib/host/websocket-proxy.cjs +214 -0
  148. package/dist-lib/index.cjs +513 -0
  149. package/dist-lib/jump-to-source-babel.cjs +93 -0
  150. package/dist-lib/menu.cjs +620 -0
  151. package/dist-lib/menu.mjs +581 -0
  152. package/dist-lib/metro.cjs +297 -0
  153. package/dist-lib/profiles.cjs +246 -0
  154. package/dist-lib/public-brand.cjs +39 -0
  155. package/dist-lib/render-mode.cjs +54 -0
  156. package/dist-lib/scripts/dev-server-scanner.cjs +1034 -0
  157. package/dist-lib/sdk.cjs +2333 -0
  158. package/dist-lib/sdk.mjs +2240 -0
  159. package/dist-lib/skills.cjs +23922 -0
  160. package/dist-lib/vite.cjs +358 -0
  161. package/package.json +189 -18
  162. package/scripts/dev-server-scanner.ts +941 -0
  163. package/scripts/optional-demo-registry.ts +113 -0
  164. package/scripts/postinstall.cjs +3 -0
  165. package/scripts/preuninstall.cjs +118 -0
  166. package/skills/contrast/SKILL.md +171 -0
  167. package/skills/rnx-debug/SKILL.md +425 -0
  168. package/skills/rnx-perf/SKILL.md +295 -0
  169. package/skills/rnx-setup/SKILL.md +160 -0
  170. package/skills/rnx-test/SKILL.md +292 -0
  171. package/skills/rnx-visual/SKILL.md +122 -0
  172. package/src/agent-daemon-client.ts +410 -0
  173. package/src/agent-events.ts +71 -0
  174. package/src/agent-identity.ts +23 -0
  175. package/src/agent-prompt.ts +71 -0
  176. package/src/agent-sessions.ts +573 -0
  177. package/src/attached-projects.ts +539 -0
  178. package/src/auth/shared-session.ts +204 -0
  179. package/src/backend-origin.ts +49 -0
  180. package/src/beta.ts +19 -0
  181. package/src/bridge-constants.ts +171 -0
  182. package/src/browser-cache.ts +16 -0
  183. package/src/cli-constants.ts +1 -0
  184. package/src/cli-version.ts +32 -0
  185. package/src/codex-client.ts +215 -0
  186. package/src/config.ts +134 -0
  187. package/src/dev-bundle-resolution.ts +236 -0
  188. package/src/disk-cleanup.ts +466 -0
  189. package/src/home-paths.ts +552 -0
  190. package/src/host/agent-host.ts +666 -0
  191. package/src/host/bridge-host.ts +2626 -0
  192. package/src/host/fetch-proxy-handler.ts +397 -0
  193. package/src/host/fetch-proxy-overrides.ts +51 -0
  194. package/src/host/open-url.ts +89 -0
  195. package/src/host/websocket-proxy.ts +210 -0
  196. package/src/index.ts +12 -705
  197. package/src/jump-to-source-babel.ts +135 -0
  198. package/src/menu.ts +734 -0
  199. package/src/metro-plugin.ts +139 -0
  200. package/src/native-dev-bundle-url.ts +96 -0
  201. package/src/native-seam-manifest.ts +341 -0
  202. package/src/plist.ts +27 -0
  203. package/src/poll-until.ts +92 -0
  204. package/src/profiles.ts +179 -0
  205. package/src/public-brand.ts +9 -0
  206. package/src/render-mode.ts +27 -0
  207. package/src/runtime-assets.ts +228 -0
  208. package/src/runtime-delivery.ts +54 -0
  209. package/src/screenshots/compose.ts +422 -0
  210. package/src/screenshots/frame-compose.ts +454 -0
  211. package/src/screenshots/orchestrate.ts +256 -0
  212. package/src/screenshots/org-deck.ts +91 -0
  213. package/src/screenshots/registry.ts +58 -0
  214. package/src/screenshots/schema.ts +364 -0
  215. package/src/sdk.ts +7 -0
  216. package/src/settings-bundle.ts +418 -0
  217. package/src/skills/builtin/a11y-review.ts +132 -0
  218. package/src/skills/builtin/compat-check.ts +71 -0
  219. package/src/skills/builtin/maestro-test.ts +121 -0
  220. package/src/skills/builtin/perf-profile.ts +90 -0
  221. package/src/skills/builtin/screenshot-all.ts +52 -0
  222. package/src/skills/builtin/visual-diff.ts +100 -0
  223. package/src/skills/registry.ts +107 -0
  224. package/src/skills/types.ts +41 -0
  225. package/src/team-machine-prompt.ts +73 -0
  226. package/src/vite-plugin-one.ts +190 -0
  227. package/src/vite-plugin.ts +1750 -0
  228. package/src/worklets-babel.ts +134 -0
@@ -0,0 +1,295 @@
1
+ ---
2
+ name: rnx-perf
3
+ description: Optimize rnx engine and app performance — scroll jank, dropped frames, slow animation, idle CPU, memory growth, and "smooth normally but laggy under load" (video calls, busy machines). Covers measuring on the right thread with the right browser, proving each perf tier actually engages via its counters, a CPU-contention test harness, and the map of existing perf machinery so you improve it instead of reinventing it. For general app debugging load /rnx-debug first.
4
+ ---
5
+
6
+ # rnx performance
7
+
8
+ optimize performance in rnx: the shell frame pipeline, the compositor
9
+ tiers, and the guest app. this skill exists because every real perf win in
10
+ this engine followed the same loop — measure on the right thread, read the
11
+ counters that prove machinery engaged, fix the one thing the counters
12
+ indict, remeasure — and every wasted week came from skipping a step.
13
+
14
+ > needs a connected, pinned sim (`/rnx-setup`). general "what's wrong"
15
+ > triage lives in `/rnx-debug`; this skill goes deeper on perf only.
16
+
17
+ ## the four laws (each paid for by a real incident)
18
+
19
+ 1. **measure the worker that painted it, in the right browser.** the tenant
20
+ hasn't painted since the two-worker split, so never read a tenant sampler
21
+ (the one reporting `avg 0.48ms (2069 fps)` for a visibly janky scroll was
22
+ removed). but "the shell" is no longer the whole answer either: the
23
+ compositor worker owns `home`, `app:one` and `app:two`, so **every guest-app
24
+ draw call, paint boundary and raster-tier decision is in the compositor's
25
+ render profile, not the shell's.** `perf shell stop` prints both, labelled
26
+ by worker — read the compositor block for anything inside an app. for
27
+ months it printed only the shell's, and a uniswap token-list fling
28
+ therefore reported `raster tier: 0 promotions / 0 blits` while the
29
+ compositor was blitting ~14 rows a frame; the giveaway was `node visits: 7`
30
+ next to `app:one 1014 paints`. if a render-profile block describes a
31
+ handful of nodes while an app surface is painting hundreds of times, you
32
+ are reading the wrong worker.
33
+ browser matters just as much: playwright's bundled headless chromium has
34
+ NO GPU — CanvasKit falls back to software rendering and every number lies
35
+ high. launch GPU-backed Chrome via `launchReapedChrome` from
36
+ `scripts/lib/reap-browser`, never a bare `chromium.launch()`. never
37
+ `channel: 'chrome'` — that is the user's own browser, and automation
38
+ launching it hijacks their Dock events.
39
+ 2. **machinery silently not engaging is the default failure mode.** the
40
+ raster tier compiled, gated, and did *nothing* for months — zero
41
+ promotions on its textbook case — until per-gate rejection counters
42
+ existed. the frame-demand gate was blind to commit storms. the vsync
43
+ pump flapped 100 msgs/s at rest. never assume a tier works because it's
44
+ wired; read its counter during the exact workload.
45
+ 3. **check free memory and sampled CPU before blaming code.** an OOM or busy
46
+ machine mimics every perf and infra failure. read `vm_stat`, then take at
47
+ least two `top -l 2` samples and use the busiest sample for admission.
48
+ attribute browser processes with `ps -eo pid,ppid,args` before treating
49
+ one as abandoned or stopping an exact pid. load average is not an
50
+ admission signal on macOS.
51
+ 4. **never trade animation fidelity for CPU.** the home clock sweeps
52
+ sub-second like real iOS; discrete ticks have been proposed and rejected
53
+ repeatedly. attack per-frame cost (layers, caching, damage), never
54
+ cadence or fidelity.
55
+
56
+ ## measuring
57
+
58
+ ### presentation proof must stay outside the simulator
59
+
60
+ Performance acceptance must not use `rnxsim record`, `liveComposite`,
61
+ `captureBitmaps`, Chrome screencast, or another in-page frame-copy path. Those
62
+ paths add work to the simulator or browser and can change the cadence being
63
+ measured. Record the visible window with the operating system's native screen
64
+ capture instead.
65
+
66
+ On macOS, use the window-object recorder in `~/screen` so the simulator does
67
+ not copy or encode its own frames:
68
+
69
+ ```sh
70
+ bun ~/screen/src/cli.ts list-windows --owner "Google Chrome for Testing" --json
71
+ bun ~/screen/src/cli.ts run --window-id <id> --duration <seconds> \
72
+ --out <artifact.mov> -- <operating-system input command>
73
+ ```
74
+
75
+ The visual run contains only normal user input. Do not query the performance,
76
+ debug, or test bridge while it records. Treat that video as visual evidence of
77
+ frame progression. Collect worker paint, flush, delivery, and tier counters in
78
+ a separate run with screen capture off. The two runs answer different questions
79
+ without making the simulator measure its own recording overhead.
80
+
81
+ Open the sim with `?debug=framestamp` to make that video machine-readable.
82
+ Every surface paint then draws its `Date.now()` low 20 bits as a 23-cell
83
+ barcode — `home` and `app:*` at 45% of the surface height, `shell-overlay` on
84
+ its own row at 55% so overlay paints (keyboard, native UI) are attributable
85
+ separately — and the decoder reports, per presented frame, which paint it
86
+ shows and how late that paint presented:
87
+
88
+ ```sh
89
+ # sim opened with ?debug=framestamp
90
+ bun ~/screen/src/cli.ts run --window-id <id> --duration <seconds> \
91
+ --out <artifact.mov> -- <operating-system input command>
92
+ bun scripts/debug/decode-framestamp.ts <artifact.mov>
93
+ ```
94
+
95
+ This is the only way to separate a frame the compositor never produced from one
96
+ it produced and the browser never presented.
97
+
98
+ **Screen capture is not presentation-neutral, so the decoded timing expires.**
99
+ Starting a ScreenCaptureKit window capture flips the 120hz `OffscreenCanvas`
100
+ out of latest-wins presentation into sequential presentation, which falls behind
101
+ roughly 0.5ms for every 1ms of capture. A capture started mid-activity begins
102
+ near zero latency and climbs from there. A recording is therefore truthful about
103
+ content and trajectory for its whole length, and truthful about timing for
104
+ roughly its first 500ms; latency growth past that is the capture, not the
105
+ engine. Live uncaptured presentation stays latest-wins and current. Never report
106
+ a late-recording latency as an engine number, and never compare two recordings
107
+ of different lengths.
108
+
109
+ Worker counters and presentation are different claims, and a green counter never
110
+ licenses a smoothness claim. A capture can report a perfect 120hz worker cadence
111
+ for motion the user never sees move: the overscroll release shipped exactly that
112
+ way, with the compositor stepping on schedule while the presented frames showed
113
+ a clamped fling instead of the bounce spring. Counters prove a tier engaged;
114
+ only a framestamp decode of an out-of-process recording proves what presented.
115
+
116
+ When an external transform control is smooth but the simulator is not, inspect
117
+ every animation on the presented element. A separate Web Animation object does
118
+ not isolate paint work: animating `box-shadow`, `filter`, or another paint-only
119
+ property on the transform owner can move the whole trajectory to the browser's
120
+ main thread. Paint stable decoration once or give it a distinct layer.
121
+
122
+ ```sh
123
+ rnxsim perf shell start # arm shell frame capture (clears prior)
124
+ # reproduce — e.g. 8 fling swipes:
125
+ # for i in {1..8}; do rnxsim do swipe 196 650 196 250 8 8 --no-wait; sleep 0.9; done
126
+ rnxsim perf shell stop # report (add --json for scripting)
127
+ ```
128
+
129
+ the report and how to read it:
130
+
131
+ - `painted frames / avg / p50 p95 p99 / jank` — work-per-painted-frame.
132
+ budget: 8ms at 120hz, 16.7ms at 60hz; p95 > 20ms is visible jank.
133
+ - `surfaces` — per-surface layout/render/flush split. `layout` spikes on a
134
+ scroll capture mean shell-side yoga re-layout when virtualized rows
135
+ materialize (mid-fling commit), not steady-state cost.
136
+ - `boundaries: N records / M replays` — layer compositor health. healthy
137
+ scroll: replays >> records, record cost near zero. records ≈ replays
138
+ means invalidation is defeating the cache (the SkPicture attempt-1
139
+ failure shape: `records: 1525, replays: 35`).
140
+ the `why recorded` line under it splits those records three ways, and each
141
+ one is a different bug: `invalidated` means something dirtied the boundary's
142
+ content (the usual culprit, and usually a style write that did not change a
143
+ value), `moved` means the boundary's record origin shifted so the cached
144
+ picture no longer lines up, and `first-record` is a boundary that had no
145
+ picture yet. a scroll dominated by `invalidated` is an invalidation bug; one
146
+ dominated by `first-record` is a warm-up problem, so look at whether the
147
+ idle pre-record reaches that content.
148
+ - `raster tier: promotions / blits` — blits per scrolled frame > 0 or the
149
+ tier is not firing; `raster skips` names the first gate each candidate
150
+ failed (rebuild / animated / cacheable). zero blits on a feed scroll is
151
+ a bug, not a tuning matter — but check you are reading the compositor's
152
+ block first (law 1), because the shell's is legitimately zero for an app
153
+ workload.
154
+ **before widening any skip bucket, read the `also fail a later gate`
155
+ line under it.** the counters name only the FIRST failed gate, so a big
156
+ bucket can be entirely blocked a second time and unwidenable: on a uniswap
157
+ token-list fling all 8298 `animated` skips also carried a style transform
158
+ or a non-cacheable chunk, and narrowing the animated gate moved 6007 to
159
+ `transform` and 2350 to `cacheable` while promoting nothing. a bucket is
160
+ worth attacking when its also-blocked share is small.
161
+ and blits firing is not the same as the tier paying for itself: on the
162
+ uniswap token list the tier blits every visible row and costs 70MB of GPU
163
+ cache for no frame-time change, while the retained row boundaries under it
164
+ cost 2.4ms a frame. the discriminator is per-row draw density, so check
165
+ `draw calls` in the same block before concluding a cache is earning its
166
+ keep.
167
+ - `worst frames` — read the layout/render split of each; they are usually
168
+ a different problem than the average.
169
+
170
+ three runs minimum; single-run p95 is noise. a fresh `perf start` between
171
+ runs clears the buffer.
172
+
173
+ deeper tools when the frame report isn't enough:
174
+
175
+ ```sh
176
+ rnxsim maestro test .maestro/scroll.yaml --profile # per-step + frame stats
177
+ rnxsim perf cpu --duration 5 --output /tmp/t.cpuprofile # sampled CPU
178
+ rnxsim open 8089 --new --driver playwright --cdp-port 9222 # cpu-profile a driveable sim
179
+ rnxsim debug enable layout,render && rnxsim debug recent layout 40
180
+ ```
181
+
182
+ ### the cadence block: frames that never happened
183
+
184
+ work-per-painted-frame cannot see a frame that was never delivered — host
185
+ rAF starved, shell worker descheduled, vsync message stuck behind a busy
186
+ queue. the report shows low avg and zero jank while the user sees stutter.
187
+ the `cadence` section of `perf shell stop` measures the delivery side and
188
+ attributes a missing frame to its hop:
189
+
190
+ - `display clock / host rAF interval` — stretch here (gaps >1.5x median)
191
+ means the page thread's rAF starved or was throttled, upstream of the
192
+ shell worker entirely.
193
+ - `delivery lag` — vsync postMessage receipt latency above the run's
194
+ best case; jitter here means the shell worker was descheduled or its
195
+ queue was busy.
196
+ - `paint interval` — the end-to-end cadence the user perceives.
197
+ - `idle breaks` — gaps >250ms, excluded from all three: demand-gated
198
+ quiesce is legitimate, not starvation.
199
+
200
+ compare baseline vs contended captures of the same scripted window; also
201
+ compare **painted-frame counts**, the crudest and most robust signal.
202
+
203
+ the compositor cadence lines separate worker delivery from work submitted by
204
+ the worker:
205
+
206
+ - `engine-empty rAF` includes only sustained intervals where adjacent frames
207
+ have no engine demand, input message, shared-slot demand epoch change,
208
+ paint, prewarm pass, or CanvasKit flush.
209
+ - `rAF with CanvasKit flush` and `rAF after CanvasKit flush` isolate the frame
210
+ that called `Surface.flush()` and the next delivered frame.
211
+ - `CanvasKit submission` measures intervals between final `Surface.flush()`
212
+ completions on frames that submitted at least one surface. The JSON
213
+ `frameSeries` preserves each phase flag and exact count for correlation.
214
+
215
+ these are engine-owned boundaries. Playwright exposes raw CDP sessions only
216
+ for Chromium, and WebKit's public Inspector timeline exposes a WebCore
217
+ `Composite` record without a RemoteLayerTree transaction id or commit event.
218
+ Do not label CanvasKit flush or compositor rAF as a RemoteLayerTree commit.
219
+
220
+ ## the contention harness ("smooth alone, laggy on a zoom call")
221
+
222
+ low-load numbers do not predict feel under real-world CPU pressure. run the
223
+ same scripted capture twice, once with synthetic load, self-terminating so
224
+ it can't leak:
225
+
226
+ ```sh
227
+ # 6 cores of load for 30s, self-terminating
228
+ for i in 1 2 3 4 5 6; do
229
+ (perl -e 'my $end=time()+30; while(time()<$end){my $x=0; $x+=rand() for 1..10000}' &)
230
+ done
231
+ ```
232
+
233
+ compare: painted-frame count (cadence), avg/p95 (work), layout spikes. the
234
+ diagnostic split:
235
+
236
+ - **work grows under load** (avg/p95 way up) → paint path too heavy;
237
+ attack per-frame cost (layers, damage, raster, draw calls).
238
+ - **work flat but frames missing** (counts drop, avg barely moves) →
239
+ delivery starvation: the host-rAF → postMessage → shell hop chain and
240
+ worker scheduling. browsers give no thread priorities, so the only
241
+ levers are fewer hops on the frame-critical path, less work per hop, and
242
+ keeping motion (scroll, native anims) as close to the paint site as
243
+ possible.
244
+ - also remember GPU contention is real (video calls encode on the GPU);
245
+ webkit-lane numbers are Metal, chrome numbers are its GL/Metal stack.
246
+
247
+ ## the perf machinery map (improve, don't reinvent)
248
+
249
+ every tier below exists and has a counter. before optimizing, identify
250
+ which tier *should* absorb your cost and prove whether it does.
251
+
252
+ | tier | what it absorbs | proof it engaged |
253
+ | --- | --- | --- |
254
+ | demand-gated frames + visibility re-marking | idle costs nothing; invisible animations quiesce | `+N skipped idle ticks`; `queryStats().needsVsync` false at rest |
255
+ | per-surface dirty scoping | one app's commit/animation repaints only its surface | idle repaints ≈ commit rate, owning surface only |
256
+ | dirty-tracked layout + tiered text measurement | color/opacity/transform commits skip yoga; repeated strings skip shaping | `layout: 0` on steady frames |
257
+ | layer compositor (`paint-boundary.ts`) | animating a child never re-records ancestors | replays >> records |
258
+ | idle boundary pre-record | the first drag replays instead of recording; cold records happen while the surface is quiet | `pre-recorded: N boundaries at idle` in the report, and the first interaction's record cost near its warm cost |
259
+ | damage rects (`damage-rect.ts`) | repaint clips to changed region (opaque-backdrop gated) | worst-frame render bounded during small updates |
260
+ | raster tier | stable rows become GPU textures; scroll skips recording | compositor block: promotions > 0 once warm, blits ≈ one per visible row (uniswap token list: ~13.7/frame) |
261
+ | flood guard, pointer coalescing, vsync hysteresis | message-storm and pump-thrash protection | msgs/s sane during gestures and at rest |
262
+
263
+ there is no whole-screen scroll blit. one existed for a few hours in June
264
+ 2026 and was reverted (`6706f3a014`): it shifted a surface RECTANGLE, so a
265
+ full-surface feed with floating translucent chrome dragged its bars along with
266
+ the content and smeared their old pixels through the feed. scroll rides the
267
+ layer, damage, and raster machinery above instead, and any future blit has to
268
+ own a scroll SUBTREE with provable backdrop correctness rather than a screen
269
+ rect. do not look for `blitBlocked`; it does not exist.
270
+
271
+ if a workload's cost lands in a tier that shows zero activity, the fix is
272
+ almost always "why didn't it engage" (a gate, a shape, an eligibility
273
+ rule), not new machinery. the area-gate fix that finally made the raster
274
+ tier fire on feeds came entirely from rejection counters.
275
+
276
+ ## guest-app-side causes (fix the app, or fix the engine honestly)
277
+
278
+ - dynamic format strings (`` `${n} messages` ``) bust the shaping LRU —
279
+ split number and label into separate `<Text>` nodes.
280
+ - `Animated.Value` on `width`/`height` forces layout per frame — use
281
+ `transform` (scale/translate).
282
+ - per-frame React work from reanimated-style code is an engine conformance
283
+ bug (valid worklet apps must not commit per frame) — fix rnx core,
284
+ never add a divergent fast-path (upstream alignment overrides perf; see
285
+ the `shouldFreezeOffscreenStackContent` incident).
286
+ - render-storm loops (poll → remount) show up as commit-rate repaints and
287
+ RSS growth; check `debug state animations` and the timeline.
288
+
289
+ ## closing the loop
290
+
291
+ done means: a named hot path + the counter or split that indicts it + the
292
+ same capture re-run showing the number moved + no new errors (`get errors`)
293
+ + nothing torn down left running (close sims you opened, kill load loops).
294
+ for engine changes, run the rnx gate (`bun run test:sootsim:gate`) and
295
+ screenshot-stable surfaces you touched.
@@ -0,0 +1,160 @@
1
+ ---
2
+ name: rnx-setup
3
+ description: Get rnx connected to your React Native app — from `rnxsim setup` to a driveable sim
4
+ ---
5
+
6
+ # rnxsim setup
7
+
8
+ you want to drive a React Native app with rnx from the CLI. this skill
9
+ takes you from `rnxsim setup` to a connected, pinned sim that every other
10
+ rnxsim skill assumes exists. no per-app rnx install is required. if
11
+ `rnxsim describe` already
12
+ returns a tree, you're past this skill — load `/rnx-debug` for
13
+ debugging or `/rnx-test` for writing tests.
14
+
15
+ ## anti-patterns (read first)
16
+
17
+ - **never `rnxsim claim --force` a sim held by another CLI.** the lease
18
+ exists for a reason. forcing strands the other agent and corrupts the
19
+ bridge state. use `rnxsim open --new` to start a fresh sim instead.
20
+ - **don't switch sims mid-investigation.** pin once with `rnxsim use
21
+ <id>` and keep it pinned. an unpinned command refuses to choose when more
22
+ than one driveable sim is live.
23
+ - **an empty `rnxsim list` is not "broken rnx".** it usually means the
24
+ daemon is down, the dev server port is wrong, or the runtime is missing.
25
+ walk the recovery checklist before reinstalling.
26
+ - **keep one sim for your whole session.** the first `rnxsim open <port>`
27
+ launches Chrome for Testing with an isolated profile. later opens reuse the
28
+ saved sim and navigate it in place. use `rnxsim do reload` when the target
29
+ has not changed:
30
+
31
+ ```sh
32
+ rnxsim open 8086 # first call creates your isolated sim
33
+ rnxsim open 8090 # same sim, different app
34
+ rnxsim do reload # same sim, same app
35
+ rnxsim close <id> # dispose it when you are done
36
+ ```
37
+
38
+ `--new`, `--profile`, and `--ephemeral` create another isolated browser
39
+ tree. use them only when separate storage or a genuinely concurrent sim is
40
+ required. repeated new trees consume enough memory and CPU to starve other
41
+ work on a shared machine.
42
+ - **pin the sim before driving it.** `rnxsim open` records the new sim for the
43
+ current CLI identity. read its id and pin it explicitly before a longer flow:
44
+
45
+ ```sh
46
+ rnxsim open 8086
47
+ rnxsim list
48
+ rnxsim use <id>
49
+ rnxsim describe --sim <id>
50
+ ```
51
+
52
+ - **never retry-loop `rnxsim open` against a wedged or slow stack.** a
53
+ connect timeout means diagnose the stack. do not retry. if the first launch
54
+ never connects, each retry can create another browser tree before there is a
55
+ saved sim to reuse. verify the development server and rnx runtime, then
56
+ retry once after fixing the cause.
57
+
58
+ ## first move
59
+
60
+ ```sh
61
+ rnxsim setup # one-time guided setup; installs daemon
62
+ rnxsim compat --json # scan the app's native package compatibility
63
+ rnxsim open 8081 # load a metro/expo dev server
64
+ rnxsim list # confirm exactly one sim is reachable
65
+ rnxsim use <id> # pin it for the rest of the workflow
66
+ rnxsim describe # smoke test — should print a render tree
67
+ ```
68
+
69
+ if you do not know the port, run `rnxsim open` with no target and pick from
70
+ the detected dev servers. for hosted or unusual targets, pass the URL
71
+ explicitly:
72
+
73
+ ```sh
74
+ rnxsim open 8081 # load a metro dev server
75
+ rnxsim open https://my-app.local # load a hosted bundle
76
+ ```
77
+
78
+ ## you're done when
79
+
80
+ - `rnxsim list` shows your sim and no orphans
81
+ - `rnxsim describe` returns a render tree, not an error
82
+ - the same sim id appears in `rnxsim describe`, `rnxsim find`, and
83
+ every subsequent command
84
+ - `rnxsim get errors 5` and `rnxsim get requests 5` are quiet (or the
85
+ warnings are ones you understand)
86
+ - the compatibility scan's partial, unsupported, unknown, and version-mismatched
87
+ packages have been summarized without treating its aggregate score as a pass/fail gate
88
+
89
+ ## compatibility feedback requires approval
90
+
91
+ run `rnxsim compat --json` from the app project root during setup. if the scan
92
+ identifies a plausible missing rnx seam, or the running app gives concrete
93
+ evidence that behavior is missing or broken specifically in rnx, summarize
94
+ the package, expected behavior, actual behavior, and runtime evidence for the
95
+ user. then offer to send that finding with `rnxsim report-issue`.
96
+
97
+ **never submit a report automatically or behind the user's back.** wait for an
98
+ explicit yes. only then run this from the app project root:
99
+
100
+ ```sh
101
+ rnxsim report-issue --yes "<package; expected behavior; actual behavior; runtime evidence>"
102
+ ```
103
+
104
+ the command automatically attaches the local compatibility scan plus bounded
105
+ CLI, runtime, operating-system, and architecture metadata. it does not attach
106
+ source files, environment variables, terminal output, logs, screenshots, git
107
+ data, or app data. `--dry-run` previews the attachment summary without sending.
108
+ do not use `--yes` merely because the agent believes a report would be useful;
109
+ it records the user's approval after the offer.
110
+
111
+ ## recovery — common failure modes
112
+
113
+ **`rnxsim list` is empty.** walk this checklist in order:
114
+
115
+ ```sh
116
+ rnxsim daemon status # is the bridge daemon up?
117
+ rnxsim runtime list # is at least one engine runtime installed?
118
+ rnxsim open <port> # does loading explicitly work?
119
+ ```
120
+
121
+ if daemon is down: run `rnxsim setup`, or `rnxsim serve` in another shell for
122
+ a foreground bridge. if no runtime is installed, `rnxsim open` installs it
123
+ before launching. if open errors with "port unreachable": confirm your dev
124
+ server is actually serving — the bundler has to be up before rnxsim can
125
+ attach.
126
+
127
+ **two sims appear unexpectedly.** an earlier `rnxsim claim` didn't
128
+ release on exit. close the orphan explicitly:
129
+
130
+ ```sh
131
+ rnxsim list # note the stale id
132
+ rnxsim close <id> # release it cleanly
133
+ ```
134
+
135
+ **`rnxsim open` errors with "no runtime".** the engine binary isn't installed.
136
+ Run `rnxsim runtime install`, then retry `rnxsim open <port>`.
137
+
138
+ **`rnxsim open` times out ("timed out waiting for opened sim to connect").**
139
+ do not retry in a loop. verify the development server first: `__soot/` must
140
+ respond and the engine watchers must be running. inspect the driver diagnostic
141
+ path printed by the failed command. restart the broken development stack at its
142
+ supervisor, then retry once. rnx owns the isolated browser profile and its
143
+ process tree; do not open the shell URL through an operating-system browser.
144
+
145
+ **bridge disconnects mid-sim.** the WebSocket dropped (laptop slept,
146
+ network blip). re-pin: `rnxsim use <id>`. the daemon retries reconnection
147
+ automatically; you usually just need to re-issue the command.
148
+
149
+ **"sim held by another CLI".** another agent or your own previous CLI
150
+ process has the lease. find it (`rnxsim list`), close it (`rnxsim close
151
+ <id>`), or start a clean sim (`rnxsim open --new`) — do **not**
152
+ `rnxsim claim --force`.
153
+
154
+ ## related
155
+
156
+ - `/rnx-debug` — once setup is good, this is where you debug
157
+ rendering, performance, and accessibility issues.
158
+ - `/rnx-test` — write or run automated flows against the connected
159
+ sim.
160
+ - `/rnx-visual` — pixel-level rendering comparisons.