From 567e9301a78bd535652626ef72162307245f2ac6 Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Mon, 9 Nov 2026 20:26:28 -0500 Subject: [PATCH 1/6] fix(ios): make QuickCrypto podspec checksum independent of checkout path --- example/ios/Podfile.lock | 2 +- .../QuickCrypto.podspec | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 4cc85ff5..558d2df6 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2837,7 +2837,7 @@ SPEC CHECKSUMS: MMKVCore: 3d16ce9f7d411e135020915fde98a056859a1efa NitroMmkv: c68bb2c29c154913707bc6e66791b2b30dd723c1 NitroModules: 1dc73273d6e3aee937eec41ef3ed9470804b9884 - QuickCrypto: a720b137bf86089e3ef05abc6184f298fbc2c914 + QuickCrypto: ff693664d419b708fbb3cb8ec3f860d44b0f0328 RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077 RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a diff --git a/packages/react-native-quick-crypto/QuickCrypto.podspec b/packages/react-native-quick-crypto/QuickCrypto.podspec index 8e026e55..9a5ba9b3 100644 --- a/packages/react-native-quick-crypto/QuickCrypto.podspec +++ b/packages/react-native-quick-crypto/QuickCrypto.podspec @@ -1,6 +1,7 @@ require "json" require "digest" require "shellwords" +require "pathname" package = JSON.parse(File.read(File.join(__dir__, "package.json"))) @@ -203,8 +204,18 @@ Pod::Spec.new do |s| # Every translation unit calls the renamed OpenSSL symbols. Missing a rename # is a link error, never a silent bind to a foreign OpenSSL. - openssl_include = File.join(openssl_dir, "include") - force_include = "-include \"#{openssl_prefix_header}\"" + # Real paths expressed relative to the Pods sandbox keep the evaluated spec (and + # so the Podfile.lock checksum) identical across checkout locations. + pods_relative = lambda do |path| + sandbox = defined?(Pod::Config) && Pod::Config.instance.sandbox_root + next path unless sandbox + sandbox_dir = Pathname.new(sandbox.to_s) + real_sandbox = File.realpath(sandbox_dir.dirname).then { |parent| Pathname.new(parent).join(sandbox_dir.basename) } + "$(PODS_ROOT)/" + Pathname.new(File.realpath(path)).relative_path_from(real_sandbox).to_s + end + + openssl_include = pods_relative.call(File.join(openssl_dir, "include")) + force_include = "-include \"#{pods_relative.call(openssl_prefix_header)}\"" xcconfig["OTHER_CFLAGS"] = "$(inherited) #{force_include}" xcconfig["OTHER_CPLUSPLUSFLAGS"] = "$(inherited) #{force_include}" @@ -231,7 +242,7 @@ Pod::Spec.new do |s| # Use absolute path from __dir__ which resolves symlinks correctly. # This is necessary in monorepo setups where the podspec is accessed via symlink # but the source files are resolved to their real paths during compilation. - real_sodium_include = File.join(__dir__, "ios", "libsodium-stable", "src", "libsodium", "include") + real_sodium_include = pods_relative.call(File.join(__dir__, "ios", "libsodium-stable", "src", "libsodium", "include")) sodium_headers = [ "\"$(PODS_TARGET_SRCROOT)/ios/libsodium-stable/src/libsodium/include\"", "\"$(PODS_TARGET_SRCROOT)/ios/libsodium-stable/src/libsodium/include/sodium\"", From eba1bbe5199da53c86e6fd46345b5f0646b5f0a6 Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Mon, 9 Nov 2026 20:32:54 -0500 Subject: [PATCH 2/6] fix(example): compile fmt as C++17 for Xcode 27 clang React Native 0.81 bundles fmt 11.0.2, whose consteval format checks fail under Xcode 27.2's clang when the pod is forced to C++20. --- example/ios/Podfile | 8 ++++++++ example/ios/Podfile.lock | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/example/ios/Podfile b/example/ios/Podfile index 629ae52a..ccc91cbd 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -59,5 +59,13 @@ target 'QuickCryptoExample' do end end + # fmt 11.0.2 (bundled by React Native 0.81) fails under Xcode 27's clang + # consteval checks; C++17 compiles it without consteval. + installer.pods_project.targets.select { |t| t.name == 'fmt' }.each do |target| + target.build_configurations.each do |config| + config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17' + end + end + end end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 558d2df6..c2822837 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2909,6 +2909,6 @@ SPEC CHECKSUMS: SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 11c9686a21e2cd82a094a723649d9f4507200fb0 -PODFILE CHECKSUM: fdc825234ea7a57e35b0bf4e2e0e6049b1652015 +PODFILE CHECKSUM: 2b1fa12c2fb50678fff8af81d9e99e04de1f3032 COCOAPODS: 1.15.2 From b0dbcb787fc67169bbee578e7a4e62d823936bc7 Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Mon, 9 Nov 2026 20:33:19 -0500 Subject: [PATCH 3/6] docs(ios): clarify sodium include path comment --- packages/react-native-quick-crypto/QuickCrypto.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-quick-crypto/QuickCrypto.podspec b/packages/react-native-quick-crypto/QuickCrypto.podspec index 9a5ba9b3..dfa76e5f 100644 --- a/packages/react-native-quick-crypto/QuickCrypto.podspec +++ b/packages/react-native-quick-crypto/QuickCrypto.podspec @@ -239,7 +239,7 @@ Pod::Spec.new do |s| ] if sodium_enabled - # Use absolute path from __dir__ which resolves symlinks correctly. + # Use the real path from __dir__, which resolves symlinks correctly. # This is necessary in monorepo setups where the podspec is accessed via symlink # but the source files are resolved to their real paths during compilation. real_sodium_include = pods_relative.call(File.join(__dir__, "ios", "libsodium-stable", "src", "libsodium", "include")) From ce27d611a985b3e410be9cb47a21e44cfbe7645d Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Mon, 9 Nov 2026 20:36:25 -0500 Subject: [PATCH 4/6] fix(ios): resolve a symlinked Pods sandbox before relativizing paths --- packages/react-native-quick-crypto/QuickCrypto.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-quick-crypto/QuickCrypto.podspec b/packages/react-native-quick-crypto/QuickCrypto.podspec index dfa76e5f..d3dad1c6 100644 --- a/packages/react-native-quick-crypto/QuickCrypto.podspec +++ b/packages/react-native-quick-crypto/QuickCrypto.podspec @@ -210,7 +210,7 @@ Pod::Spec.new do |s| sandbox = defined?(Pod::Config) && Pod::Config.instance.sandbox_root next path unless sandbox sandbox_dir = Pathname.new(sandbox.to_s) - real_sandbox = File.realpath(sandbox_dir.dirname).then { |parent| Pathname.new(parent).join(sandbox_dir.basename) } + real_sandbox = sandbox_dir.exist? ? sandbox_dir.realpath : Pathname.new(File.realpath(sandbox_dir.dirname)).join(sandbox_dir.basename) "$(PODS_ROOT)/" + Pathname.new(File.realpath(path)).relative_path_from(real_sandbox).to_s end @@ -221,7 +221,7 @@ Pod::Spec.new do |s| # Add cpp subdirectories to header search paths cpp_headers = [ - # Absolute path as well as the pod-relative one: in monorepos the podspec is + # Real path (relative to $(PODS_ROOT)) as well as the pod-relative one: in monorepos the podspec is # reached through a symlink but sources resolve to their real paths. "\"$(PODS_TARGET_SRCROOT)/ios/openssl/include\"", "\"#{openssl_include}\"", From 555120287a836215fe6d7b87c0823500f4ad494c Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Mon, 9 Nov 2026 20:41:43 -0500 Subject: [PATCH 5/6] fix(example): adopt UIScene lifecycle for iOS 27 iOS 27 terminates apps without a scene manifest (UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption). The React Native root now starts from a SceneDelegate. --- .../ios/QuickCryptoExample/AppDelegate.swift | 27 +++++++++++++++---- example/ios/QuickCryptoExample/Info.plist | 17 ++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/example/ios/QuickCryptoExample/AppDelegate.swift b/example/ios/QuickCryptoExample/AppDelegate.swift index 40ff82de..51f9baf8 100644 --- a/example/ios/QuickCryptoExample/AppDelegate.swift +++ b/example/ios/QuickCryptoExample/AppDelegate.swift @@ -21,15 +21,32 @@ class AppDelegate: UIResponder, UIApplicationDelegate { reactNativeDelegate = delegate reactNativeFactory = factory - window = UIWindow(frame: UIScreen.main.bounds) + return true + } +} + +// iOS 27 terminates apps that have not adopted the UIScene lifecycle. +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? - factory.startReactNative( + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = scene as? UIWindowScene, + let appDelegate = UIApplication.shared.delegate as? AppDelegate + else { return } + + let window = UIWindow(windowScene: windowScene) + self.window = window + appDelegate.window = window + + appDelegate.reactNativeFactory?.startReactNative( withModuleName: "QuickCryptoExample", in: window, - launchOptions: launchOptions + launchOptions: nil ) - - return true } } diff --git a/example/ios/QuickCryptoExample/Info.plist b/example/ios/QuickCryptoExample/Info.plist index 958e5f7b..f6f325c0 100644 --- a/example/ios/QuickCryptoExample/Info.plist +++ b/example/ios/QuickCryptoExample/Info.plist @@ -39,6 +39,23 @@ MaterialCommunityIcons.ttf + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities From f30e0f09c8bbc61a1d56b3b92cfce079446b6c5e Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Mon, 9 Nov 2026 20:46:32 -0500 Subject: [PATCH 6/6] fix(e2e): tap Run All by frame when iOS 27 reports it covered On iOS 27 the accessibility tree exposes a non-interactive full-screen toolbar above the tab content, so agent-device refuses the semantic press. A real tap at the button frame works. --- example/test/e2e/runner.mjs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/example/test/e2e/runner.mjs b/example/test/e2e/runner.mjs index 014329b9..b325a4d0 100644 --- a/example/test/e2e/runner.mjs +++ b/example/test/e2e/runner.mjs @@ -694,9 +694,28 @@ async function runSuite(device, metroPort) { owned.session, ...selector, ]); + // iOS 27 reports a non-interactive full-screen toolbar above the content, so + // agent-device refuses the semantic press; a real tap at the frame works. + const runAll = ( + await agent([ + 'get', + 'attrs', + 'id="run-all-tests-button"', + '--session', + owned.session, + ...selector, + ]) + ).data?.node; + const pressTarget = + runAll?.interactionBlocked === 'covered' && runAll.rect + ? [ + String(Math.round(runAll.rect.x + runAll.rect.width / 2)), + String(Math.round(runAll.rect.y + runAll.rect.height / 2)), + ] + : ['id="run-all-tests-button"']; await agent([ 'press', - 'id="run-all-tests-button"', + ...pressTarget, '--settle', '--timeout', '10000',