Tracking document for this fork of React Native macOS 0.87. The published branch is main.
- Record the upstream baseline this branch is derived from.
- Record every local commit and what it changes relative to upstream.
- Provide a conflict map for the next upstream fetch/rebase.
- Mark
vendor/react-native-macos/**as off-limits for the workspace's external lint/format/check tooling.
Direct Meta React Native synchronization is tracked separately in
react-native-upstream.md. Do not put
Meta-only commits in this Microsoft/macOS rebase record.
The workspace contract that consumes this fork is
docs/react-native-macos-architecture.md.
upstream points at the canonical Microsoft repo:
| Remote | URL |
|---|---|
upstream |
https://github.com/microsoft/react-native-macos.git |
origin points at the fork's own remote; its URL is intentionally not listed
here. Publish only origin/main. Follow Microsoft from upstream/0.87-merge
onto main (see Sync procedure). Do not keep aw/* line branches.
CI on this fork is JS only. Source types are Flow (yarn flow-check). TypeScript
is only the generated public .d.ts consumer test (yarn build-types then
yarn test-typescript-legacy with workspace typescript@6). Do not install
TypeScript 7 here: Yarn's builtin compat patch still expects lib/_tsc.js, and
the Go tsc is not this repo's typechecker. Native jobs are not run.
- Upstream remote branch:
upstream/0.87-merge - Baseline commit:
d8c588e4a67cbe9d3cdebb27a8909a93448807cd - Local branch:
main - Last functional tip:
b033ab7f1b(microsoft#3074 TextInput centering, plus687571372dRedBox 2.0,decd178e6eHermes v1 pin,22d8dea9e3color/types, and earlier in the table). Laterdocs(fork)commits sit on top;git rev-parse mainis the branch HEAD. - Merge-base check:
git merge-base main upstream/0.87-mergeshould print the baseline commit above. - Behind
upstream/0.87-merge: 0 commits. Microsoft 0.87-merge is frozen; extra macOS fixes onupstream/mainare sampled by cherry-pick, not rebase.
Functional local patches and the original tracking commit only. Meta
cherry-picks and later docs(fork) bookkeeping live in
react-native-upstream.md or in history, not
here.
| Commit | Date | Message |
|---|---|---|
b033ab7f1b |
2026-08-30 | fix(macos): vertically center single-line TextInput content (#3074) |
22d8dea9e3 |
2026-08-30 | fix(js): tighten float color object parsing and VirtualizedList types |
decd178e6e |
2026-08-30 | feat(hermes): pin Hermes v1 174344e and fix prebuild include paths |
687571372d |
2026-08-30 | feat(macos): port RedBox 2.0 AppKit overlay via RCTUIKit primitives |
285a66c810 |
2026-08-19 | feat(js): fold float color objects in Animated string interpolation |
c5017f4233 |
2026-08-19 | feat(js): emit float color objects from oklch()/oklab() and parse color() |
e06759da6b |
2026-08-19 | fix(macos): guard Meta tintColor copy and unmount UIView assert |
be9f567c4a |
2026-08-19 | fix(macos): alias UIFontDescriptorAttributeName for variable fonts |
b691d36eb5 |
2026-08-19 | fix(macos): treat interactive roles as keyboard-focusable |
45eb9af2b6 |
2026-08-18 | fix(jest): merge duplicate moduleNameMapper keys in jest.config.js |
02b12987df |
2026-08-18 | feat(js): accept {space, r, g, b, a} float color objects on macOS |
ee8ed05893 |
2026-08-18 | feat(js): parse oklch() and oklab() colors in normalize-color |
440b68323c |
2026-08-13 | fix(deps): normalize ReactNativeDependencies macOS framework |
fe8053d19c |
2026-08-13 | fix(prebuild): portable Core XCFramework and macOS Hermes slice |
d1cfd044ac |
2026-08-13 | fix(macos): host multiline TextInput and high-contrast types |
ead226acfc |
2026-08-13 | fix(macos): bind display vsync to NSWindow.displayLink |
903871d965 |
2026-08-01 | docs(fork): add fork.md upstream tracking document |
3184529fff |
2026-08-01 | feat(fork): Hermes 0.87 compiler upgrade and AppHost runtime patches |
dd8f582b78 |
2026-07-21 | feat: prepare ActivityWatch React Native 0.87 |
e2a5a43f17 |
2026-07-21 | fix: make local packages version-aware |
b3f897bec0 |
2026-07-21 | feat: add local macOS package closure |
Keep this table in the same commit that changes fork.md. After a rebase, rewrite
the SHAs here to the new local commits.
main tracks upstream 0.87 with three kinds of local change:
- Local package closure (
b3f897bec0,e2a5a43f17,dd8f582b78): makes the React Native macOS packages resolvable and version-aware inside this pnpm workspace. - Hermes 0.87 / Metro 0.87 runtime patches (
3184529fff): compiler, executor, and JS toolchain divergence from upstream. - macOS host and Core production patches (
ead226acfc…440b68323c): window-bound vsync, TextInput host, portable Core/Hermes XCFrameworks, and ReactNativeDependencies layout that Xcode 27 will load. - CSS Color 4 JS parsing (
ee8ed05893,c5017f4233,285a66c810):oklch()/oklab()/color(srgb|display-p3 ...)support in@react-native/normalize-colors, ahead of upstream TODO T213000437. The conversion math mirrorspackages/design-system/src/appearance/color.tsin the consuming workspace (same Ottosson constants, chroma-shrinking gamut mapping). Sincec5017f4233these normalize to{space, r, g, b, a}float objects (not packed int32s) so Display P3 fidelity survives to the native parsers;foldColorObjectToSrgbInt32packs them back for int32 consumers, and285a66c810wires that fold into Animated string interpolation. Drop when upstream ships equivalent parsing. - Float color object pass-through on macOS (
02b12987df):normalizeColorObjectaccepts{space: 'srgb' | 'display-p3', r, g, b, a}so the design-systemfloatcolor format reaches the existing native float/P3 consumers (RCTConvert -UIColor:, FabricfromRawValueShared) without 8-bit quantization. Upstream has the native halves but no JS producer gate; drop if upstream adds an equivalent object form. - macOS follow-ups to Meta ports (
b691d36eb5,be9f567c4a,e06759da6b): keyboard-focus analog for interactiverole, RCTUIKit alias so variable-font axes compile, and iOS-onlytintColor/RCTUIViewunmount-assert guards. Provenance is inreact-native-upstream.md; these SHAs are local and must survive a Microsoft rebase. - RedBox 2.0 AppKit overlay (FX-124 / FX-125): compile and fatal errors
use the structured RedBox 2.0 sheet on macOS (code frame, ANSI highlight,
native Metro
/hotreload). Adapted from microsoft#3054 (c631f745); do not take #3058. Drop when0.87-mergeormainships an equivalent AppKit V2 controller. Fill the local SHA in the table above when this patch is committed.
Reduce Motion policy lives in the application motion layer, not this fork. Do not look for it here during rebase.
When git rebase --onto upstream/0.87-merge <BASELINE> main hits a file
below, keep the local behavior unless upstream already shipped an equivalent
fix. Prefer re-applying the intent over preserving the exact hunk.
| Commit | Files | Keep our behavior when | Upstream usually changes |
|---|---|---|---|
ead226acfc |
ReactApple/Libraries/RCTUIKit/RCTPlatformDisplayLink.m |
Vsync comes from NSWindow.displayLink (fallback NSScreen.displayLink). Pause is CADisplayLink.paused. Do not restore CVDisplayLinkCreateWithActiveCGDisplays. |
Any CVDisplayLink rewrite, pause/stop threading, timestamp source |
d1cfd044ac |
TextInput.js, TextInput.flow.js, TextInput-macos-test.js, AccessibilityInfo.d.ts |
macOS multiline uses the iOS native host. isHighContrastEnabled stays on the typed surface. |
Platform.OS === 'ios' TextInput branches; new AccessibilityInfo methods |
fe8053d19c |
scripts/ios-prebuild.js, ios-prebuild/build.js, headers-config.js, hermes.js, xcframework.js |
Caller-owned RN_MACOS_CORE_PREBUILD_ROOT; compiler prefix maps; dSYM embed; strip producer rpaths; versioned Headers/Modules; Hermes universal XCFramework includes the macOS slice; macOS view headers in Core. |
setup/build/compose flow, xcodebuild flags, symbol copy, Hermes download |
440b68323c |
ReactNativeDependencies.podspec, replace_dependencies_version.js, scripts/cocoapods/rncore.rb |
Extracted macOS RND framework is versioned (Versions/A + Current links, bundles under Resources). Last-build marker and extract paths are under Pods root. Prebuilt Core HEADER_SEARCH_PATHS stay. |
tar extract script, last-build file, VFS overlay flags |
3184529fff |
Hermes pins, RCTBridgeModule.h, Hermes executor, babel/metro, RCTViewComponentView.mm |
Hermes 0.87 compiler + AppHost runtime patches. | Any overlapping Hermes/Metro bump |
ee8ed05893 |
packages/normalize-color/index.js, __tests__/normalizeColor-test.js |
oklch()/oklab() matchers and OKLab→sRGB conversion with chroma-shrinking gamut mapping. Drop only if upstream ships T213000437 with equivalent CSS Color 4 semantics. |
matcher table, parse helpers, test file |
c5017f4233 |
packages/normalize-color/index.js, index.js.flow, __tests__/normalizeColor-test.js |
oklch()/oklab()/color() emit {space, r, g, b, a} float objects; srgb classification keeps a half-quantization-step tolerance; foldColorObjectToSrgbInt32 is exported. Drop only with an upstream float-object emitting parser. |
matcher table, emission helpers, flow decl |
285a66c810 |
Libraries/Animated/nodes/AnimatedInterpolation.js, __tests__/processColorModernSyntax-test.js |
mapStringToNumericComponents folds float color objects via foldColorObjectToSrgbInt32 before the PlatformColor invariant. |
string interpolation internals |
02b12987df |
Libraries/StyleSheet/PlatformColorValueTypes.macos.js, __tests__/PlatformColorValueTypesMacOS-float-test.js |
_normalizeColorObject keeps the 'space' in color branch (validate space/channels, fill a, pass through). Drop only if upstream ships an equivalent float color object form. |
LocalNativeColorValue shape, branch order in _normalizeColorObject |
45eb9af2b6 |
jest.config.js |
moduleNameMapper stays a single merged object (setup-env alias + out-of-tree react-native/* mappers). Re-merge if upstream adds new mapper entries. |
upstream mapper entries in the first literal |
b691d36eb5 |
RCTViewComponentView.mm |
acceptsFirstResponder honors interactive role / accessibilityRole. needsPanelToBecomeKey stays false. |
iOS Full Keyboard Access / canBecomeFocused ports |
be9f567c4a |
RCTUIKitCompat.h |
UIFontDescriptorAttributeName aliases to NSFontDescriptorAttributeName. |
Apple variable-font / RCTFontUtils |
e06759da6b |
RCTTextInputUtils.mm, RCTViewComponentView.mm |
tintColor copy stays iOS-only; unmount assert uses RCTUIView, not UIView. |
TextInput backing-view swap; unmount index assert |
| FX-124 | RCTUITableView.{h,m}, RCTUIButton.{h,m}, RCTUILabel.m, RCTUIKit.h, RCTRedBox.mm, RCTRedBox2Controller.mm, RCTRedBox2AnsiParser* |
macOS RedBox uses V2 (TARGET_OS_OSX or redBoxV2IOS()). Keep table/button primitives and the AppKit controller. Restore iOS autoRetryTick background guard. Do not restore !TARGET_OS_OSX compile-outs on the V2 controller. |
microsoft#3054 landing on main/0.87-merge; RCTUIKit table/button additions |
Interrupted Core production can leave packages/react-native/.build mapped to
the workspace cache. Remove that leftover before the next Core build. Do not
commit it.
During Core production the workspace overlays these files from installed
upstream React Native (0.87.0-rc.1) and must restore them afterwards:
packages/react-native/React/Base/RCTVersion.mpackages/react-native/ReactCommon/cxxreact/ReactNativeVersion.h
The committed fork versions stay 1000.0.0. A dirty 0.87.0-rc.1 overlay is
producer residue, not a patch. Restore with
git checkout -- those two paths before committing.
Source-Hermes Apple framework scripts passed LINKER:-ld_classic for every
Xcode ≥ 15. Xcode 27 removed the classic linker, so that flag fails the
source-Hermes lane. Gate it to Xcode 15–16:
packages/react-native/sdks/hermes-engine/utils/build-hermes-xcode.shpackages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh
Prebuilt Maven Hermes is unchanged. This is only for the source-Hermes
compatibility lane (FX-69: rebuild V1 at static_h 174344e).
FX-69 V1 pin: sdks/.hermes-source-sha is the 40-hex facebook/hermes
revision (currently 174344e637705bf9bfd036689e3b89ab5f67fd10).
sdks/.hermesv1version stays hermes-v174344e.0.1 for RN version strings.
rn-macos native ensure builds the pinned SHA tarball when it is missing.
The VM is always MinSizeRel + HERMESVM_ALLOW_JIT=1, dual-arch macosx, with
HERMES_ENABLE_DEBUGGER compiled in (RN still gates the inspector on Debug).
native ensure / pack native refuse Maven and overlay destroot into consumer
Pods/hermes-engine after pod install. Advance by changing
sdks/.hermes-source-sha and running native ensure; roll back the same way.
HBC remains 99.
static_h 174344e versioned _HERMES_CTORCONFIG_STRUCT (HERMES_GCCONFIG_VERSION /
HERMES_RUNTIME_CONFIG_VERSION, plus RuntimeConfig.FinalizerThreadRunner).
HermesInstance.cpp / HermesExecutorFactory.cpp must compile against
destroot/include from that tarball, not the 260318099 Public headers:
React-RuntimeHermes HEADER_SEARCH_PATHS and Package.swift searchPaths put
that include first, and both TUs #error if the version macros are missing.
Local tarball HERMES_CLI_PATH is destroot/bin/hermesc (not hermes-compiler npm).
FX-124 RedBox 2.0 AppKit (microsoft#3054)
macOS Debug compile/fatal overlay. Tracking: FX-124 (parent), FX-125 (this adaptation).
Provenance is microsoft#3054 head c631f745 (tree
be278997). Taken:
- RCTUIKit primitives:
RCTUITableView,RCTUIButton,RCTUILabelgetter/setter completions, umbrella imports - RedBox 2.0 AppKit controller + ANSI parser un-gating
RCTRedBox.mmreachability so macOS can instantiate V2
Not taken: #3058 (0.85 RN merge; already an ancestor of main), #3056 V1
table consumer, native tests, RNTester project wiring.
Local deltas on top of #3054:
- macOS always selects RedBox 2.0 (
TARGET_OS_OSX || redBoxV2IOS()). TheredBoxV2IOSdefault staysfalseso iOS is unchanged. autoRetryTickkeeps the iOSUIApplicationStateActiveguard that #3054 dropped for AppKit.- Parser accepts Metro 0.87 oxc
path: messagesyntax errors andUnable to resolve module … from …:frames (not only BabelTransformError/UnableToResolveError in /path: msg (line:col)). Resolve errors expose specifier, importer, and tried paths. Code-frame> N |fills row when Metro omits(line:col). - macOS overlay is an inspector (kind, location, specifier/tried, source),
not a LogBox red banner. Copy emits that structured report. Clicking the
summary or source opens the file via
/open-stack-frame. - AppKit sheet tracks
_sheetPresentedplus the host captured at present time. Dismiss pairspresentViewControllerAsSheet:withdismissViewController:on that host (notRCTKeyWindow(), which is the sheet).endSheetalone leaves the VC presented and the next present asserts. Auto-retry timer is added toNSModalPanelRunLoopMode. RCTUILabelwrapping +RCTUITableViewautomatic row measurement. Custom RedBox cells callprepareForCustomContentso the empty defaulttextLabeldoes not pin the row to ~2px.- Single presenter on macOS: DEV JS errors (fatals, component errors,
console.error, Metro compile) and nativeRCTLog/ bundle-load failures go toRCTRedBox2Controller. Warnings (Warning:/type: warn) stay out of the overlay. LogBox'sRCTLogBoxViewsheet is not shown. Overlay chrome follows Expo LogBox (kind chip, source card, collapsed ignore-listed frames).
RedBox overlay iteration must not go through APP1 native ensure (Core
fingerprint invalidates every selected Pod). Use RNTester-macOS against
this worktree (fork source + pinned Hermes destroot tarball, not Maven):
- Parser: compile
ReactCommon/react/debug/redbox/RedBoxErrorParser.cppwith a tiny driver (seconds). - Overlay GUI:
packages/rn-tester/scripts/run-macos-redbox.sh.podssetsHERMES_ENGINE_TARBALL_PATHfromsdks/.hermes-source-sha,RCT_NEW_ARCH_ENABLED=1,RN_PLATFORMS=macOS, overlays destroot (HERMES_GCCONFIG_VERSION), then Debug-builds. APIs → RedBox 2.0 overlay. Canned fatal messages do not rebuild native. Breakjs/RedBoxCompileProbe.jsand save for a real Metro overlay. - APP1 pack +
native ensure --configuration Debugonly when a batch is ready to land on the packed consumer.
This invalidates React Core (RCTUIKit + CoreModules). After the fork commit: pack the npm closure and rebuild native dependency artifacts before a session can show the new sheet.
24 files, consolidating the fork's runtime changes for the Hermes 0.87 / Metro 0.87 stack:
- Dependency pins —
packages/react-native/package.json,packages/community-cli-plugin/package.json,packages/metro-config/package.json,yarn.lock: hermes-compiler260318099.0.1, metro^0.87, exact Metro dependency bumps. - Hermes toolchain —
packages/react-native/sdks/.hermesversion,.hermesv1version,version.properties,hermes-utils.rb,build-hermesc-xcode.sh,react-native-xcode.sh: compiler flags (hermes-canary, async generators). - Native runtime —
RCTBridgeModule.h(dispatch_queue_t OS_OBJECT_USE_OBJC compat),HermesExecutorFactory.cpp/HermesInstance.cpp(executor/instance fixes),ReactNativeFeatureFlagsDefaults.h(enableCppPropsIteratorSetter,preventShadowTreeCommitExhaustion),RCTViewComponentView.mm.RCTPlatformDisplayLink.min that commit is the older CVDisplayLink wrapper;ead226acfcreplaced it. - JS toolchain —
react-native-babel-preset(unstable_disableTypeScriptTransform),react-native-babel-transformer(babel.config.cjs fallback),packages/polyfills/console.js(spread fix),asset-utils/assets-registry(density buckets),debugger-shell(environment check),generateSchemaInfos.js(codegen separator). - Release workflow —
.github/workflows/create-draft-release.yml(Hermes version hint).
upstream/0.87-merge is the merge baseline and is fully in sync. New macOS
fixes after that freeze land on Microsoft main. Do not rebase this fork onto
Microsoft main. Cherry-pick product commits; skip docsite/npm publishing.
| Upstream | Status | Notes |
|---|---|---|
08ecdfded7 #3074 TextInput vertical center |
taken b033ab7f1b |
Applied clean on 0.87 TextInput |
78f5b04387 #3023 Dev loading view clipping |
deferred | Valuable; RCTDevLoadingView.mm does not apply. Hand-port later |
efb2eb5010 #3056 RCTUITableView + RedBox V1/V2 |
skip | Overlaps FX-124 RCTUITableView / RedBox 2.0 |
a93f65c117 #3024 visionOS platform declare |
skip | Not shipping visionOS |
| docsite / nx / npm trusted publishing | skip | Not runtime |
cd vendor/react-native-macos
git fetch upstream
# How many upstream commits does main not have yet?
# 0 means fully in sync — stop here, nothing to do.
git rev-list --count <BASELINE>..upstream/0.87-merge
# Sanity: merge-base must still be <BASELINE>
git merge-base main upstream/0.87-mergegit rebase --onto upstream/0.87-merge <BASELINE> mainResolve conflicts using the table above. After each conflict, confirm the pinned behavior still holds (window-bound display link, multiline TextInput, portable Core, versioned RND).
Verify every local commit survived:
git log --oneline <new-merge-base>..main # expect the same local commits as before- Update
<BASELINE>and the local-commit table above to the new values. - Keep the conflict map aligned with the surviving commits.
- If the rebase rewrote the fork.md commit itself, amend it so this document stays a single clean commit on top of the patch set.
The parent repo's submodule pointer must reference a commit that already exists on the fork's remote — push the submodule branch before the parent.
# a) Push the submodule branch to the fork's own remote
cd vendor/react-native-macos
git push origin main
# b) Record the new submodule HEAD in the parent repo, then push the parent
cd <workspace-root>
git add vendor/react-native-macos
git commit -m "chore(vendor): bump react-native-macos submodule to <new-head>"
git push <parent-remote> <parent-branch>cd vendor/react-native-macos
git status --short # clean
git log --oneline <new-merge-base>..main # all local commits present
cd <workspace-root>
git status --short -- vendor/react-native-macos # no pending submodule change
git ls-tree HEAD vendor/react-native-macos # pointer matches submodule HEAD<BASELINE> and <new-merge-base> are placeholders for the values recorded
in the Upstream baseline section and the post-rebase merge-base respectively.
vendor/react-native-macos must never be linted, formatted or checked by
workspace-external tooling. Exclusions live in:
.oxfmtrc.json—ignorePatterns: ["vendor/react-native-macos/**"]oxlint.config.ts—ignorePatterns: ["vendor/react-native-macos/**"]sgconfig.yml—ignore: ["vendor/react-native-macos/**"]