peachsim 0.1.648 → 0.1.650

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 (132) hide show
  1. package/README.md +10 -9
  2. package/cli/command-registry.ts +2 -1
  3. package/cli/commands/guest-host.ts +67 -0
  4. package/cli/commands/kotlin.ts +231 -0
  5. package/cli/commands/project-report.ts +1 -1
  6. package/cli/commands/report-issue.ts +55 -32
  7. package/cli/commands/setup.ts +10 -14
  8. package/cli/commands/support.ts +399 -0
  9. package/cli/commands/swift.ts +6 -52
  10. package/cli/commands/telemetry.ts +10 -13
  11. package/cli/main.ts +22 -10
  12. package/cli/outbound-endpoints.ts +23 -2
  13. package/cli/privacy.ts +23 -25
  14. package/cli/runtime-notes.ts +3 -2
  15. package/cli/telemetry.ts +109 -14
  16. package/dist-lib/agent-daemon-client.cjs +1 -1
  17. package/dist-lib/agent-events.cjs +1 -1
  18. package/dist-lib/agent-identity.cjs +1 -1
  19. package/dist-lib/agent-sessions.cjs +1 -1
  20. package/dist-lib/attached-projects.cjs +1 -1
  21. package/dist-lib/auth/shared-session.cjs +1 -1
  22. package/dist-lib/backend-origin.cjs +1 -1
  23. package/dist-lib/beta.cjs +1 -1
  24. package/dist-lib/beta.mjs +1 -1
  25. package/dist-lib/bridge-constants.cjs +1 -1
  26. package/dist-lib/bridge-contract-input.cjs +1 -1
  27. package/dist-lib/bridge-contract-input.mjs +1 -1
  28. package/dist-lib/bridge-contract.cjs +1 -1
  29. package/dist-lib/bridge-contract.mjs +1 -1
  30. package/dist-lib/capture-contract.cjs +1 -1
  31. package/dist-lib/capture-contract.mjs +1 -1
  32. package/dist-lib/cli-constants.cjs +1 -1
  33. package/dist-lib/cloud-contract.cjs +1 -1
  34. package/dist-lib/cloud-contract.mjs +1 -1
  35. package/dist-lib/cloud-test-args.cjs +1 -1
  36. package/dist-lib/cloud-test-args.mjs +1 -1
  37. package/dist-lib/cloud.cjs +1 -1
  38. package/dist-lib/cloud.mjs +1 -1
  39. package/dist-lib/config.cjs +1 -1
  40. package/dist-lib/detox/index.cjs +1 -1
  41. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  42. package/dist-lib/home-paths.cjs +3 -3
  43. package/dist-lib/host/bridge-host.cjs +1 -1
  44. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  45. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  46. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  47. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  48. package/dist-lib/host/websocket-proxy.cjs +1 -1
  49. package/dist-lib/index.cjs +1 -1
  50. package/dist-lib/jump-to-source-babel.cjs +1 -1
  51. package/dist-lib/jump-to-source-native.cjs +1 -1
  52. package/dist-lib/kotlin-host.cjs +102 -21
  53. package/dist-lib/menu.cjs +1 -1
  54. package/dist-lib/menu.mjs +1 -1
  55. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  56. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  57. package/dist-lib/metro-production-bundle.cjs +1 -1
  58. package/dist-lib/metro-production-bundle.mjs +1 -1
  59. package/dist-lib/metro.cjs +1 -1
  60. package/dist-lib/native-app-identity.cjs +1 -1
  61. package/dist-lib/native-app-identity.mjs +1 -1
  62. package/dist-lib/profiles.cjs +1 -1
  63. package/dist-lib/public-brand.cjs +1 -1
  64. package/dist-lib/react-native-host-modules.cjs +1 -1
  65. package/dist-lib/react-native-host-modules.mjs +1 -1
  66. package/dist-lib/render-mode.cjs +1 -1
  67. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  68. package/dist-lib/sdk.cjs +1 -1
  69. package/dist-lib/sdk.mjs +1 -1
  70. package/dist-lib/skills.cjs +28 -39
  71. package/dist-lib/swift-compile.cjs +6 -3
  72. package/dist-lib/swift-compile.mjs +6 -3
  73. package/dist-lib/swift-package.cjs +1 -1
  74. package/dist-lib/swift-package.mjs +1 -1
  75. package/dist-lib/swift-submit.cjs +1 -1
  76. package/dist-lib/swift-submit.mjs +1 -1
  77. package/dist-lib/swift.cjs +1 -1
  78. package/dist-lib/vite-swift.cjs +6 -3
  79. package/dist-lib/vite.cjs +1 -1
  80. package/kotlin/AppSources/android/app/Activity.kt +85 -0
  81. package/kotlin/AppSources/android/app/Application.kt +7 -0
  82. package/kotlin/AppSources/android/content/Context.kt +5 -0
  83. package/kotlin/AppSources/android/content/Intent.kt +16 -0
  84. package/kotlin/AppSources/android/os/Bundle.kt +5 -0
  85. package/kotlin/AppSources/android/util/SuperNotCalledException.kt +3 -0
  86. package/kotlin/AppSources/android/view/View.kt +72 -0
  87. package/kotlin/AppSources/android/view/Window.kt +44 -0
  88. package/kotlin/AppSources/android/widget/FrameLayout.kt +6 -0
  89. package/kotlin/AppSources/androidx/activity/ComponentActivity.kt +57 -0
  90. package/kotlin/AppSources/androidx/activity/compose/ComponentActivity.kt +29 -0
  91. package/kotlin/AppSources/androidx/compose/ui/platform/ComposeView.kt +67 -0
  92. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +136 -0
  93. package/kotlin/AppSources/dev/peach/app/PeachWindowHost.kt +13 -0
  94. package/kotlin/ComposeSources/PeachComposeHost.kt +316 -0
  95. package/kotlin/Sources/dev/onejs/one/OneNativeSourceViewDispatch.kt +13 -0
  96. package/kotlin/Sources/org/json/JSON.kt +6 -0
  97. package/kotlin/fixtures/counter/Counter.d.kt.ts +16 -0
  98. package/kotlin/fixtures/counter/Counter.kt +18 -0
  99. package/kotlin/fixtures/counter/CounterApp.native.tsx +34 -0
  100. package/kotlin/fixtures/counter/CounterApp.tsx +3 -0
  101. package/kotlin/fixtures/counter/CounterScreen.d.kt.ts +7 -0
  102. package/kotlin/fixtures/counter/CounterScreen.kt +18 -3
  103. package/kotlin/fixtures/counter/app/_layout.tsx +4 -0
  104. package/kotlin/fixtures/counter/app/index+spa.tsx +1 -0
  105. package/kotlin/fixtures/counter/app/routes.d.ts +18 -0
  106. package/kotlin/fixtures/counter/package.json +7 -0
  107. package/kotlin/fixtures/counter/tsconfig.json +19 -0
  108. package/kotlin/fixtures/counter/vite.config.ts +10 -0
  109. package/kotlin/fixtures/headless-counter/Headless.kt +5 -0
  110. package/kotlin/fixtures/kotlin-app/app/build.gradle.kts +30 -0
  111. package/kotlin/fixtures/kotlin-app/app/src/main/AndroidManifest.xml +13 -0
  112. package/kotlin/fixtures/kotlin-app/app/src/main/java/dev/contrast/kotlinapp/MainActivity.kt +60 -0
  113. package/kotlin/fixtures/kotlin-app/app/src/main/java/dev/contrast/kotlinapp/ProbeApplication.kt +18 -0
  114. package/kotlin/fixtures/kotlin-app/build.gradle.kts +5 -0
  115. package/kotlin/fixtures/kotlin-app/gradle.properties +4 -0
  116. package/kotlin/fixtures/kotlin-app/settings.gradle.kts +4 -0
  117. package/kotlin/fork/NOTICE +7 -5
  118. package/kotlin/fork/README.md +41 -49
  119. package/kotlin/fork/build.sh +3 -2
  120. package/kotlin/fork/compose-worker-globals.patch +22 -11
  121. package/kotlin/fork/fork.json +2 -2
  122. package/kotlin/fork/publish.ts +30 -15
  123. package/kotlin/fork/skiko-worker-clock.patch +38 -0
  124. package/kotlin/toolchain.json +9 -9
  125. package/package.json +1 -1
  126. package/skills/contrast/SKILL.md +2 -1
  127. package/skills/peach-setup/SKILL.md +2 -2
  128. package/src/{swift-guest.ts → guest-bundle.ts} +22 -7
  129. package/src/home-paths.ts +5 -3
  130. package/src/kotlin-app.ts +411 -0
  131. package/src/kotlin-host.ts +148 -25
  132. package/src/swift-compile.ts +7 -2
