peachsim 0.1.659 → 0.1.661

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 (96) hide show
  1. package/README.md +10 -15
  2. package/cli/cli-update.ts +53 -21
  3. package/cli/commands/compat.ts +6 -2
  4. package/cli/commands/control.ts +12 -3
  5. package/cli/commands/inspect.ts +16 -14
  6. package/cli/commands/upgrade.ts +4 -2
  7. package/cli/current-sim.ts +4 -0
  8. package/cli/drivers/linux-browser-libs.lock.json +1088 -0
  9. package/cli/drivers/linux-browser-libs.ts +182 -0
  10. package/cli/drivers/playwright-provisioning.ts +20 -98
  11. package/cli/drivers/playwright-sim-host.ts +9 -0
  12. package/cli/drivers/playwright.ts +2 -1
  13. package/dist-lib/agent-daemon-client.cjs +1 -1
  14. package/dist-lib/agent-events.cjs +1 -1
  15. package/dist-lib/agent-identity.cjs +1 -1
  16. package/dist-lib/agent-sessions.cjs +1 -1
  17. package/dist-lib/attached-projects.cjs +1 -1
  18. package/dist-lib/auth/shared-session.cjs +1 -1
  19. package/dist-lib/backend-origin.cjs +18 -1
  20. package/dist-lib/beta.cjs +1 -1
  21. package/dist-lib/beta.mjs +1 -1
  22. package/dist-lib/bridge-constants.cjs +1 -1
  23. package/dist-lib/bridge-contract-input.cjs +1 -1
  24. package/dist-lib/bridge-contract-input.mjs +1 -1
  25. package/dist-lib/bridge-contract.cjs +1 -1
  26. package/dist-lib/bridge-contract.mjs +1 -1
  27. package/dist-lib/capture-contract.cjs +1 -1
  28. package/dist-lib/capture-contract.mjs +1 -1
  29. package/dist-lib/cli-constants.cjs +1 -1
  30. package/dist-lib/cloud-contract.cjs +1 -1
  31. package/dist-lib/cloud-contract.mjs +1 -1
  32. package/dist-lib/cloud-test-args.cjs +1 -1
  33. package/dist-lib/cloud-test-args.mjs +1 -1
  34. package/dist-lib/cloud.cjs +1 -1
  35. package/dist-lib/cloud.mjs +1 -1
  36. package/dist-lib/config.cjs +1 -1
  37. package/dist-lib/detox/index.cjs +1 -1
  38. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  39. package/dist-lib/home-paths.cjs +1 -1
  40. package/dist-lib/host/bridge-host.cjs +45 -41
  41. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  42. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  43. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  44. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  45. package/dist-lib/host/websocket-proxy.cjs +1 -1
  46. package/dist-lib/index.cjs +5 -1
  47. package/dist-lib/jump-to-source-babel.cjs +1 -1
  48. package/dist-lib/jump-to-source-native.cjs +1 -1
  49. package/dist-lib/kotlin-host.cjs +8 -3
  50. package/dist-lib/menu.cjs +1 -1
  51. package/dist-lib/menu.mjs +1 -1
  52. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  53. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  54. package/dist-lib/metro-production-bundle.cjs +1 -1
  55. package/dist-lib/metro-production-bundle.mjs +1 -1
  56. package/dist-lib/metro.cjs +1 -1
  57. package/dist-lib/native-app-identity.cjs +1 -1
  58. package/dist-lib/native-app-identity.mjs +1 -1
  59. package/dist-lib/plist.cjs +1 -1
  60. package/dist-lib/plist.mjs +1 -1
  61. package/dist-lib/profiles.cjs +1 -1
  62. package/dist-lib/public-brand.cjs +1 -1
  63. package/dist-lib/react-native-host-modules.cjs +1 -1
  64. package/dist-lib/react-native-host-modules.mjs +1 -1
  65. package/dist-lib/render-mode.cjs +1 -1
  66. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  67. package/dist-lib/sdk.cjs +1 -1
  68. package/dist-lib/sdk.mjs +1 -1
  69. package/dist-lib/skills.cjs +1339 -180
  70. package/dist-lib/swift-compile.cjs +1 -1
  71. package/dist-lib/swift-compile.mjs +1 -1
  72. package/dist-lib/swift-package.cjs +1 -1
  73. package/dist-lib/swift-package.mjs +1 -1
  74. package/dist-lib/swift-submit.cjs +1 -1
  75. package/dist-lib/swift-submit.mjs +1 -1
  76. package/dist-lib/swift.cjs +1 -1
  77. package/dist-lib/vite-swift.cjs +1 -1
  78. package/dist-lib/vite.cjs +1 -1
  79. package/kotlin/AppSources/android/app/Activity.kt +48 -3
  80. package/kotlin/AppSources/android/app/Application.kt +4 -0
  81. package/kotlin/AppSources/android/content/res/Configuration.kt +4 -0
  82. package/kotlin/AppSources/android/content/res/Resources.kt +3 -1
  83. package/kotlin/AppSources/android/view/Window.kt +15 -9
  84. package/kotlin/AppSources/androidx/activity/ComponentActivity.kt +36 -3
  85. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +233 -86
  86. package/kotlin/ComposeSources/PeachComposeHost.kt +15 -3
  87. package/kotlin/fork/README.md +1 -0
  88. package/kotlin/fork/compose-worker-globals.patch +26 -0
  89. package/kotlin/fork/fork.json +1 -1
  90. package/kotlin/toolchain.json +3 -3
  91. package/package.json +2 -1
  92. package/src/android-theme.ts +14 -0
  93. package/src/backend-origin.ts +25 -0
  94. package/src/host/bridge-host.ts +24 -37
  95. package/src/kotlin-app.ts +29 -4
  96. package/src/kotlin-host.ts +7 -2
