rnxsim 0.0.0 → 0.1.312
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.
- package/LICENSE +21 -0
- package/README.md +512 -0
- package/detox/colors.ts +54 -0
- package/detox/config-loader.ts +135 -0
- package/detox/element-types.ts +39 -0
- package/detox/expectations.ts +537 -0
- package/detox/gestures.ts +480 -0
- package/detox/index.ts +2118 -0
- package/detox/jest-preset.cjs +51 -0
- package/detox/jest-setup-after-env.cjs +18 -0
- package/detox/matchers.ts +30 -0
- package/detox/motion-change.cjs +109 -0
- package/detox/motion-change.d.cts +35 -0
- package/detox/navigation.ts +43 -0
- package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
- package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
- package/detox/screenshots/color-test-rest-norngh.png +0 -0
- package/detox/screenshots/theme-blue-toggle.png +0 -0
- package/detox/screenshots/theme-blue.png +0 -0
- package/detox/screenshots/theme-red-toggle.png +0 -0
- package/detox/screenshots/theme-red.png +0 -0
- package/dist-cli/bin.js +15 -0
- package/dist-cli/chunks/agent-NXVDDCFG.js +48 -0
- package/dist-cli/chunks/agent-wrapper-DXJCFKL3.js +17 -0
- package/dist-cli/chunks/app-fonts-W5AJZQJF.js +4 -0
- package/dist-cli/chunks/assert-3LOJEUDT.js +49 -0
- package/dist-cli/chunks/auth-MFTYYPNX.js +12 -0
- package/dist-cli/chunks/beta-GT3NN67B.js +4 -0
- package/dist-cli/chunks/camera-6EAK7CUL.js +22 -0
- package/dist-cli/chunks/chunk-274UD5FD.js +4 -0
- package/dist-cli/chunks/chunk-2SA2VTGP.js +5 -0
- package/dist-cli/chunks/chunk-33FKQH54.js +5 -0
- package/dist-cli/chunks/chunk-37I5AIS7.js +3 -0
- package/dist-cli/chunks/chunk-3D57IBPC.js +4 -0
- package/dist-cli/chunks/chunk-4BXCZHAU.js +4 -0
- package/dist-cli/chunks/chunk-5OX7ANRA.js +5 -0
- package/dist-cli/chunks/chunk-66NF2KNG.js +9 -0
- package/dist-cli/chunks/chunk-6UKJB2VC.js +125 -0
- package/dist-cli/chunks/chunk-7INBNA3U.js +26 -0
- package/dist-cli/chunks/chunk-7VWOSTD5.js +5 -0
- package/dist-cli/chunks/chunk-AY47WKS5.js +23 -0
- package/dist-cli/chunks/chunk-B7KMNHX7.js +4 -0
- package/dist-cli/chunks/chunk-BKNOLP3J.js +4 -0
- package/dist-cli/chunks/chunk-BRIZOR4K.js +28 -0
- package/dist-cli/chunks/chunk-DM6KUAOZ.js +4 -0
- package/dist-cli/chunks/chunk-EUAVBTKU.js +4 -0
- package/dist-cli/chunks/chunk-FTC4M26Q.js +4 -0
- package/dist-cli/chunks/chunk-FWYYCUAZ.js +4 -0
- package/dist-cli/chunks/chunk-G74YIEVS.js +7 -0
- package/dist-cli/chunks/chunk-GZDHEONW.js +507 -0
- package/dist-cli/chunks/chunk-HG7ORJMT.js +5 -0
- package/dist-cli/chunks/chunk-HMDOLCKG.js +15 -0
- package/dist-cli/chunks/chunk-JG6S4RSH.js +4 -0
- package/dist-cli/chunks/chunk-KR5BGMBZ.js +4 -0
- package/dist-cli/chunks/chunk-KZW5WBGU.js +983 -0
- package/dist-cli/chunks/chunk-L24Z3CRS.js +30 -0
- package/dist-cli/chunks/chunk-LDQ7HVCW.js +4 -0
- package/dist-cli/chunks/chunk-MENV7VLD.js +4 -0
- package/dist-cli/chunks/chunk-MNGQ42QE.js +568 -0
- package/dist-cli/chunks/chunk-N6A3SXMR.js +4 -0
- package/dist-cli/chunks/chunk-NLEMQ4PY.js +6 -0
- package/dist-cli/chunks/chunk-NPVGMH2N.js +101 -0
- package/dist-cli/chunks/chunk-OR3XTXEB.js +107 -0
- package/dist-cli/chunks/chunk-PEPUGJVI.js +4 -0
- package/dist-cli/chunks/chunk-Q3RGLERJ.js +142 -0
- package/dist-cli/chunks/chunk-SI3BUTQR.js +4 -0
- package/dist-cli/chunks/chunk-T4FT6CM7.js +4 -0
- package/dist-cli/chunks/chunk-TBMVBI26.js +7 -0
- package/dist-cli/chunks/chunk-TCWQJILS.js +5 -0
- package/dist-cli/chunks/chunk-TGQNQ6XT.js +766 -0
- package/dist-cli/chunks/chunk-TYE3WBSR.js +5 -0
- package/dist-cli/chunks/chunk-U4PN6JOT.js +6 -0
- package/dist-cli/chunks/chunk-VUFRB347.js +4 -0
- package/dist-cli/chunks/chunk-VZBLFMYC.js +10 -0
- package/dist-cli/chunks/chunk-WBT32R6N.js +25 -0
- package/dist-cli/chunks/chunk-XRGEEPJ5.js +38 -0
- package/dist-cli/chunks/chunk-Y2FSZSO7.js +71 -0
- package/dist-cli/chunks/chunk-YRV7HR3P.js +74 -0
- package/dist-cli/chunks/chunk-YZRFF4YA.js +14 -0
- package/dist-cli/chunks/chunk-ZMZTHU7V.js +4 -0
- package/dist-cli/chunks/cleanup-H3GWQB63.js +25 -0
- package/dist-cli/chunks/cli-version-3QPAJ3LC.js +4 -0
- package/dist-cli/chunks/compat-5WG2XJV7.js +20 -0
- package/dist-cli/chunks/config-ANMBVVLL.js +13 -0
- package/dist-cli/chunks/control-NSGNQFJG.js +4 -0
- package/dist-cli/chunks/daemon-JHF4BJ3U.js +4 -0
- package/dist-cli/chunks/debug-CHV2O2KF.js +200 -0
- package/dist-cli/chunks/desktop-FIPIK4CH.js +31 -0
- package/dist-cli/chunks/detox-CVUJP7K6.js +52 -0
- package/dist-cli/chunks/device-6WCUM4W6.js +18 -0
- package/dist-cli/chunks/diagnose-I6EMWEII.js +43 -0
- package/dist-cli/chunks/disk-cleanup-P27CYFTN.js +4 -0
- package/dist-cli/chunks/drivers-HQ6AKZT6.js +4 -0
- package/dist-cli/chunks/film-UOQFFYNF.js +74 -0
- package/dist-cli/chunks/flow-N27MRRNL.js +4 -0
- package/dist-cli/chunks/help-SF6FEGEE.js +4 -0
- package/dist-cli/chunks/hidden-runtime-alias-NJYFSJKZ.js +4 -0
- package/dist-cli/chunks/home-paths-XOV44JEF.js +4 -0
- package/dist-cli/chunks/inspect-CWFRFQTY.js +4 -0
- package/dist-cli/chunks/install-desktop-KLQDNXZQ.js +4 -0
- package/dist-cli/chunks/login-BPFQENCX.js +28 -0
- package/dist-cli/chunks/logout-5A45ZGY7.js +4 -0
- package/dist-cli/chunks/maestro-E4CSDMLM.js +104 -0
- package/dist-cli/chunks/maestro-generate-CKMOJQX2.js +36 -0
- package/dist-cli/chunks/mode-SZYNMIQF.js +26 -0
- package/dist-cli/chunks/optional-demo-registry-FRH4SFVG.js +4 -0
- package/dist-cli/chunks/perf-VXU2XJAX.js +48 -0
- package/dist-cli/chunks/permissions-IRIAOVND.js +13 -0
- package/dist-cli/chunks/record-RY7FPLLH.js +72 -0
- package/dist-cli/chunks/report-issue-KNWXOE53.js +29 -0
- package/dist-cli/chunks/runtime-AR3GJTGK.js +4 -0
- package/dist-cli/chunks/screenshot-command-QLALR6GY.js +159 -0
- package/dist-cli/chunks/screenshot-layers-OQFMFVRL.js +20 -0
- package/dist-cli/chunks/screenshots-capture-6TSRJFGB.js +42 -0
- package/dist-cli/chunks/serve-F54J2NQO.js +44 -0
- package/dist-cli/chunks/setup-7V6UKX4U.js +32 -0
- package/dist-cli/chunks/skills-QQBOSETH.js +50 -0
- package/dist-cli/chunks/storage-TAPMKK5O.js +37 -0
- package/dist-cli/chunks/store-7NX3S43H.js +4 -0
- package/dist-cli/chunks/telemetry-E7BUK6RA.js +4 -0
- package/dist-cli/chunks/timeline-IAM564NA.js +24 -0
- package/dist-cli/chunks/upgrade-CD55QXIX.js +7 -0
- package/dist-cli/chunks/upload-GZ6LIMYM.js +4 -0
- package/dist-cli/chunks/version-3DYJ5AS3.js +6 -0
- package/dist-cli/chunks/web-6NW6YLPI.js +4 -0
- package/dist-cli/chunks/what-happened-CZZDOOVG.js +25 -0
- package/dist-lib/agent-daemon-client.cjs +468 -0
- package/dist-lib/agent-events.cjs +48 -0
- package/dist-lib/agent-identity.cjs +53 -0
- package/dist-lib/agent-sessions.cjs +725 -0
- package/dist-lib/attached-projects.cjs +526 -0
- package/dist-lib/auth/shared-session.cjs +195 -0
- package/dist-lib/backend-origin.cjs +70 -0
- package/dist-lib/beta.cjs +41 -0
- package/dist-lib/beta.mjs +13 -0
- package/dist-lib/bridge-constants.cjs +161 -0
- package/dist-lib/cli-constants.cjs +32 -0
- package/dist-lib/config.cjs +96 -0
- package/dist-lib/detox/index.cjs +2863 -0
- package/dist-lib/detox/jest-preset.cjs +51 -0
- package/dist-lib/dev-bundle-resolution.cjs +223 -0
- package/dist-lib/home-paths.cjs +421 -0
- package/dist-lib/host/bridge-host.cjs +5600 -0
- package/dist-lib/host/fetch-proxy-handler.cjs +373 -0
- package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
- package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
- package/dist-lib/host/websocket-proxy.cjs +214 -0
- package/dist-lib/index.cjs +513 -0
- package/dist-lib/jump-to-source-babel.cjs +93 -0
- package/dist-lib/menu.cjs +620 -0
- package/dist-lib/menu.mjs +581 -0
- package/dist-lib/metro.cjs +297 -0
- package/dist-lib/profiles.cjs +246 -0
- package/dist-lib/public-brand.cjs +39 -0
- package/dist-lib/render-mode.cjs +54 -0
- package/dist-lib/scripts/dev-server-scanner.cjs +1034 -0
- package/dist-lib/sdk.cjs +2333 -0
- package/dist-lib/sdk.mjs +2240 -0
- package/dist-lib/skills.cjs +23911 -0
- package/dist-lib/vite.cjs +358 -0
- package/package.json +189 -18
- package/scripts/dev-server-scanner.ts +941 -0
- package/scripts/optional-demo-registry.ts +113 -0
- package/scripts/postinstall.cjs +3 -0
- package/scripts/preuninstall.cjs +118 -0
- package/skills/contrast/SKILL.md +171 -0
- package/skills/rnx-debug/SKILL.md +425 -0
- package/skills/rnx-perf/SKILL.md +295 -0
- package/skills/rnx-setup/SKILL.md +160 -0
- package/skills/rnx-test/SKILL.md +292 -0
- package/skills/rnx-visual/SKILL.md +122 -0
- package/src/agent-daemon-client.ts +410 -0
- package/src/agent-events.ts +71 -0
- package/src/agent-identity.ts +23 -0
- package/src/agent-prompt.ts +71 -0
- package/src/agent-sessions.ts +573 -0
- package/src/attached-projects.ts +539 -0
- package/src/auth/shared-session.ts +204 -0
- package/src/backend-origin.ts +49 -0
- package/src/beta.ts +19 -0
- package/src/bridge-constants.ts +171 -0
- package/src/browser-cache.ts +16 -0
- package/src/cli-constants.ts +1 -0
- package/src/cli-version.ts +32 -0
- package/src/codex-client.ts +215 -0
- package/src/config.ts +134 -0
- package/src/dev-bundle-resolution.ts +236 -0
- package/src/disk-cleanup.ts +466 -0
- package/src/home-paths.ts +552 -0
- package/src/host/agent-host.ts +666 -0
- package/src/host/bridge-host.ts +2626 -0
- package/src/host/fetch-proxy-handler.ts +397 -0
- package/src/host/fetch-proxy-overrides.ts +51 -0
- package/src/host/open-url.ts +89 -0
- package/src/host/websocket-proxy.ts +210 -0
- package/src/index.ts +12 -705
- package/src/jump-to-source-babel.ts +135 -0
- package/src/menu.ts +734 -0
- package/src/metro-plugin.ts +139 -0
- package/src/native-dev-bundle-url.ts +96 -0
- package/src/native-seam-manifest.ts +341 -0
- package/src/plist.ts +27 -0
- package/src/poll-until.ts +92 -0
- package/src/profiles.ts +179 -0
- package/src/public-brand.ts +9 -0
- package/src/render-mode.ts +27 -0
- package/src/runtime-assets.ts +228 -0
- package/src/runtime-delivery.ts +54 -0
- package/src/screenshots/compose.ts +422 -0
- package/src/screenshots/frame-compose.ts +454 -0
- package/src/screenshots/orchestrate.ts +256 -0
- package/src/screenshots/org-deck.ts +91 -0
- package/src/screenshots/registry.ts +58 -0
- package/src/screenshots/schema.ts +364 -0
- package/src/sdk.ts +7 -0
- package/src/settings-bundle.ts +418 -0
- package/src/skills/builtin/a11y-review.ts +132 -0
- package/src/skills/builtin/compat-check.ts +71 -0
- package/src/skills/builtin/maestro-test.ts +121 -0
- package/src/skills/builtin/perf-profile.ts +90 -0
- package/src/skills/builtin/screenshot-all.ts +52 -0
- package/src/skills/builtin/visual-diff.ts +100 -0
- package/src/skills/registry.ts +107 -0
- package/src/skills/types.ts +41 -0
- package/src/team-machine-prompt.ts +73 -0
- package/src/vite-plugin-one.ts +190 -0
- package/src/vite-plugin.ts +1750 -0
- 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.
|