@@ -0,0 +1,60 @@
1
+ package dev.contrast.kotlinapp
2
+
3
+ import android.os.Bundle
4
+ import androidx.activity.ComponentActivity
5
+ import androidx.activity.compose.setContent
6
+ import androidx.compose.foundation.background
7
+ import androidx.compose.foundation.layout.Arrangement
8
+ import androidx.compose.foundation.layout.Column
9
+ import androidx.compose.foundation.layout.fillMaxSize
10
+ import androidx.compose.foundation.layout.padding
11
+ import androidx.compose.foundation.layout.safeDrawingPadding
12
+ import androidx.compose.material3.Button
13
+ import androidx.compose.material3.Text
14
+ import androidx.compose.runtime.Composable
15
+ import androidx.compose.runtime.LaunchedEffect
16
+ import androidx.compose.runtime.SideEffect
17
+ import androidx.compose.runtime.getValue
18
+ import androidx.compose.runtime.mutableIntStateOf
19
+ import androidx.compose.runtime.remember
20
+ import androidx.compose.runtime.setValue
21
+ import androidx.compose.ui.Modifier
22
+ import androidx.compose.ui.graphics.Color
23
+ import androidx.compose.ui.platform.testTag
24
+ import androidx.compose.ui.unit.dp
25
+
26
+ class MainActivity : ComponentActivity() {
27
+ override fun onCreate(savedInstanceState: Bundle?) {
28
+ super.onCreate(savedInstanceState)
29
+ Probe.log += "activity.onCreate ${intent.action}"
30
+ setContent { ProbeScreen(content = "first", replace = { replaceContent() }) }
31
+ }
32
+
33
+ // a second setContent on the same activity: android reuses its ComposeView
34
+ private fun replaceContent() {
35
+ Probe.log += "activity.setContent"
36
+ setContent { ProbeScreen(content = "second", replace = { replaceContent() }) }
37
+ }
38
+
39
+ override fun onStart() { super.onStart(); Probe.log += "activity.onStart" }
40
+ override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState); Probe.log += "activity.onPostCreate" }
41
+ override fun onResume() { super.onResume(); Probe.log += "activity.onResume" }
42
+ override fun onPostResume() { super.onPostResume(); Probe.log += "activity.onPostResume" }
43
+ }
44
+
45
+ @Composable
46
+ fun ProbeScreen(content: String, replace: () -> Unit) {
47
+ var count by remember { mutableIntStateOf(0) }
48
+ SideEffect { if ("compose" !in Probe.log) Probe.log += "compose" }
49
+ LaunchedEffect(Unit) { Probe.log += "effect" }
50
+ Column(
51
+ Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
52
+ verticalArrangement = Arrangement.spacedBy(12.dp),
53
+ ) {
54
+ Text("Content: $content", Modifier.testTag("content"))
55
+ Text("Greeting: ${Probe.greeting}", Modifier.testTag("greeting"))
56
+ Button(onClick = { count += 1 }, Modifier.testTag("count")) { Text("Count: $count") }
57
+ Button(onClick = replace, Modifier.testTag("replace")) { Text("Replace content") }
58
+ Text(Probe.log.joinToString("\n"), Modifier.testTag("log"))
59
+ }
60
+ }
@@ -0,0 +1,18 @@
1
+ package dev.contrast.kotlinapp
2
+
3
+ import android.app.Application
4
+
5
+ // process globals the probe reads back: the cold-start order and a value only
6
+ // the application sets before any activity exists.
7
+ object Probe {
8
+ val log = mutableListOf<String>()
9
+ var greeting = "unset"
10
+ }
11
+
12
+ class ProbeApplication : Application() {
13
+ override fun onCreate() {
14
+ super.onCreate()
15
+ Probe.log += "app.onCreate"
16
+ Probe.greeting = "set by application"
17
+ }
18
+ }
@@ -0,0 +1,5 @@
1
+ plugins {
2
+ id("com.android.application") version "9.2.1" apply false
3
+ id("org.jetbrains.kotlin.android") version "2.2.0" apply false
4
+ id("org.jetbrains.kotlin.plugin.compose") version "2.2.0" apply false
5
+ }
@@ -0,0 +1,4 @@
1
+ android.useAndroidX=true
2
+ android.builtInKotlin=false
3
+ android.newDsl=false
4
+ org.gradle.jvmargs=-Xmx2g
@@ -0,0 +1,4 @@
1
+ pluginManagement { repositories { google(); mavenCentral(); gradlePluginPortal() } }
2
+ dependencyResolutionManagement { repositories { google(); mavenCentral() } }
3
+ rootProject.name = "KotlinApp"
4
+ include(":app")
@@ -17,9 +17,11 @@ The license text is in LICENSE beside this file.
17
17
  https://android.googlesource.com/platform/frameworks/base
