peachsim 0.1.577 → 0.1.579

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 (178) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +11 -9
  3. package/cli/app-catalog.ts +77 -0
  4. package/cli/app-fonts.ts +4 -20
  5. package/cli/app-splash.ts +2 -18
  6. package/cli/bridge-flow-runner.ts +505 -139
  7. package/cli/cloud-client.ts +1 -4
  8. package/cli/cloud-session.ts +1 -4
  9. package/cli/cloud-test-result-files.ts +3 -0
  10. package/cli/cloud-test-tunnel.ts +2 -5
  11. package/cli/command-registry.ts +2 -0
  12. package/cli/commands/agent.ts +149 -23
  13. package/cli/commands/assert.ts +1 -4
  14. package/cli/commands/box/peach-command.ts +62 -0
  15. package/cli/commands/box/plane-share.ts +0 -70
  16. package/cli/commands/box/rnx-command.ts +1 -60
  17. package/cli/commands/box/search-command.ts +14 -0
  18. package/cli/commands/box.ts +2 -2
  19. package/cli/commands/control.ts +121 -27
  20. package/cli/commands/cpu-profile.ts +23 -10
  21. package/cli/commands/daemon.ts +1 -9
  22. package/cli/commands/detox.ts +26 -18
  23. package/cli/commands/device.ts +129 -1
  24. package/cli/commands/film.ts +25 -16
  25. package/cli/commands/flow.ts +9 -2
  26. package/cli/commands/inspect/actions.ts +75 -1
  27. package/cli/commands/inspect/core.ts +81 -2
  28. package/cli/commands/inspect/describe.ts +2 -6
  29. package/cli/commands/inspect/env.ts +1 -1
  30. package/cli/commands/inspect/settling.ts +20 -3
  31. package/cli/commands/inspect/shared.ts +1 -4
  32. package/cli/commands/inspect/sleep.ts +1 -1
  33. package/cli/commands/inspect.ts +16 -49
  34. package/cli/commands/install-desktop.ts +8 -6
  35. package/cli/commands/login.ts +3 -5
  36. package/cli/commands/maestro-generate.ts +1 -4
  37. package/cli/commands/maestro.ts +38 -21
  38. package/cli/commands/no-bridge-hint.ts +0 -14
  39. package/cli/commands/permissions.ts +2 -2
  40. package/cli/commands/platform.ts +13 -6
  41. package/cli/commands/screenshot.ts +6 -1
  42. package/cli/commands/skills.ts +1 -1
  43. package/cli/commands/test.ts +6 -2
  44. package/cli/commands/three-mode.ts +162 -19
  45. package/cli/commands/upload.ts +3 -18
  46. package/cli/current-sim.ts +0 -1
  47. package/cli/desktop-companion.ts +40 -8
  48. package/cli/drivers/playwright-provisioning.ts +1 -4
  49. package/cli/flow-export.ts +1 -8
  50. package/cli/flow-session.ts +0 -4
  51. package/cli/hints.ts +1 -16
  52. package/cli/inspect-notice-state.ts +0 -4
  53. package/cli/main.ts +2 -4
  54. package/cli/outbound-endpoints.ts +1 -24
  55. package/cli/parse-args.ts +1 -2
  56. package/cli/rnx-in-process.ts +3 -1
  57. package/cli/run-rnx-core.ts +4 -2
  58. package/cli/run-rnx.ts +18 -10
  59. package/cli/send-to-box.ts +1 -4
  60. package/cli/telemetry.ts +1 -1
  61. package/cli/ws-bridge.ts +1 -5
  62. package/detox/gestures.ts +20 -6
  63. package/detox/index.ts +379 -123
  64. package/detox/navigation-lifecycle.ts +22 -13
  65. package/dist-lib/agent-daemon-client.cjs +21 -15
  66. package/dist-lib/agent-events.cjs +1 -1
  67. package/dist-lib/agent-identity.cjs +3 -17
  68. package/dist-lib/agent-sessions.cjs +5 -4
  69. package/dist-lib/attached-projects.cjs +1 -77
  70. package/dist-lib/auth/shared-session.cjs +1 -1
  71. package/dist-lib/backend-origin.cjs +11 -1
  72. package/dist-lib/beta.cjs +1 -1
  73. package/dist-lib/beta.mjs +1 -1
  74. package/dist-lib/bridge-constants.cjs +1 -1
  75. package/dist-lib/bridge-contract-input.cjs +7 -5
  76. package/dist-lib/bridge-contract-input.mjs +7 -5
  77. package/dist-lib/bridge-contract.cjs +1 -1
  78. package/dist-lib/bridge-contract.mjs +1 -1
  79. package/dist-lib/capture-contract.cjs +30 -7
  80. package/dist-lib/capture-contract.mjs +30 -7
  81. package/dist-lib/cli-constants.cjs +1 -1
  82. package/dist-lib/cloud-contract.cjs +1 -1
  83. package/dist-lib/cloud-contract.mjs +1 -1
  84. package/dist-lib/cloud-test-args.cjs +1 -1
  85. package/dist-lib/cloud-test-args.mjs +1 -1
  86. package/dist-lib/cloud.cjs +17 -11
  87. package/dist-lib/cloud.mjs +17 -11
  88. package/dist-lib/config.cjs +1 -1
  89. package/dist-lib/detox/index.cjs +321 -122
  90. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  91. package/dist-lib/home-paths.cjs +1 -1
  92. package/dist-lib/host/bridge-host.cjs +427 -212
  93. package/dist-lib/host/fetch-proxy-handler.cjs +5 -28
  94. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  95. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  96. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  97. package/dist-lib/host/websocket-proxy.cjs +1 -1
  98. package/dist-lib/index.cjs +315 -58
  99. package/dist-lib/jump-to-source-babel.cjs +1 -1
  100. package/dist-lib/jump-to-source-native.cjs +1 -1
  101. package/dist-lib/menu.cjs +67 -31
  102. package/dist-lib/menu.mjs +64 -28
  103. package/dist-lib/metro-fingerprint-registry.cjs +8 -4
  104. package/dist-lib/metro-fingerprint-registry.mjs +6 -4
  105. package/dist-lib/metro-production-bundle.cjs +2 -2
  106. package/dist-lib/metro-production-bundle.mjs +2 -2
  107. package/dist-lib/metro.cjs +8 -4
  108. package/dist-lib/profiles.cjs +1 -1
  109. package/dist-lib/public-brand.cjs +7 -4
  110. package/dist-lib/react-native-host-modules.cjs +1 -1
  111. package/dist-lib/react-native-host-modules.mjs +1 -1
  112. package/dist-lib/render-mode.cjs +3 -13
  113. package/dist-lib/scripts/dev-server-scanner.cjs +5 -17
  114. package/dist-lib/sdk.cjs +129 -10
  115. package/dist-lib/sdk.mjs +127 -10
  116. package/dist-lib/skills.cjs +1635 -1121
  117. package/dist-lib/swift-compile.cjs +277 -70
  118. package/dist-lib/swift-compile.mjs +275 -70
  119. package/dist-lib/swift-package.cjs +17 -5
  120. package/dist-lib/swift-package.mjs +15 -4
  121. package/dist-lib/swift-submit.cjs +32 -8
  122. package/dist-lib/swift-submit.mjs +29 -7
  123. package/dist-lib/swift.cjs +30912 -577
  124. package/dist-lib/vite-kotlin.cjs +356 -0
  125. package/dist-lib/vite-swift.cjs +1743 -0
  126. package/dist-lib/vite.cjs +208 -1039
  127. package/kotlin/README.md +18 -0
  128. package/kotlin/Sources/androidx/annotation/Keep.kt +4 -0
  129. package/kotlin/Sources/dev/onejs/one/OneNativeSourceDispatch.kt +7 -0
  130. package/kotlin/Sources/dev/onejs/one/source/OneModule.kt +4 -0
  131. package/kotlin/Sources/org/json/JSON.kt +244 -0
  132. package/kotlin/toolchain.json +19 -0
  133. package/package.json +14 -2
  134. package/scripts/dev-server-scanner.ts +0 -18
  135. package/scripts/optional-demo-registry.ts +0 -17
  136. package/skills/contrast/SKILL.md +2 -2
  137. package/skills/peach-setup/SKILL.md +16 -0
  138. package/src/agent-daemon-client.ts +36 -22
  139. package/src/agent-identity.ts +0 -15
  140. package/src/agent-prompt.ts +12 -0
  141. package/src/attached-projects.ts +0 -72
  142. package/src/backend-origin.ts +10 -0
  143. package/src/bridge-contract-input.ts +2 -5
  144. package/src/bridge-contract.ts +2 -0
  145. package/src/capture-contract.ts +53 -11
  146. package/src/cloud.ts +4 -9
  147. package/src/dev-server-open.ts +0 -99
  148. package/src/home-paths.ts +2 -3
  149. package/src/host/agent-host.ts +82 -36
  150. package/src/host/bridge-host.ts +5 -11
  151. package/src/host/fetch-proxy-handler.ts +19 -43
  152. package/src/is-record.ts +4 -0
  153. package/src/menu.ts +82 -37
  154. package/src/metro-fingerprint-registry.ts +1 -4
  155. package/src/metro-production-bundle.ts +1 -1
  156. package/src/plist.ts +1 -4
  157. package/src/public-brand.ts +5 -3
  158. package/src/render-mode.ts +0 -12
  159. package/src/runtime-assets.ts +9 -2
  160. package/src/screenshots/compose.ts +7 -1
  161. package/src/screenshots/frame-compose.ts +21 -10
  162. package/src/screenshots/schema.ts +1 -4
  163. package/src/sim-client.ts +2 -4
  164. package/src/skills/builtin/maestro-test.ts +1 -4
  165. package/src/sleep.ts +4 -0
  166. package/src/swift-compile.ts +209 -47
  167. package/src/swift-icu.ts +140 -0
  168. package/src/swift-package.ts +26 -2
  169. package/src/swift-submit.ts +84 -4
  170. package/src/swift.ts +2 -0
  171. package/src/vite-plugin-kotlin.ts +376 -0
  172. package/src/vite-plugin-one.ts +224 -36
  173. package/src/vite-plugin-swift.ts +382 -96
  174. package/src/vite-plugin.ts +121 -134
  175. package/src/vite.d.ts +10 -0
  176. package/cli/resolve-assets.ts +0 -63
  177. package/src/rolldown-plugin-swift.ts +0 -244
  178. package/src/team-machine-prompt.ts +0 -73
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.577 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.579 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -139,7 +139,7 @@ var RNXSIM_METRO_MODULE_PATHS_PREFIX = "globalThis.__sootsimModulePaths=";
139
139
  var RNX_METRO_MODULE_IDENTITY_VERSION = 2;
