peachsim 0.1.899 → 0.1.908

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 (103) hide show
  1. package/README.md +8 -0
  2. package/cli/app-fonts.ts +27 -2
  3. package/cli/commands/box/bundle-plane.ts +13 -4
  4. package/cli/commands/box.ts +22 -1
  5. package/cli/commands/control.ts +29 -16
  6. package/cli/commands/flow.ts +8 -1
  7. package/cli/commands/platform.ts +15 -2
  8. package/cli/commands/skills.ts +125 -10
  9. package/cli/commands/upload.ts +53 -39
  10. package/cli/flow-live-status.ts +11 -6
  11. package/detox/expectations.ts +22 -15
  12. package/detox/index.ts +4 -19
  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 +1 -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/bundle-channel.cjs +8 -5
  28. package/dist-lib/capture-contract.cjs +1 -1
  29. package/dist-lib/capture-contract.mjs +1 -1
  30. package/dist-lib/cli-constants.cjs +1 -1
  31. package/dist-lib/cloud-contract.cjs +1 -1
  32. package/dist-lib/cloud-contract.mjs +1 -1
  33. package/dist-lib/cloud-test-args.cjs +1 -1
  34. package/dist-lib/cloud-test-args.mjs +1 -1
  35. package/dist-lib/cloud.cjs +1 -1
  36. package/dist-lib/cloud.mjs +1 -1
  37. package/dist-lib/config.cjs +1 -1
  38. package/dist-lib/detox/index.cjs +15 -18
  39. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  40. package/dist-lib/expo-swift-bundle.cjs +8 -5
  41. package/dist-lib/expo-swift-metadata.cjs +1 -1
  42. package/dist-lib/expo-swift-metadata.mjs +1 -1
  43. package/dist-lib/home-paths.cjs +1 -1
  44. package/dist-lib/host/bridge-host.cjs +8 -5
  45. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  46. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  47. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  48. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  49. package/dist-lib/host/websocket-proxy.cjs +1 -1
  50. package/dist-lib/index.cjs +1 -1
  51. package/dist-lib/jump-to-source-babel.cjs +1 -1
  52. package/dist-lib/jump-to-source-native.cjs +1 -1
  53. package/dist-lib/kotlin-host.cjs +1 -1
  54. package/dist-lib/menu.cjs +1 -1
  55. package/dist-lib/menu.mjs +1 -1
  56. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  57. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  58. package/dist-lib/metro-production-bundle.cjs +1 -1
  59. package/dist-lib/metro-production-bundle.mjs +1 -1
  60. package/dist-lib/metro.cjs +1 -1
  61. package/dist-lib/native-app-identity.cjs +1 -1
  62. package/dist-lib/native-app-identity.mjs +1 -1
  63. package/dist-lib/native-firebase-metadata.cjs +1 -1
  64. package/dist-lib/native-firebase-metadata.mjs +1 -1
  65. package/dist-lib/plist.cjs +1 -1
  66. package/dist-lib/plist.mjs +1 -1
  67. package/dist-lib/profiles.cjs +1 -1
  68. package/dist-lib/public-brand.cjs +1 -1
  69. package/dist-lib/react-native-host-modules.cjs +1 -1
  70. package/dist-lib/react-native-host-modules.mjs +1 -1
  71. package/dist-lib/render-mode.cjs +1 -1
  72. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  73. package/dist-lib/sdk.cjs +1 -1
  74. package/dist-lib/sdk.mjs +1 -1
  75. package/dist-lib/skills.cjs +136 -25
  76. package/dist-lib/swift-compile.cjs +1 -1
  77. package/dist-lib/swift-compile.mjs +1 -1
  78. package/dist-lib/swift-package.cjs +1 -1
  79. package/dist-lib/swift-package.mjs +1 -1
  80. package/dist-lib/swift-submit.cjs +1 -1
  81. package/dist-lib/swift-submit.mjs +1 -1
  82. package/dist-lib/swift-toolchain.cjs +1 -1
  83. package/dist-lib/swift.cjs +1 -1
  84. package/dist-lib/vite-swift.cjs +1 -1
  85. package/dist-lib/vite.cjs +1 -1
  86. package/dist-lib/worklet-executable.cjs +1 -1
  87. package/dist-lib/worklet-executable.mjs +1 -1
  88. package/kotlin/fork/NOTICE +3 -0
  89. package/kotlin/fork/README.md +10 -2
  90. package/kotlin/fork/build.sh +14 -14
  91. package/kotlin/fork/compose-worker-globals.patch +105 -0
  92. package/kotlin/fork/fork.json +3 -3
  93. package/kotlin/fork/publish.ts +8 -1
  94. package/kotlin/fork/skia-android-shaping.patch +30 -0
  95. package/kotlin/fork/skiko-local.init.gradle +10 -0
  96. package/kotlin/fork/skiko-worker-clock.patch +17 -0
  97. package/kotlin/toolchain.json +9 -9
  98. package/package.json +1 -1
  99. package/skills/peach-debug/SKILL.md +15 -153
  100. package/skills/peach-debug/references/accessibility.md +102 -0
  101. package/skills/peach-debug/references/commands.md +56 -0
  102. package/src/expo-swift-host.ts +15 -4
  103. package/src/vite-plugin.ts +3 -3
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/native-firebase-metadata.ts
4
4
  var PREFIX = "globalThis.__peachNativeFirebaseConfig=";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/plist.ts