18
18
 
19
19
  Modifications: compose-worker-globals.patch and skiko-worker-clock.patch
20
- change four upstream source files so their web code reads globalThis instead
21
- of window (performance.now, isSecureContext, navigator.clipboard,
22
- navigator.languages). Each modified file carries a notice saying so. No other
23
- upstream file changes; the built klibs are ui-wasm-js and ui-text-wasm-js
20
+ change clock and clipboard reads to globalThis rather than window, and add an
21
+ explicit ui-text locale provider and Skiko host OS provider whose defaults
22
+ retain upstream navigator behavior. Each modified file carries a notice saying so. The Skiko
23
+ loader build also reads import.meta.url instead of self.location.href, which
24
+ React Native tenants do not expose. The built klibs are ui-wasm-js and ui-text-wasm-js
24
25
  1.13.0-alpha01 and skiko-wasm-js 0.152.0-alpha02 compiled from the patched
25
- sources.
26
+ sources; skiko-wasm-0.152.0-alpha02.jar contains that worker-safe loader and
27
+ Skiko's own wasm runtime.
@@ -1,59 +1,51 @@
1
1
  # Peach's JetBrains web fork
2
2
 
3
- Peach runs Compose in a tenant worker, which has no `window`, like any browser
4
- worker. Three web functions in Compose UI 1.13.0-alpha01 and one in Skiko
5
- 0.152.0-alpha02 name `window` anyway, so a Compose scene throws
6
- `window is not defined` before its first frame. Peach carries patched builds
7
- of those modules until upstream supports workers.
3
+ Peach runs Compose in a React Native tenant worker, with no `window`,
4
+ `document`, `location`, or browser navigator language/platform fields. The official
5
+ Compose UI 1.13.0-alpha01 and Skiko 0.152.0-alpha02 platform actuals assume
6
+ browser globals that this native host does not publish. Peach carries patched
7
+ builds until upstream supplies equivalent worker and native host hooks.
8
8
 
9
- The patches replace `window` with `globalThis` and nothing else:
10
-
11
- | module | file | read |
9
+ | module | file | native host binding |
12
10
  | --- | --- | --- |
