peachsim 0.1.660 → 0.1.662

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 (86) hide show
  1. package/README.md +10 -15
  2. package/cli/commands/inspect/wait-ready.ts +10 -1
  3. package/cli/drivers/linux-browser-libs.lock.json +1088 -0
  4. package/cli/drivers/linux-browser-libs.ts +182 -0
  5. package/cli/drivers/playwright-provisioning.ts +20 -98
  6. package/cli/drivers/playwright-sim-host.ts +9 -0
  7. package/cli/drivers/playwright.ts +2 -1
  8. package/dist-lib/agent-daemon-client.cjs +1 -1
  9. package/dist-lib/agent-events.cjs +1 -1
  10. package/dist-lib/agent-identity.cjs +1 -1
  11. package/dist-lib/agent-sessions.cjs +1 -1
  12. package/dist-lib/attached-projects.cjs +1 -1
  13. package/dist-lib/auth/shared-session.cjs +1 -1
  14. package/dist-lib/backend-origin.cjs +18 -1
  15. package/dist-lib/beta.cjs +1 -1
  16. package/dist-lib/beta.mjs +1 -1
  17. package/dist-lib/bridge-constants.cjs +1 -1
  18. package/dist-lib/bridge-contract-input.cjs +1 -1
  19. package/dist-lib/bridge-contract-input.mjs +1 -1
  20. package/dist-lib/bridge-contract.cjs +1 -1
  21. package/dist-lib/bridge-contract.mjs +1 -1
  22. package/dist-lib/capture-contract.cjs +1 -1
  23. package/dist-lib/capture-contract.mjs +1 -1
  24. package/dist-lib/cli-constants.cjs +1 -1
  25. package/dist-lib/cloud-contract.cjs +1 -1
  26. package/dist-lib/cloud-contract.mjs +1 -1
  27. package/dist-lib/cloud-test-args.cjs +1 -1
  28. package/dist-lib/cloud-test-args.mjs +1 -1
  29. package/dist-lib/cloud.cjs +1 -1
  30. package/dist-lib/cloud.mjs +1 -1
  31. package/dist-lib/config.cjs +1 -1
  32. package/dist-lib/detox/index.cjs +1 -1
  33. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  34. package/dist-lib/home-paths.cjs +1 -1
  35. package/dist-lib/host/bridge-host.cjs +45 -41
  36. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  37. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  38. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  39. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  40. package/dist-lib/host/websocket-proxy.cjs +1 -1
  41. package/dist-lib/index.cjs +1 -1
  42. package/dist-lib/jump-to-source-babel.cjs +1 -1
  43. package/dist-lib/jump-to-source-native.cjs +1 -1
  44. package/dist-lib/kotlin-host.cjs +191 -59
  45. package/dist-lib/menu.cjs +1 -1
  46. package/dist-lib/menu.mjs +1 -1
  47. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  48. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  49. package/dist-lib/metro-production-bundle.cjs +1 -1
  50. package/dist-lib/metro-production-bundle.mjs +1 -1
  51. package/dist-lib/metro.cjs +1 -1
  52. package/dist-lib/native-app-identity.cjs +1 -1
  53. package/dist-lib/native-app-identity.mjs +1 -1
  54. package/dist-lib/plist.cjs +1 -1
  55. package/dist-lib/plist.mjs +1 -1
  56. package/dist-lib/profiles.cjs +1 -1
  57. package/dist-lib/public-brand.cjs +1 -1
  58. package/dist-lib/react-native-host-modules.cjs +1 -1
  59. package/dist-lib/react-native-host-modules.mjs +1 -1
  60. package/dist-lib/render-mode.cjs +1 -1
  61. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  62. package/dist-lib/sdk.cjs +1 -1
  63. package/dist-lib/sdk.mjs +1 -1
  64. package/dist-lib/skills.cjs +1332 -177
  65. package/dist-lib/swift-compile.cjs +1 -1
  66. package/dist-lib/swift-compile.mjs +1 -1
  67. package/dist-lib/swift-package.cjs +1 -1
  68. package/dist-lib/swift-package.mjs +1 -1
  69. package/dist-lib/swift-submit.cjs +1 -1
  70. package/dist-lib/swift-submit.mjs +1 -1
  71. package/dist-lib/swift.cjs +1 -1
  72. package/dist-lib/vite-swift.cjs +1 -1
  73. package/dist-lib/vite.cjs +1 -1
  74. package/kotlin/AppSources/android/view/Window.kt +15 -9
  75. package/kotlin/AppSources/androidx/activity/ActivityViewModelLazy.kt +21 -0
  76. package/kotlin/AppSources/androidx/activity/ComponentActivity.kt +5 -3
  77. package/kotlin/AppSources/androidx/lifecycle/AndroidViewModel.kt +10 -0
  78. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +37 -16
  79. package/kotlin/tools/abi-classes.main.kts +41 -0
  80. package/package.json +2 -1
  81. package/src/android-theme.ts +14 -0
  82. package/src/backend-origin.ts +25 -0
  83. package/src/host/bridge-host.ts +24 -37
  84. package/src/kotlin-app.ts +22 -4
  85. package/src/kotlin-host.ts +94 -12
  86. package/src/kotlin-view-models.ts +133 -0
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
@@ -6367,11 +6367,10 @@ async function installVerifiedTarball(tarball) {
6367
6367
  const { url, digest, destDir, installedMarker, label } = tarball;
6368
6368
  const downloadDir = import_node_fs7.default.mkdtempSync(import_node_path7.default.join(import_node_os2.default.tmpdir(), "runtime-delivery-download-"));
6369
6369
  const archivePath = import_node_path7.default.join(downloadDir, "archive.tar.gz");
6370
- const tmpDir = import_node_path7.default.join(
6371
- import_node_path7.default.dirname(destDir),
6372
- `.installing-${import_node_path7.default.basename(destDir)}-${process.pid}`
6370
+ import_node_fs7.default.mkdirSync(import_node_path7.default.dirname(destDir), { recursive: true });
6371
+ const tmpDir = import_node_fs7.default.mkdtempSync(
6372
+ import_node_path7.default.join(import_node_path7.default.dirname(destDir), `.installing-${import_node_path7.default.basename(destDir)}-`)
6373
6373
  );
6374
- import_node_fs7.default.rmSync(tmpDir, { recursive: true, force: true });
6375
6374
  try {
6376
6375
  await downloadToFile(url, archivePath);
6377
6376
  const actual = await digestFile(archivePath, digest.algorithm, digest.encoding);
@@ -6380,20 +6379,38 @@ async function installVerifiedTarball(tarball) {
6380
6379
  `${digest.algorithm} mismatch for ${label}: expected ${digest.value}, actual ${actual}`
6381
6380
  );
6382
6381
  }
6383
- import_node_fs7.default.mkdirSync(tmpDir, { recursive: true });
6384
6382
  await extractTarball(archivePath, tmpDir, tarball.stripComponents ?? 0);
6385
6383
  if (!import_node_fs7.default.existsSync(import_node_path7.default.join(tmpDir, installedMarker))) {
6386
6384
  throw new Error(`extracted tarball for ${label} is missing ${installedMarker}`);
6387
6385
  }
6388
- import_node_fs7.default.rmSync(destDir, { recursive: true, force: true });
6389
- import_node_fs7.default.renameSync(tmpDir, destDir);
6390
- } catch (err) {
6391
- import_node_fs7.default.rmSync(tmpDir, { recursive: true, force: true });
6392
- throw err;
6386
+ try {
6387
+ import_node_fs7.default.renameSync(tmpDir, destDir);
6388
+ } catch (err) {
6389
+ if (!isErrnoCode(err, "ENOTEMPTY", "EEXIST")) throw err;
6390
+ const aside = `${tmpDir}-replaced`;
6391
+ try {
6392
+ import_node_fs7.default.renameSync(destDir, aside);
6393
+ } catch (moveErr) {
6394
+ if (!isErrnoCode(moveErr, "ENOENT")) throw moveErr;
6395
+ }
6396
+ try {
6397
+ import_node_fs7.default.renameSync(tmpDir, destDir);
6398
+ } catch (landErr) {
6399
+ if (!isErrnoCode(landErr, "ENOTEMPTY", "EEXIST") || !import_node_fs7.default.existsSync(import_node_path7.default.join(destDir, installedMarker))) {
6400
+ throw landErr;
6401
+ }
6402
+ } finally {
6403
+ import_node_fs7.default.rmSync(aside, { recursive: true, force: true });
6404
+ }
6405
+ }
6393
6406
  } finally {
6407
+ import_node_fs7.default.rmSync(tmpDir, { recursive: true, force: true });
6394
6408
  import_node_fs7.default.rmSync(downloadDir, { recursive: true, force: true });
6395
6409
  }
6396
6410
  }
