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
package/src/menu.ts ADDED
@@ -0,0 +1,777 @@
1
+ import { rnxPublicBrand } from './public-brand'
2
+
3
+ // public sootsim menu protocol. this file is deliberately pure data and pure
4
+ // functions so hosts can import it without pulling the engine or canvaskit graph.
5
+
6
+ export const SOOTSIM_ENGINE_EVENT_NAME = 'sootsim:engine-event'
7
+ export const SOOTSIM_SHELL_COMMAND_NAME = 'sootsim:shell-command'
8
+
9
+ export type SootSimMenuPlatform = 'browser' | 'electron'
10
+ export type SootSimScaleMode = 'physical' | 'point' | 'pixel' | 'fit'
11
+
12
+ // ambient glow strength. one setting with three states rather than an on/off
13
+ // pair plus a separate strength: every host that carries the preference (the
14
+ // menus, the persisted setting, the ?ambientGlow= boot seed, the live
15
+ // set-ambient-glow push, electron's shared config) then carries exactly one
16
+ // value and cannot end up half-configured.
17
+ export type SootSimAmbientGlowLevel = 'off' | 'soft' | 'full'
18
+ export const SOOTSIM_AMBIENT_GLOW_LEVELS = ['off', 'soft', 'full'] as const
19
+
20
+ export function isSootSimAmbientGlowLevel(
21
+ value: unknown,
22
+ ): value is SootSimAmbientGlowLevel {
23
+ return value === 'off' || value === 'soft' || value === 'full'
24
+ }
25
+
26
+ export const SOOTSIM_AMBIENT_GLOW_LABELS: Record<SootSimAmbientGlowLevel, string> = {
27
+ off: 'Off',
28
+ soft: 'Soft',
29
+ full: 'Full',
30
+ }
31
+
32
+ // which ios home screen the simulator portrays. the home screen is authored
33
+ // twice (the flat ios 26 home and the ios 27 home measured off apple's art),
34
+ // so the era is one value every host carries: the menus, the persisted
35
+ // setting, the ?homeScreen= boot seed, the live set-home-screen push and
36
+ // electron's shared config.
37
+ export type SootSimHomeScreenVersion = 'ios26' | 'ios27'
38
+ export const SOOTSIM_HOME_SCREEN_VERSIONS = ['ios26', 'ios27'] as const
39
+
40
+ export function isSootSimHomeScreenVersion(
41
+ value: unknown,
42
+ ): value is SootSimHomeScreenVersion {
43
+ return value === 'ios26' || value === 'ios27'
44
+ }
45
+
46
+ export const SOOTSIM_HOME_SCREEN_VERSION_LABELS: Record<
47
+ SootSimHomeScreenVersion,
48
+ string
49
+ > = {
50
+ ios26: 'iOS 26',
51
+ ios27: 'iOS 27',
52
+ }
53
+
54
+ export type SootSimMenuItemDef =
55
+ | { type: 'separator' }
56
+ | {
57
+ type?: 'normal' | 'checkbox' | 'radio'
58
+ label: string
59
+ accelerator?: string
60
+ browserAccelerator?: boolean
61
+ checked?: boolean
62
+ enabled?: boolean
63
+ action?: string
64
+ actionPayload?: string
65
+ shortcutLabel?: string
66
+ showInShortcutDocs?: boolean
67
+ items?: SootSimMenuItemDef[]
68
+ hiddenIn?: SootSimMenuPlatform
69
+ }
70
+
71
+ export type SootSimMenuDef = {
72
+ label: string
73
+ items: SootSimMenuItemDef[]
74
+ hiddenIn?: SootSimMenuPlatform
75
+ notMinimal?: boolean
76
+ brand?: boolean
77
+ }
78
+
79
+ export interface SootSimMenuState {
80
+ currentDevice: string
81
+ homeScreenVersion: SootSimHomeScreenVersion
82
+ // whether this surface is a contributor development surface. unreleased
83
+ // simulator surfaces (the iPhone Duo and its home-screen era switch) are
84
+ // offered here and nowhere a user can reach.
85
+ developmentMode: boolean
86
+ showBezels: boolean
87
+ showMenuBar: boolean
88
+ showRail: boolean
89
+ solidWindow: boolean
90
+ ambientGlow: SootSimAmbientGlowLevel
91
+ // presentation: the real rendering, or the clean structural wireframe
92
+ wireframe: boolean
93
+ screenshotMode: boolean
94
+ threeMode: boolean
95
+ inspectMode: boolean
96
+ devtoolsOpen: boolean
97
+ alwaysShowLiveWebViews: boolean
98
+ // whether "send to box" hands the box the signed-in session it captured
99
+ sendToBoxCarryAuth: boolean
100
+ stayOnTop: boolean
101
+ scaleMode: SootSimScaleMode
102
+ }
103
+
104
+ export interface SootSimMenuDeviceChoice {
105
+ model: string
106
+ label: string
107
+ platform: string
108
+ }
109
+
110
+ export interface SootSimMenuShortcutEntry {
111
+ menuLabel: string
112
+ label: string
113
+ accelerator: string
114
+ action: string
115
+ actionPayload?: string
116
+ }
117
+
118
+ export interface SootSimShortcutDocEntry {
119
+ keys: string
120
+ label: string
121
+ }
122
+
123
+ export interface SootSimShortcutDocSection {
124
+ title: string
125
+ entries: SootSimShortcutDocEntry[]
126
+ }
127
+
128
+ export type MenuItemDef = SootSimMenuItemDef
129
+ export type MenuDef = SootSimMenuDef
130
+ export type MenuState = SootSimMenuState
131
+ export type AmbientGlowLevel = SootSimAmbientGlowLevel
132
+ export type ShortcutPlatform = SootSimMenuPlatform
133
+ export type MenuShortcutEntry = SootSimMenuShortcutEntry
134
+ export type ShortcutDocEntry = SootSimShortcutDocEntry
135
+ export type ShortcutDocSection = SootSimShortcutDocSection
136
+
137
+ export function buildSootSimMenuDefs(
138
+ state: SootSimMenuState,
139
+ deviceChoices: readonly SootSimMenuDeviceChoice[],
140
+ ): SootSimMenuDef[] {
141
+ return [
142
+ appMenu(),
143
+ fileMenu(state, deviceChoices),
144
+ editMenu(),
145
+ deviceMenu(state, deviceChoices),
146
+ debugMenu(state),
147
+ windowMenu(state),
148
+ helpMenu(),
149
+ ]
150
+ }
151
+
152
+ function appMenu(): SootSimMenuDef {
153
+ return {
154
+ label: rnxPublicBrand.name,
155
+ brand: true,
156
+ items: [
157
+ { label: `About ${rnxPublicBrand.name}`, action: 'about' },
158
+ { type: 'separator' },
159
+ {
160
+ label: 'Preferences...',
161
+ accelerator: 'CmdOrCtrl+,',
162
+ action: 'preferences',
163
+ browserAccelerator: true,
164
+ shortcutLabel: 'settings',
165
+ showInShortcutDocs: true,
166
+ },
167
+ {
168
+ label: 'Settings Pages',
169
+ items: [
170
+ {
171
+ label: 'Accessibility',
172
+ action: 'open-settings-page',
173
+ actionPayload: 'accessibility',
174
+ },
175
+ {
176
+ label: 'Display & Text Size',
177
+ action: 'open-settings-page',
178
+ actionPayload: 'display-and-text-size',
179
+ },
180
+ {
181
+ label: 'Notifications',
182
+ action: 'open-settings-page',
183
+ actionPayload: 'notifications',
184
+ },
185
+ {
186
+ label: 'Apps',
187
+ action: 'open-settings-page',
188
+ actionPayload: 'apps',
189
+ },
190
+ {
191
+ label: 'Privacy & Security',
192
+ action: 'open-settings-page',
193
+ actionPayload: 'privacy',
194
+ },
195
+ ],
196
+ },
197
+ { type: 'separator' },
198
+ {
199
+ label: 'Hide rnx',
200
+ accelerator: 'CmdOrCtrl+H',
201
+ action: 'hide',
202
+ hiddenIn: 'browser',
203
+ },
204
+ {
205
+ label: 'Hide Others',
206
+ accelerator: 'CmdOrCtrl+Alt+H',
207
+ action: 'hide-others',
208
+ hiddenIn: 'browser',
209
+ },
210
+ { label: 'Show All', action: 'unhide', hiddenIn: 'browser' },
211
+ { type: 'separator' },
212
+ {
213
+ label: 'Quit rnx',
214
+ accelerator: 'CmdOrCtrl+Q',
215
+ action: 'quit',
216
+ hiddenIn: 'browser',
217
+ },
218
+ ],
219
+ }
220
+ }
221
+
222
+ function fileMenu(
223
+ state: SootSimMenuState,
224
+ deviceChoices: readonly SootSimMenuDeviceChoice[],
225
+ ): SootSimMenuDef {
226
+ return {
227
+ label: 'File',
228
+ notMinimal: true,
229
+ items: [
230
+ { label: 'New Window', accelerator: 'CmdOrCtrl+N', action: 'new-window' },
231
+ {
232
+ label: 'New Simulator',
233
+ hiddenIn: 'browser',
234
+ items: buildSootSimDeviceChoiceDefs(state.currentDevice, deviceChoices, {
235
+ action: 'new-simulator',
236
+ }),
237
+ },
238
+ {
239
+ label: 'Close Window',
240
+ accelerator: 'CmdOrCtrl+W',
241
+ action: 'close-window',
242
+ hiddenIn: 'browser',
243
+ },
244
+ { type: 'separator' },
245
+ {
246
+ label: 'Save Screen',
247
+ accelerator: 'CmdOrCtrl+S',
248
+ action: 'save-screen',
249
+ },
250
+ {
251
+ label: 'Copy Screen',
252
+ accelerator: 'CmdOrCtrl+Alt+C',
253
+ action: 'copy-screen',
254
+ },
255
+ { type: 'separator' },
256
+ {
257
+ label: 'Record',
258
+ accelerator: 'Ctrl+CmdOrCtrl+R',
259
+ action: 'toggle-recording',
260
+ browserAccelerator: true,
261
+ shortcutLabel: 'start/stop recording',
262
+ showInShortcutDocs: true,
263
+ },
264
+ {
265
+ label: 'Send to Box',
266
+ action: 'send-to-box',
267
+ },
268
+ ],
269
+ }
270
+ }
271
+
272
+ function editMenu(): SootSimMenuDef {
273
+ return {
274
+ label: 'Edit',
275
+ hiddenIn: 'browser',
276
+ items: [
277
+ { label: 'Undo', accelerator: 'CmdOrCtrl+Z', action: 'undo' },
278
+ { label: 'Redo', accelerator: 'CmdOrCtrl+Shift+Z', action: 'redo' },
279
+ { type: 'separator' },
280
+ { label: 'Cut', accelerator: 'CmdOrCtrl+X', action: 'cut' },
281
+ { label: 'Copy', accelerator: 'CmdOrCtrl+C', action: 'copy' },
282
+ { label: 'Paste', accelerator: 'CmdOrCtrl+V', action: 'paste' },
283
+ { label: 'Select All', accelerator: 'CmdOrCtrl+A', action: 'select-all' },
284
+ ],
285
+ }
286
+ }
287
+
288
+ function deviceMenu(
289
+ state: SootSimMenuState,
290
+ deviceChoices: readonly SootSimMenuDeviceChoice[],
291
+ ): SootSimMenuDef {
292
+ return {
293
+ label: 'Device',
294
+ items: [
295
+ { label: 'Restart', accelerator: 'CmdOrCtrl+R', action: 'reload' },
296
+ { label: 'Erase All Content and Settings...', action: 'erase-all' },
297
+ { type: 'separator' },
298
+ {
299
+ label: 'Home',
300
+ accelerator: 'Shift+CmdOrCtrl+H',
301
+ action: 'home',
302
+ browserAccelerator: true,
303
+ shortcutLabel: 'go home',
304
+ showInShortcutDocs: true,
305
+ },
306
+ {
307
+ label: 'Lock',
308
+ accelerator: 'CmdOrCtrl+L',
309
+ action: 'lock',
310
+ shortcutLabel: 'lock / unlock',
311
+ showInShortcutDocs: true,
312
+ },
313
+ { label: 'Shake', accelerator: 'Ctrl+CmdOrCtrl+Z', action: 'shake' },
314
+ {
315
+ label: 'App Switcher',
316
+ accelerator: 'Ctrl+Shift+CmdOrCtrl+H',
317
+ action: 'app-switcher',
318
+ browserAccelerator: true,
319
+ shortcutLabel: 'app switcher',
320
+ showInShortcutDocs: true,
321
+ },
322
+ { type: 'separator' },
323
+ { label: 'Trigger Screenshot', action: 'screenshot' },
324
+ { type: 'separator' },
325
+ ...buildSootSimDeviceChoiceDefs(state.currentDevice, deviceChoices, {
326
+ action: 'switch-device',
327
+ }),
328
+ ],
329
+ }
330
+ }
331
+
332
+ function debugMenu(state: SootSimMenuState): SootSimMenuDef {
333
+ return {
334
+ label: 'Debug',
335
+ items: [
336
+ // both toggles are stateful in the browser, where the open state lives
337
+ // in the same document. electron's menu process cannot see it, so it
338
+ // keeps the plain (stateless) items rather than a permanently
339
+ // unchecked checkbox — same reasoning as the screenshot/3d entries
340
+ // being hidden there. accelerators stay single-registered per surface.
341
+ {
342
+ label: 'Toggle Devtools',
343
+ accelerator: 'Alt+I',
344
+ action: 'toggle-devtools',
345
+ showInShortcutDocs: true,
346
+ hiddenIn: 'browser',
347
+ },
348
+ {
349
+ label: 'Toggle Devtools',
350
+ type: 'checkbox',
351
+ checked: state.devtoolsOpen,
352
+ accelerator: 'Alt+I',
353
+ action: 'toggle-devtools',
354
+ browserAccelerator: true,
355
+ showInShortcutDocs: true,
356
+ hiddenIn: 'electron',
357
+ },
358
+ {
359
+ label: 'Inspect',
360
+ accelerator: 'Shift+CmdOrCtrl+C',
361
+ action: 'toggle-inspect',
362
+ hiddenIn: 'browser',
363
+ },
364
+ {
365
+ label: 'Inspect',
366
+ type: 'checkbox',
367
+ checked: state.inspectMode,
368
+ accelerator: 'Shift+CmdOrCtrl+C',
369
+ action: 'toggle-inspect',
370
+ browserAccelerator: true,
371
+ hiddenIn: 'electron',
372
+ },
373
+ { type: 'separator' },
374
+ {
375
+ label: 'Open Chrome Devtools',
376
+ accelerator: 'CmdOrCtrl+Alt+I',
377
+ action: 'open-devtools',
378
+ hiddenIn: 'browser',
379
+ shortcutLabel: 'developer tools',
380
+ showInShortcutDocs: true,
381
+ },
382
+ { type: 'separator' },
383
+ {
384
+ label: 'Always Show Live WebViews',
385
+ type: 'checkbox',
386
+ checked: state.alwaysShowLiveWebViews,
387
+ action: 'toggle-live-webviews',
388
+ },
389
+ {
390
+ label: 'Send to Box Carries Sign-In',
391
+ type: 'checkbox',
392
+ checked: state.sendToBoxCarryAuth,
393
+ action: 'toggle-send-to-box-auth',
394
+ },
395
+ { type: 'separator' },
396
+ {
397
+ label: 'Clear Cache and Reload',
398
+ accelerator: 'CmdOrCtrl+Shift+R',
399
+ action: 'clear-cache',
400
+ },
401
+ { label: 'Clear Local Storage', action: 'clear-localstorage' },
402
+ { label: 'Clear IndexedDB', action: 'clear-indexeddb' },
403
+ { label: 'Clear Service Workers', action: 'clear-serviceworkers' },
404
+ { type: 'separator' },
405
+ {
406
+ label: 'Open User Data Folder',
407
+ action: 'open-userdata',
408
+ hiddenIn: 'browser',
409
+ },
410
+ ],
411
+ }
412
+ }
413
+
414
+ function windowMenu(state: SootSimMenuState): SootSimMenuDef {
415
+ return {
416
+ label: 'Window',
417
+ items: [
418
+ {
419
+ label: 'Minimize',
420
+ accelerator: 'CmdOrCtrl+M',
421
+ action: 'minimize',
422
+ hiddenIn: 'browser',
423
+ },
424
+ {
425
+ label: 'Close',
426
+ accelerator: 'CmdOrCtrl+W',
427
+ action: 'close-window',
428
+ hiddenIn: 'browser',
429
+ },
430
+ { type: 'separator' },
431
+ {
432
+ label: 'Toggle Appearance',
433
+ accelerator: 'Shift+CmdOrCtrl+A',
434
+ action: 'toggle-appearance',
435
+ browserAccelerator: true,
436
+ shortcutLabel: 'toggle appearance',
437
+ showInShortcutDocs: true,
438
+ },
439
+ { type: 'separator' },
440
+ {
441
+ label: 'Show Device Bezels',
442
+ type: 'checkbox',
443
+ checked: state.showBezels,
444
+ action: 'toggle-bezels',
445
+ },
446
+ {
447
+ label: 'Show Menu Bar',
448
+ type: 'checkbox',
449
+ checked: state.showMenuBar,
450
+ action: 'toggle-menubar',
451
+ hiddenIn: 'electron',
452
+ },
453
+ {
454
+ label: 'Show Rail',
455
+ type: 'checkbox',
456
+ checked: state.showRail,
457
+ action: 'toggle-rail',
458
+ hiddenIn: 'electron',
459
+ },
460
+ {
461
+ // desktop only. the device window is frameless and transparent, which
462
+ // is right for a phone floating on the desktop and wrong for the
463
+ // full-window modes; this pins the solid style on all the time. 3d
464
+ // mode switches to it on its own either way.
465
+ label: 'Solid Window',
466
+ type: 'checkbox',
467
+ checked: state.solidWindow,
468
+ action: 'toggle-solid-window',
469
+ hiddenIn: 'browser',
470
+ },
471
+ {
472
+ // browser only: the wash needs several glow radii of stage around the
473
+ // device to fade out in, and electron's transparent window is sized to
474
+ // the device, so there it can only paint a clipped rectangle.
475
+ label: 'Ambient Glow',
476
+ hiddenIn: 'electron',
477
+ items: SOOTSIM_AMBIENT_GLOW_LEVELS.map((level) => ({
478
+ label: SOOTSIM_AMBIENT_GLOW_LABELS[level],
479
+ type: 'radio' as const,
480
+ checked: state.ambientGlow === level,
481
+ action: 'set-ambient-glow',
482
+ actionPayload: level,
483
+ })),
484
+ },
485
+ // both eras stay selectable while the ios 27 home surfaces land one at a
486
+ // time, and the device's own os picks the starting value. the switch is
487
+ // the iPhone Duo's, so it is offered exactly where the Duo is.
488
+ ...(state.developmentMode
489
+ ? [
490
+ {
491
+ label: 'Home Screen',
492
+ items: SOOTSIM_HOME_SCREEN_VERSIONS.map((version) => ({
493
+ label: SOOTSIM_HOME_SCREEN_VERSION_LABELS[version],
494
+ type: 'radio' as const,
495
+ checked: state.homeScreenVersion === version,
496
+ action: 'set-home-screen',
497
+ actionPayload: version,
498
+ })),
499
+ },
500
+ ]
501
+ : []),
502
+ {
503
+ label: 'Wireframe',
504
+ type: 'checkbox',
505
+ checked: state.wireframe,
506
+ action: 'toggle-wireframe',
507
+ hiddenIn: 'electron',
508
+ },
509
+ {
510
+ label: 'Screenshot',
511
+ type: 'checkbox',
512
+ checked: state.screenshotMode,
513
+ action: 'toggle-screenshot-mode',
514
+ hiddenIn: 'electron',
515
+ },
516
+ {
517
+ label: '3D',
518
+ type: 'checkbox',
519
+ checked: state.threeMode,
520
+ action: 'toggle-three-mode',
521
+ hiddenIn: 'electron',
522
+ },
523
+ {
524
+ label: 'Stay On Top',
525
+ type: 'checkbox',
526
+ checked: state.stayOnTop,
527
+ action: 'toggle-stay-on-top',
528
+ hiddenIn: 'browser',
529
+ },
530
+ { type: 'separator' },
531
+ {
532
+ label: 'Physical Size',
533
+ accelerator: 'CmdOrCtrl+1',
534
+ type: 'radio',
535
+ checked: state.scaleMode === 'physical',
536
+ action: 'scale-physical',
537
+ },
538
+ {
539
+ label: 'Point Accurate',
540
+ accelerator: 'CmdOrCtrl+2',
541
+ type: 'radio',
542
+ checked: state.scaleMode === 'point',
543
+ action: 'scale-point',
544
+ },
545
+ {
546
+ label: 'Pixel Accurate',
547
+ accelerator: 'CmdOrCtrl+3',
548
+ type: 'radio',
549
+ checked: state.scaleMode === 'pixel',
550
+ action: 'scale-pixel',
551
+ },
552
+ {
553
+ label: 'Fit Screen',
554
+ accelerator: 'CmdOrCtrl+0',
555
+ type: 'radio',
556
+ checked: state.scaleMode === 'fit',
557
+ action: 'scale-fit',
558
+ },
559
+ ],
560
+ }
561
+ }
562
+
563
+ function helpMenu(): SootSimMenuDef {
564
+ return {
565
+ label: 'Help',
566
+ notMinimal: true,
567
+ items: [
568
+ { label: `${rnxPublicBrand.name} Website`, action: 'open-website' },
569
+ {
570
+ label: 'Send Feedback...',
571
+ accelerator: 'CmdOrCtrl+Shift+I',
572
+ action: 'open-devtools-report',
573
+ showInShortcutDocs: true,
574
+ },
575
+ ],
576
+ }
577
+ }
578
+
579
+ export function buildSootSimDeviceChoiceDefs(
580
+ currentDevice: string,
581
+ deviceChoices: readonly SootSimMenuDeviceChoice[],
582
+ options: { action: 'switch-device' | 'new-simulator' },
583
+ ): SootSimMenuItemDef[] {
584
+ const items: SootSimMenuItemDef[] = []
585
+ let lastPlatform = ''
586
+ const isStateful = options.action === 'switch-device'
587
+
588
+ for (const choice of deviceChoices) {
589
+ if (lastPlatform && choice.platform !== lastPlatform)
590
+ items.push({ type: 'separator' })
591
+ lastPlatform = choice.platform
592
+
593
+ if (isStateful) {
594
+ items.push({
595
+ label: choice.label,
596
+ type: 'radio',
597
+ checked: choice.model === currentDevice,
598
+ action: options.action,
599
+ actionPayload: choice.model,
600
+ })
601
+ } else {
602
+ items.push({
603
+ label: choice.label,
604
+ action: options.action,
605
+ actionPayload: choice.model,
606
+ })
607
+ }
608
+ }
609
+
610
+ return items
611
+ }
612
+
613
+ function shouldIncludeItemForPlatform(
614
+ item: Exclude<SootSimMenuItemDef, { type: 'separator' }>,
615
+ platform: SootSimMenuPlatform,
616
+ ) {
617
+ if (item.hiddenIn === platform) return false
618
+ if (!item.accelerator || !item.action) return false
619
+ if (platform === 'browser') return item.browserAccelerator === true
620
+ return true
621
+ }
622
+
623
+ function shouldIncludeItemInDocs(
624
+ item: Exclude<SootSimMenuItemDef, { type: 'separator' }>,
625
+ platform: SootSimMenuPlatform,
626
+ ) {
627
+ if (!item.showInShortcutDocs) return false
628
+ if (platform === 'browser' && !item.browserAccelerator) return false
629
+ return true
630
+ }
631
+
632
+ function collectMenuShortcutsFromDefs(
633
+ menus: readonly SootSimMenuDef[],
634
+ platform: SootSimMenuPlatform,
635
+ includeItem: (
636
+ item: Exclude<SootSimMenuItemDef, { type: 'separator' }>,
637
+ platform: SootSimMenuPlatform,
638
+ ) => boolean,
639
+ ) {
640
+ const entries: SootSimMenuShortcutEntry[] = []
641
+
642
+ const visit = (menuLabel: string, items: readonly SootSimMenuItemDef[]) => {
643
+ for (const item of items) {
644
+ if (item.type === 'separator') continue
645
+ if (item.items?.length) visit(menuLabel, item.items)
646
+ if (!includeItem(item, platform)) continue
647
+ const { accelerator, action } = item
648
+ if (!accelerator || !action) continue
649
+ entries.push({
650
+ menuLabel,
651
+ label: item.shortcutLabel ?? item.label,
652
+ accelerator,
653
+ action,
654
+ actionPayload: item.actionPayload,
655
+ })
656
+ }
657
+ }
658
+
659
+ for (const menu of menus) {
660
+ if (menu.hiddenIn === platform) continue
661
+ visit(menu.label, menu.items)
662
+ }
663
+
664
+ return entries
665
+ }
666
+
667
+ export function getMenuShortcutEntriesFromDefs(
668
+ menus: readonly SootSimMenuDef[],
669
+ platform: SootSimMenuPlatform,
670
+ ): SootSimMenuShortcutEntry[] {
671
+ return collectMenuShortcutsFromDefs(menus, platform, shouldIncludeItemForPlatform)
672
+ }
673
+
674
+ export function getMenuShortcutEntries(
675
+ state: SootSimMenuState,
676
+ deviceChoices: readonly SootSimMenuDeviceChoice[],
677
+ platform: SootSimMenuPlatform,
678
+ ): SootSimMenuShortcutEntry[] {
679
+ return getMenuShortcutEntriesFromDefs(
680
+ buildSootSimMenuDefs(state, deviceChoices),
681
+ platform,
682
+ )
683
+ }
684
+
685
+ export function getShortcutDocSections(
686
+ state: SootSimMenuState,
687
+ deviceChoices: readonly SootSimMenuDeviceChoice[],
688
+ platform: SootSimMenuPlatform,
689
+ ): SootSimShortcutDocSection[] {
690
+ return getShortcutDocSectionsFromDefs(
691
+ buildSootSimMenuDefs(state, deviceChoices),
692
+ platform,
693
+ )
694
+ }
695
+
696
+ export function getShortcutDocSectionsFromDefs(
697
+ menus: readonly SootSimMenuDef[],
698
+ platform: SootSimMenuPlatform,
699
+ ): SootSimShortcutDocSection[] {
700
+ const sections: SootSimShortcutDocSection[] = []
701
+ const byMenu = new Map<string, SootSimShortcutDocEntry[]>()
702
+
703
+ for (const entry of collectMenuShortcutsFromDefs(
704
+ menus,
705
+ platform,
706
+ shouldIncludeItemInDocs,
707
+ )) {
708
+ const items = byMenu.get(entry.menuLabel) ?? []
709
+ items.push({
710
+ keys: formatAccelerator(entry.accelerator),
711
+ label: entry.label,
712
+ })
713
+ byMenu.set(entry.menuLabel, items)
714
+ }
715
+
716
+ for (const [title, entries] of byMenu) {
717
+ sections.push({
718
+ title: title.toLowerCase(),
719
+ entries,
720
+ })
721
+ }
722
+
723
+ return sections
724
+ }
725
+
726
+ export function formatAccelerator(accel: string): string {
727
+ return accel
728
+ .replace(/CmdOrCtrl\+/g, '\u2318')
729
+ .replace(/Ctrl\+/g, '\u2303')
730
+ .replace(/Alt\+/g, '\u2325')
731
+ .replace(/Shift\+/g, '\u21e7')
732
+ .replace(/Left/g, '\u2190')
733
+ .replace(/Right/g, '\u2192')
734
+ .replace(/Up/g, '\u2191')
735
+ .replace(/Down/g, '\u2193')
736
+ }
737
+
738
+ const ARROW_KEYS: Record<string, string> = {
739
+ Left: 'arrowleft',
740
+ Right: 'arrowright',
741
+ Up: 'arrowup',
742
+ Down: 'arrowdown',
743
+ }
744
+
745
+ function normalizeAcceleratorKey(key: string) {
746
+ return ARROW_KEYS[key] ?? key.toLowerCase()
747
+ }
748
+
749
+ function normalizeEventKey(key: string) {
750
+ return key.toLowerCase()
751
+ }
752
+
753
+ type KeyboardEventLike = Pick<
754
+ KeyboardEvent,
755
+ 'altKey' | 'ctrlKey' | 'key' | 'metaKey' | 'shiftKey'
756
+ >
757
+
758
+ export function matchesAccelerator(event: KeyboardEventLike, accelerator: string) {
759
+ const tokens = accelerator.split('+')
760
+ const keyToken = tokens[tokens.length - 1]
761
+ const wantsCmdOrCtrl = tokens.includes('CmdOrCtrl')
762
+ const wantsCtrl = tokens.includes('Ctrl')
763
+ const wantsAlt = tokens.includes('Alt')
764
+ const wantsShift = tokens.includes('Shift')
765
+
766
+ if (!keyToken) return false
767
+ if (event.altKey !== wantsAlt) return false
768
+ if (event.shiftKey !== wantsShift) return false
769
+
770
+ const hasCmdOrCtrl = event.metaKey || event.ctrlKey
771
+ if (wantsCmdOrCtrl && !hasCmdOrCtrl) return false
772
+ if (!wantsCmdOrCtrl && event.metaKey) return false
773
+ if (wantsCtrl && !event.ctrlKey) return false
774
+ if (!wantsCtrl && !wantsCmdOrCtrl && event.ctrlKey) return false
775
+
776
+ return normalizeEventKey(event.key) === normalizeAcceleratorKey(keyToken)
777
+ }