13
- | `org.jetbrains.compose.ui:ui` | `Actuals.wasm.kt` | `currentTimeMillis()` from `globalThis.performance.now()` |
14
- | `org.jetbrains.compose.ui:ui` | `PlatformClipboard.web.kt` | `globalThis.isSecureContext`, `globalThis.navigator.clipboard` |
15
- | `org.jetbrains.compose.ui:ui-text` | `PlatformLocale.web.kt` | `globalThis.navigator.languages` |
16
- | `org.jetbrains.skiko:skiko` | `Actuals.web.kt` | `currentNanoTime()` from `globalThis.performance.now()` |
17
-
18
- In a document `globalThis` is the window, so behavior there is unchanged. In a
19
- worker each read returns the worker's real value: its clock, its secure
20
- context, its languages, and no clipboard API. Nothing defines a `window`.
21
-
22
- ## files
23
-
24
- - `fork.json`: upstream repositories, tags and commits, patch sha256s, and the
25
- builder's pinned JDK and Android command-line tools.
26
- - `compose-worker-globals.patch`, `skiko-worker-clock.patch`: the whole fork.
27
- Every modified file carries a notice saying it was modified.
28
- - `LICENSE` (Apache 2.0, both upstreams) and `NOTICE`.
29
- - `build.sh`: fetches each upstream at its commit, applies its patch and runs
30
- the upstream Gradle build for the changed modules only.
31
-
32
- `packages/peachsim/kotlin/toolchain.json` names each built klib by its sha256
33
- at `https://r2.rnxsim.com/peach-kotlin-fork/<sha256>/<file>`, beside a copy of
34
- the patches, `fork.json`, `LICENSE` and `NOTICE`. Every other Compose library
35
- is the official Maven artifact. The klibs load only for an app whose Kotlin
36
- imports Compose, like the rest of the Compose closure.
37
-
38
- ## rebuilding
11
+ | Compose `ui` | `Actuals.wasm.kt` | clock reads `globalThis.performance.now()` |
12
+ | Compose `ui` | `PlatformClipboard.web.kt` | worker-safe secure-context and clipboard reads |
13
+ | Compose `ui-text` | `PlatformLocale.web.kt` | explicit `setPlatformLocaleProvider`; its default keeps upstream navigator behavior |
14
+ | Skiko | `OsArch.web.kt` | explicit `setHostOsProvider`; its default lazily retains upstream OS detection |
15
+ | Skiko | `Actuals.web.kt` | nanosecond clock reads `globalThis.performance.now()` |
16
+ | Skiko loader build | `OptimizeSkikoWasmTask.kt` | script directory reads `import.meta.url`, without `self.location` |
17
+
18
+ The Compose host installs the locale provider once before any scene, from the
19
+ same device locale it passes to `PlatformContext`, and installs `OS.Android`
20
+ through Skiko’s host OS provider before any scene. Browser navigator fields
21
+ stay absent in guest JS. Changes to device language after launch are outside
22
+ this contract.
23
+
24
+ `fork.json` pins repositories, commits, patch hashes, JDK and Android tools.
25
+ Every changed upstream file carries a modification notice. `LICENSE` and
26
+ `NOTICE` travel with the patches. `build.sh` builds the changed KLIBs and Skiko
27
+ runtime jar with the upstream Gradle builds on the Linux builder.
39
28
 
40
29
  ```sh
41
30
  bun remote bash packages/peachsim/kotlin/fork/build.sh
42
31
  bun packages/peachsim/kotlin/fork/publish.ts .remote-out/kotlin-fork
43
32
  ```
44
33
 
45
- The build runs on the Linux builder under its idle scheduling and memory
46
- bound; a warm rebuild takes a few minutes. The build delivers the klibs with
47
- the record it used (`fork.json`, patches, LICENSE, NOTICE, `klibs.sha256`).
48
- `publish.ts` uploads exactly that delivery under each klib's sha256, refuses to
49
- replace an existing object with other bytes, and prints the `toolchain.json`
50
- entries. Moving a pin means a new tag, commit, regenerated patch and patch
51
- sha256 in `fork.json`, then a rebuild.
52
-
53
- ## exit
54
-
55
- Delete this directory and point `toolchain.json` back at Maven Central when
56
- official Compose Multiplatform and Skiko releases read worker-safe globals in
57
- all four places, and those releases match the Android pin table in
58
- `plans/peach/kotlin-everywhere.md`. The upstream issue text lives with that
59
- plan's owner until it is filed.
34
+ The delivery includes `artifacts.sha256` and its exact fork record. Publication
35
+ stores each binary at `peach-kotlin-fork/<binary-sha256>/<file>` and its record
36
+ at `peach-kotlin-fork/<binary-sha256>/records/<record-sha256>/`. It refuses to
37
+ replace existing bytes. A metadata change cannot overwrite a previous record
38
+ when a binary is unchanged. `toolchain.json` pins these immutable binary URLs;
39
+ all other Compose libraries remain official Maven artifacts. Everything stays
40
+ lazy for Kotlin/Compose apps.
41
+
42
+ The isolated worker probe cannot certify the native tenant's absent globals.
43
+ `scripts/debug/peach-compose-mount/tenant-run.ts` boots the actual capture tenant
44
+ with the imported `.kt` component, asserts those globals stay absent, and
45
+ checks `Locale.current` and `LocalLocaleList` against an ar-EG device setting
46
+ with an en-US browser.
47
+
48
+ Delete the fork when official releases provide worker-safe clocks/clipboard,
49
+ a location-independent loader and equivalent native locale/host OS provider hooks,
50
+ then rematch the Android pins in `plans/peach/kotlin-everywhere.md`. The prepared
51
+ upstream issue names these hooks and the full guest regression vehicle.
@@ -115,7 +115,8 @@ checkout 1
115
115
  cd "${SOURCE}/skiko"
116
116
  gradle -Pdeploy.version=0.152.0-alpha02 -Pdeploy.release=true \
117
117
  -Pskiko.wasm.enabled=true -Pskiko.awt.enabled=false \
118
- :publishWasmJsPublicationToMavenLocal
118
+ :publishWasmJsPublicationToMavenLocal :skikoWasmJar
119
+ cp build/libs/skiko-wasm-0.152.0-alpha02.jar "${DELIVER}/"
119
120
  )
120
121
 