6411
+ function isErrnoCode(err, ...codes) {
6412
+ return err instanceof Error && "code" in err && codes.includes(String(err.code));
6413
+ }
6397
6414
  async function downloadToFile(url, destPath) {
6398
6415
  const res = await fetch(url);
6399
6416
  if (!res.ok || !res.body) {
@@ -9692,24 +9709,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
9692
9709
  this.wsIsAlive.set(ws, true);
9693
9710
  });
9694
9711
  this.agentHost.registerSocket(ws);
9695
- if (role === "sim") {
9696
- sim = {
9697
- id: this.allocateSimId(),
9698
- ws,
9699
- origin,
9700
- connectedAt: Date.now(),
9701
- lastSeenAt: Date.now(),
9702
- lastActiveAt: 0,
9703
- recentActions: []
9704
- };
9705
- this.sims.set(sim.id, sim);
9706
- this.writeConnectedRuntimeSnapshot();
9707
- if (this.shouldPromoteSim(sim)) {
9708
- this.primarySimId = sim.id;
9709
- }
9710
- this.broadcastSimAssignments();
9711
- this.broadcastSimClientStates();
9712
- } else {
9712
+ if (role === "cli") {
9713
9713
  const fallbackKey = `ws-${this.nextCliFallbackId++}`;
9714
9714
  this.cliIdentityKeyBySocket.set(ws, fallbackKey);
9715
9715
  }
@@ -9790,25 +9790,29 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
9790
9790
  }