140
140
  var RNXSIM_METRO_SOURCE_MAP_COMMENT = "//# sourceMappingURL=";
141
141
  function detachRNXMetroSourceMapUrl(bundleText) {
142
- const trimmedEnd = bundleText.replace(/\s+$/, "");
142
+ const trimmedEnd = bundleText.trimEnd();
143
143
  const commentStart = trimmedEnd.lastIndexOf(`
144
144
  ${RNXSIM_METRO_SOURCE_MAP_COMMENT}`);
145
145
  if (commentStart < 0) return { source: bundleText, sourceMappingUrl: null };
@@ -161,13 +161,14 @@ ${RNXSIM_METRO_SOURCE_MAP_COMMENT}${detached.sourceMappingUrl}`;
161
161
 
162
162
  // src/public-brand.ts
163
163
  var name = "Peach";
164
- var rnxPublicBrand = Object.freeze({
164
+ var peachPublicBrand = Object.freeze({
165
165
  name,
166
166
  packageName: "peachsim",
167
167
  commandName: "peach",
168
- domain: "rnxsim.com",
169
- origin: "https://rnxsim.com"
168
+ domain: "peachsim.com",
169
+ origin: "https://peachsim.com"
170
170
  });
171
+ var rnxPublicBrand = peachPublicBrand;
171
172
 
172
173
  // src/home-paths.ts
173
174
  var import_node_fs = __toESM(require("node:fs"), 1);
@@ -295,6 +296,11 @@ var sinks = new import_node_async_hooks.AsyncLocalStorage();
295
296
  // cli/auth.ts
296
297
  var CHOOSE_ACCOUNT_HINT = "this sign-in belongs to several accounts; run `peach login --account <name>` to choose the one to bill";
297
298
 
299
+ // src/is-record.ts
300
+ function isRecord(value) {
301
+ return typeof value === "object" && value !== null && !Array.isArray(value);
302
+ }
303
+
298
304
  // cli/cloud-session.ts
299
305
  var CLOUD_SESSION_VERSION = 3;
300
306
  var MAX_ENCODED_SESSION_BYTES = 32 * 1024;
@@ -302,9 +308,6 @@ var PEACH_CLOUD_SESSION_ENV = "PEACH_CLOUD_SESSION";
302
308
  var RNX_SHELL_UPDATE_PREFIX = "__RNX_SHELL_UPDATE_V1__";
303
309
  var RNX_SHELL_UPDATE_SET_PREFIX = `${RNX_SHELL_UPDATE_PREFIX}set:`;
304
310
  var RNX_SHELL_UPDATE_UNSET = `${RNX_SHELL_UPDATE_PREFIX}unset`;
305
- function isRecord(value) {
306
- return value !== null && typeof value === "object" && !Array.isArray(value);
307
- }
308
311
  function normalizeCloudOrigin(source) {
309
312
  let parsed;
310
313
  try {
@@ -432,9 +435,6 @@ function readCloudSession(environment = process.env) {
432
435
 
433
436
  // cli/cloud-client.ts
434
437
  var DEFAULT_RNX_CLOUD_ORIGIN = rnxPublicBrand.origin;
435
- function isRecord2(value) {
436
- return value !== null && typeof value === "object" && !Array.isArray(value);
437
- }
438
438
  function failureSentence(text) {
439
439
  let parsed;
440
440
  try {
@@ -442,8 +442,8 @@ function failureSentence(text) {
442
442
  } catch {
443
443
  return null;
444
444
  }
445
- if (!isRecord2(parsed)) return null;
446
- if (isRecord2(parsed.error) && typeof parsed.error.message === "string") {
445
+ if (!isRecord(parsed)) return null;
446
+ if (isRecord(parsed.error) && typeof parsed.error.message === "string") {
447
447
  const code = typeof parsed.error.code === "string" && parsed.error.code ? ` [${parsed.error.code}]` : "";
448
448
  return `${parsed.error.message}${code}`;
449
449
  }
@@ -535,7 +535,7 @@ var CloudBridge = class {
535
535
  } catch {
536
536
  throw new Error("remote simulator command returned unreadable JSON");
537
537
  }
538
- if (!isRecord2(parsed) || parsed.id !== body.id) {
538
+ if (!isRecord(parsed) || parsed.id !== body.id) {
539
539
  throw new Error("remote simulator command returned an invalid command receipt");
540
540
  }
541
541
  if (typeof parsed.error === "string" && parsed.error) throw new Error(parsed.error);
@@ -606,7 +606,7 @@ var CloudBoxBridge = class {
606
606
  } catch {
607
607
  throw new Error("box command returned unreadable JSON");
608
608
  }
609
- if (!isRecord2(parsed) || parsed.id !== id) {
609
+ if (!isRecord(parsed) || parsed.id !== id) {
610
610
  throw new Error("box command returned an invalid command receipt");
611
611
  }
612
612
  if (typeof parsed.error === "string" && parsed.error) throw new Error(parsed.error);
@@ -829,8 +829,6 @@ var CURRENT_SIM_PATH_ENV = "SOOTSIM_CLI_CURRENT_SIM_PATH";
829
829
  var IDENTITY_ENV_VARS = [
830
830
  "PEACH_CLI_IDENTITY",
831
831
  // explicit override — fastest path, skips all pid lookups
832
- "TM_SESSION",
833
- // Team Machine session id; routes prompt-bar followups back to this agent
834
832
  "CLAUDE_CODE_SESSION_ID",
835
833
  // claude code, if ever set
836
834
  "CODEX_THREAD_ID",
@@ -1448,6 +1446,24 @@ async function inspectDescribe(bridge, dumpOpts) {
1448
1446
  nodeCount: result.total
1449
1447
  };
1450
1448
  }
1449
+ function layoutBoundFontFamily(style) {
1450
+ const raw = typeof style === "object" && style !== null ? style.fontFamily : void 0;
1451
+ if (typeof raw !== "string") return void 0;
1452
+ const first = raw.split(",")[0].trim().replace(/^['"]|['"]$/g, "");
1453
+ if (!first) return void 0;
1454
+ const systemFamilies = /* @__PURE__ */ new Set([
1455
+ "system",
1456
+ "-apple-system",
1457
+ "system-ui",
1458
+ "blinkmacsystemfont",
1459
+ "segoe ui",
1460
+ "roboto",
1461
+ "sans-serif",
1462
+ "serif",
1463
+ "monospace"
1464
+ ]);
1465
+ return systemFamilies.has(first.toLowerCase()) ? void 0 : first;
1466
+ }
1451
1467
  function scoreLayoutTextContrast(input) {
1452
1468
  const required = input.fontSize >= 18 || input.fontSize >= 14 && (input.fontWeight ?? 400) >= 700 ? 3 : 4.5;
1453
1469
  const { normal, background } = input;
@@ -1600,6 +1616,7 @@ function layoutGetNativeEval(opts = {}) {
1600
1616
  const n = w === 'bold' ? 700 : (typeof w === 'number' ? w : (typeof w === 'string' ? parseInt(w, 10) : NaN))
1601
1617
  return Number.isFinite(n) && n >= 600 ? n : undefined
1602
1618
  }
1619
+ const familyOf = ${layoutBoundFontFamily.toString()}
1603
1620
  const decoOf = (style) => {
1604
1621
  const d = style && (style.textDecorationLine || style.textDecoration)
1605
1622
  return d && d !== 'none' && d !== 'normal' ? String(d).split(' ')[0] : undefined
@@ -1647,6 +1664,27 @@ function layoutGetNativeEval(opts = {}) {
1647
1664
  const layout = n.layout || { x: 0, y: 0, width: 0, height: 0 }
1648
1665
  const abs = n.absolute || n.absolutePosition || { x: layout.x, y: layout.y }
1649
1666
  const text = typeof n.text === 'string' && n.text.trim() ? n.text.trim() : undefined
1667
+ const rawHitSlop =
1668
+ n.hitSlop != null
1669
+ ? n.hitSlop
1670
+ : (n.props && n.props.hitSlop != null)
1671
+ ? n.props.hitSlop
1672
+ : (n.style && n.style.hitSlop != null)
1673
+ ? n.style.hitSlop
1674
+ : (n.authoredStyle && n.authoredStyle.hitSlop != null)
1675
+ ? n.authoredStyle.hitSlop
1676
+ : undefined
1677
+ const hitSlop =
1678
+ typeof rawHitSlop === 'number'
1679
+ ? (rawHitSlop !== 0 ? rawHitSlop : undefined)
1680
+ : rawHitSlop && typeof rawHitSlop === 'object'
1681
+ ? {
1682
+ t: round(rawHitSlop.top ?? rawHitSlop.t),
1683
+ r: round(rawHitSlop.right ?? rawHitSlop.r),
1684
+ b: round(rawHitSlop.bottom ?? rawHitSlop.b),
1685
+ l: round(rawHitSlop.left ?? rawHitSlop.l),
1686
+ }
1687
+ : undefined
1650
1688
  const base = {
1651
1689
  ...(typeof n.nodeId === 'number'
1652
1690
  ? {
@@ -1661,6 +1699,7 @@ function layoutGetNativeEval(opts = {}) {
1661
1699
  type: typeof n.type === 'string' ? n.type : 'node',
1662
1700
  text,
1663
1701
  box: { x: round(abs.x), y: round(abs.y), w: round(layout.width), h: round(layout.height) },
1702
+ ...(hitSlop !== undefined ? { hitSlop } : {}),
1664
1703
  }
1665
1704
  if (!includeStyle) return base
1666
1705
  // computedStyle has the resolved values; style is the curated subset \u2014
@@ -1677,6 +1716,7 @@ function layoutGetNativeEval(opts = {}) {
1677
1716
  bg: colorStr(style.backgroundColor),
1678
1717
  opacity: opacityOf(style),
1679
1718
  fontWeight: weightOf(style),
1719
+ fontFamily: text ? familyOf(style) : undefined,
1680
1720
  textDecoration: decoOf(style),
1681
1721
  overflow: overflowOf(style),
1682
1722
  borderWidth,
@@ -1737,6 +1777,7 @@ async function inspectAccessibilityTree(bridge) {
1737
1777
  });
1738
1778
  if (!isSemanticQueryResult(result)) return [];
1739
1779
  return result.nodes.flatMap((node) => {
1780
+ if (node.accessibilityHidden) return [];
1740
1781
  if (!node.role && !node.label && !node.text && !node.pressable) return [];
1741
1782
  return [
1742
1783
  {
@@ -2049,7 +2090,8 @@ async function waitForSootsimIdle({
2049
2090
  maxMs,
2050
2091
  pollMs = 50,
2051
2092
  stablePolls = 3,
2052
- strict = false
2093
+ strict = false,
2094
+ paintStill = false
2053
2095
  }) {
2054
2096
  const result = await bridge.send(
2055
2097
  {
@@ -2061,6 +2103,7 @@ async function waitForSootsimIdle({
2061
2103
  const pollMs = ${Math.max(1, Math.round(pollMs))}
2062
2104
  const requiredStablePolls = ${Math.max(1, Math.round(stablePolls))}
2063
2105
  const strict = ${strict ? "true" : "false"}
2106
+ const paintStill = ${paintStill ? "true" : "false"}
2064
2107
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
2065
2108
 
2066
2109
  // route-aware settle: a tap that pushes/pops a screen is async \u2014 the
@@ -2092,10 +2135,12 @@ async function waitForSootsimIdle({
2092
2135
  let requestTotal = 0
2093
2136
  let requestInFlight = 0
2094
2137
  let renderStatsAvailable = false
2138
+ let frameCount = -1
2095
2139
  try {
2096
2140
  const stats = await window.__sootsimRenderHost?.queryStats?.()
2097
2141
  if (stats) {
2098
2142
  renderStatsAvailable = true
2143
+ frameCount = typeof stats.frameCount === 'number' ? stats.frameCount : -1
2099
2144
  animating =
2100
2145
  stats.hasActiveAnims === true ||
2101
2146
  stats.hasActiveNativeAnimations === true ||
@@ -2128,7 +2173,7 @@ async function waitForSootsimIdle({
2128
2173
  }
2129
2174
  walk(root)
2130
2175
  }
2131
- return { layout: nodes.join(';'), animating, layoutDirty, pendingFetches, renderStatsAvailable, requestTotal, requestInFlight }
2176
+ return { layout: nodes.join(';'), frameCount, animating, layoutDirty, pendingFetches, renderStatsAvailable, requestTotal, requestInFlight }
2132
2177
  }
2133
2178
 
2134
2179
  // how long generic background network is allowed to keep us waiting AFTER
@@ -2143,6 +2188,8 @@ async function waitForSootsimIdle({
2143
2188
  // only within this grace once the screen is visually still.
2144
2189
  const networkQuietGraceMs = 1200
2145
2190
  let lastLayout = ''
2191
+ let lastFrameCount = -2
2192
+ let paintedPolls = 0
2146
2193
  let lastRequestTotal = -1
2147
2194
  let stable = 0
2148
2195
  let visuallyStillSince = 0
@@ -2159,13 +2206,18 @@ async function waitForSootsimIdle({
2159
2206
  if (snapshot.layoutDirty) dirtyPolls++
2160
2207
  if (snapshot.animating) animatingPolls++
2161
2208
  if (polls > 1 && snapshot.layout !== lastLayout) layoutChangedPolls++
2209
+ const painted = paintStill && snapshot.frameCount !== lastFrameCount
2210
+ if (polls > 1 && painted) paintedPolls++
2162
2211
  const strictOk =
2163
2212
  !strict ||
2164
2213
  (snapshot.renderStatsAvailable && !snapshot.animating && !snapshot.layoutDirty)
2165
2214
  // visually settled: no animations, no image content loading, layout
2166
2215
  // unchanged since the last poll.
2167
2216
  const visuallyStill =
2168
- strictOk && snapshot.pendingFetches === 0 && snapshot.layout === lastLayout
2217
+ strictOk &&
2218
+ !painted &&
2219
+ snapshot.pendingFetches === 0 &&
2220
+ snapshot.layout === lastLayout
2169
2221
  // generic network quiet: nothing in flight and no new requests issued.
2170
2222
  const networkQuiet =
2171
2223
  snapshot.requestInFlight === 0 && snapshot.requestTotal === lastRequestTotal
@@ -2186,6 +2238,7 @@ async function waitForSootsimIdle({
2186
2238
  visuallyStillSince = 0
2187
2239
  }
2188
2240
  lastLayout = snapshot.layout
2241
+ lastFrameCount = snapshot.frameCount
2189
2242
  lastRequestTotal = snapshot.requestTotal
2190
2243
  await sleep(pollMs)
2191
2244
  }
@@ -2196,6 +2249,7 @@ async function waitForSootsimIdle({
2196
2249
  if (last.layoutDirty) blockers.push('layout still dirty')
2197
2250
  if (last.pendingFetches > 0) blockers.push(last.pendingFetches + ' image fetches pending')
2198
2251
  if (last.layout !== lastLayout) blockers.push('layout still changing')
2252
+ if (paintStill && last.frameCount !== lastFrameCount) blockers.push('still painting')
2199
2253
  if (last.requestInFlight > 0) blockers.push(last.requestInFlight + ' requests in flight')
2200
2254
  return {
2201
2255
  settled: false,
@@ -2203,7 +2257,7 @@ async function waitForSootsimIdle({
2203
2257
  blockedBy:
2204
2258
  (blockers.length ? blockers.join(', ') : 'unknown') +
2205
2259
  ' over ' + polls + ' polls (layout changed ' + layoutChangedPolls +
2206
- 'x, layoutDirty ' + dirtyPolls + 'x, animating ' + animatingPolls + 'x)',
2260
+ 'x, layoutDirty ' + dirtyPolls + 'x, animating ' + animatingPolls + 'x, painted ' + paintedPolls + 'x)',
2207
2261
  }
2208
2262
  })()`
2209
2263
  },