121
122
  cp "${M2}/org/jetbrains/compose/ui/ui-wasm-js/1.13.0-alpha01/ui-wasm-js-1.13.0-alpha01.klib" \
@@ -125,4 +126,4 @@ cp "${M2}/org/jetbrains/compose/ui/ui-wasm-js/1.13.0-alpha01/ui-wasm-js-1.13.0-a
125
126
  # the record travels with the klibs it describes, so publishing attaches what
126
127
  # this build used, not whatever the checkout holds later
127
128
  cp "${FORK}/fork.json" "${FORK}/LICENSE" "${FORK}/NOTICE" "${FORK}"/*.patch "${DELIVER}/"
128
- (cd "${DELIVER}" && sha256sum -- *.klib > klibs.sha256 && cat klibs.sha256)
129
+ (cd "${DELIVER}" && sha256sum -- *.klib *.jar > artifacts.sha256 && cat artifacts.sha256)
@@ -1,5 +1,4 @@
1
1
  diff --git a/compose/ui/ui-text/src/webMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.web.kt b/compose/ui/ui-text/src/webMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.web.kt
2
- index 6e80e51..28f1de1 100644
3
2
  --- a/compose/ui/ui-text/src/webMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.web.kt
4
3
  +++ b/compose/ui/ui-text/src/webMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.web.kt
5
4
  @@ -12,6 +12,10 @@
@@ -7,20 +6,32 @@ index 6e80e51..28f1de1 100644
7
6
  * See the License for the specific language governing permissions and
8
7
  * limitations under the License.
9
8
  + *
10
- + * Modified for Peach (https://peachsim.com): reads worker-safe globals through
11
- + * globalThis instead of window, so this code runs in a Web Worker as well as a
12
- + * document. See the Peach fork record for the patch and upstream revision.
9
+ + * modified for Peach: an explicit locale provider lets a native host supply
10
+ + * device languages without exposing browser navigator fields to guest code.
11
+ + * see the fork record for the patch and upstream revision.
13
12
  */
14
13
 
15
14
  package androidx.compose.ui.text.intl
16
- @@ -127,4 +131,4 @@ private fun userPreferredLanguages(): List<String> {
15
+ @@ -64,8 +68,18 @@
16
+ // The locale code en-001 denotes the English language spoken in the World.
17
+ private const val FALLBACK_LANGUAGE_TAG = "en-001"
17
18
 
18
- @OptIn(ExperimentalWasmJsInterop::class)
19
- private fun getUserPreferredLanguagesAsArray(): JsArray<JsString> =
20
- - js("window.navigator.languages")
21
-
22
- + js("globalThis.navigator.languages")
23
-
19
+ +private var currentLocaleProvider: () -> LocaleList = {
20
+ + localeListFromLanguageTags(userPreferredLanguages())
21
+ +}
22
+ +
23
+ +// the host installs the device provider before constructing its first scene.
24
+ +@InternalComposeUiApi
25
+ +fun setPlatformLocaleProvider(provider: () -> LocaleList) {
26
+ + currentLocaleProvider = provider
27
+ +}
28
+ +
29
+ internal actual fun createPlatformLocaleDelegate(): PlatformLocaleDelegate =
30
+ object : PlatformLocaleDelegate {
31
+ override val current: LocaleList
32
+ - get() = localeListFromLanguageTags(userPreferredLanguages())
33
+ + get() = currentLocaleProvider()
34
+ }
24
35
  diff --git a/compose/ui/ui/src/wasmJsMain/kotlin/androidx/compose/ui/Actuals.wasm.kt b/compose/ui/ui/src/wasmJsMain/kotlin/androidx/compose/ui/Actuals.wasm.kt
25
36
  index 26aa8ea..aa4f6d4 100644
26
37
  --- a/compose/ui/ui/src/wasmJsMain/kotlin/androidx/compose/ui/Actuals.wasm.kt
@@ -18,7 +18,7 @@
18
18
  "tag": "v1.13.0-alpha01",
19
19
  "commit": "a9e47e8aa04c95cfe572ab4297c770f3cbfb234e",
20
20
  "patch": "compose-worker-globals.patch",
21
- "patchSha256": "7f7df0788145b8d44c32a9ca7746cf254c363beca816922f84d73faa0effed5a"
21
+ "patchSha256": "51fa45bda1e0aae7db2eab1b056def406352958257e1d3b899c5c3d8aa5edddb"
22
22
  },
23
23
  {
24
24
  "name": "skiko",
@@ -27,7 +27,7 @@
27
27
  "tag": "v0.152.0-alpha02",
28
28
  "commit": "96378296bb23c23c5f30cd2b0d42d6f8fdeff730",
29
29
  "patch": "skiko-worker-clock.patch",
30
- "patchSha256": "d55007f3aa4c2b61e44f036bdb43f37ab38455bf800664fdb6598955874d10da"
30
+ "patchSha256": "735a42f7a9d3be1ab288f4d67bef44924ec2b6bbcd59f5a5f76edf22f912c347"
31
31
  }
32
32
  ]
33
33
  }
@@ -1,6 +1,6 @@
1
- // uploads the klibs one build.sh run delivered to R2, each under its sha256
1
+ // uploads the artifacts one build.sh run delivered to R2, each under its sha256
2
2
  // beside the record that run copied (patches, fork.json, LICENSE, NOTICE,
3
- // klibs.sha256), and prints the toolchain.json entries that name them. keys
3
+ // artifacts.sha256), and prints the toolchain.json entries that name them. keys
4
4
  // are immutable: an existing object with other bytes stops the publish. needs