9791
9791
  return;
9792
9792
  }
9793
- if (role === "sim" && msg.type === "bridge:hello" && sim && !sim.url) {
9794
- const provisionalId = sim.id;
9795
- this.sims.delete(provisionalId);
9796
- if (this.primarySimId === provisionalId) {
9797
- this.primarySimId = this.getOpenSim()?.id ?? null;
9798
- }
9799
- sim = null;
9793
+ if (role === "sim" && msg.type === "bridge:hello" && !sim) {
9800
9794
  role = "cli";
9801
9795
  this.cliIdentityKeyBySocket.set(ws, `ws-${this.nextCliFallbackId++}`);
9802
- this.writeConnectedRuntimeSnapshot();
9803
- this.broadcastSimAssignments();
9804
- this.broadcastSimClientStates();
9805
9796
  }
9806
9797
  if (role === "sim") {
9807
9798
  if (sim) {
9808
9799
  sim.lastSeenAt = Date.now();
9809
9800
  }
9810
- if (msg.type === "bridge:register" && sim) {
9801
+ if (msg.type === "bridge:register") {
9811
9802
  const registration = msg;
9803
+ const firstRegistration = !sim;
9804
+ if (!sim) {
9805
+ sim = {
9806
+ id: this.allocateSimId(),
9807
+ ws,
9808
+ origin,
9809
+ connectedAt: Date.now(),
9810
+ lastSeenAt: Date.now(),
9811
+ lastActiveAt: 0,
9812
+ recentActions: []
9813
+ };
9814
+ this.sims.set(sim.id, sim);
9815
+ }
9812
9816
  const restored = this.tryRestoreSimId(sim, registration.simId);
9813
9817
  sim.url = registration.url;
9814
9818
  sim.title = registration.title;
@@ -9822,7 +9826,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
9822
9826
  }
9823
9827
  const reElected = this.primarySimId !== sim.id && this.shouldPromoteSim(sim);
9824
9828
  if (reElected) this.primarySimId = sim.id;
9825
- if (restored || reElected) {
9829
+ if (firstRegistration || restored || reElected) {
9826
9830
  this.broadcastSimAssignments();
9827
9831
  this.broadcastSimClientStates();
9828
9832
  }
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/host/fetch-proxy-overrides.ts
4
4
  var FETCH_PROXY_BROWSER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
@@ -65,6 +65,81 @@ function isRecord(value) {
65
65
  return typeof value === "object" && value !== null && !Array.isArray(value);
66
66
  }
67
67
 
68
+ // src/kotlin-view-models.ts
69
+ var VIEW_MODEL = "androidx.lifecycle.ViewModel";
70
+ var ANDROID_VIEW_MODEL = "androidx.lifecycle.AndroidViewModel";
71
+ var APPLICATION = "android.app.Application";
72
+ var HANDLE = "androidx.lifecycle.SavedStateHandle";
73
+ function strings(value) {
74
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string"))
75
+ throw new Error("abi-classes printed an invalid list");
76
+ return value;
77
+ }
78
+ function readAbiClasses(output) {
79
+ return output.split("\n").filter((line) => line.startsWith("{")).map((line) => {
80
+ const row = JSON.parse(line);
81
+ if (!isRecord(row) || typeof row.name !== "string" || typeof row.kind !== "string" || typeof row.modality !== "string" || typeof row.inner !== "boolean" || typeof row.generic !== "boolean" || !Array.isArray(row.constructors))
82
+ throw new Error(`abi-classes printed an invalid class: ${line}`);
83
+ return {
84
+ name: row.name,
85
+ kind: row.kind,
86
+ modality: row.modality,
87
+ inner: row.inner,
88
+ generic: row.generic,
89
+ supers: strings(row.supers),
90
+ constructors: row.constructors.map((constructor) => {
91
+ if (!isRecord(constructor) || typeof constructor.defaulted !== "boolean")
92
+ throw new Error(`abi-classes printed an invalid constructor: ${line}`);
93
+ return {
94
+ parameters: strings(constructor.parameters),
95
+ defaulted: constructor.defaulted
96
+ };
97
+ })
98
+ };
99
+ });
100
+ }
101
+ function matching(row, signature) {
102
+ for (const { parameters } of row.constructors) {
103
+ if (parameters.join() === signature.join()) return "match";
104
+ if (parameters.length === signature.length && signature.every((type) => parameters.includes(type)))
105
+ return "misordered";
106
+ }
107
+ return null;
108
+ }
109
+ function signatureOf(row, android) {
110
+ const handled = matching(row, android ? [APPLICATION, HANDLE] : [HANDLE]);
111
+ if (handled === "misordered") return "misordered";
112
+ if (handled) return android ? "application-handle" : "handle";
113
+ if (android)
114
+ return row.constructors.some(({ parameters }) => parameters.join() === APPLICATION) ? "application" : null;
115
+ return row.constructors.some(
116
+ ({ parameters, defaulted }) => parameters.length === 0 || defaulted
117
+ ) ? "none" : null;
118
+ }
119
+ function viewModelClasses(rows) {
120
+ const byName = new Map(rows.map((row) => [row.name, row]));
121
+ const base = (row, seen) => {
122
+ for (const name of row.supers) {
123
+ if (name === VIEW_MODEL || name === ANDROID_VIEW_MODEL) return name;
124
+ const parent = byName.get(name);
125
+ if (parent && !seen.has(name)) {
126
+ seen.add(name);
127
+ const found = base(parent, seen);
128
+ if (found) return found;
129
+ }
130
+ }
131
+ return null;
132
+ };
133
+ return rows.flatMap((row) => {
134
+ if (row.kind !== "CLASS" || row.inner || row.generic) return [];
135
+ if (row.modality === "ABSTRACT" || row.modality === "SEALED") return [];
136
+ const root = base(row, /* @__PURE__ */ new Set());
137
+ if (!root) return [];
138
+ const signature = signatureOf(row, root === ANDROID_VIEW_MODEL);
139
+ return signature ? [{ name: row.name, signature }] : [];
140
+ });
141
+ }
142
+
68
143
  // src/kotlin-host.ts
69
144
  var run = (0, import_node_util.promisify)(import_node_child_process.execFile);
70
145
  var KOTLIN_ARTIFACT_PREFIX = "/__peach/kotlin-artifact/";
@@ -236,7 +311,7 @@ function entrySource(ids) {
236
311
  ""
237
312
  ].join("\n");
238
313
  }
239
- var BUILD_FORMAT = 7;
314
+ var BUILD_FORMAT = 8;
240
315
  var builds = /* @__PURE__ */ new Map();
241
316
  function buildsDir() {
242
317
  return import_node_path2.default.join(kotlinHome(), "builds");
@@ -283,44 +358,68 @@ function viewsSource(viewIds) {
283
358
  "}"
284
359
  ].join("\n");
285
360
  }
361
+ function viewModelInitializer({ name, signature }) {
362
+ const call = (args) => ` addInitializer(${name}::class) { ${name}(${args}) }`;
363
+ switch (signature) {
364
+ case "none":
365
+ return call("");
366
+ case "handle":
367
+ return call("createSavedStateHandle()");
368
+ case "application":
369
+ return call("dev.peach.app.peachApplication()");
370
+ case "application-handle":
371
+ return call("dev.peach.app.peachApplication(), createSavedStateHandle()");
372
+ case "misordered":
373
+ return ` addInitializer(${name}::class) { throw UnsupportedOperationException(${JSON.stringify(
374
+ `Class ${name.split(".").pop()} must have parameters in the proper order: [class android.app.Application, class androidx.lifecycle.SavedStateHandle]`
375
+ )}) }`;
376
+ }
377
+ }
286
378
  async function kotlinAppArtifact(app, moduleRoot, origin) {
287
379
  const toolchain = readToolchain(moduleRoot);
288
380
  const kotlinString = (value) => JSON.stringify(value).replaceAll("$", "\\$");
381
+ const appEntry = (viewModels) => [
382
+ "@file:OptIn(kotlin.js.ExperimentalJsExport::class)",
383
+ "",
384
+ "import androidx.lifecycle.createSavedStateHandle",
385
+ "",
386
+ "@JsExport",
387
+ "fun peachAppMount(density: Float, fontScale: Float, locale: String, backend: String, stencilBits: Int,",
388
+ " regular: String, medium: String, bold: String, insets: String, night: Boolean,",
389
+ " invalidate: (Boolean) -> Unit): String = dev.peach.app.peachStartApp(",
390
+ " dev.peach.app.PeachAppSpec(",
391
+ ` android.content.ComponentName(${kotlinString(app.packageName)}, ${kotlinString(app.launcher.name)}),`,
392
+ ` android.content.ComponentName(${kotlinString(app.packageName)}, ${kotlinString(app.launcher.targetClass)}),`,
393
+ " android.view.WindowTheme(",
394
+ ` ${app.window.edgeToEdgeEnforced}, 0x${app.window.statusBarColor.toString(16).padStart(8, "0")}.toInt(),`,
395
+ ` 0x${app.window.navigationBarColor.toString(16).padStart(8, "0")}.toInt(),`,
396
+ ` ${app.window.statusBarContrastEnforced}, ${app.window.navigationBarContrastEnforced},`,
397
+ ` ${app.window.lightStatusBar}, ${app.window.lightNavigationBar},`,
398
+ " ),",
399
+ ` { ${app.applicationClass ?? "android.app.Application"}() },`,
400
+ ` { ${app.launcher.targetClass}() },`,
401
+ ` ${app.handlesUiMode},`,
402
+ " androidx.lifecycle.viewmodel.viewModelFactory {",
403
+ ...viewModels.map(viewModelInitializer),
404
+ " },",
405
+ " ),",
406
+ " density, fontScale, locale, backend, stencilBits, regular, medium, bold, insets, night, invalidate,",
407
+ ")",
408
+ "",
409
+ "@JsExport",
410
+ `fun peachAppBack(): Boolean = dev.peach.app.peachAppBack(${app.predictiveBack})`,
411
+ "",
412
+ "@JsExport",
413
+ "fun peachAppTask(foreground: Boolean) = dev.peach.app.peachAppTask(foreground)",
414
+ "",
415
+ "@JsExport",
416
+ "fun peachAppNight(night: Boolean) = dev.peach.app.peachAppNight(night)",
417
+ ""
418
+ ].join("\n");
289
419
  const generated = {
290
420
  "PeachEntry.kt": entrySource([]),
291
421
  "PeachViews.kt": viewsSource([]),
292
- "PeachAppEntry.kt": [
293
- "@file:OptIn(kotlin.js.ExperimentalJsExport::class)",
294
- "",
295
- "@JsExport",
296
- "fun peachAppMount(density: Float, fontScale: Float, locale: String, backend: String, stencilBits: Int,",
297
- " regular: String, medium: String, bold: String, statusBar: Int, navigationBar: Int, night: Boolean,",
298
- " invalidate: (Boolean) -> Unit): String = dev.peach.app.peachStartApp(",
299
- " dev.peach.app.PeachAppSpec(",
300
- ` android.content.ComponentName(${kotlinString(app.packageName)}, ${kotlinString(app.launcher.name)}),`,
301
- ` android.content.ComponentName(${kotlinString(app.packageName)}, ${kotlinString(app.launcher.targetClass)}),`,
302
- " android.view.WindowTheme(",
303
- ` ${app.window.edgeToEdgeEnforced}, 0x${app.window.statusBarColor.toString(16).padStart(8, "0")}.toInt(),`,
304
- ` ${app.window.statusBarContrastEnforced}, ${app.window.navigationBarContrastEnforced},`,
305
- ` ${app.window.lightStatusBar}, ${app.window.lightNavigationBar},`,
306
- " ),",
307
- ` { ${app.applicationClass ?? "android.app.Application"}() },`,
308
- ` { ${app.launcher.targetClass}() },`,
309
- ` ${app.handlesUiMode},`,
310
- " ),",
311
- " density, fontScale, locale, backend, stencilBits, regular, medium, bold, statusBar, navigationBar, night, invalidate,",
312
- ")",
313
- "",
314
- "@JsExport",
315
- `fun peachAppBack(): Boolean = dev.peach.app.peachAppBack(${app.predictiveBack})`,
316
- "",
317
- "@JsExport",
318
- "fun peachAppTask(foreground: Boolean) = dev.peach.app.peachAppTask(foreground)",
319
- "",
320
- "@JsExport",
321
- "fun peachAppNight(night: Boolean) = dev.peach.app.peachAppNight(night)",
322
- ""
323
- ].join("\n")
422
+ "PeachAppEntry.kt": appEntry([])
324
423
  };
325
424
  const shims = [
326
425
  ...kotlinSources(import_node_path2.default.join(moduleRoot, "Sources")),
@@ -335,13 +434,27 @@ async function kotlinAppArtifact(app, moduleRoot, origin) {
335
434
  sources: app.sources,
336
435
  generated,
337
436
  shims,
338
- usesCompose: true
437
+ usesCompose: true,
438
+ viewModels: {
439
+ file: "PeachAppEntry.kt",
440
+ render: appEntry,
441
+ script: import_node_path2.default.join(moduleRoot, "tools/abi-classes.main.kts")
442
+ }
339
443
  },
340
444
  origin
341
445
  );
342
446
  }
343
447
  async function compileKotlin(input, origin) {
344
- const { root, toolchain, language, sources, generated, shims, usesCompose } = input;
448
+ const {
449
+ root,
450
+ toolchain,
451
+ language,
452
+ sources,
453
+ generated,
454
+ shims,
455
+ usesCompose,
456
+ viewModels
457
+ } = input;
345
458
  const digest = (0, import_node_crypto.createHash)("sha256");
346
459
  digest.update(JSON.stringify({ format: BUILD_FORMAT, toolchain, language }));
347
460
  for (const file of [...shims, ...sources])
@@ -387,30 +500,49 @@ async function compileKotlin(input, origin) {
387
500
  "-libraries",
388
501
  libraries.join(import_node_path2.default.delimiter)
389
502
  ];
503
+ const compileKlib = (outputDir) => run(
504
+ kotlinc,
505
+ [
506
+ ...common,
507
+ ...usesCompose ? [
508
+ `-Xplugin=${import_node_path2.default.resolve(import_node_path2.default.dirname(kotlinc), "../lib/compose-compiler-plugin.jar")}`
509
+ ] : [],
510
+ "-language-version",
511
+ language,
512
+ "-api-version",
513
+ language,
514
+ "-nowarn",
515
+ "-nopack",
516
+ "-ir-output-dir",
517
+ outputDir,
518
+ "-ir-output-name",
519
+ "app",
520
+ ...shims,
521
+ ...sources,
522
+ ...Object.keys(generated).map((name) => import_node_path2.default.join(generatedDir, name))
523
+ ],
524
+ { maxBuffer: 16 * 1024 * 1024 }
525
+ );
390
526
  try {
391
- await run(
392
- kotlinc,
393
- [
394
- ...common,
395
- ...usesCompose ? [
396
- `-Xplugin=${import_node_path2.default.resolve(import_node_path2.default.dirname(kotlinc), "../lib/compose-compiler-plugin.jar")}`
397
- ] : [],
398
- "-language-version",
399
- language,
400
- "-api-version",
401
- language,
402
- "-nowarn",
403
- "-nopack",
404
- "-ir-output-dir",
405
- import_node_path2.default.join(work, "klib"),
406
- "-ir-output-name",
407
- "app",
408
- ...shims,
409
- ...sources,
410
- ...Object.keys(generated).map((name) => import_node_path2.default.join(generatedDir, name))
411
- ],
412
- { maxBuffer: 16 * 1024 * 1024 }
413
- );
527
+ if (viewModels && sources.some((file) => import_node_fs.default.readFileSync(file, "utf8").includes("ViewModel"))) {
528
+ await compileKlib(import_node_path2.default.join(work, "abi"));
529
+ const { stdout } = await run(
530
+ import_node_path2.default.join(import_node_path2.default.dirname(kotlinc), "kotlinc"),
531
+ [
532
+ "-cp",
533
+ import_node_path2.default.resolve(import_node_path2.default.dirname(kotlinc), "../lib/kotlin-compiler.jar"),
534
+ "-script",
535
+ viewModels.script,
536
+ import_node_path2.default.join(work, "abi")
537
+ ],
538
+ { maxBuffer: 64 * 1024 * 1024 }
539
+ );
540
+ import_node_fs.default.writeFileSync(
541
+ import_node_path2.default.join(generatedDir, viewModels.file),
542
+ viewModels.render(viewModelClasses(readAbiClasses(stdout)))
543
+ );
544
+ }
545
+ await compileKlib(import_node_path2.default.join(work, "klib"));
414
546
  await run(
415
547
  kotlinc,
416
548
  [
package/dist-lib/menu.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
package/dist-lib/menu.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/public-brand.ts
4
4
  var name = "Peach";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/is-record.ts
4
4
  function isRecord(value) {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/metro-production-bundle.ts
4
4
  var RNXSIM_METRO_MODULE_PATHS_PREFIX = "globalThis.__sootsimModulePaths=";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/native-app-identity.ts
4
4
  var NATIVE_APP_IDENTITY_PREFIX = "globalThis.__sootsimNativeAppIdentity=";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/plist.ts
4
4
  import plistModule from "@expo/plist";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/react-native-host-modules.ts
4
4
  var REACT_NATIVE_PASSTHROUGH_SPECIFIERS = [
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
package/dist-lib/sdk.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (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;
package/dist-lib/sdk.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.660 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.662 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);