From 653f2553220412130cdd614cc487c4272d8909bd Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 14 Nov 2025 14:40:40 +0000 Subject: [PATCH 1/6] chore(template): dynamically resolve node modules root (#2757) Followup from https://github.com/microsoft/react-native-macos/pull/2756#discussion_r2510617835 --- .../local-cli/generator-macos/templates/macos/Podfile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/react-native/local-cli/generator-macos/templates/macos/Podfile b/packages/react-native/local-cli/generator-macos/templates/macos/Podfile index cc3684c055c0a8..cb8277f75a963c 100644 --- a/packages/react-native/local-cli/generator-macos/templates/macos/Podfile +++ b/packages/react-native/local-cli/generator-macos/templates/macos/Podfile @@ -1,10 +1,15 @@ -require_relative '../node_modules/react-native-macos/scripts/react_native_pods' -require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' +require 'pathname' + +ws_dir = Pathname.new(__dir__) +ws_dir = ws_dir.parent until + File.exist?("#{ws_dir}/node_modules/react-native-macos/scripts/react_native_pods.rb") || + ws_dir.expand_path.to_s == '/' +require "#{ws_dir}/node_modules/react-native-macos/scripts/react_native_pods.rb" prepare_react_native_project! target 'HelloWorld-macOS' do - platform :macos, '11.0' + platform :macos, '14.0' use_native_modules! # Flags change depending on the env values. From 5e11dc208166747a078a80313669775cc076e1ff Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Tue, 14 Oct 2025 08:07:43 -0700 Subject: [PATCH 2/6] fix: implement RCTAppearanceProxy (#2729) --- .../Base/UIKitProxies/RCTAppearanceProxy.h | 28 ++++++ .../Base/UIKitProxies/RCTAppearanceProxy.mm | 92 +++++++++++++++++++ .../UIKitProxies/RCTInitializeUIKitProxies.mm | 7 +- .../React/CoreModules/RCTAppearance.mm | 43 +++++---- .../React/CoreModules/RCTDeviceInfo.mm | 4 + 5 files changed, 154 insertions(+), 20 deletions(-) create mode 100644 packages/react-native/React/Base/UIKitProxies/RCTAppearanceProxy.h create mode 100644 packages/react-native/React/Base/UIKitProxies/RCTAppearanceProxy.mm diff --git a/packages/react-native/React/Base/UIKitProxies/RCTAppearanceProxy.h b/packages/react-native/React/Base/UIKitProxies/RCTAppearanceProxy.h new file mode 100644 index 00000000000000..c3886a109ac5ee --- /dev/null +++ b/packages/react-native/React/Base/UIKitProxies/RCTAppearanceProxy.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) Microsoft Corporation. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#if TARGET_OS_OSX // [macOS +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTAppearanceProxy : NSObject + ++ (instancetype)sharedInstance; + +/* + * Property to access the current appearance. + * Thread safe. + */ +@property (nonatomic, readonly) NSAppearance *currentAppearance; + +- (void)startObservingAppearance; + +@end + +NS_ASSUME_NONNULL_END +#endif // macOS] diff --git a/packages/react-native/React/Base/UIKitProxies/RCTAppearanceProxy.mm b/packages/react-native/React/Base/UIKitProxies/RCTAppearanceProxy.mm new file mode 100644 index 00000000000000..9e25b3604f996f --- /dev/null +++ b/packages/react-native/React/Base/UIKitProxies/RCTAppearanceProxy.mm @@ -0,0 +1,92 @@ +/* + * Copyright (c) Microsoft Corporation. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#if TARGET_OS_OSX // [macOS +#import "RCTAppearanceProxy.h" + +#import +#import + +#import + +@implementation RCTAppearanceProxy { + BOOL _isObserving; + std::mutex _mutex; + NSAppearance *_currentAppearance; +} + ++ (instancetype)sharedInstance +{ + static RCTAppearanceProxy *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [RCTAppearanceProxy new]; + }); + return sharedInstance; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _isObserving = NO; + _currentAppearance = [NSApp effectiveAppearance]; + } + return self; +} + +- (void)startObservingAppearance +{ + RCTAssertMainQueue(); + std::lock_guard lock(_mutex); + if (!_isObserving) { + _isObserving = YES; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_appearanceDidChange:) + name:RCTUserInterfaceStyleDidChangeNotification + object:nil]; + } +} + +- (NSAppearance *)currentAppearance +{ + { + std::lock_guard lock(_mutex); + if (_isObserving) { + return _currentAppearance; + } + } + + __block NSAppearance *appearance = nil; + if (RCTIsMainQueue()) { + appearance = [NSApp effectiveAppearance]; + } else { + dispatch_sync(dispatch_get_main_queue(), ^{ + appearance = [NSApp effectiveAppearance]; + }); + } + return appearance; +} + +- (void)_appearanceDidChange:(NSNotification *)notification +{ + std::lock_guard lock(_mutex); + + NSDictionary *userInfo = [notification userInfo]; + if (userInfo) { + NSAppearance *appearance = userInfo[RCTUserInterfaceStyleDidChangeNotificationAppearanceKey]; + if (appearance != nil) { + _currentAppearance = appearance; + return; + } + } + + _currentAppearance = [NSApp effectiveAppearance]; +} + +@end +#endif // macOS] diff --git a/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm b/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm index 09dfb43d4ab2da..4e059fab360340 100644 --- a/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm +++ b/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm @@ -10,6 +10,9 @@ #import "RCTKeyWindowValuesProxy.h" #import "RCTTraitCollectionProxy.h" #import "RCTWindowSafeAreaProxy.h" +#if TARGET_OS_OSX // [macOS +#import "RCTAppearanceProxy.h" +#endif // macOS] void RCTInitializeUIKitProxies(void) { @@ -19,7 +22,9 @@ void RCTInitializeUIKitProxies(void) #if !TARGET_OS_OSX // [macOS] [[RCTTraitCollectionProxy sharedInstance] startObservingTraitCollection]; [[RCTInitialAccessibilityValuesProxy sharedInstance] recordAccessibilityValues]; -#endif // [macOS] +#else // [macOS + [[RCTAppearanceProxy sharedInstance] startObservingAppearance]; +#endif // macOS] [[RCTKeyWindowValuesProxy sharedInstance] startObservingWindowSizeIfNecessary]; }); } diff --git a/packages/react-native/React/CoreModules/RCTAppearance.mm b/packages/react-native/React/CoreModules/RCTAppearance.mm index 4053d54a9e1a12..857c18d6ce5650 100644 --- a/packages/react-native/React/CoreModules/RCTAppearance.mm +++ b/packages/react-native/React/CoreModules/RCTAppearance.mm @@ -14,6 +14,12 @@ #import "CoreModulesPlugins.h" +#if TARGET_OS_OSX // [macOS +#import + +#import "RCTAppearanceProxy.h" +#endif // macOS] + using namespace facebook::react; NSString *const RCTAppearanceColorSchemeLight = @"light"; @@ -119,7 +125,7 @@ - (instancetype)init UITraitCollection *traitCollection = [RCTTraitCollectionProxy sharedInstance].currentTraitCollection; _currentColorScheme = RCTColorSchemePreference(traitCollection); #else // [macOS - NSAppearance *appearance = RCTSharedApplication().appearance; + NSAppearance *appearance = [RCTAppearanceProxy sharedInstance].currentAppearance; _currentColorScheme = RCTColorSchemePreference(appearance); #endif // macOS] [[NSNotificationCenter defaultCenter] addObserver:self @@ -134,7 +140,11 @@ - (instancetype)init + (BOOL)requiresMainQueueSetup { +#if !TARGET_OS_OSX // [macOS] return NO; +#else // [macOS + return YES; +#endif // macOS] } - (dispatch_queue_t)methodQueue @@ -160,13 +170,15 @@ - (dispatch_queue_t)methodQueue window.overrideUserInterfaceStyle = userInterfaceStyle; } #else // [macOS - NSAppearance *appearance = nil; - if ([style isEqualToString:@"light"]) { - appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; - } else if ([style isEqualToString:@"dark"]) { - appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]; - } - RCTSharedApplication().appearance = appearance; + RCTExecuteOnMainQueue(^{ + NSAppearance *appearance = nil; + if ([style isEqualToString:@"light"]) { + appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; + } else if ([style isEqualToString:@"dark"]) { + appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]; + } + RCTSharedApplication().appearance = appearance; + }); #endif // macOS] } @@ -177,10 +189,7 @@ - (dispatch_queue_t)methodQueue UITraitCollection *traitCollection = [RCTTraitCollectionProxy sharedInstance].currentTraitCollection; _currentColorScheme = RCTColorSchemePreference(traitCollection); #else // [macOS - __block NSAppearance *appearance = nil; - RCTUnsafeExecuteOnMainQueueSync(^{ - appearance = RCTKeyWindow().appearance; - }); + NSAppearance *appearance = [RCTAppearanceProxy sharedInstance].currentAppearance; _currentColorScheme = RCTColorSchemePreference(appearance); #endif // macOS] } @@ -190,23 +199,19 @@ - (dispatch_queue_t)methodQueue - (void)appearanceChanged:(NSNotification *)notification { +#if !TARGET_OS_OSX // [macOS NSDictionary *userInfo = [notification userInfo]; -#if !TARGET_OS_OSX // [macOS] UITraitCollection *traitCollection = nil; if (userInfo) { traitCollection = userInfo[RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey]; } NSString *newColorScheme = RCTColorSchemePreference(traitCollection); #else // [macOS - NSAppearance *appearance = nil; - if (userInfo) { - appearance = userInfo[RCTUserInterfaceStyleDidChangeNotificationAppearanceKey]; - } - NSString *newColorScheme = RCTColorSchemePreference(appearance); + NSString *newColorScheme = RCTColorSchemePreference([RCTAppearanceProxy sharedInstance].currentAppearance); #endif // macOS] if (![_currentColorScheme isEqualToString:newColorScheme]) { _currentColorScheme = newColorScheme; - [self sendEventWithName:@"appearanceChanged" body:@{@"colorScheme" : newColorScheme}]; + [self sendEventWithName:@"appearanceChanged" body:@{ @"colorScheme" : newColorScheme }]; } } diff --git a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm index 3e1bd9504930e5..ec175bd0f0ea83 100644 --- a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm +++ b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm @@ -64,7 +64,11 @@ - (void)observeValueForKeyPath:(NSString *)keyPath + (BOOL)requiresMainQueueSetup { +#if !TARGET_OS_OSX // [macOS] + return NO; +#else // [macOS return YES; +#endif // macOS] } - (dispatch_queue_t)methodQueue From 55c7b3142cafdc2ba6f6e6ba4170b64a19b39234 Mon Sep 17 00:00:00 2001 From: zhongjiahao <71920909+zhongjiahao-M@users.noreply.github.com> Date: Wed, 15 Oct 2025 07:50:46 +0800 Subject: [PATCH 3/6] fix: Add proper constraints for macOS dev loading view (#2707) - Add contentView initialization and container constraints for macOS - Fix layout issues with the dev loading view on macOS platform - Ensure proper positioning and sizing of the loading message window Fixes #2706 ## Summary: ## Test Plan: --------- Co-authored-by: ZJH <> --- .../React/CoreModules/RCTDevLoadingView.mm | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/CoreModules/RCTDevLoadingView.mm b/packages/react-native/React/CoreModules/RCTDevLoadingView.mm index 3298232b47e981..64276ea56676ad 100644 --- a/packages/react-native/React/CoreModules/RCTDevLoadingView.mm +++ b/packages/react-native/React/CoreModules/RCTDevLoadingView.mm @@ -150,7 +150,7 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo #if !TARGET_OS_OSX // [macOS] [self->_window.rootViewController.view addSubview:self->_container]; #else // [macOS - [self->_window.contentViewController.view addSubview:self->_container]; + [self->_window.contentView addSubview:self->_container]; #endif // macOS] [self->_container addSubview:self->_label]; @@ -175,6 +175,20 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo [self->_label.bottomAnchor constraintEqualToAnchor:self->_container.bottomAnchor constant:-5], ]]; #else // [macOS + // Container constraints + [NSLayoutConstraint activateConstraints:@[ + [self->_container.topAnchor constraintEqualToAnchor:self->_window.contentView.topAnchor], + [self->_container.leadingAnchor constraintEqualToAnchor:self->_window.contentView.leadingAnchor], + [self->_container.trailingAnchor constraintEqualToAnchor:self->_window.contentView.trailingAnchor], + [self->_container.bottomAnchor constraintEqualToAnchor:self->_window.contentView.bottomAnchor], + + // Label constraints + [self->_label.centerXAnchor constraintEqualToAnchor:self->_container.centerXAnchor], + [self->_label.centerYAnchor constraintEqualToAnchor:self->_container.centerYAnchor], + [self->_label.leadingAnchor constraintGreaterThanOrEqualToAnchor:self->_container.leadingAnchor constant:10], + [self->_label.trailingAnchor constraintLessThanOrEqualToAnchor:self->_container.trailingAnchor constant:-10], + ]]; + if (![[RCTKeyWindow() sheets] doesContain:self->_window]) { [RCTKeyWindow() beginSheet:self->_window completionHandler:^(NSModalResponse returnCode) { [self->_window orderOut:self]; From aeb31c35d3036397515134cd1e8f03145065b064 Mon Sep 17 00:00:00 2001 From: Erik Schwiebert Date: Tue, 14 Oct 2025 16:49:07 -0700 Subject: [PATCH 4/6] fix: Suppress deprecation warnings for minimum of OS 26 (#2725) ## Summary: While Microsoft is still far away from dropping support for iOS 18, we want to get a head-start on handling deprecated APIs for the latest OS releases. Silence these deprecated API warnings when making macOS 26, iOS 26, and watchOS 26 our minimums. ## Test Plan: There is no change to actual code. --- .../react-native/Libraries/LinkingIOS/RCTLinkingManager.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm b/packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm index ad75ed482c49f1..f0c943f6aa300a 100644 --- a/packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm +++ b/packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm @@ -155,6 +155,8 @@ - (void)handleOpenURLNotification:(NSNotification *)notification RCT_EXPORT_METHOD(getInitialURL : (RCTPromiseResolveBlock)resolve reject : (__unused RCTPromiseRejectBlock)reject) { NSURL *initialURL = nil; +#pragma clang diagnostic push // [macOS] +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // [macOS] if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) { initialURL = self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]; } else { @@ -164,6 +166,7 @@ - (void)handleOpenURLNotification:(NSNotification *)notification initialURL = ((NSUserActivity *)userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"]).webpageURL; } } +#pragma clang diagnostic pop // [macOS] resolve(RCTNullIfNil(initialURL.absoluteString)); } From 4a965b4b2c7767a747a0cc5a01e6a0428e8f0631 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 14 Nov 2025 14:46:37 +0000 Subject: [PATCH 5/6] version plan --- .nx/version-plans/version-plan-1763131586011.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .nx/version-plans/version-plan-1763131586011.md diff --git a/.nx/version-plans/version-plan-1763131586011.md b/.nx/version-plans/version-plan-1763131586011.md new file mode 100644 index 00000000000000..c1c2c8dc0515a6 --- /dev/null +++ b/.nx/version-plans/version-plan-1763131586011.md @@ -0,0 +1,5 @@ +--- +__default__: patch +--- + +Pick a bunch of bug fixes From 046205cd0c152d12635a2fe0cb6ba04135d51bb8 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:46:00 -0700 Subject: [PATCH 6/6] fix: "Lint PR Title" job cache validation error (#2722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The "Lint PR Title" job in the PR workflow was consistently failing on first run with the following error: > Error: Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved. After a rerun, the job would usually succeed, but this was annoying and required manual intervention. ## Root Cause The `lint-title` job uses the `microsoft-setup-toolchain` action, which has `cache-npm-dependencies: yarn` enabled by default. This causes the underlying `actions/setup-node` action to attempt to cache yarn directories. However, the `lint-title` job never runs `yarn install` - it only executes `npx @rnx-kit/commitlint-lite@2.0.0` to validate the PR title. Since the cache paths don't exist when the job runs for the first time (before dependencies are installed), the cache action fails with a validation error. ## Solution Disabled caching for the `lint-title` job by setting `cache-npm-dependencies: ''` in the setup-toolchain step. This is appropriate because: 1. The job doesn't install any dependencies - it only uses `npx` to run a single package 2. Caching provides no benefit for this job 3. This eliminates the cache path validation error ## Testing - Verified the YAML syntax is valid - The change is minimal and only affects the `lint-title` job - Other jobs that need caching remain unchanged Fixes #2403
Original prompt > > ---- > > *This section details on the original issue you should resolve* > > "Lint PR Title" PR Job always fails > The job always fails with the following error: > > > Error: Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved. > > After a rerun it usually succeeds. But it's quite annoying, and would be nice to fix. > > ## Comments on the Issue (you are @copilot in this section) > > > >
Fixes microsoft/react-native-macos#2721 --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Saadnajmi <6722175+Saadnajmi@users.noreply.github.com> --- .github/workflows/microsoft-pr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/microsoft-pr.yml b/.github/workflows/microsoft-pr.yml index fc077dda043ecf..ef918ae5c07ac1 100644 --- a/.github/workflows/microsoft-pr.yml +++ b/.github/workflows/microsoft-pr.yml @@ -24,6 +24,7 @@ jobs: uses: ./.github/actions/microsoft-setup-toolchain with: node-version: '22' + cache-npm-dependencies: '' # We lint the PR title instead of the commit message to avoid script injection attacks. # Using environment variables prevents potential security vulnerabilities as described in: # https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#example-of-a-script-injection-attack