5
5
  // R2 credentials; reads are public.
6
6
  //
@@ -22,27 +22,32 @@ const files = readdirSync(delivered)
22
22
  const record = files.filter(
23
23
  (name) =>
24
24
  name.endsWith('.patch') ||
25
- ['fork.json', 'LICENSE', 'NOTICE', 'klibs.sha256'].includes(name),
25
+ ['fork.json', 'LICENSE', 'NOTICE', 'artifacts.sha256'].includes(name),
26
+ )
27
+ if (
28
+ record.length !==
29
+ files.filter((name) => !name.endsWith('.klib') && !name.endsWith('.jar')).length
26
30
  )
27
- if (record.length !== files.filter((name) => !name.endsWith('.klib')).length)
28
31
  throw new Error(`${delivered} holds files build.sh does not deliver`)
29
- if (!record.includes('klibs.sha256') || !record.includes('fork.json'))
32
+ if (!record.includes('artifacts.sha256') || !record.includes('fork.json'))
30
33
  throw new Error(`${delivered} is not a build.sh delivery`)
31
34
 
32
- // the klibs are exactly the ones the build hashed
35
+ // the artifacts are exactly the ones the build hashed
33
36
  const built = new Map(
34
- readFileSync(path.join(delivered, 'klibs.sha256'), 'utf8')
37
+ readFileSync(path.join(delivered, 'artifacts.sha256'), 'utf8')
35
38
  .trim()
36
39
  .split('\n')
37
40
  .map((line) => {
38
41
  const [hash, name] = line.split(/\s+/)
39
- if (!hash || !name) throw new Error(`malformed klibs.sha256 line: ${line}`)
42
+ if (!hash || !name) throw new Error(`malformed artifacts.sha256 line: ${line}`)
40
43
  return [name, hash]
41
44
  }),
42
45
  )
43
- const klibs = files.filter((name) => name.endsWith('.klib')).sort()
44
- if (klibs.length === 0 || klibs.length !== built.size)
45
- throw new Error(`${delivered} klibs do not match klibs.sha256`)
46
+ const artifacts = files
47
+ .filter((name) => name.endsWith('.klib') || name.endsWith('.jar'))
48
+ .sort()
49
+ if (artifacts.length === 0 || artifacts.length !== built.size)
50
+ throw new Error(`${delivered} artifacts do not match artifacts.sha256`)
46
51
 
47
52
  async function put(key: string, bytes: Buffer, contentType: string) {
48
53
  const existing = await readR2Object(key, 120000)
@@ -53,23 +58,33 @@ async function put(key: string, bytes: Buffer, contentType: string) {
53
58
  await uploadToR2(key, bytes, contentType, IMMUTABLE)
54
59
  }
55
60
 
61
+ const recordHash = sha256(
62
+ Buffer.concat(
63
+ record
64
+ .sort()
65
+ .flatMap((file) => [
66
+ Buffer.from(file + '\0'),
67
+ readFileSync(path.join(delivered, file)),
68
+ ]),
69
+ ),
70
+ )
56
71
  const entries = []
57
- for (const name of klibs) {
72
+ for (const name of artifacts) {
58
73
  const bytes = readFileSync(path.join(delivered, name))
59
74
  const hash = sha256(bytes)
60
75
  if (built.get(name) !== hash)
61
- throw new Error(`${name} is not the klib the build hashed`)
76
+ throw new Error(`${name} is not the artifact the build hashed`)
62
77
  const prefix = `peach-kotlin-fork/${hash}`
63
78
  await put(`${prefix}/${name}`, bytes, 'application/octet-stream')
64
79
  for (const file of record)
65
80
  await put(
66
- `${prefix}/${file}`,
81
+ `${prefix}/records/${recordHash}/${file}`,
67
82
  readFileSync(path.join(delivered, file)),
68
83
  'text/plain; charset=utf-8',
69
84
  )
70
85
  // the loader caches a library by its name, so the name carries the hash
71
86
  entries.push({
72
- name: name.replace(/\.klib$/, `-peach-${hash.slice(0, 12)}.klib`),
87
+ name: name.replace(/\.(klib|jar)$/, `-peach-${hash.slice(0, 12)}.$1`),
73
88
  url: `${RNX_PUBLIC.publicR2Origin}/${prefix}/${name}`,
74
89
  sha256: hash,
75
90
  })
@@ -31,3 +31,41 @@ index 366ed37..d56c267 100644
31
31
  + js("globalThis.performance.now() * 1000000")
32
32
 
33
33
  annotation class WebImport(val name : String)
34
+ diff --git a/skiko/buildSrc/src/main/kotlin/OptimizeSkikoWasmTask.kt b/skiko/buildSrc/src/main/kotlin/OptimizeSkikoWasmTask.kt
35
+ --- a/skiko/buildSrc/src/main/kotlin/OptimizeSkikoWasmTask.kt
36
+ +++ b/skiko/buildSrc/src/main/kotlin/OptimizeSkikoWasmTask.kt
37
+ @@ -65,6 +65,12 @@ abstract class OptimizeSkikoWasmTask : AbstractSkikoNativeToolTask() {
38
+ "${inputFile.nameWithoutExtension}.wasm",
39
+ "${libOutputFileName.get()}.wasm"
40
+ )
41
+ + // peach tenants hide location from react native;
42
+ + // emscripten already uses this module url below.
43
+ + .replace(
44
+ + "scriptDirectory=self.location.href",
45
+ + "scriptDirectory=import.meta.url"
46
+ + )
47
+ )
48
+ }
49
+ }
50
+ diff --git a/skiko/src/webMain/kotlin/org/jetbrains/skiko/OsArch.web.kt b/skiko/src/webMain/kotlin/org/jetbrains/skiko/OsArch.web.kt
51
+ --- a/skiko/src/webMain/kotlin/org/jetbrains/skiko/OsArch.web.kt
52
+ +++ b/skiko/src/webMain/kotlin/org/jetbrains/skiko/OsArch.web.kt
53
+ @@ -3,6 +3,15 @@
54
+ import kotlinx.browser.window
55
+ import kotlin.js.js
56
+ -
57
+ +
58
+ -actual val hostOs: OS = detectHostOs()
59
+ +private val defaultHostOs: OS by lazy { detectHostOs() }
60
+ +private var hostOsProvider: () -> OS = { defaultHostOs }
61
+ +
62
+ +// modified for Peach: native hosts install their device's platform before
63
+ +// any compose scene. see the fork record for the patch and upstream revision.
64
+ +fun setHostOsProvider(provider: () -> OS) {
65
+ + hostOsProvider = provider
66
+ +}
67
+ +
68
+ +actual val hostOs: OS get() = hostOsProvider()
69
+ -
70
+ +
71
+ actual val hostArch: Arch = Arch.Unknown
@@ -33,9 +33,9 @@
33
33
  "sha256": "6187e199922b061cffa16fd9ae2ed920d6a59f0b0f7c3207e829b983ff132d38"