@@ -2583,6 +2637,11 @@ async function openAppUrl(bridge, target) {
2583
2637
  return normalized;
2584
2638
  }
2585
2639
 
2640
+ // src/sleep.ts
2641
+ function sleep(ms) {
2642
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
2643
+ }
2644
+
2586
2645
  // src/sim-client.ts
2587
2646
  var SimClientError = class extends Error {
2588
2647
  constructor(message) {
@@ -2625,9 +2684,6 @@ var SimResetError = class extends SimClientError {
2625
2684
  this.result = result;
2626
2685
  }
2627
2686
  };
2628
- function sleep(ms) {
2629
- return new Promise((resolve3) => setTimeout(resolve3, ms));
2630
- }
2631
2687
  function decodePngDataUrl(dataUrl) {
2632
2688
  if (typeof dataUrl !== "string" || !dataUrl.startsWith("data:image/png;base64,")) {
2633
2689
  throw new SimClientError(
@@ -2650,7 +2706,8 @@ var TARGET_SEARCH_PARAMS = [
2650
2706
  "port",
2651
2707
  "inspectOpen",
2652
2708
  "appFonts",
2653
- "appSplash"
2709
+ "appSplash",
2710
+ "appCatalog"
2654
2711
  ];
2655
2712
  function buildSimOpenUrl(currentUrl, target) {
2656
2713
  const url = new URL(currentUrl);
@@ -3149,27 +3206,26 @@ function rnxRasterFrames(tree, nodeIds) {
3149
3206
  width: node.layout.width,
3150
3207
  height: node.layout.height
3151
3208
  };
3152
- const liquidGlass = node.style._liquidGlass;
3153
- const capturePadding = liquidGlass && typeof liquidGlass === "object" && typeof Reflect.get(liquidGlass, "capturePadding") === "number" ? Reflect.get(liquidGlass, "capturePadding") : 0;
3209
+ const glassOutset = liquidGlassPaintOutset(node.style._liquidGlass, frame);
3154
3210
  const shadowRadius = typeof node.style.shadowRadius === "number" ? node.style.shadowRadius : 0;
3155
3211
  const shadowOffset = node.style.shadowOffset;
3156
3212
  const shadowOffsetX = shadowOffset && typeof shadowOffset === "object" && typeof Reflect.get(shadowOffset, "width") === "number" ? Reflect.get(shadowOffset, "width") : 0;
3157
3213
  const shadowOffsetY = shadowOffset && typeof shadowOffset === "object" && typeof Reflect.get(shadowOffset, "height") === "number" ? Reflect.get(shadowOffset, "height") : 0;
3158
3214
  const left = Math.max(
3159
3215
  0,
3160
- frame.x - Math.max(capturePadding, shadowRadius - shadowOffsetX)
3216
+ frame.x - Math.max(glassOutset.x, shadowRadius - shadowOffsetX, 0)
3161
3217
  );
3162
3218
  const top = Math.max(
3163
3219
  0,
3164
- frame.y - Math.max(capturePadding, shadowRadius - shadowOffsetY)
3220
+ frame.y - Math.max(glassOutset.top, shadowRadius - shadowOffsetY, 0)
3165
3221
  );
3166
3222
  const right = Math.min(
3167
3223
  tree.width,
3168
- frame.x + frame.width + Math.max(capturePadding, shadowRadius + shadowOffsetX)
3224
+ frame.x + frame.width + Math.max(glassOutset.x, shadowRadius + shadowOffsetX, 0)
3169
3225
  );
3170
3226
  const bottom = Math.min(
3171
3227
  tree.height,
3172
- frame.y + frame.height + Math.max(capturePadding, shadowRadius + shadowOffsetY)
3228
+ frame.y + frame.height + Math.max(glassOutset.bottom, shadowRadius + shadowOffsetY, 0)
3173
3229
  );
3174
3230
  if (right > left && bottom > top) {
3175
3231
  frames.set(node.nodeId, {
@@ -3182,6 +3238,30 @@ function rnxRasterFrames(tree, nodeIds) {
3182
3238
  }
3183
3239
  return frames;
3184
3240
  }
3241
+ function liquidGlassPaintOutset(glass, frame) {
3242
+ const outset = { x: 0, top: 0, bottom: 0 };
3243
+ if (!glass || typeof glass !== "object") return outset;
3244
+ const contour = Reflect.get(glass, "contour");
3245
+ const shadows = [
3246
+ Reflect.get(glass, "shadow"),
3247
+ Math.min(frame.width, frame.height) > 62 ? Reflect.get(glass, "tallShadow") : null,
3248
+ ...Array.isArray(contour) ? contour : []
3249
+ ];
3250
+ for (const shadow of shadows) {
3251
+ if (!shadow || typeof shadow !== "object") continue;
3252
+ const sigma = Reflect.get(shadow, "sigma");
3253
+ if (typeof sigma !== "number") continue;
3254
+ const offsetY = Reflect.get(shadow, "offsetY");
3255
+ const spread = Reflect.get(shadow, "spread");
3256
+ const [spreadX, spreadY] = typeof spread === "number" ? [spread, spread] : Array.isArray(spread) && typeof spread[0] === "number" && typeof spread[1] === "number" ? [spread[0], spread[1]] : [0, 0];
3257
+ const dy = typeof offsetY === "number" ? offsetY : 0;
3258
+ const blur = sigma * 3;
3259
+ outset.x = Math.max(outset.x, spreadX + blur);
3260
+ outset.top = Math.max(outset.top, spreadY + blur - dy);
3261
+ outset.bottom = Math.max(outset.bottom, spreadY + blur + dy);
3262
+ }
3263
+ return outset;
3264
+ }
3185
3265
  function rnxCaptureRasterRegions(capture) {
3186
3266
  const tree = {
3187
3267
  width: capture.tree.viewport.width,
@@ -3369,7 +3449,10 @@ function isRnxCaptureRegionAtlas(value) {
3369
3449
  // src/vite-plugin-one.ts
3370
3450
  var import_fs4 = __toESM(require("fs"), 1);
3371
3451
  var import_node_crypto4 = require("node:crypto");
3452
+ var import_node_module = require("node:module");
3372
3453
  var import_path3 = __toESM(require("path"), 1);
3454
+ var import_vxrn = require("vxrn");
3455
+ var import_ws2 = require("ws");
3373
3456
 
3374
3457
  // src/dev-server-open.ts
3375
3458
  var import_node_child_process = require("node:child_process");
@@ -3933,7 +4016,10 @@ var ROOT_RUNTIME_PATHS = [
3933
4016
  "/spike/",
3934
4017
  "/test-wallpaper.jpg",
3935
4018
  "/preview-sw.js",
3936
- "/sootsim.svg"
4019
+ "/sootsim.svg",
4020
+ "/bedrock-sqlite.wasm",
4021
+ "/bedrock-sqlite.js",
4022
+ "/sootsim/"
3937
4023
  ];
3938
4024
  function resolveActiveRuntimeRoot() {
3939
4025
  const devRoot = resolveDevCheckoutRuntimeRoot();
@@ -3973,10 +4059,10 @@ function resolveRuntimeRootForHostname(hostname) {
3973
4059
  const version = runtimeVersionFromHostname(hostname);
3974
4060
  return version ? resolveInstalledRuntimeRoot(version) : null;
3975
4061
  }
3976
- function resolveRuntimeRootForRequestHost(host2) {
3977
- if (!host2) return resolveActiveRuntimeRoot();
4062
+ function resolveRuntimeRootForRequestHost(host) {
4063
+ if (!host) return resolveActiveRuntimeRoot();
3978
4064
  try {
3979
- return resolveRuntimeRootForHostname(new URL(`http://${host2}`).hostname);
4065
+ return resolveRuntimeRootForHostname(new URL(`http://${host}`).hostname);
3980
4066
  } catch {
3981
4067
  return null;
3982
4068
  }
@@ -3990,7 +4076,8 @@ function resolveRuntimeFilePath(runtimeRoot, pathname) {
3990
4076
  for (const segment of pathname.split("/")) {
3991
4077
  if (segment === "..") return null;
3992
4078
  }
3993
- const fullPath = import_path2.default.resolve(runtimeRoot, pathname.replace(/^\/+/, ""));
4079
+ const normalized = pathname.startsWith("/sootsim/") ? pathname.slice("/sootsim".length) : pathname;
4080
+ const fullPath = import_path2.default.resolve(runtimeRoot, normalized.replace(/^\/+/, ""));
3994
4081
  const rootWithSep = runtimeRoot.endsWith(import_path2.default.sep) ? runtimeRoot : runtimeRoot + import_path2.default.sep;
3995
4082
  if (!fullPath.startsWith(rootWithSep) && fullPath !== runtimeRoot) return null;
3996
4083
  if (!import_fs3.default.existsSync(fullPath) || !import_fs3.default.statSync(fullPath).isFile()) return null;
@@ -4016,19 +4103,14 @@ function serveRuntimeFile(req, res, fullPath) {
4016
4103
  import_fs3.default.createReadStream(fullPath).pipe(res);
4017
4104
  }
4018
4105
 
4019
- // src/swift-compile.ts
4020
- var import_node_child_process2 = require("node:child_process");
4021
- var import_node_util = require("node:util");
4022
- var run = (0, import_node_util.promisify)(import_node_child_process2.execFile);
4023
- var ROOT_LOCKS = Symbol.for("peachsim.swiftCompile.rootLocks");
4024
- var host = globalThis;
4025
- var rootLocks = host[ROOT_LOCKS] ??= /* @__PURE__ */ new Map();
4026
-
4027
- // src/vite-plugin-swift.ts
4028
- var BUNDLE_MAX_FILE_BYTES = 2 * 1024 * 1024;
4029
- var BUNDLE_MAX_TOTAL_BYTES = 8 * 1024 * 1024;
4030
-
4031
4106
  // src/vite-plugin-one.ts
4107
+ var SWIFT_HMR_PATH = "/__peach/swift-hmr";
4108
+ var SWIFT_ROUTE_PREFIX = "/__peach/swift";
4109
+ var swiftHost;
4110
+ var loadSwiftHost = () => swiftHost ??= import("peachsim/vite-swift");
4111
+ var KOTLIN_ROUTE_PREFIX = "/__peach/kotlin";
4112
+ var kotlinHost;
4113
+ var loadKotlinHost = () => kotlinHost ??= import("peachsim/vite-kotlin");
4032
4114
  function rnxPlugin(options = {}) {
4033
4115
  const prefix2 = options.prefix || "/__soot";
4034
4116
  return {
@@ -4046,21 +4128,196 @@ function rnxPlugin(options = {}) {
4046
4128
  },
4047
4129
  configureServer(server) {
4048
4130
  if (options.enabled === false) return;
4049
- const bundleUrl = options.bundleUrl || "/node_modules/one/metro-entry.bundle?platform=ios&dev=true&minify=false";
4050
- const appName = resolveRnxDesktopAppName(options.open, server.config.root);
4051
- const replacementToken = (0, import_node_crypto4.randomUUID)();
4131
+ let swiftPackages;
4132
+ let swiftModuleRoot;
4133
+ const moduleRootFor = () => {
4134
+ if (swiftModuleRoot === void 0) {
4135
+ const peachsimPackage = (0, import_node_module.createRequire)(
4136
+ import_path3.default.join(server.config.root, "package.json")
4137
+ ).resolve("peachsim/package.json");
4138
+ const moduleRoot = import_path3.default.resolve(
4139
+ import_path3.default.dirname(peachsimPackage),
4140
+ "../sootsim-swift"
4141
+ );
4142
+ swiftModuleRoot = import_fs4.default.existsSync(import_path3.default.join(moduleRoot, "toolchain.json")) ? moduleRoot : null;
4143
+ }
4144
+ return swiftModuleRoot ?? void 0;
4145
+ };
4146
+ const activeSwiftPackages = /* @__PURE__ */ new Set();
4147
+ const packageMap = () => {
4148
+ swiftPackages ??= (0, import_vxrn.swiftPackageDirectories)(server.config.root);
4149
+ return swiftPackages;
4150
+ };
4151
+ const wsOptions = server.config.server.ws;
4152
+ const httpServer = server.httpServer ?? (wsOptions && typeof wsOptions === "object" ? wsOptions.server : void 0);
4153
+ const swiftSockets = httpServer ? new import_ws2.WebSocketServer({ noServer: true }) : void 0;
4154
+ const hmrPath = SWIFT_HMR_PATH;
4052
4155
  const configuredPort = server.config.server.port || 8081;
4053
4156
  const listeningPort = () => {
4054
4157
  const address = server.httpServer?.address();
4055
4158
  return address && typeof address === "object" ? address.port : configuredPort;
4056
4159
  };
4057
- const bundleForPort = (port2) => tagRnxBundleForPlugin(
4058
- bundleUrl,
4059
- `http://localhost:${port2}`,
4060
- replacementToken,
4061
- server.config.root,
4062
- appName
4063
- );
4160
+ const devOrigin = () => `http://localhost:${listeningPort()}`;
4161
+ const sendSwiftChange = (change) => {
4162
+ const message = JSON.stringify(change);
4163
+ for (const client of swiftSockets?.clients ?? []) {
4164
+ if (client.readyState === import_ws2.WebSocket.OPEN) client.send(message);
4165
+ }
4166
+ };
4167
+ if (httpServer && swiftSockets) {
4168
+ httpServer.on("upgrade", (req, socket, head) => {
4169
+ if (new URL(req.url ?? "/", "http://localhost").pathname !== SWIFT_HMR_PATH)
4170
+ return;
4171
+ swiftSockets.handleUpgrade(req, socket, head, (client) => {
4172
+ swiftSockets.emit("connection", client, req);
4173
+ });
4174
+ });
4175
+ httpServer.once("close", () => {
4176
+ for (const client of swiftSockets.clients) client.terminate();
4177
+ swiftSockets.close();
4178
+ });
4179
+ }
4180
+ server.watcher.on("add", (file) => {
4181
+ if (import_path3.default.basename(file) === "Package.swift") swiftPackages = void 0;
4182
+ });
4183
+ server.watcher.on("unlink", (file) => {
4184
+ if (import_path3.default.basename(file) === "Package.swift") swiftPackages = void 0;
4185
+ });
4186
+ let kotlinModuleRoot;
4187
+ server.watcher.on("all", (_event, file) => {
4188
+ if (!kotlinModuleRoot) return;
4189
+ if (file.endsWith(".kt") || file.startsWith(`${kotlinModuleRoot}${import_path3.default.sep}`))
4190
+ sendSwiftChange({ type: "kotlin", packageName: "kotlin" });
4191
+ });
4192
+ server.middlewares.use((req, res, next) => {
4193
+ const pathname = (req.url ?? "").split("?")[0];
4194
+ if (!pathname.startsWith(KOTLIN_ROUTE_PREFIX)) return next();
4195
+ loadKotlinHost().then(async (host) => {
4196
+ if (host.serveKotlinArtifact(pathname, res)) return;
4197
+ if (pathname !== "/__peach/kotlin-package") return next();
4198
+ if (!kotlinModuleRoot) {
4199
+ const moduleRoot = import_path3.default.join(
4200
+ import_path3.default.dirname(
4201
+ (0, import_node_module.createRequire)(import_path3.default.join(server.config.root, "package.json")).resolve(
4202
+ "peachsim/package.json"
4203
+ )
4204
+ ),
4205
+ "kotlin"
4206
+ );
4207
+ kotlinModuleRoot = moduleRoot;
4208
+ server.watcher.add(import_path3.default.join(moduleRoot, "Sources"));
4209
+ }
4210
+ try {
4211
+ const artifact = await host.kotlinArtifact(
4212
+ server.config.root,
4213
+ kotlinModuleRoot,
4214
+ devOrigin()
4215
+ );
4216
+ res.setHeader("access-control-allow-origin", "*");
4217
+ res.setHeader("content-type", "application/json");
4218
+ res.end(JSON.stringify({ ...artifact, hmrPath }));
4219
+ } catch (error) {
4220
+ res.statusCode = 500;
4221
+ res.setHeader("access-control-allow-origin", "*");
4222
+ res.end(error instanceof Error ? error.message : String(error));
4223
+ }
4224
+ }).catch(next);
4225
+ });
4226
+ server.watcher.on("change", (file) => {
4227
+ if (activeSwiftPackages.size === 0) return;
4228
+ const moduleRoot = moduleRootFor();
4229
+ let packages;
4230
+ try {
4231
+ packages = packageMap();
4232
+ } catch (error) {
4233
+ for (const packageName of activeSwiftPackages) {
4234
+ sendSwiftChange({
4235
+ type: "error",
4236
+ packageName,
4237
+ message: error instanceof Error ? error.message : String(error)
4238
+ });
4239
+ }
4240
+ return;
4241
+ }
4242
+ for (const packageName of activeSwiftPackages) {
4243
+ const packagePath = packages.get(packageName);
4244
+ if (!packagePath) continue;
4245
+ if (!file.startsWith(`${packagePath}${import_path3.default.sep}`) && !(moduleRoot && file.startsWith(`${moduleRoot}${import_path3.default.sep}`)))
4246
+ continue;
4247
+ void loadSwiftHost().then(
4248
+ (host) => host.swiftPackageMetadata(
4249
+ packagePath,
4250
+ packageName,
4251
+ moduleRoot,
4252
+ devOrigin()
4253
+ )
4254
+ ).then((metadata) => {
4255
+ sendSwiftChange({
4256
+ type: "package",
4257
+ packageName,
4258
+ metadata: { ...metadata, hmrPath }
4259
+ });
4260
+ }).catch((error) => {
4261
+ sendSwiftChange({
4262
+ type: "error",
4263
+ packageName,
4264
+ message: error instanceof Error ? error.message : String(error)
4265
+ });
4266
+ });
4267
+ }
4268
+ });
4269
+ server.middlewares.use((req, res, next) => {
4270
+ const pathname = (req.url ?? "").split("?")[0];
4271
+ if (!pathname.startsWith(SWIFT_ROUTE_PREFIX)) return next();
4272
+ loadSwiftHost().then((host) => {
4273
+ if (host.serveSwiftPackageAsset(pathname, res)) return;
4274
+ const prefix3 = "/__peach/swift-package/";
4275
+ if (!pathname.startsWith(prefix3)) return next();
4276
+ if (!swiftSockets) {
4277
+ res.statusCode = 501;
4278
+ res.end("Peach Swift hot reload requires a Vite HTTP server");
4279
+ return;
4280
+ }
4281
+ const packageName = pathname.slice(prefix3.length);
4282
+ let packagePath;
4283
+ try {
4284
+ packagePath = packageMap().get(packageName);
4285
+ } catch (error) {
4286
+ res.statusCode = 500;
4287
+ res.end(error instanceof Error ? error.message : String(error));
4288
+ return;
4289
+ }
4290
+ if (!packagePath) {
4291
+ res.statusCode = 404;
4292
+ res.end(`swift package ${packageName} was not found`);
4293
+ return;
4294
+ }
4295
+ const moduleRoot = moduleRootFor();
4296
+ activeSwiftPackages.add(packageName);
4297
+ server.watcher.add(host.swiftPackageWatchFiles(packagePath, moduleRoot));
4298
+ void host.swiftPackageMetadata(packagePath, packageName, moduleRoot, devOrigin()).then((metadata) => {
4299
+ res.setHeader("access-control-allow-origin", "*");
4300
+ res.setHeader("content-type", "application/json");
4301
+ res.end(JSON.stringify({ ...metadata, hmrPath }));
4302
+ }).catch((error) => {
4303
+ res.statusCode = 500;
4304
+ res.end(error instanceof Error ? error.message : String(error));
4305
+ });
4306
+ }).catch(next);
4307
+ });
4308
+ const bundleUrl = options.bundleUrl || "/node_modules/one/metro-entry.bundle?platform=ios&dev=true&minify=false";
4309
+ const appName = resolveRnxDesktopAppName(options.open, server.config.root);
4310
+ const replacementToken = (0, import_node_crypto4.randomUUID)();
4311
+ const bundleForPort = (port2) => {
4312
+ const fullBundleUrl = bundleUrl.startsWith("http") ? bundleUrl : `http://localhost:${port2}${bundleUrl.startsWith("/") ? "" : "/"}${bundleUrl}`;
4313
+ return tagRnxBundleForPlugin(
4314
+ fullBundleUrl,
4315
+ `http://localhost:${port2}`,
4316
+ replacementToken,
4317
+ server.config.root,
4318
+ appName
4319
+ );
4320
+ };
4064
4321
  const buildShellHtml = (runtimeRoot) => {
4065
4322
  const html = import_fs4.default.readFileSync(import_path3.default.join(runtimeRoot, "index.html"), "utf8");
4066
4323
  const lock = readDaemonLockfile();
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.577 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.579 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.577 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.579 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;