peachsim 0.1.664 → 0.1.665

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 (81) hide show
  1. package/cli/app-fonts.ts +6 -3
  2. package/cli/commands/control.ts +18 -2
  3. package/cli/commands/inspect/core.ts +19 -6
  4. package/cli/commands/inspect/settling.ts +1 -5
  5. package/dist-lib/agent-daemon-client.cjs +1 -1
  6. package/dist-lib/agent-events.cjs +1 -1
  7. package/dist-lib/agent-identity.cjs +1 -1
  8. package/dist-lib/agent-sessions.cjs +1 -1
  9. package/dist-lib/attached-projects.cjs +1 -1
  10. package/dist-lib/auth/shared-session.cjs +1 -1
  11. package/dist-lib/backend-origin.cjs +1 -1
  12. package/dist-lib/beta.cjs +1 -1
  13. package/dist-lib/beta.mjs +1 -1
  14. package/dist-lib/bridge-constants.cjs +1 -1
  15. package/dist-lib/bridge-contract-input.cjs +1 -1
  16. package/dist-lib/bridge-contract-input.mjs +1 -1
  17. package/dist-lib/bridge-contract.cjs +1 -1
  18. package/dist-lib/bridge-contract.mjs +1 -1
  19. package/dist-lib/capture-contract.cjs +1 -1
  20. package/dist-lib/capture-contract.mjs +1 -1
  21. package/dist-lib/cli-constants.cjs +1 -1
  22. package/dist-lib/cloud-contract.cjs +1 -1
  23. package/dist-lib/cloud-contract.mjs +1 -1
  24. package/dist-lib/cloud-test-args.cjs +1 -1
  25. package/dist-lib/cloud-test-args.mjs +1 -1
  26. package/dist-lib/cloud.cjs +1 -1
  27. package/dist-lib/cloud.mjs +1 -1
  28. package/dist-lib/config.cjs +1 -1
  29. package/dist-lib/detox/index.cjs +1 -1
  30. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  31. package/dist-lib/home-paths.cjs +1 -1
  32. package/dist-lib/host/bridge-host.cjs +2191 -335
  33. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  34. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  35. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  36. package/dist-lib/host/replacement-module-handler.cjs +7 -7
  37. package/dist-lib/host/websocket-proxy.cjs +1 -1
  38. package/dist-lib/index.cjs +7 -7
  39. package/dist-lib/jump-to-source-babel.cjs +1 -1
  40. package/dist-lib/jump-to-source-native.cjs +1 -1
  41. package/dist-lib/kotlin-host.cjs +1 -1
  42. package/dist-lib/menu.cjs +1 -1
  43. package/dist-lib/menu.mjs +1 -1
  44. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  45. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  46. package/dist-lib/metro-production-bundle.cjs +1 -1
  47. package/dist-lib/metro-production-bundle.mjs +1 -1
  48. package/dist-lib/metro.cjs +7 -7
  49. package/dist-lib/native-app-identity.cjs +1 -1
  50. package/dist-lib/native-app-identity.mjs +1 -1
  51. package/dist-lib/plist.cjs +1 -1
  52. package/dist-lib/plist.mjs +1 -1
  53. package/dist-lib/profiles.cjs +1 -1
  54. package/dist-lib/public-brand.cjs +1 -1
  55. package/dist-lib/react-native-host-modules.cjs +1 -1
  56. package/dist-lib/react-native-host-modules.mjs +1 -1
  57. package/dist-lib/render-mode.cjs +1 -1
  58. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  59. package/dist-lib/sdk.cjs +13 -9
  60. package/dist-lib/sdk.mjs +13 -9
  61. package/dist-lib/skills.cjs +2769 -944
  62. package/dist-lib/swift-compile.cjs +1 -1
  63. package/dist-lib/swift-compile.mjs +1 -1
  64. package/dist-lib/swift-package.cjs +1 -1
  65. package/dist-lib/swift-package.mjs +1 -1
  66. package/dist-lib/swift-submit.cjs +1 -1
  67. package/dist-lib/swift-submit.mjs +1 -1
  68. package/dist-lib/swift.cjs +1 -1
  69. package/dist-lib/vite-swift.cjs +1 -1
  70. package/dist-lib/vite.cjs +7 -7
  71. package/kotlin/ComposeSources/PeachComposeHost.kt +6 -3
  72. package/kotlin/fixtures/kotlin-app-variants/Overlays.kt +56 -0
  73. package/kotlin/fixtures/kotlin-app-variants/Scroll.kt +63 -0
  74. package/kotlin/fixtures/kotlin-app-variants/Sheet.kt +43 -0
  75. package/kotlin/fixtures/kotlin-app-variants/Tabs.kt +47 -0
  76. package/kotlin/fixtures/kotlin-app-variants/Widgets.kt +13 -2
  77. package/package.json +2 -2
  78. package/skills/peach-setup/SKILL.md +1 -1
  79. package/src/expo-swift-host.ts +248 -0
  80. package/src/host/bridge-host.ts +19 -0
  81. package/src/vite-plugin.ts +3 -3
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.664 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.665 | (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.664 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.665 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-compile.ts
4
4
  import { execFile as execFile2 } from "node:child_process";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.664 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.665 | (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.664 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.665 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-package.ts
4
4
  function isSwiftManifestFile(file) {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.664 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.665 | (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.664 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.665 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-submit.ts
4
4
  var SWIFT_COMPILE_PATH = "/api/swift-compile";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.664 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.665 | (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.664 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.665 | (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/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.664 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.665 | (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;
@@ -317,12 +317,12 @@ function transformsFor(filePath) {
317
317
  function isLocalSpecifier(specifier) {
318
318
  return specifier.startsWith("./") || specifier.startsWith("../");
319
319
  }
320
- function isContrastKitSpecifier(specifier) {
321
- return specifier === "contrast-kit" || specifier.startsWith("contrast-kit/");
320
+ function isPeachKitSpecifier(specifier) {
321
+ return specifier === "peach-kit" || specifier.startsWith("peach-kit/");
322
322
  }
323
323
  function unsupportedSpecifier(specifier) {
324
324
  return new Error(
325
- `replacement modules may only import react, react-native, contrast-kit, the packages rnx stubs for this app, and other project files by relative path; '${specifier}' is none of those. vendor the code into a project file, or add the package to peach.config so the simulator bridges it.`
325
+ `replacement modules may only import react, react-native, peach-kit, the packages rnx stubs for this app, and other project files by relative path; '${specifier}' is none of those. vendor the code into a project file, or add the package to peach.config so the simulator bridges it.`
326
326
  );
327
327
  }
328
328
  function bridgeDeclaration(name, specifier) {
@@ -333,8 +333,8 @@ function bridgeDeclaration(name, specifier) {
333
333
  if (specifier === "react-native") {
334
334
  return `const ${name} = ${REACT_NATIVE_BRIDGE}; if (!${name}) throw new Error(${notReady("React Native")});`;
335
335
  }
336
- if (isContrastKitSpecifier(specifier)) {
337
- return `const ${name} = (globalThis.__contrast_kit_modules && globalThis.__contrast_kit_modules[${JSON.stringify(specifier)}]) || (${PACKAGE_BRIDGES} || {})[${JSON.stringify(specifier)}]; if (!${name}) throw new Error(${JSON.stringify(`[peach] replacement package bridge ${specifier} is not ready`)});`;
336
+ if (isPeachKitSpecifier(specifier)) {
337
+ return `const ${name} = (globalThis.__peach_kit_modules && globalThis.__peach_kit_modules[${JSON.stringify(specifier)}]) || (${PACKAGE_BRIDGES} || {})[${JSON.stringify(specifier)}]; if (!${name}) throw new Error(${JSON.stringify(`[peach] replacement package bridge ${specifier} is not ready`)});`;
338
338
  }
339
339
  if (JSX_RUNTIME_SPECIFIERS.has(specifier)) {
340
340
  return `const ${name} = (() => { const react = ${REACT_BRIDGE}; if (!react) throw new Error(${notReady("React")}); const jsx = (type, props, key) => react.createElement(type, key === undefined ? props : { ...props, key }); return { Fragment: react.Fragment, jsx, jsxs: jsx, jsxDEV: jsx }; })();`;
@@ -462,7 +462,7 @@ function compileReplacementModule(input) {
462
462
  pointAtLocalModule(specifierToken, specifier);
463
463
  continue;
464
464
  }
465
- if (specifier !== "react" && specifier !== "react-native" && !JSX_RUNTIME_SPECIFIERS.has(specifier) && !isContrastKitSpecifier(specifier) && !input.runtimePackages.has(specifier)) {
465
+ if (specifier !== "react" && specifier !== "react-native" && !JSX_RUNTIME_SPECIFIERS.has(specifier) && !isPeachKitSpecifier(specifier) && !input.runtimePackages.has(specifier)) {
466
466
  throw unsupportedSpecifier(specifier);
467
467
  }
468
468
  const bridge = bindings.length > 0 ? bridgeFor(specifier) : null;
@@ -133,7 +133,10 @@ internal class PeachComposeMount(
133
133
  var props by mutableStateOf("")
134
134
  // the configuration's night mode, which isSystemInDarkTheme reads
135
135
  var night by mutableStateOf(night)
136
- var constraints = Constraints()
136
+ // the bounds the host last measured or laid the view out in. like a
137
+ // ComposeView before its parent measures it, the content has none and
138
+ // lays out nothing until the first arrive
139
+ var constraints: Constraints? = null
137
140
  set(value) { if (field != value) { field = value; needsMeasure = true } }
138
141
  private val events = mutableListOf<JSONObject>()
139
142
  // state reported once a frame, read when the frame draws: like the decor,
@@ -177,7 +180,7 @@ internal class PeachComposeMount(
177
180
  }
178
181
 
179
182
  fun measure(): IntSize {
180
- val measured = scene.measureContent(constraints)
183
+ val measured = scene.measureContent(checkNotNull(constraints))
181
184
  scene.size = measured
182
185
  platform.size = measured
183
186
  measuredSize = measured
@@ -188,7 +191,7 @@ internal class PeachComposeMount(
188
191
  fun frame(nanos: Long): String {
189
192
  Snapshot.sendApplyNotifications()
190
193
  if (clockRunning()) recomposer.performFrame(nanos)
191
- val measured = if (needsMeasure) measure() else measuredSize
194
+ val measured = if (needsMeasure && constraints != null) measure() else measuredSize
192
195
  // a zero-size composition still runs its effects and reports its size;
193
196
  // its backing surface uses one pixel until layout gives it real bounds.
194
197
  val backing = IntSize(measured.width.coerceAtLeast(1), measured.height.coerceAtLeast(1))
@@ -0,0 +1,56 @@
1
+ package dev.contrast.kotlinapp
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.layout.Arrangement
5
+ import androidx.compose.foundation.layout.Box
6
+ import androidx.compose.foundation.layout.Column
7
+ import androidx.compose.foundation.layout.fillMaxSize
8
+ import androidx.compose.foundation.layout.padding
9
+ import androidx.compose.foundation.layout.safeDrawingPadding
10
+ import androidx.compose.material3.AlertDialog
11
+ import androidx.compose.material3.Button
12
+ import androidx.compose.material3.DropdownMenu
13
+ import androidx.compose.material3.DropdownMenuItem
14
+ import androidx.compose.material3.Text
15
+ import androidx.compose.material3.TextButton
16
+ import androidx.compose.runtime.Composable
17
+ import androidx.compose.runtime.getValue
18
+ import androidx.compose.runtime.mutableStateOf
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.unit.dp
24
+
25
+ // an alert dialog and a dropdown menu, each opened by a button and closed by
26
+ // one of its own items
27
+ @Composable
28
+ fun Overlays() {
29
+ var dialog by remember { mutableStateOf(false) }
30
+ var menu by remember { mutableStateOf(false) }
31
+ var result by remember { mutableStateOf("none") }
32
+ Column(
33
+ Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
34
+ verticalArrangement = Arrangement.spacedBy(12.dp),
35
+ ) {
36
+ Text("Result: $result")
37
+ Button(onClick = { dialog = true }) { Text("Open dialog") }
38
+ Box {
39
+ Button(onClick = { menu = true }) { Text("Open menu") }
40
+ DropdownMenu(expanded = menu, onDismissRequest = { menu = false }) {
41
+ listOf("First", "Second", "Third").forEach { item ->
42
+ DropdownMenuItem(text = { Text(item) }, onClick = { result = item; menu = false })
43
+ }
44
+ }
45
+ }
46
+ }
47
+ if (dialog) {
48
+ AlertDialog(
49
+ onDismissRequest = { dialog = false },
50
+ title = { Text("Discard draft?") },
51
+ text = { Text("The draft will be deleted.") },
52
+ confirmButton = { TextButton(onClick = { result = "confirmed"; dialog = false }) { Text("Discard") } },
53
+ dismissButton = { TextButton(onClick = { dialog = false }) { Text("Keep") } },
54
+ )
55
+ }
56
+ }
@@ -0,0 +1,63 @@
1
+ package dev.contrast.kotlinapp
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.layout.Column
5
+ import androidx.compose.foundation.layout.fillMaxSize
6
+ import androidx.compose.foundation.layout.fillMaxWidth
7
+ import androidx.compose.foundation.layout.height
8
+ import androidx.compose.foundation.layout.padding
9
+ import androidx.compose.foundation.layout.safeDrawingPadding
10
+ import androidx.compose.foundation.lazy.LazyColumn
11
+ import androidx.compose.foundation.lazy.rememberLazyListState
12
+ import androidx.compose.material3.HorizontalDivider
13
+ import androidx.compose.material3.Text
14
+ import androidx.compose.runtime.Composable
15
+ import androidx.compose.runtime.getValue
16
+ import androidx.compose.runtime.mutableStateOf
17
+ import androidx.compose.runtime.remember
18
+ import androidx.compose.runtime.setValue
19
+ import androidx.compose.ui.Modifier
20
+ import androidx.compose.ui.geometry.Offset
21
+ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
22
+ import androidx.compose.ui.input.nestedscroll.NestedScrollSource
23
+ import androidx.compose.ui.input.nestedscroll.nestedScroll
24
+ import androidx.compose.ui.unit.Velocity
25
+ import kotlin.math.roundToInt
26
+ import androidx.compose.ui.graphics.Color
27
+ import androidx.compose.ui.unit.dp
28
+
29
+ // a lazy list a drag scrolls and flings: the header reports the drag and the
30
+ // fling velocity the list received, then where it settled
31
+ @Composable
32
+ fun Scroll() {
33
+ val state = rememberLazyListState()
34
+ var moved by remember { mutableStateOf(false) }
35
+ if (state.isScrollInProgress) moved = true
36
+ val settled = moved && !state.isScrollInProgress
37
+ var dragged by remember { mutableStateOf(0f) }
38
+ var velocity by remember { mutableStateOf(0f) }
39
+ val connection = remember {
40
+ object : NestedScrollConnection {
41
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
42
+ if (source == NestedScrollSource.UserInput) dragged += available.y
43
+ return Offset.Zero
44
+ }
45
+
46
+ override suspend fun onPreFling(available: Velocity): Velocity {
47
+ velocity = available.y
48
+ return Velocity.Zero
49
+ }
50
+ }
51
+ }
52
+ Column(Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding()) {
53
+ Text("Scroll: ${if (settled) "settled" else if (moved) "moving" else "idle"}", Modifier.padding(16.dp))
54
+ Text("First: ${state.firstVisibleItemIndex} ${state.firstVisibleItemScrollOffset}", Modifier.padding(horizontal = 16.dp))
55
+ Text("Drag: ${dragged.roundToInt()} velocity ${velocity.roundToInt()}", Modifier.padding(horizontal = 16.dp))
56
+ LazyColumn(Modifier.fillMaxWidth().weight(1f).nestedScroll(connection), state = state) {
57
+ items(200) { index ->
58
+ Text("Row $index", Modifier.fillMaxWidth().height(56.dp).padding(16.dp))
59
+ HorizontalDivider()
60
+ }
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,43 @@
1
+ package dev.contrast.kotlinapp
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.layout.Arrangement
5
+ import androidx.compose.foundation.layout.Column
6
+ import androidx.compose.foundation.layout.fillMaxSize
7
+ import androidx.compose.foundation.layout.padding
8
+ import androidx.compose.foundation.layout.safeDrawingPadding
9
+ import androidx.compose.material3.Button
10
+ import androidx.compose.material3.ExperimentalMaterial3Api
11
+ import androidx.compose.material3.ModalBottomSheet
12
+ import androidx.compose.material3.Text
13
+ import androidx.compose.runtime.Composable
14
+ import androidx.compose.runtime.getValue
15
+ import androidx.compose.runtime.mutableStateOf
16
+ import androidx.compose.runtime.remember
17
+ import androidx.compose.runtime.setValue
18
+ import androidx.compose.ui.Modifier
19
+ import androidx.compose.ui.graphics.Color
20
+ import androidx.compose.ui.unit.dp
21
+
22
+ // a modal bottom sheet opened by a button and closed by one inside it
23
+ @OptIn(ExperimentalMaterial3Api::class)
24
+ @Composable
25
+ fun Sheet() {
26
+ var open by remember { mutableStateOf(false) }
27
+ var closed by remember { mutableStateOf(0) }
28
+ Column(
29
+ Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
30
+ verticalArrangement = Arrangement.spacedBy(12.dp),
31
+ ) {
32
+ Text("Closed: $closed")
33
+ Button(onClick = { open = true }) { Text("Open sheet") }
34
+ }
35
+ if (open) {
36
+ ModalBottomSheet(onDismissRequest = { open = false; closed += 1 }) {
37
+ Column(Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
38
+ Text("Sheet body")
39
+ Button(onClick = { open = false; closed += 1 }) { Text("Close sheet") }
40
+ }
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,47 @@
1
+ package dev.contrast.kotlinapp
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.layout.Box
5
+ import androidx.compose.foundation.layout.padding
6
+ import androidx.compose.foundation.layout.size
7
+ import androidx.compose.foundation.shape.CircleShape
8
+ import androidx.compose.material3.ExperimentalMaterial3Api
9
+ import androidx.compose.material3.NavigationBar
10
+ import androidx.compose.material3.NavigationBarItem
11
+ import androidx.compose.material3.Scaffold
12
+ import androidx.compose.material3.Text
13
+ import androidx.compose.material3.TopAppBar
14
+ import androidx.compose.runtime.Composable
15
+ import androidx.compose.runtime.getValue
16
+ import androidx.compose.runtime.mutableStateOf
17
+ import androidx.compose.runtime.remember
18
+ import androidx.compose.runtime.setValue
19
+ import androidx.compose.ui.Modifier
20
+ import androidx.compose.ui.graphics.Color
21
+ import androidx.compose.ui.unit.dp
22
+
23
+ // a scaffold with a top app bar and a navigation bar whose tabs swap the body
24
+ @OptIn(ExperimentalMaterial3Api::class)
25
+ @Composable
26
+ fun Tabs() {
27
+ val tabs = listOf("Home", "Search", "Profile")
28
+ var selected by remember { mutableStateOf(0) }
29
+ Scaffold(
30
+ containerColor = Color(0xFFE8EEF8),
31
+ topBar = { TopAppBar(title = { Text("Inbox") }) },
32
+ bottomBar = {
33
+ NavigationBar {
34
+ tabs.forEachIndexed { index, tab ->
35
+ NavigationBarItem(
36
+ selected = selected == index,
37
+ onClick = { selected = index },
38
+ icon = { Box(Modifier.size(24.dp).background(Color(0xFF6750A4), CircleShape)) },
39
+ label = { Text(tab) },
40
+ )
41
+ }
42
+ }
43
+ },
44
+ ) { padding ->
45
+ Text("Tab: ${tabs[selected]}", Modifier.padding(padding).padding(16.dp))
46
+ }
47
+ }
@@ -1,6 +1,7 @@
1
1
  package dev.contrast.kotlinapp
2
2
 
3
3
  import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.isSystemInDarkTheme
4
5
  import androidx.compose.foundation.layout.Arrangement
5
6
  import androidx.compose.foundation.layout.Column
6
7
  import androidx.compose.foundation.layout.Row
@@ -14,11 +15,14 @@ import androidx.compose.material3.Checkbox
14
15
  import androidx.compose.material3.FilterChip
15
16
  import androidx.compose.material3.HorizontalDivider
16
17
  import androidx.compose.material3.LinearProgressIndicator
18
+ import androidx.compose.material3.MaterialTheme
17
19
  import androidx.compose.material3.OutlinedButton
18
20
  import androidx.compose.material3.RadioButton
19
21
  import androidx.compose.material3.Slider
20
22
  import androidx.compose.material3.Switch
21
23
  import androidx.compose.material3.Text
24
+ import androidx.compose.material3.darkColorScheme
25
+ import androidx.compose.material3.lightColorScheme
22
26
  import androidx.compose.runtime.Composable
23
27
  import androidx.compose.runtime.getValue
24
28
  import androidx.compose.runtime.mutableStateOf
@@ -29,15 +33,22 @@ import androidx.compose.ui.Modifier
29
33
  import androidx.compose.ui.graphics.Color
30
34
  import androidx.compose.ui.unit.dp
31
35
 
32
- // material controls in both states: the toggle flips every one at once
36
+ // material controls in both states: the toggle flips every one at once. the
37
+ // baseline scheme follows the device's dark theme
33
38
  @Composable
34
39
  fun Widgets() {
40
+ val dark = isSystemInDarkTheme()
41
+ MaterialTheme(colorScheme = if (dark) darkColorScheme() else lightColorScheme()) { Controls(dark) }
42
+ }
43
+
44
+ @Composable
45
+ fun Controls(dark: Boolean) {
35
46
  var on by remember { mutableStateOf(false) }
36
47
  Column(
37
48
  Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
38
49
  verticalArrangement = Arrangement.spacedBy(8.dp),
39
50
  ) {
40
- Text("Selection: $on")
51
+ Text("Selection: $on${if (dark) " dark" else ""}")
41
52
  Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
42
53
  Switch(checked = on, onCheckedChange = { on = it })
43
54
  Checkbox(checked = on, onCheckedChange = { on = it })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peachsim",
3
- "version": "0.1.664",
3
+ "version": "0.1.665",
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",
@@ -333,7 +333,7 @@
333
333
  "@peach/skills": "workspace:*",
334
334
  "@peach/compat": "workspace:*",
335
335
  "@types/ws": "^8.5.13",
336
- "contrast-kit": "workspace:*",
336
+ "peach-kit": "workspace:*",
337
337
  "peach-cloud-box": "workspace:*",
338
338
  "peach-engine": "workspace:*",
339
339
  "typescript": "7.0.2",
@@ -135,7 +135,7 @@ export default defineConfig({
135
135
 
136
136
  a `nativeViews` file default-exports a component that receives the native
137
137
  view's props and renders what it shows on device, using `react-native` and
138
- `contrast-kit` primitives. a `nativeModules` file exports the module's
138
+ `peach-kit` primitives. a `nativeModules` file exports the module's
139
139
  methods, served to `NativeModules` and to Expo's `requireNativeModule`. a
140
140
  file may import other project files by relative path, so a large view splits
141
141
  into components and helpers. reopen the app to load the change.