34
34
  },
35
35
  {
36
- "name": "skiko-wasm-js-0.152.0-alpha02-peach-a480f0730dab.klib",
37
- "url": "https://r2.rnxsim.com/peach-kotlin-fork/a480f0730dabea887e2fa22f5f5683454d3594445557a8d8149647af90d3d312/skiko-wasm-js-0.152.0-alpha02.klib",
38
- "sha256": "a480f0730dabea887e2fa22f5f5683454d3594445557a8d8149647af90d3d312"
36
+ "name": "skiko-wasm-js-0.152.0-alpha02-peach-3834433e1790.klib",
37
+ "url": "https://r2.rnxsim.com/peach-kotlin-fork/3834433e17902a3d86dc973abb245b7af5aae0012ca29e1ac9668d74335f3115/skiko-wasm-js-0.152.0-alpha02.klib",
38
+ "sha256": "3834433e17902a3d86dc973abb245b7af5aae0012ca29e1ac9668d74335f3115"
39
39
  },
40
40
  {
41
41
  "name": "annotation-wasm-js-1.9.1.klib",
@@ -73,9 +73,9 @@
73
73
  "sha256": "dab0b337dd99ec99e8a3cc4efd365d8101db050971490eff6839fc566aeadc6c"
74
74
  },
75
75
  {
76
- "name": "ui-text-wasm-js-1.13.0-alpha01-peach-a2c25f211136.klib",
77
- "url": "https://r2.rnxsim.com/peach-kotlin-fork/a2c25f2111369c638e785f192312934a78fcacc15186da681429b580b143ddd7/ui-text-wasm-js-1.13.0-alpha01.klib",
78
- "sha256": "a2c25f2111369c638e785f192312934a78fcacc15186da681429b580b143ddd7"
76
+ "name": "ui-text-wasm-js-1.13.0-alpha01-peach-10c54ff0fa5f.klib",
77
+ "url": "https://r2.rnxsim.com/peach-kotlin-fork/10c54ff0fa5fc0ff066fb4561e2d5ca9d5c41fedcb32a61414016ef215b035ba/ui-text-wasm-js-1.13.0-alpha01.klib",
78
+ "sha256": "10c54ff0fa5fc0ff066fb4561e2d5ca9d5c41fedcb32a61414016ef215b035ba"
79
79
  },
80
80
  {
81
81
  "name": "ui-unit-wasm-js-1.13.0-alpha01.klib",
@@ -290,9 +290,9 @@
290
290
  "skiko": "0.152.0-alpha02"
291
291
  },
292
292
  "composeRuntime": {
293
- "name": "skiko-wasm-0.152.0-alpha02.jar",
294
- "url": "https://repo1.maven.org/maven2/org/jetbrains/skiko/skiko-wasm-js/0.152.0-alpha02/skiko-wasm-js-0.152.0-alpha02-skiko-runtime.jar",
295
- "sha256": "14e37c567f10d906a10a74df80b44ee5a5dd5e3a75929f351a32d5b123dad643"
293
+ "name": "skiko-wasm-0.152.0-alpha02-peach-4814686165d0.jar",
294
+ "url": "https://r2.rnxsim.com/peach-kotlin-fork/4814686165d0ba869e605e661d0fb261ecc79f5e984ed6ae78dfe20300923c98/skiko-wasm-0.152.0-alpha02.jar",
295
+ "sha256": "4814686165d0ba869e605e661d0fb261ecc79f5e984ed6ae78dfe20300923c98"
296
296
  },