4
4
  import plist from "plist";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } 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.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } 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.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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);
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -4370,6 +4370,8 @@ var init_supported_native_packages = __esm({
4370
4370
  // coverage
4371
4371
  "@callstack/liquid-glass",
4372
4372
  // preset+seam
4373
+ "@callstack/react-native-brownfield",
4374
+ // seam
4373
4375
  "@callstack/react-native-local-notifications",
4374
4376
  // seam
4375
4377
  "@candlefinance/faster-image",
@@ -6460,7 +6462,7 @@ var init_registry = __esm({
6460
6462
  range: ">=5.0.0",
6461
6463
  coverage: 0.95,
6462
6464
  note: "real upstream package \u2014 not intercepted. the app's expo-application JS runs over the ExpoApplication native module, whose identity constants follow the live Expo config.",
6463
- working: "upstream constants and platform availability checks, applicationName, applicationId, nativeApplicationVersion, nativeBuildVersion, getIosIdForVendorAsync (localStorage-backed), getIosApplicationReleaseTypeAsync SIMULATOR, ApplicationReleaseType enum, getInstallationTimeAsync, getIosPushNotificationServiceEnvironmentAsync",
6465
+ working: "upstream constants and platform availability checks, applicationName, applicationId, nativeApplicationVersion, nativeBuildVersion, getIosIdForVendorAsync (localStorage-backed), getIosApplicationReleaseTypeAsync SIMULATOR, ApplicationReleaseType enum, getInstallationTimeAsync (persisted iOS Documents creation date), getIosPushNotificationServiceEnvironmentAsync",
6464
6466
  missing: "bare/frozen apps without resolved Expo config use simulator metadata; Android id/install referrer on non-Android platforms; push entitlement inspection"
6465
6467
  }
6466
6468
  ]
@@ -6837,7 +6839,7 @@ var init_registry = __esm({
6837
6839
  range: ">=10.0.0",
6838
6840
  coverage: 1,
6839
6841
  note: "comprehensive live-device mock \u2014 hardware fields follow the active device spec and app identity fields follow the live Expo config",
6840
- working: "all sync and async getters: uniqueId, model, brand, systemName/Version, live bundleId/buildNumber/version/appName, deviceName, deviceType, manufacturer, carrier, totalMemory, diskCapacity, freeDiskStorage, fontScale, isEmulator, isTablet, hasNotch, hasDynamicIsland, isLandscape, brightness, getBatteryLevel and ~60 more"
6842
+ working: "all sync and async getters: uniqueId, model, brand, systemName/Version, live bundleId/buildNumber/version/appName, deviceName, deviceType, manufacturer, carrier, totalMemory, diskCapacity, freeDiskStorage, fontScale, isEmulator, isTablet, hasNotch, hasDynamicIsland, isLandscape, brightness, getBatteryLevel, getFirstInstallTime (persisted iOS Documents creation date) and ~60 more"
6841
6843
  }
6842
6844
  ]
6843
6845
  },
@@ -12373,8 +12375,10 @@ var init_registry = __esm({
12373
12375
  versions: [
12374
12376
  {
12375
12377
  range: ">=1.0.0",
12376
- coverage: 0,
12377
- note: "the reference iOS simulator mounts React Native inside the native host. Peach passes children through and does not exercise that integration, so import safety receives no behavior credit"
12378
+ coverage: 0.5,
12379
+ note: "published ReactNativeBrownfield.js runs unchanged against a ReactNativeBrownfield seam that mirrors the hostless regime: silent methods, subscribed but never-emitted messages",
12380
+ working: "module presence; ios popToNative, setNativeBackGestureAndButtonEnabled, postMessage all silent; onMessage subscribe and remove with zero events on both platforms; android back-button flag write",
12381
+ missing: "host integration paths: android postMessage dispatch, android popToNative back press, native-to-js message emission"
12378
12382
  }
12379
12383
  ]
12380
12384
  },
@@ -37638,7 +37642,7 @@ async function collectExpoSwiftSources(appRoot) {
37638
37642
  const namespacesSeen = /* @__PURE__ */ new Set();
37639
37643
  let bytes = 0;
37640
37644
  let fileCount = 0;
37641
- for (const entry of Object.values(search)) {
37645
+ for (const [, entry] of Object.entries(search).sort(([a], [b]) => a.localeCompare(b))) {
37642
37646
  if (!entry || typeof entry !== "object")
37643
37647
  throw new Error("invalid Expo autolinking entry");
37644
37648
  const sourceRoot = Reflect.get(entry, "path");
@@ -37663,8 +37667,8 @@ async function collectExpoSwiftSources(appRoot) {
37663
37667
  const files2 = {};
37664
37668
  const classes = [];
37665
37669
  for (const module2 of modules) {
37666
- const className = module2 && typeof module2 === "object" ? Reflect.get(module2, "class") : null;
37667
- const name2 = module2 && typeof module2 === "object" ? Reflect.get(module2, "name") : null;
37670
+ const className = typeof module2 === "string" ? module2 : module2 && typeof module2 === "object" ? Reflect.get(module2, "class") : null;
37671
+ const name2 = typeof module2 === "string" ? null : module2 && typeof module2 === "object" ? Reflect.get(module2, "name") : null;
37668
37672
  if (typeof className !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(className) || name2 !== null && typeof name2 !== "string")
37669
37673
  throw new Error(`${sourceRoot}: invalid Expo Apple module descriptor`);
37670
37674
  classes.push({
@@ -37690,7 +37694,10 @@ async function collectExpoSwiftSources(appRoot) {
37690
37694
  fileCount++;
37691
37695
  if (fileCount > MAX_SWIFT_COMPILE_FILES - 2 || bytes > MAX_SWIFT_COMPILE_SOURCE_BYTES)
37692
37696
  throw new Error("Expo Swift sources exceed compile service caps");
37693
- files2[`Sources/${namespace}/${import_node_path21.default.relative(ios, file).split(import_node_path21.default.sep).join("/")}`] = `#sourceLocation(file: ${JSON.stringify(file)}, line: 1)
37697
+ files2[`Sources/${namespace}/${import_node_path21.default.relative(ios, file).split(import_node_path21.default.sep).join("/")}`] = // CocoaPods' iOS umbrella header imports UIKit into every Swift file
37698
+ // through the underlying module; these source units have no clang pod.
37699
+ `import UIKit
37700
+ #sourceLocation(file: ${JSON.stringify(file)}, line: 1)
37694
37701
  ${import_node_fs20.default.readFileSync(file, "utf8")}`;
37695
37702
  }
37696
37703
  };
@@ -49615,6 +49622,9 @@ var init_app_config = __esm({
49615
49622
  function discoverNativeLinkedAppFonts(opts) {
49616
49623
  const metroOrigin = loopbackOriginOf(opts.bundleUrl);
49617
49624
  if (!metroOrigin) return [];
49625
+ return (0, import_config_plugin_fonts.metroFontSpecsFromPaths)(discoverNativeLinkedAppFontPaths(opts), metroOrigin);
49626
+ }
49627
+ function discoverNativeLinkedAppFontPaths(opts) {
49618
49628
  const project = resolveAppProject(opts.projectDir);
49619
49629
  const selectedAppDir = project?.appDir ?? null;
49620
49630
  if (!selectedAppDir) return [];
@@ -49701,7 +49711,7 @@ function discoverNativeLinkedAppFonts(opts) {
49701
49711
  if (!relativeRoot.startsWith("..") && !import_node_path39.default.isAbsolute(relativeRoot)) {
49702
49712
  collectFontFiles(root, relativeRoot.split(import_node_path39.default.sep).join("/"));
49703
49713
  }
49704
- const bundleDir = import_node_path39.default.posix.dirname(new URL(opts.bundleUrl).pathname).replace(/^\/+/, "");
49714
+ const bundleDir = import_node_path39.default.posix.dirname(opts.bundleUrl ? new URL(opts.bundleUrl).pathname : "/").replace(/^\/+/, "");
49705
49715
  const appRelativeDir = bundleDir.split("/").join(import_node_path39.default.sep);
49706
49716
  const metroRootCandidates = [selectedAppDir];
49707
49717
  if (bundleDir && selectedAppDir.endsWith(import_node_path39.default.sep + appRelativeDir)) {
@@ -49792,7 +49802,7 @@ function discoverNativeLinkedAppFonts(opts) {
49792
49802
  } catch {
49793
49803
  }
49794
49804
  }
49795
- return (0, import_config_plugin_fonts.metroFontSpecsFromPaths)(fontPaths, metroOrigin);
49805
+ return [...new Set(fontPaths)];
49796
49806
  }
49797
49807
  async function resolveMetroProjectRoot(bundleUrl, opts) {
49798
49808
  const origin = loopbackOriginOf(bundleUrl);
@@ -53110,9 +53120,98 @@ function targetFromHome(envName, fallbackDir) {
53110
53120
  const home = process.env[envName] || path35.join(os9.homedir(), fallbackDir);
53111
53121
  return path35.join(home, "skills");
53112
53122
  }
53123
+ function codexTargets() {
53124
+ const homes = /* @__PURE__ */ new Map();
53125
+ const userHome = process.env.HOME || os9.homedir();
53126
+ const primaryCodexHome = process.env.CODEX_HOME || path35.join(userHome, ".codex");
53127
+ homes.set(path35.resolve(primaryCodexHome), "codex");
53128
+ const tmCodexHomes = process.env.TM_CODEX_HOMES;
53129
+ if (tmCodexHomes) {
53130
+ for (const h of tmCodexHomes.split(path35.delimiter)) {
53131
+ if (h.trim()) {
53132
+ const resolved = path35.resolve(h.trim());
53133
+ if (!homes.has(resolved)) {
53134
+ homes.set(resolved, path35.basename(resolved).replace(/^\./, ""));
53135
+ }
53136
+ }
53137
+ }
53138
+ }
53139
+ const tmHome = process.env.TM_HOME || path35.join(userHome, ".team-machine");
53140
+ const enginePath = path35.join(tmHome, "engine.yaml");
53141
+ if (fs30.existsSync(enginePath)) {
53142
+ try {
53143
+ const content = fs30.readFileSync(enginePath, "utf8");
53144
+ let inCodex = false;
53145
+ let inAccounts = false;
53146
+ let currentAccount = "";
53147
+ for (const line of content.split("\n")) {
53148
+ const indent = line.search(/\S/);
53149
+ const trimmed = line.trim();
53150
+ if (indent === -1) continue;
53151
+ if (indent === 4) {
53152
+ inCodex = trimmed === "codex:";
53153
+ inAccounts = false;
53154
+ currentAccount = "";
53155
+ continue;
53156
+ }
53157
+ if (!inCodex) continue;
53158
+ if (indent === 6 && trimmed === "accounts:") {
53159
+ inAccounts = true;
53160
+ currentAccount = "";
53161
+ continue;
53162
+ }
53163
+ if (!inAccounts) continue;
53164
+ if (indent === 8 && trimmed.endsWith(":")) {
53165
+ currentAccount = trimmed.slice(0, -1).trim();
53166
+ continue;
53167
+ }
53168
+ if (indent >= 10 && currentAccount) {
53169
+ const match = /^home:\s*(.+)$/.exec(trimmed);
53170
+ if (match) {
53171
+ let homeDir = match[1].trim();
53172
+ if (homeDir.startsWith("~")) {
53173
+ homeDir = path35.join(userHome, homeDir.slice(1));
53174
+ }
53175
+ const resolved = path35.resolve(homeDir);
53176
+ if (!homes.has(resolved)) {
53177
+ homes.set(resolved, `codex-${currentAccount}`);
53178
+ }
53179
+ }
53180
+ }
53181
+ }
53182
+ } catch {
53183
+ }
53184
+ }
53185
+ try {
53186
+ for (const entry of fs30.readdirSync(userHome)) {
53187
+ if (entry.startsWith(".codex-")) {
53188
+ const fullPath = path35.join(userHome, entry);
53189
+ try {
53190
+ const stat2 = fs30.statSync(fullPath);
53191
+ if (stat2.isDirectory()) {
53192
+ const resolved = path35.resolve(fullPath);
53193
+ if (!homes.has(resolved)) {
53194
+ homes.set(resolved, entry.replace(/^\./, ""));
53195
+ }
53196
+ }
53197
+ } catch {
53198
+ }
53199
+ }
53200
+ }
53201
+ } catch {
53202
+ }
53203
+ const targets = [];
53204
+ for (const [homePath, label] of homes) {
53205
+ targets.push({
53206
+ label,
53207
+ dir: path35.join(homePath, "skills")
53208
+ });
53209
+ }
53210
+ return targets;
53211
+ }
53113
53212
  function defaultAgentTargets() {
53114
53213
  return [
53115
- { label: "codex", dir: targetFromHome("CODEX_HOME", ".codex") },
53214
+ ...codexTargets(),
53116
53215
  { label: "claude", dir: targetFromHome("CLAUDE_CONFIG_DIR", ".claude") }
53117
53216
  ];
53118
53217
  }
@@ -53618,16 +53717,26 @@ async function ensureBridgeForOpen(preferredPort, explicitPort = false, resource
53618
53717
  );
53619
53718
  }
53620
53719
  const { executable, prefixArgs } = rnxSelfInvocation();
53621
- const child = (0, import_node_child_process16.spawn)(
53622
- executable,
53623
- [...prefixArgs, "serve", "--quiet", "--port", String(preferredPort)],
53624
- {
53625
- detached: true,
53626
- stdio: "ignore",
53627
- // this bridge does not supply the shell, so it needs no runtime updates.
53628
- env: runtimeSource === "external" ? { ...process.env, PEACH_UPDATE_CHECK: "off" } : process.env
53629
- }
53720
+ const bridgeLogPath = import_node_path43.default.join(
53721
+ import_node_os11.default.tmpdir(),
53722
+ `peach-bridge-${Date.now()}-${process.pid}.log`
53630
53723
  );
53724
+ const bridgeLogFd = import_node_fs41.default.openSync(bridgeLogPath, "a");
53725
+ let child;
53726
+ try {
53727
+ child = (0, import_node_child_process16.spawn)(
53728
+ executable,
53729
+ [...prefixArgs, "serve", "--quiet", "--port", String(preferredPort)],
53730
+ {
53731
+ detached: true,
53732
+ stdio: ["ignore", "ignore", bridgeLogFd],
53733
+ // this bridge does not supply the shell, so it needs no runtime updates.
53734
+ env: runtimeSource === "external" ? { ...process.env, PEACH_UPDATE_CHECK: "off" } : process.env
53735
+ }
53736
+ );
53737
+ } finally {
53738
+ import_node_fs41.default.closeSync(bridgeLogFd);
53739
+ }
53631
53740
  if (resources) {
53632
53741
  resources.bridge = child;
53633
53742
  resources.port = preferredPort;
@@ -53658,13 +53767,15 @@ async function ensureBridgeForOpen(preferredPort, explicitPort = false, resource
53658
53767
  }
53659
53768
  return selectedPort;
53660
53769
  }
53661
- if (childExit) {
53662
- throw new Error(`peach bridge exited before becoming ready (exit ${childExit})`);
53663
- }
53770
+ if (childExit) break;
53664
53771
  await sleep(150);
53665
53772
  }
53773
+ const reason = childExit ? `peach bridge exited before becoming ready (exit ${childExit})` : `peach bridge did not start within ${Math.round(OPEN_BRIDGE_SPAWN_TIMEOUT_MS / 1e3)}s`;
53774
+ const bridgeLog = import_node_fs41.default.readFileSync(bridgeLogPath, "utf8").trim().slice(-2e3);
53666
53775
  throw new Error(
53667
- `peach bridge did not start within ${Math.round(OPEN_BRIDGE_SPAWN_TIMEOUT_MS / 1e3)}s`
53776
+ `${reason}${bridgeLog ? `
53777
+ ${bridgeLog}` : ""}
53778
+ bridge log: ${bridgeLogPath}`
53668
53779
  );
53669
53780
  }
53670
53781
  function assertExplicitBridgeLock(preferredPort, explicitPort, lock) {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-compile.ts
4
4
  import { execFile as execFile2, execFileSync } from "node:child_process";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (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.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } 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.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.899 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.908 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/worklet-executable.ts
4
4
  import { parse } from "acorn";
@@ -46,3 +46,6 @@ The FreeType patch preserves explicit forced autohinting and disables implicit
46
46
  autohinting, matching Android. The Wasm compiler uses Android text gamma 1.4
47
47
  and contrast 0; the pinned surface and FreeType units replace their archive
48
48
  counterparts. FreeType headers come from the same dependency revision as Skia.
49
+
50
+ Android centered text uses the platform intrinsic padding, integer origin and
51
+ line bounds. Compose selects the added Skiko alignment ordinal only on Android.
@@ -17,7 +17,9 @@ Android behavior for the approved web differences and static text shaping below.
17
17
  | Compose `ui-skiko` | `SkikoText.nonAndroid.kt`, `SkikoParagraphBuilder.nonAndroid.kt` | Android static advances and letter-spacing scale; Animated text retains fractional shaping |
18
18
  | Skia shaper | `SkShaper_harfbuzz.cpp` | measurement-font copy truncates static shaping size; the painted run font retains the requested size |
19
19
  | Skia raster | `SkFontHost_FreeType.cpp`, `SkFontHost_FreeType_common.cpp`, `SkSurface.cpp` | Android implicit autohinting policy and text gamma 1.4, contrast 0 |
20
- | Skia paragraph | `Run.cpp`, `TextLine.cpp`, `TextWrapper.cpp` | Android inter-glyph spacing, caret boundaries and edge trimming before line fits |
20
+ | Skia paragraph | `Run.cpp`, `TextLine.cpp`, `TextWrapper.cpp`, `DartTypes.h` | Android inter-glyph spacing, caret boundaries, edge trimming before line fits and integer center origins |
21
+ | Compose `ui-skiko` | `SkikoParagraphIntrinsics.nonAndroid.kt`, `SkikoParagraph.nonAndroid.kt`, `SkikoParagraphBuilder.nonAndroid.kt` | Android letter-spacing intrinsic padding, centered line bounds and explicit center alignment |
22
+ | Skiko paragraph | `Alignment.kt` | transports the Android center ordinal without changing existing alignments |
21
23
  | Compose `material3` | `PlatformDateFormat.web.kt` | timestamp formatting uses UTC, as Android does |
22
24
  | Navigation `navigation-compose` | `DefaultNavTransitions.web.kt` | Android forward fades and predictive pop defaults |
23
25
  | Skiko | `OsArch.web.kt` | explicit `setHostOsProvider`; its default lazily retains upstream OS detection |
@@ -41,7 +43,13 @@ the FreeType binary remains the archive version. Raster changes add no allocatio
41
43
  and retain the existing A8 preblend path. This adds no allocation beyond the existing
42
44
  measurement-font copy, and truncates once per shaped font, outside the glyph loop.
43
45
  Paragraph spacing adds constant arithmetic per glyph or cluster; line fits trim
44
- the same edge half-spaces as completed lines, without allocation.
46
+ the same edge half-spaces as completed lines, without allocation. Android centered
47
+ lines use the platform integer origin; other platforms retain fractional centering.
48
+ Intrinsic measurement checks span letter spacing once and adds Android’s 0.5 px
49
+ padding; line bounds use the platform floor and ceil. These corrections add no
50
+ allocation. Skiko builds before Compose, whose `skiko-wasm-js` dependency resolves
51
+ from this invocation’s Maven directory through `skiko-local.init.gradle`. The init
52
+ script travels in the immutable build record with the patches.
45
53
 
46
54
  ```sh
47
55
  delivery="scripts/tmp/kotlin-fork-$(date +%s)-$$"
@@ -102,19 +102,6 @@ gradle() {
102
102
  rm -rf "${DELIVER}" "${M2}"
103
103
  mkdir -p "${DELIVER}"
104
104
 
105
- checkout 0
106
- (
107
- cd "${SOURCE}"
108
- gradle -Pcompose.platforms=web -Pjetbrains.publication.version.COMPOSE=1.13.0-alpha01 \
109
- -Pjetbrains.publication.version.COMPOSE_MATERIAL3=1.13.0-alpha01 \
110
- -Pjetbrains.publication.version.NAVIGATION=2.10.0-beta01 \
111
- :compose:ui:ui:publishWasmJsPublicationToMavenLocal \
112
- :compose:ui:ui-text:publishWasmJsPublicationToMavenLocal \
113
- :compose:ui:ui-skiko:publishWasmJsPublicationToMavenLocal \
114
- :compose:material3:material3:publishWasmJsPublicationToMavenLocal \
115
- :navigation:navigation-compose:publishWasmJsPublicationToMavenLocal
116
- )
117
-
118
105
  checkout 1
119
106
  SKIKO_SOURCE=${SOURCE}
120
107
  checkout 2
@@ -161,6 +148,19 @@ cp "${SOURCE}/src/image/SkSurface.cpp" "${SKIKO_SOURCE}/skiko/src/nativeJsMain/c
161
148
  cp build/libs/skiko-wasm-0.152.0-alpha02.jar "${DELIVER}/"
162
149
  )
163
150
 
151
+ checkout 0
152
+ (
153
+ cd "${SOURCE}"
154
+ gradle -I "${FORK}/skiko-local.init.gradle" -Pcompose.platforms=web -Pjetbrains.publication.version.COMPOSE=1.13.0-alpha01 \
155
+ -Pjetbrains.publication.version.COMPOSE_MATERIAL3=1.13.0-alpha01 \
156
+ -Pjetbrains.publication.version.NAVIGATION=2.10.0-beta01 \
157
+ :compose:ui:ui:publishWasmJsPublicationToMavenLocal \
158
+ :compose:ui:ui-text:publishWasmJsPublicationToMavenLocal \
159
+ :compose:ui:ui-skiko:publishWasmJsPublicationToMavenLocal \
160
+ :compose:material3:material3:publishWasmJsPublicationToMavenLocal \
161
+ :navigation:navigation-compose:publishWasmJsPublicationToMavenLocal
162
+ )
163
+
164
164
  cp "${M2}/org/jetbrains/compose/ui/ui-wasm-js/1.13.0-alpha01/ui-wasm-js-1.13.0-alpha01.klib" \
165
165
  "${M2}/org/jetbrains/compose/ui/ui-text-wasm-js/1.13.0-alpha01/ui-text-wasm-js-1.13.0-alpha01.klib" \
166
166
  "${M2}/org/jetbrains/compose/ui/ui-skiko-wasm-js/1.13.0-alpha01/ui-skiko-wasm-js-1.13.0-alpha01.klib" \
@@ -170,5 +170,5 @@ cp "${M2}/org/jetbrains/compose/ui/ui-wasm-js/1.13.0-alpha01/ui-wasm-js-1.13.0-a
170
170
  "${DELIVER}/"
171
171
  # the record travels with the klibs it describes, so publishing attaches what
172
172
  # this build used, not whatever the checkout holds later
173
- cp "${FORK}/fork.json" "${FORK}/LICENSE" "${FORK}/NOTICE" "${FORK}/SKIA_LICENSE" "${FORK}"/*.patch "${DELIVER}/"
173
+ cp "${FORK}/fork.json" "${FORK}/LICENSE" "${FORK}/NOTICE" "${FORK}/SKIA_LICENSE" "${FORK}"/*.patch "${FORK}/skiko-local.init.gradle" "${DELIVER}/"
174
174
  (cd "${DELIVER}" && sha256sum -- *.klib *.jar > artifacts.sha256 && cat artifacts.sha256)
@@ -329,3 +329,108 @@ diff --git a/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/t
329
329
  )
330
330
  for (i in 1 until activeStyles.size) {
331
331
  style.merge(density, activeStyles[i])
332
+ diff --git a/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/SkikoParagraph.nonAndroid.kt b/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/SkikoParagraph.nonAndroid.kt
333
+ --- a/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/SkikoParagraph.nonAndroid.kt
334
+ +++ b/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/SkikoParagraph.nonAndroid.kt
335
+ @@ -1,3 +1,4 @@
336
+ +// modified for peach: Android text bounds and intrinsic padding match the platform.
337
+ /*
338
+ * Copyright 2023 The Android Open Source Project
339
+ *
340
+ @@ -35,6 +36,8 @@
341
+ import androidx.compose.ui.graphics.toComposeRect
342
+ import androidx.compose.ui.text.PlatformParagraph
343
+ import androidx.compose.ui.text.platform.SkikoParagraphIntrinsics
344
+ +import androidx.compose.ui.text.platform.Platform
345
+ +import androidx.compose.ui.text.platform.currentPlatform
346
+ import androidx.compose.ui.text.platform.cursorHorizontalPosition
347
+ import androidx.compose.ui.text.style.LineHeightStyle
348
+ import androidx.compose.ui.text.style.ResolvedTextDirection
349
+ @@ -43,6 +46,7 @@
350
+ import androidx.compose.ui.text.style.TextOverflow
351
+ import androidx.compose.ui.unit.Constraints
352
+ import androidx.compose.ui.unit.isUnspecified
353
+ +import kotlin.math.ceil
354
+ import kotlin.math.floor
355
+ import org.jetbrains.skia.FontMetrics
356
+ import org.jetbrains.skia.IRange
357
+ @@ -217,10 +221,18 @@
358
+ }
359
+
360
+ override fun getLineLeft(lineIndex: Int): Float =
361
+ - lineMetrics.getOrNull(lineIndex)?.left?.toFloat() ?: 0f
362
+ + lineMetrics.getOrNull(lineIndex)?.let { line ->
363
+ + if (currentPlatform() == Platform.Android && paragraphIntrinsics.style.textAlign == TextAlign.Center) {
364
+ + floor((width - line.width.toFloat()) / 2)
365
+ + } else line.left.toFloat()
366
+ + } ?: 0f
367
+
368
+ override fun getLineRight(lineIndex: Int): Float =
369
+ - lineMetrics.getOrNull(lineIndex)?.right?.toFloat() ?: 0f
370
+ + lineMetrics.getOrNull(lineIndex)?.let { line ->
371
+ + if (currentPlatform() == Platform.Android && paragraphIntrinsics.style.textAlign == TextAlign.Center) {
372
+ + ceil((width + line.width.toFloat()) / 2)
373
+ + } else line.right.toFloat()
374
+ + } ?: 0f
375
+
376
+ override fun getLineTop(lineIndex: Int) =
377
+ lineMetrics.getOrNull(lineIndex)?.let { line ->
378
+ diff --git a/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/platform/SkikoParagraphIntrinsics.nonAndroid.kt b/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/platform/SkikoParagraphIntrinsics.nonAndroid.kt
379
+ --- a/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/platform/SkikoParagraphIntrinsics.nonAndroid.kt
380
+ +++ b/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/platform/SkikoParagraphIntrinsics.nonAndroid.kt
381
+ @@ -1,3 +1,4 @@
382
+ +// modified for peach: Android text bounds and intrinsic padding match the platform.
383
+ @file:OptIn(InternalComposeUiApi::class)
384
+
385
+ /*
386
+ @@ -26,6 +27,7 @@
387
+ import androidx.compose.ui.text.ParagraphIntrinsics
388
+ import androidx.compose.ui.text.Placeholder
389
+ import androidx.compose.ui.text.StrongDirectionType
390
+ +import androidx.compose.ui.text.SpanStyle
391
+ import androidx.compose.ui.text.TextStyle
392
+ import androidx.compose.ui.text.firstStrongDirectionType
393
+ import androidx.compose.ui.text.font.FontFamily
394
+ @@ -35,11 +37,12 @@
395
+ import androidx.compose.ui.text.style.ResolvedTextDirection
396
+ import androidx.compose.ui.text.style.TextDirection
397
+ import androidx.compose.ui.unit.Density
398
+ +import androidx.compose.ui.unit.TextUnit
399
+ import kotlin.math.ceil
400
+
401
+ internal class SkikoParagraphIntrinsics(
402
+ val text: String,
403
+ - private val style: TextStyle,
404
+ + val style: TextStyle,
405
+ private val annotations: List<AnnotatedString.Range<out AnnotatedString.Annotation>>,
406
+ private val placeholders: List<Range<Placeholder>>,
407
+ private val density: Density,
408
+ @@ -79,6 +82,16 @@
409
+ val para = layouter!!.layoutParagraph(Float.POSITIVE_INFINITY)
410
+ minIntrinsicWidth = ceil(para.minIntrinsicWidth)
411
+ maxIntrinsicWidth = ceil(para.maxIntrinsicWidth)
412
+ + val hasLetterSpacing =
413
+ + (style.letterSpacing != TextUnit.Unspecified && style.letterSpacing.value != 0f) ||
414
+ + annotations.any {
415
+ + val span = it.item
416
+ + span is SpanStyle && span.letterSpacing != TextUnit.Unspecified &&
417
+ + span.letterSpacing.value != 0f
418
+ + }
419
+ + if (currentPlatform() == Platform.Android && maxIntrinsicWidth != 0f && hasLetterSpacing) {
420
+ + maxIntrinsicWidth += 0.5f
421
+ + }
422
+ }
423
+ }
424
+
425
+ diff --git a/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/platform/SkikoParagraphBuilder.nonAndroid.kt b/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/platform/SkikoParagraphBuilder.nonAndroid.kt
426
+ --- a/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/platform/SkikoParagraphBuilder.nonAndroid.kt
427
+ +++ b/compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/platform/SkikoParagraphBuilder.nonAndroid.kt
428
+ @@ -832,7 +832,7 @@
429
+ return when (this) {
430
+ TextAlign.Left -> SkAlignment.LEFT
431
+ TextAlign.Right -> SkAlignment.RIGHT
432
+ - TextAlign.Center -> SkAlignment.CENTER
433
+ + TextAlign.Center -> if (currentPlatform() == Platform.Android) SkAlignment.CENTER_ANDROID else SkAlignment.CENTER
434
+ TextAlign.Justify -> SkAlignment.JUSTIFY
435
+ TextAlign.Start -> SkAlignment.START
436
+ TextAlign.End -> SkAlignment.END