@@ -592,24 +592,11 @@ export class SootSimBridgeHost {
592
592
  // subscriptions get cleaned up automatically on close.
593
593
  this.agentHost.registerSocket(ws)
594
594
 
595
- if (role === 'sim') {
596
- sim = {
597
- id: this.allocateSimId(),
598
- ws,
599
- origin,
600
- connectedAt: Date.now(),
601
- lastSeenAt: Date.now(),
602
- lastActiveAt: 0,
603
- recentActions: [],
604
- }
605
- this.sims.set(sim.id, sim)
606
- this.writeConnectedRuntimeSnapshot()
607
- if (this.shouldPromoteSim(sim)) {
608
- this.primarySimId = sim.id
609
- }
610
- this.broadcastSimAssignments()
611
- this.broadcastSimClientStates()
612
- } else {
595
+ // a browser socket becomes a sim when its page registers. the shell's
596
+ // agent bridge and other page helpers share this endpoint without ever
597
+ // registering, so giving them a sim id at connect listed ghost sims
598
+ // that took an id and could be neither driven nor closed.
599
+ if (role === 'cli') {
613
600
  const fallbackKey = `ws-${this.nextCliFallbackId++}`
614
601
  this.cliIdentityKeyBySocket.set(ws, fallbackKey)
615
602
  }
@@ -709,26 +696,30 @@ export class SootSimBridgeHost {
709
696
  // Origin-based role before this socket has registered a page. This reuses the existing
710
697
  // daemon transport and call envelope; it does not add a second routing
711
698
  // surface or let a registered sim switch roles mid-connection.
712
- if (role === 'sim' && msg.type === 'bridge:hello' && sim && !sim.url) {
713
- const provisionalId = sim.id
714
- this.sims.delete(provisionalId)
715
- if (this.primarySimId === provisionalId) {
716
- this.primarySimId = this.getOpenSim()?.id ?? null
717
- }
718
- sim = null
699
+ if (role === 'sim' && msg.type === 'bridge:hello' && !sim) {
719
700
  role = 'cli'
720
701
  this.cliIdentityKeyBySocket.set(ws, `ws-${this.nextCliFallbackId++}`)
721
- this.writeConnectedRuntimeSnapshot()
722
- this.broadcastSimAssignments()
723
- this.broadcastSimClientStates()
724
702
  }
725
703
 
726
704
  if (role === 'sim') {
727
705
  if (sim) {
728
706
  sim.lastSeenAt = Date.now()
729
707
  }
730
- if (msg.type === 'bridge:register' && sim) {
708
+ if (msg.type === 'bridge:register') {
731
709
  const registration = msg as BridgeSimRegistrationMessage
710
+ const firstRegistration = !sim
711
+ if (!sim) {
712
+ sim = {
713
+ id: this.allocateSimId(),
714
+ ws,
715
+ origin,
716
+ connectedAt: Date.now(),
717
+ lastSeenAt: Date.now(),
718
+ lastActiveAt: 0,
719
+ recentActions: [],
720
+ }
721
+ this.sims.set(sim.id, sim)
722
+ }
732
723
  const restored = this.tryRestoreSimId(sim, registration.simId)
733
724
  sim.url = registration.url
734
725
  sim.title = registration.title
@@ -743,16 +734,12 @@ export class SootSimBridgeHost {
743
734
  if (registration.meta && typeof registration.meta === 'object') {
744
735
  sim.meta = registration.meta as Record<string, unknown>
745
736
  }
746
- // re-evaluate primary now that the sim has a real page. promotion
747
- // is otherwise decided once at bare-connect, before `url` is
748
- // known — so a page-less sim that grabbed primary as the
749
- // last-resort fallback (or a stale zombie still holding it)
750
- // would never be superseded by this freshly-registered, actually
751
- // driveable sim (QA F19-2). only adopt a *different* sim so a
752
- // routine heartbeat re-register doesn't churn the assignment.
737
+ // elect primary once the sim has a page; only adopt a *different*
738
+ // sim so a routine heartbeat re-register doesn't churn the
739
+ // assignment.
753
740
  const reElected = this.primarySimId !== sim.id && this.shouldPromoteSim(sim)
754
741
  if (reElected) this.primarySimId = sim.id
755
- if (restored || reElected) {
742
+ if (firstRegistration || restored || reElected) {
756
743
  this.broadcastSimAssignments()
757
744
  this.broadcastSimClientStates()
758
745
  }
package/src/kotlin-app.ts CHANGED
@@ -5,7 +5,11 @@
5
5
 
6
6
  import fs from 'node:fs'
7
7
  import path from 'node:path'
8
- import { readResourceValues, resolveThemeAttribute } from './android-theme'
8
+ import {
9
+ frameworkColor,
10
+ readResourceValues,
11
+ resolveThemeAttribute,
12
+ } from './android-theme'
9
13
  import { parseXml, type XmlElement } from './android-xml'
10
14
  import frameworkThemes from './generated/android-framework-themes.json' with { type: 'json' }
11
15
 
@@ -18,10 +22,11 @@ export interface KotlinAppProject {
18
22
  namespace: string
19
23
  targetSdk: number
20
24
  // the launched activity's window as its theme and target SDK configure it;
21
- // statusBarColor is ARGB
25
+ // statusBarColor and navigationBarColor are ARGB
22
26
  window: {
23
27
  edgeToEdgeEnforced: boolean
24
28
  statusBarColor: number
29
+ navigationBarColor: number
25
30
  statusBarContrastEnforced: boolean
26
31
  navigationBarContrastEnforced: boolean
27
32
  lightStatusBar: boolean
@@ -32,6 +37,9 @@ export interface KotlinAppProject {
32
37
  // back reaches the activity through its window's back dispatch rather than
33
38
  // the back key's onBackPressed
34
39
  predictiveBack: boolean
40
+ // the activity's configChanges includes uiMode: a night mode change reaches
41
+ // onConfigurationChanged instead of relaunching it
42
+ handlesUiMode: boolean
35
43
  }
36
44
 
37
45
  export interface ComposeAndroidPins {
@@ -218,9 +226,22 @@ function readWindowTheme(
218
226
  lightStatusBar: flag('windowLightStatusBar'),
219
227
  lightNavigationBar: flag('windowLightNavigationBar'),
220
228
  }
229
+ // the theme's navigation bar color; left at the framework's transparent
230
+ // default, a window that is not edge to edge takes the compatible color
231
+ // unless the device's config_navBarDefaultTransparent is set, which it is
232
+ // not on the pixel 8 android 17 device the oracle measures
233
+ const navigationColor = resolveThemeAttribute(name, 'navigationBarColor', sources)
234
+ if (typeof navigationColor === 'boolean')
235
+ throw new Error(`${name}: navigationBarColor is not a color`)
236
+ const navigationDefault = frameworkColor('navigation_bar_default', sources)
237
+ const navigationTheme = navigationColor ?? navigationDefault
238
+ const navigationBarColor =
239
+ navigationTheme === navigationDefault && !edgeToEdgeEnforced
240
+ ? frameworkColor('navigation_bar_compatible', sources)
241
+ : navigationTheme
221
242
  // PhoneWindow enforces edge to edge apart from whether the theme draws bar
222
243
  // backgrounds: either way the bar is transparent
223
- if (edgeToEdgeEnforced) return { ...window, statusBarColor: 0 }
244
+ if (edgeToEdgeEnforced) return { ...window, statusBarColor: 0, navigationBarColor }
224
245
  if (!flag('windowDrawsSystemBarBackgrounds'))
225
246
  throw new Error(
226
247
  `${name} does not draw system bar backgrounds; peach kotlin draws only theme status bar colors`,
@@ -228,7 +249,7 @@ function readWindowTheme(
228
249
  const color = resolveThemeAttribute(name, 'statusBarColor', sources)
229
250
  if (typeof color === 'boolean')
230
251
  throw new Error(`${name}: statusBarColor is not a color`)
231
- return { ...window, statusBarColor: color ?? 0xff000000 }
252
+ return { ...window, statusBarColor: color ?? 0xff000000, navigationBarColor }
232
253
  }
233
254
 
234
255
  function enabled(element: XmlElement): boolean {
@@ -402,6 +423,9 @@ export function readKotlinApp(
402
423
  (themeOwner?.attributes[`${ANDROID_NS}enableOnBackInvokedCallback`] ??
403
424
  application.attributes[`${ANDROID_NS}enableOnBackInvokedCallback`] ??
404
425
  String(targetSdk > 35)) === 'true'
426
+ const handlesUiMode = (themeOwner?.attributes[`${ANDROID_NS}configChanges`] ?? '')
427
+ .split('|')
428
+ .includes('uiMode')
405
429
 
406
430
  const sources: string[] = []
407
431
  for (const set of ['java', 'kotlin']) {
@@ -432,5 +456,6 @@ export function readKotlinApp(
432
456
  applicationClass: applicationName ? className(applicationName) : null,
433
457
  launcher: { name: launcherName, targetClass },
434
458
  predictiveBack,
459
+ handlesUiMode,
435
460
  }
436
461
  }
@@ -337,20 +337,22 @@ export async function kotlinAppArtifact(
337
337
  '',
338
338
  '@JsExport',
339
339
  'fun peachAppMount(density: Float, fontScale: Float, locale: String, backend: String, stencilBits: Int,',
340
- ' regular: String, medium: String, bold: String, statusBar: Int, navigationBar: Int, night: Boolean,',
340
+ ' regular: String, medium: String, bold: String, insets: String, night: Boolean,',
341
341
  ' invalidate: (Boolean) -> Unit): String = dev.peach.app.peachStartApp(',
342
342
  ' dev.peach.app.PeachAppSpec(',
343
343
  ` android.content.ComponentName(${kotlinString(app.packageName)}, ${kotlinString(app.launcher.name)}),`,
344
344
  ` android.content.ComponentName(${kotlinString(app.packageName)}, ${kotlinString(app.launcher.targetClass)}),`,
345
345
  ' android.view.WindowTheme(',
346
346
  ` ${app.window.edgeToEdgeEnforced}, 0x${app.window.statusBarColor.toString(16).padStart(8, '0')}.toInt(),`,
347
+ ` 0x${app.window.navigationBarColor.toString(16).padStart(8, '0')}.toInt(),`,
347
348
  ` ${app.window.statusBarContrastEnforced}, ${app.window.navigationBarContrastEnforced},`,
348
349
  ` ${app.window.lightStatusBar}, ${app.window.lightNavigationBar},`,
349
350
  ' ),',
350
351
  ` { ${app.applicationClass ?? 'android.app.Application'}() },`,
351
352
  ` { ${app.launcher.targetClass}() },`,
353
+ ` ${app.handlesUiMode},`,
352
354
  ' ),',
353
- ' density, fontScale, locale, backend, stencilBits, regular, medium, bold, statusBar, navigationBar, night, invalidate,',
355
+ ' density, fontScale, locale, backend, stencilBits, regular, medium, bold, insets, night, invalidate,',
354
356
  ')',
355
357
  '',
356
358
  '@JsExport',
@@ -359,6 +361,9 @@ export async function kotlinAppArtifact(
359
361
  '@JsExport',
360
362
  'fun peachAppTask(foreground: Boolean) = dev.peach.app.peachAppTask(foreground)',
361
363
  '',
364
+ '@JsExport',
365
+ 'fun peachAppNight(night: Boolean) = dev.peach.app.peachAppNight(night)',
366
+ '',
362
367
  ].join('\n'),
363
368
  }
364
369
  const shims = [