297
297
  "composeJsLibraries": [
298
298
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peachsim",
3
- "version": "0.1.648",
3
+ "version": "0.1.650",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for peach.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -20,6 +20,7 @@ automation, inspecting the render tree, and managing sims.
20
20
  | command | description |
21
21
  | --- | --- |
22
22
  | `peach swift` | preview a SwiftPM app with hosted compilation and live Swift edits |
23
+ | `peach kotlin` | run an Android Compose app module from its launcher activity |
23
24
  | `peach maestro` | author, generate, and run Maestro YAML flows against peach |
24
25
  | `peach detox` | run existing Detox suites against peach on headless Chromium |
25
26
  | `peach cleanup` | inspect and reclaim local peach disk usage |
@@ -56,7 +57,7 @@ automation, inspecting the render tree, and managing sims.
56
57
  | `peach setup` | configure optional local conveniences |
57
58
  | `peach compat` | check package compatibility |
58
59
  | `peach project-report` | print local project and toolchain metadata |
59
- | `peach report-issue` | send an explicitly approved compatibility report |
60
+ | `peach support` | ask for help, report a problem, and read our replies |
60
61
  | `peach desktop` | launch or install the desktop companion |
61
62
  | `peach daemon` | manage the recommended background bridge service |
62
63
  | `peach version` | show the CLI + engine runtime versions you are running |
@@ -119,13 +119,13 @@ run `peach compat --json` from the app project root during setup. if the scan
119
119
  identifies a plausible missing Peach seam, or the running app gives concrete
120
120
  evidence that behavior is missing or broken specifically in Peach, summarize
121
121
  the package, expected behavior, actual behavior, and runtime evidence for the
122
- user. then offer to send that finding with `peach report-issue`.
122
+ user. then offer to send that finding with `peach support report`.
123
123
 
124
124
  **never submit a report automatically or behind the user's back.** wait for an
125
125
  explicit yes. only then run this from the app project root:
126
126
 
127
127
  ```sh
128
- peach report-issue --yes "<package; expected behavior; actual behavior; runtime evidence>"
128
+ peach support report --yes "<package; expected behavior; actual behavior; runtime evidence>"
129
129
  ```
130
130
 
131
131
  the command automatically attaches the local compatibility scan plus bounded
@@ -1,5 +1,6 @@
1
- // the built-in guest uses the existing Metro runtime and host module seam.
2
- // the author supplies Swift; this entry carries no engine or compiler code.
1
+ // the built-in guests use the existing Metro runtime and host module seam.
2
+ // the author supplies Swift or Kotlin; an entry carries no engine or compiler
3
+ // code, only the registration of the host module's root.
3
4
  import {
4
5
  appendRNXMetroModuleIdentityFooter,
5
6
  RNX_METRO_MODULE_IDENTITY_VERSION,
@@ -7,12 +8,26 @@ import {
7
8
  import { METRO_REQUIRE_POLYFILL } from './metro-runtime-source'
8
9
 
9
10
  export function swiftGuestBundle(packageName: string): string {
11
+ return guestBundle(
12
+ 'peachsim/swift',
13
+ `AppRegistry.registerComponent('PeachSwift', () => host.createSwiftPreviewEntry(${JSON.stringify(packageName)}))`,
14
+ )
15
+ }
16
+
17
+ export function kotlinGuestBundle(): string {
18
+ return guestBundle(
19
+ 'peachsim/kotlin',
20
+ `AppRegistry.registerComponent('PeachKotlin', () => host.KotlinAppRoot)`,
21
+ )
22
+ }
23
+
24
+ function guestBundle(hostModule: string, registration: string): string {
10
25
  const modules = [
11
26
  {
12
- name: 'peach-swift-entry.js',
27
+ name: 'peach-guest-entry.js',
13
28
  code: `const { AppRegistry } = require(1)
14
- const { createSwiftPreviewEntry } = require(2)
15
- AppRegistry.registerComponent('PeachSwift', () => createSwiftPreviewEntry(${JSON.stringify(packageName)}))`,
29
+ const host = require(2)
30
+ ${registration}`,
16
31
  deps: [1, 2],
17
32
  },
18
33
  {
@@ -21,8 +36,8 @@ AppRegistry.registerComponent('PeachSwift', () => createSwiftPreviewEntry(${JSON
21
36
  deps: [],
22
37
  },
23
38
  {
24
- name: 'peachsim/swift',
25
- code: `module.exports = global.__contrastHostModules['peachsim/swift']`,
39
+ name: hostModule,
40
+ code: `module.exports = global.__contrastHostModules[${JSON.stringify(hostModule)}]`,
26
41
  deps: [],
27
42
  },
28
43
  {
package/src/home-paths.ts CHANGED
@@ -264,13 +264,15 @@ export interface PrivacyPreferences {
264
264
  configured: boolean
265
265
  }
266
266
 
267
+ // bounded diagnostics and automatic crash reports are on until the person turns
268
+ // them off (`peach config telemetry off`, or DO_NOT_TRACK for one run).
267
269
  export function readPrivacyPreferences(): PrivacyPreferences {
268
270
  const settings = readSharedConfig().settings
269
271
  const productAnalytics = settings?.productAnalytics
270
272
  const crashReports = settings?.crashReports
271
273
  return {
272
- productAnalytics: typeof productAnalytics === 'boolean' ? productAnalytics : false,
273
- crashReports: typeof crashReports === 'boolean' ? crashReports : false,
274
+ productAnalytics: typeof productAnalytics === 'boolean' ? productAnalytics : true,
275
+ crashReports: typeof crashReports === 'boolean' ? crashReports : true,
274
276
  configured: typeof productAnalytics === 'boolean',
275
277
  }
276
278
  }
@@ -286,7 +288,7 @@ export function writePrivacyPreferences(
286
288
  })
287
289
  }
288
290
 
289
- // created only after explicit consent, alongside the privacy preferences.
291
+ // created on the first diagnostic sent, alongside the privacy preferences.
290
292
  export function readOrCreateAnonymousId(): string {
291
293
  const existing = readSharedConfig().settings?.anonymousId
292
294
  if (