Skip to content

[verification] synchronous transform overlay + RNTester flag override - #1

Draft
tomekzaw wants to merge 151 commits into
mainfrom
synchronous-transform-overlay-verification
Draft

tomekzaw wants to merge 151 commits into
mainfrom
synchronous-transform-overlay-verification

Conversation

@tomekzaw

Copy link
Copy Markdown
Owner

Verification branch for react#59392: the same change plus an RNTester override enabling enableSynchronousTransformOverlay. Not meant to be merged; hosts the device evidence attached below.

mozzius and others added 30 commits October 16, 2026 03:49
)

Summary:
`YogaLayoutableShadowNode::updateYogaChildren` marks a cloned node dirty if any of its children is dirty. For a `display: none` node that's never needed: Yoga lays it out with zero size whatever its children are.

It's also actively costly. When Yoga lays out a hidden node's parent, `zeroOutLayoutRecursively` resets the hidden subtree and the hidden node is marked clean, but its descendants keep their dirty flags. Fabric's `layout()` doesn't recurse into `display: none` nodes either. So a child that was created or changed while its parent was hidden stays dirty until the parent is shown. Every later commit that clones the hidden node sees that child, dirties the hidden node and every ancestor up to the root, and `RootShadowNode::layoutIfNeeded` re-runs `layoutTree`, including `roundLayoutResultsToPixelGrid` over every node in the surface.

React commits lay out from the root anyway, so this costs the commits that don't go through React: `setNativeProps`, JS-driven `Animated` (which uses `setNativeProps` on Fabric), native state updates and animation libraries that commit to the ShadowTree. The clearest case in core is a JS-driven `Animated.loop` on a view inside a `display: 'none'` container, which re-lays out the whole surface on every frame. Content hidden by `<Activity mode="hidden">` ends up in the same state, since the renderer hides it by cloning the top-level host nodes with `display: 'none'`.

This change skips the children comparison for `display: none` nodes. It keeps the node's own dirty check and the child count check. Showing the node again changes its style, which dirties it in `updateYogaProps`, and the children that changed while it was hidden are laid out then.

Fixes react#59112

## Changelog:

[GENERAL] [FIXED] - Commits under a `display: 'none'` view no longer re-run layout over the whole surface when something under it was created or changed while it was hidden

Pull Request resolved: react#59113

Test Plan:
**Unit tests:** two new `YogaDirtyFlagTest` cases in `ViewTest.cpp`:
- `dirtyChildOfHiddenNodeMustNotDirtyIt`: hides the inner node, changes a child's width while it's hidden, then changes the child's opacity. Neither commit may need layout.
- `showingHiddenNodeMustDirtyItAndLayOutItsChildren`: hides the inner node, changes a child's width while hidden, then shows the node again. That commit must need layout, and the child must come out 42 wide.

There's no OSS runner for these, so I compiled the `YogaDirtyFlagTest` cases from this branch into the repro app, against RN 0.87.1 built from source, with a minimal stand-in for the gtest macros. With this change, all 10 cases pass. Without it, `dirtyChildOfHiddenNodeMustNotDirtyIt` fails both of its `EXPECT_FALSE` checks and the other 9 pass. I'm still relying on CI to run them properly.

**End to end:** with the repro from react#59112 (https://github.com/mozzius/hidden-subtree-relayout-repro). Its `fixed` branch applies this exact change to RN 0.87.1 built from source. iOS 26.5 simulator, Release, 1000 rows on screen; per-commit time on the JS thread, median / p90 over 9 runs of 120 commits:

| | stock | with this change |
|---|---:|---:|
| `setNativeProps` on a view created inside a `display: 'none'` parent | 1.06 / 2.02 ms | 0.15 / 0.21 ms |
| … after React re-rendered a sibling while the parent was hidden | 1.01 / 2.06 ms | 0.16 / 0.20 ms |
| … inside a hidden `<Activity>`, after a re-render while hidden | 1.18 / 2.03 ms | 0.16 / 0.21 ms |
| JS-driven `Animated.loop` on a view inside a `display: 'none'` parent | 1.09 / 1.95 ms | 0.13 / 0.17 ms |
| control: `setNativeProps` on a view in a visible parent | 0.19 / 0.23 ms | 0.19 / 0.24 ms |

Counting with lldb, stock ran `YogaLayoutableShadowNode::layoutTree` for every commit in the first four rows, and this change runs it for none. A `yoga::Node::setDirty` breakpoint on stock shows each of those commits starting with the hidden node going from clean to dirty in `updateYogaChildren ← ShadowNode::cloneTree ← UIManager::setNativeProps_DEPRECATED`.

**Showing hidden views again:** the repro's `-scenario unhide` hides three containers, re-renders a label in each and changes each child 30 times with `setNativeProps` while hidden. It then shows them again with `setNativeProps({style: {display: 'flex'}})`, with a React state change, and by switching an `<Activity>` back to visible. All three lay out the same as a never-hidden reference, with and without this change.

**Context:** we found this in the Bluesky app, where a Reanimated `useAnimatedStyle` on a hidden spinner in the profile header committed on every scroll frame, and each commit re-ran layout for the whole screen. We worked around it in app code (bluesky-social/social-app#12000). It's a sibling of react#59100, fixed in react#59101: the same symptom with a different cause, and the two changes are independent.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed By: christophpurrer

Differential Revision: D125454304

Pulled By: javache

fbshipit-source-id: 1952fa3d9798e1fb5d2405fbb4c47a346008834b
Summary:
`RCTInspectorNetworkReporter.h` declares `+reportRequestFailed:cancelled:` with a `BOOL` parameter, but the implementation takes `bool`. They're the same type on arm64, but on x86_64 macOS and x86_64 Mac Catalyst `BOOL` is `signed char`, so the definition doesn't match its declaration and clang warns with `-Wmismatched-parameter-types`.

This makes the implementation take `BOOL`, like the header.

## Changelog:

[IOS] [FIXED] - Fix the `cancelled` parameter type of `RCTInspectorNetworkReporter`'s `reportRequestFailed:cancelled:` to match its declaration

Pull Request resolved: react#59119

Test Plan: Made the same change in React Native macOS 0.81, which has the same mismatch, and built it for x86_64 macOS with warnings as errors: `-Wmismatched-parameter-types` no longer fires.

Reviewed By: cipolleschi

Differential Revision: D125650488

Pulled By: cortinico

fbshipit-source-id: 176dce93ec06768eda4c57b3898c5f1e6c059411
Summary:
`PreparePrefabHeadersTask` copies ReactAndroid's public headers into `build/prefab-headers`, the prefab package an app's `appmodules` and every library's C++ compile against, while `libreactnative.so` compiles against `ReactCommon` directly. The task's only input is the list of directory paths:

```kotlin
  get:Input abstract val input: ListProperty<PrefabPreprocessingEntry>
```

so after a header under those directories changes, Gradle finds the task up to date and the prefab package keeps the old header. Everything built against the prefab package then disagrees with `libreactnative.so` about that header: **a struct that gained a field is allocated at its old size by a library and used at its new size by React Native.**

A header changes in place whenever React Native is built from source and patched with `patch-package`, or while `ReactCommon` is edited during development. In an app built from source, a patch that added two fields to Yoga's `CachedMeasurement` left `prefab-headers/reactnative/yoga/node/CachedMeasurement.h`, `LayoutResults.h`, `Node.h` and `style/Style.h` at their old content across two builds, and the app crashed at launch with `SIGSEGV` under `YogaLayoutableShadowNode::replaceChild` ← `cloneChildInPlace` ← `configureYogaTree`.

The task now declares the headers it copies as `InputFiles`, read through the same filtered file trees the copy uses, so the declared inputs and the copied headers cannot disagree. Each library's folder is deleted before it is copied into, so a header removed from `ReactCommon` leaves no copy behind once the task runs again.

## Changelog:

[ANDROID] [FIXED] - Re-copy the prefab headers when a header under ReactCommon changes

Pull Request resolved: react#59127

Test Plan:
- `PreparePrefabHeadersTaskTest` gains `preparePrefabHeadersTask_declaresTheHeadersItCopiesAsInputs` (the task's input files are exactly the copied header, not the `.cpp` beside it) and `preparePrefabHeadersTask_withARemovedHeader_removesItsCopy`. On `main` both fail: the input files are `[]`, and `removed.h` is still in the output after the second run. With the change, all 10 cases pass.
- `./gradlew build -PenableWarningsAsErrors=true` in `packages/gradle-plugin`, in `reactnativecommunity/react-native-android:latest`: 238 tests, 0 failures. `ktfmt` 0.59.0 (`--dry-run --set-exit-if-changed`) is clean on both files.
- In the app above, with the change applied to its `react-native/gradle-plugin`: the next build ran `preparePrefab` instead of finding it up to date, every Yoga header in `prefab-headers` and in the debug prefab package matched `ReactCommon`, every library recompiled against them, and the app launched without the crash.

Reviewed By: cipolleschi

Differential Revision: D125650684

Pulled By: cortinico

fbshipit-source-id: ba1c7815ead1e27ea47fac6b726aa8e7fa2ec113
Summary:
Two workflows stopped working after the move to `react/react-native`, because their regexes only allow `github.com/facebook/react-native`:

- **Merged pull requests no longer get the release FAQ comment.** Before the move, `react-native-bot` posted "This pull request was successfully merged by author in **sha**" with links to "When will my fix make it into a release?" and "How to file a pick request?". The last one is on react#56769 (June 9). Pull requests merged on October 15 (react#59122, react#59050, react#59016) only have the `meta-codesync` comment.
- **Issues that link a pull request as the reproducer still get `Needs: Repro`.** Examples: react#58970, react#58495, react#58445, react#55708. The bot's message for `Needs: Repro` asks for a pull request that edits `RNTesterPlayground.js`, but the bot does not accept a link to one.

### Why

1. `.github/workflows/close-pr.yml` looks for `Pull Request resolved: https://github.com/facebook/react-native/pull/N` in the commit message. Commits now say `https://github.com/react/react-native/pull/N`, so `getClosedPrIfExists` returns `undefined` and the script stops before `createComment`. react#57167 changed the `if:` line in this file to `react/react-native`, but not the regex. The regex is written with escaped slashes (`facebook\/react-native`).
2. `.github/workflow-scripts/checkForReproducer.js` builds the pull request pattern in a template literal with single backslashes. In a template literal, `\d` is just `d`, so the pattern is `https?://github.com/facebook/react-native/pull/d+/?`. It did not match real links even before the move (the line was added in react#50848). The other two patterns in the same function use `\\` and work.

In the whole repo, `facebook\/react-native` appears only in these two lines.

### Fix

Both regexes now accept `facebook` and `react`. People still write links with `facebook`: the four issues above all do. In `checkForReproducer.js`, the pattern now uses `\\` like the other two patterns. `(?:...)` does not capture, so the group numbers in `close-pr.yml` do not change.

The new test file is `checkForReproducer-links-test.js`, so it does not conflict with `checkForReproducer-test.js` from react#58429. That PR fixes a different problem in the same script: the bot treats its own `Needs: Repro` label as a maintainer change. Because of that, an issue that already has the label keeps it after an edit, also with this PR. New issues with a pull request link do not get the label.

## Changelog:

[INTERNAL] [FIXED] - Fix pull request link regexes in the close-pr and check-for-reproducer workflows after the move to react/react-native

Pull Request resolved: react#59128

Test Plan:
Windows 11, Node 22.17.1, Yarn 1.22.22.

New Jest test for `checkForReproducer` with 4 cases: a link to a pull request in `react/react-native`, the same link with `facebook/react-native`, a pull request in another repository, and no link.

```
yarn test .github/workflow-scripts
```

| Code | Result |
| --- | --- |
| `main` (e04aaf6) | 2 of the 4 new tests fail: both pull request links are not accepted |
| this PR | 11 suites, 72 tests pass |

Each part of the fix is needed:

- `facebook|react` with single backslashes: both link tests fail.
- Double backslashes with only `facebook`: the `react` link test fails.

`close-pr.yml` runs an inline script, so it has no Jest test. I took `prClosingRegex` from the file and ran `getClosedPrIfExists` (copied from the file) on real commit messages from `main`:

| Commit message | `main` | this PR |
| --- | --- | --- |
| react#59016: `Pull Request resolved: https://github.com/react/react-native/pull/59016` | `undefined` | `59016` |
| react#56769: `Pull Request resolved: https://github.com/facebook/react-native/pull/56769` | `56769` | `56769` |
| "Update React Native DevTools binaries", no pull request link | `undefined` | `undefined` |

`npx prettier --check` passes for the 3 files.

Not covered:

- I did not run the workflows. They run on push to `main` and on issue events in this repository.
- `yarn lint` does not check these files, because ESLint skips the `.github` folder.

Reviewed By: cipolleschi

Differential Revision: D125650549

Pulled By: cortinico

fbshipit-source-id: c1d1b49381730f755d13e3c822ac1df33a4808b8
…ch) (react#58829)

Summary:
Fixes react#55367.

On the New Architecture, `<TextInput dataDetectorTypes="all" />` has no effect on iOS, while individual types (`link`, `phoneNumber`, …) work.

`RCTUITextViewDataDetectorTypesFromStringVector` reads the mapped `NSNumber` with `unsignedIntValue`. `UIDataDetectorTypeAll` is `NSUIntegerMax`, so it gets truncated to `0x00000000ffffffff`, which is not equal to `UIDataDetectorTypeAll` and UIKit does not treat it as "all".

This switches to `unsignedIntegerValue`, matching the width of `UIDataDetectorTypes` (`NSUInteger`). The legacy `RCTConvert` path already reads the same mapping with `unsignedLongLongValue`, which is why the Old Architecture is unaffected.

## Changelog:

[IOS] [FIXED] - Fix `TextInput` `dataDetectorTypes="all"` having no effect on the New Architecture

Pull Request resolved: react#58829

Test Plan:
I compiled `RCTUITextViewDataDetectorTypesFromStringVector`, copied verbatim from the file before and after this change, into a small harness against the iOS SDK (stubbing only `RCTNSStringFromString`), and ran it on an iOS 26 simulator:

```
== before
  all                    -> 0x00000000ffffffff
  link                   -> 0x0000000000000002
  link+phoneNumber       -> 0x0000000000000003
  lookupSuggestion       -> 0x0000000000000040
== after
  all                    -> 0xffffffffffffffff  (== UIDataDetectorTypeAll)
  link                   -> 0x0000000000000002
  link+phoneNumber       -> 0x0000000000000003
  lookupSuggestion       -> 0x0000000000000040
```

`all` now maps to exactly `UIDataDetectorTypeAll`, and the other values are unchanged.

I did not run this end-to-end in RNTester. The issue reporter and another user ([comment](react#55367)) confirmed that this same patch, applied via patch-package, fixes link detection on 0.81 and 0.86.2.

Reviewed By: javache

Differential Revision: D125664939

Pulled By: cortinico

fbshipit-source-id: a7eaf87de91216345517d517bf49a75ec8cbbb70
react#58824)

Summary:
`react-native/eslint-plugin`'s `no-deep-imports` rule keeps a hard-coded allowlist of the subpaths that `react-native` intentionally exposes (`isSecondaryEntryPoint`). `react-native/package.json` is exposed by the `"exports"` map in `packages/react-native/package.json` (`"./package.json": "./package.json"`) but is missing from that list, so importing it is reported as a deprecated deep import:

```
'react-native/package.json' React Native deep imports are deprecated. Please use the top level import instead.
```

Reading `react-native/package.json` is the standard way for tooling and libraries to detect the installed React Native version, and there is no top level equivalent to migrate to, so the only way out today is an `eslint-disable`.

This adds `react-native/package.json` to `isSecondaryEntryPoint`, which brings the allowlist back in line with every non-wildcard entry of the `"exports"` map (`.`, `./asset-registry`, `./react-private-interface`, `./setup-env`, `./unstable-internals-do-not-use`, `./package.json`). `Libraries/*` and `src/*` deep imports are still reported exactly as before.

## Changelog:

[GENERAL] [FIXED] - Stop `no-deep-imports` from reporting `react-native/package.json`, which is a supported entry point

Pull Request resolved: react#58824

Test Plan:
Added `import` and `require` cases for `react-native/package.json` to the `valid` list in `no-deep-imports-test.js`.

Before the fix (new tests only):

```
$ yarn jest packages/eslint-plugin-react-native/__tests__/no-deep-imports-test.js
  ✕ import {version} from 'react-native/package.json';
  ✕ require('react-native/package.json');

  ● ../no-deep-imports › valid › import {version} from 'react-native/package.json';
        message: "'react-native/package.json' React Native deep imports are deprecated. Please use the top level import instead.",
```

After the fix:

```
$ yarn jest packages/eslint-plugin-react-native
PASS packages/eslint-plugin-react-native/__tests__/no-deep-imports-test.js
PASS packages/eslint-plugin-react-native/__tests__/platform-colors-test.js

Test Suites: 2 passed, 2 total
Tests:       42 passed, 42 total
```

`eslint` is clean on the changed files.

Reviewed By: christophpurrer

Differential Revision: D125659549

Pulled By: cortinico

fbshipit-source-id: c13b3895bbfb480166f548b704838e92ec3792c5
Summary:
`Style::operator==` compares every style field except `boxSizing_`, so two
styles that only differ in box sizing compare equal. `YGNodeCopyStyle` then
skips the copy and leaves the destination node with the old box sizing, and
callers that compare styles to decide whether a node needs relayout miss the
change.

Compare `boxSizing_` together with the other enum fields.

Reviewed By: cortinico

Differential Revision: D125427467

fbshipit-source-id: c7d442a200931d6b998b2e6d58b83a024c03766c
Summary:
Pull Request resolved: react#59137

Update the Fantom regression test to reflect Yoga style equality including `boxSizing`. Assert that native content-box updates invalidate layout on the first rerender, and remove the stale follow-up rerender.

Changelog: [Internal]

Differential Revision: D125660351

fbshipit-source-id: 3d0991ad5aa3e8fa9bdf539405db77b6b7415589
Summary:
With `enableViewRecycling` on, a recycled view keeps the accessibility state its previous props applied. `BaseViewManager.prepareToRecycleView` clears the accessibility tags, but not the content description, `importantForAccessibility`, the selected state, the live region, `screenReaderFocusable`, the `role` and collection tags, or the actions its accessibility delegate cached. The next view of that type is built from it and applies only the props it declares, so TalkBack announces a label and a role the view never had, the delegate sends TalkBack's click to an action only the previous props declared, and a view last used inside an `aria-hidden` subtree hides its new content from accessibility services. With `experimental_accessibilityOrder` on as well, a recycled `ReactViewGroup` keeps its previous order list, and a recycled child keeps the parent and focusability tags its former parent's order put on it.

This resets that state in `prepareToRecycleView` to what a new view has, clears the order list in `ReactViewGroup.initView`, and restores `TextView`'s default importance in `ReactTextView.recycleView`. The delegate stays, since `ReactScrollView`, `ReactHorizontalScrollView` and `ReactNestedScrollView` install theirs in their constructors: a new `ReactAccessibilityDelegate.prepareToRecycle()` clears the actions it cached, and `accessibilityLabelledBy`, read only in the call that sets it, is a local there. It sits beside the `enabled` reset react#58799 added.

## Changelog:

[ANDROID] [FIXED] - Recycled views no longer keep the accessibility state of their previous props

Pull Request resolved: react#59047

Test Plan:
New cases in `BaseViewManagerTest` and `ReactTextViewPropertyTest` recycle a view through `onDropViewInstance` and `createView` and compare it with a new one. Before the change six fail: content description `"Dismiss sheet"`, `role` tag `LIST`, a click dispatched to the previous props' `activate` action instead of performed, importance `4` where a new text view has `1`, the order list `["first"]`, and the former parent in `accessibility_order_parent`. Another case recycles each scroll view and checks that it keeps the `ReactScrollViewAccessibilityDelegate` its constructor installed. With the change, `./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest -PenableWarningsAsErrors=true` passes (727 tests) and the release sources compile with the same flag; the RNTester debug build and the rest of `build_android` were not run.

RNTester with view recycling on, on an Android 16 emulator with TalkBack, [before and after](react#59047 (comment)): plain rows mounted after labelled rows were removed are read as a selected "Dismiss sheet" button without the change, and as their text with it.

On a Galaxy S23 (Android 14) app with view recycling on, plain rows mounted after rows carrying `aria-label`, `role`, `aria-selected`, `aria-disabled` and `aria-hidden` were removed reported the removed rows' label as their content description before the change. Built from source with it, the same rows report no label, role, selected or disabled state, and their text is in the accessibility tree.

Both device runs used the first revision of this change, which cleared the delegate on recycle instead of resetting it.

Reviewed By: Abbondanzo

Differential Revision: D125427268

Pulled By: javache

fbshipit-source-id: 1dad635ef6f1e5cef351d355865edc567f7bce58
Summary:
Pull Request resolved: react#59121

X-link: react/metro#2073

Deploy `flow-bin` 0.336.0 for React Native, Metro, and Relay, updating OSS Flow version constraints, dependency lockfiles, and the shared Yarn offline mirror.

Changelog: [Internal]

Reviewed By: panagosg7, noschiff

Differential Revision: D125498569

fbshipit-source-id: 35fa6fa99c5ffab1922dbad9198ed7137505adfe
Summary:
Pull Request resolved: react#59147

`cloneTypedArray` passes `value.buffer` to `cloneArrayBuffer(value: ArrayBuffer)`. With TypeScript's ES2024 `ArrayBuffer` definitions, `$TypedArray` (meaning `$TypedArray<ArrayBufferLike>`) has an `ArrayBufferLike` buffer, which is no longer an `ArrayBuffer`. Hermes, React Native's default engine, has no `SharedArrayBuffer`, and `cloneArrayBuffer` already treats its input and its `slice(0)` copy as an `ArrayBuffer`. This narrows `isTypedArray`'s refinement and `cloneTypedArray`'s parameter to `$TypedArray<ArrayBuffer>`. It is a type-only change.

Changelog: [Internal]

Differential Revision: D125721761

fbshipit-source-id: 77ccc4b89e9018c3d6656ec7bfc4cb6874f48e86
Summary:
Pull Request resolved: react#59151

Upgrade the developer type-stripping dependency to `flow-remove-types@2.336.0`, with matching `flow-parser` and `flow-estree@0.336.0` dependencies. Regenerate the package locks and validate the upgraded tooling against its existing consumers.

Changelog: [Internal]

Reviewed By: SamChou19815

Differential Revision: D125751591

fbshipit-source-id: 28c2eb20cfe62e34706a7e6ae61a4684f77366eb
Summary:
A successful Release build followed by adding a new CocoaPods dependency can cause the next Debug build to fail with undefined Hermes debugger symbols, including `CDPDebugAPI::create` and `CDPAgent::create`.

The Release build installs the Release Hermes binary and records `Release` in `Pods/hermes-engine/.last_build_configuration`. When a subsequent `pod install` installs another pod, CocoaPods cleans the existing Hermes pod. The `Pre-built` subspec does not preserve the configuration marker, so the marker is removed while the Release framework remains on disk.

On the next Debug build, `replace_hermes_version.js` receives the correct `Debug` configuration but assumes that a missing marker means Hermes is already Debug. It skips replacement, leaving Debug React Native code to link against a Release Hermes binary that does not export the required debugger symbols.

This change:

- Adds `.last_build_configuration` to the `Pre-built` subspec's `preserve_paths`, so installing another pod does not discard the configuration recorded for the existing Hermes binary.
- Removes the assumption that a missing marker implies Debug. If the marker is absent, the replacement script extracts the requested configuration and writes its marker.
- Adds regression coverage for missing markers, configuration switches, fresh installations, and the existing matching-configuration fast path.

A matching marker still skips extraction. A fresh Debug installation without a marker now performs one initial extraction; subsequent builds with the same recorded configuration continue to skip it.

## Changelog:

[IOS] [FIXED] - Fix Hermes Debug link failures after installing another pod following a Release build.

Pull Request resolved: react#59159

Test Plan:
### Environment

The failure was reproduced in RNTester on the unmodified main baseline `27e77216ef2557ccbf31a01067a44dc1dd2ec846`, using:

- Xcode 27.0 (`27A266a`), iPhoneOS SDK, arm64.
- Ruby 3.3.11, Bundler 2.5.22, CocoaPods 1.15.2.
- Node.js 22.22.3, prebuilt Hermes `260318099.0.2`, and `hermes-compiler` `260318099.0.4` selected by the repository's installation hook.
- Default RNTester CocoaPods integration, with `USE_FRAMEWORKS` unset.

Both the unmodified and patched versions were tested through complete Debug → Release → add a pod → Debug sequences. Each sequence used the same workspace and DerivedData directory throughout; the patched sequence started with fresh Pods and a separate empty DerivedData directory. JavaScript bundling was enabled. Signing was disabled with `CODE_SIGNING_ALLOWED=NO`; the app was not launched on a device.

### Reproduction on the unmodified baseline

1. Start with a fresh checkout of the baseline above, without Masonry in RNTester's Podfile. Prepare the repository and RNTester dependencies from the repository root:

   ```bash
   yarn install
   yarn --cwd packages/react-native-codegen build
   cd packages/rn-tester
   bundle install
   bundle exec pod update hermes-engine --no-repo-update
   ```

   On this specific baseline, the checked-in RNTester lockfile references an older Hermes version than the local podspec. The final command aligns that dependency once, matching the repository's RNTester CI setup. This is environment preparation; the dependency installation that triggers the bug below uses ordinary `pod install`.

2. Define the build command in the same terminal. The directory is inside RNTester and is shared by Debug and Release:

   ```bash
   REPRO_DIR="$PWD/build/hermes-marker-repro-$(date +%Y%m%d-%H%M%S)"
   mkdir -p "$REPRO_DIR"

   build_rntester() {
     xcodebuild \
       -workspace RNTesterPods.xcworkspace \
       -scheme RNTester \
       -configuration "$1" \
       -sdk iphoneos \
       -destination 'generic/platform=iOS' \
       -derivedDataPath "$REPRO_DIR/DerivedData" \
       -jobs 6 \
       CODE_SIGNING_ALLOWED=NO \
       build
   }
   ```

3. Build Debug to establish a working baseline:

   ```bash
   build_rntester Debug
   ```

   Result: `BUILD SUCCEEDED`.

4. Build Release to simulate a release build:

   ```bash
   build_rntester Release
   ```

   Result: `BUILD SUCCEEDED`. The normal Xcode build phase switches Hermes to Release and writes the `Release` marker.

5. Add a new third-party dependency inside the existing RNTester target in `packages/rn-tester/Podfile`:

   ```ruby
   target 'RNTester' do
     pods('RNTester')
     pod 'Masonry', '1.1.0'
   end
   ```

   Masonry is a published pod used only to trigger a normal new-pod installation in this reproduction. You can replace it with any other compatible pod that is not already installed in the project.

6. Install the dependency, retaining the existing Pods and DerivedData directories:

   ```bash
   bundle exec pod install
   ```

   Result: installation succeeds and reports `Installing Masonry (1.1.0)`. The Hermes configuration marker is now absent, but the installed arm64 Hermes binary still matches the Release archive.

7. Build Debug again:

   ```bash
   build_rntester Debug
   ```

   Result: `BUILD FAILED`, with `xcodebuild` exiting with code 65. The failing link is RNTester's `RNTester.debug.dylib`; unresolved references originate from `libReact-hermes.a`.

   The Hermes build phase reports:

   ```text
   No previous build detected, but Debug Configuration. No need to replace Hermes engine
   ```

   The linker reports undefined arm64 symbols, including:

   ```text

Reviewed By: fabriziocucci

Differential Revision: D125847848

Pulled By: cipolleschi

fbshipit-source-id: 7a9cc0e9fbadfcc67c62a4f734b459acc303bd35
…rarchy (react#59150)

Summary:
Pull Request resolved: react#59150

## Summary

`PreparedLayoutTextView` currently derives its exposed text from `Layout.getText()`. When Android ellipsizes a `StaticLayout`, that value is backed by `TextUtils.Ellipsizer`, whose string representation contains the rendered ellipsis instead of the complete source text. Accessibility services and view-hierarchy consumers can therefore receive truncated content.

Preserve the original `CharSequence` in `PreparedLayout` and expose it from `PreparedLayoutTextView.getText()`. Drawing, hit testing, span handling, and selection continue to use the rendered layout text, so visual ellipsizing behaviour is unchanged.

Changelog:
[Android][Fixed] - Preserve complete text for accessibility when prepared text layouts are ellipsized

Reviewed By: derekmeta

Differential Revision: D125204355

fbshipit-source-id: 165de41f7feda9b269837e5a6bb98b41a5f1f72c
Summary:
The latest **Hermes** engine work well with destructuring syntax. I decided to checked perf. on `RN 0.88.x` + Samsung S10 and based on the result I don't see the reason to keep transforming destructuring syntax:

<img width="1120" height="640" alt="Screenshot 2026-10-09 at 12 07 16" src="https://github.com/user-attachments/assets/27896728-e57e-4d9c-94e8-423da1f27bb4" />
<img width="1463" height="584" alt="Screenshot 2026-10-09 at 12 07 33" src="https://github.com/user-attachments/assets/b4115974-3dcb-492c-811b-c1790ce3e576" />
<img width="1404" height="562" alt="Screenshot 2026-10-09 at 12 07 49" src="https://github.com/user-attachments/assets/2d004860-8ef1-4551-b518-00e03002fd7c" />

## Changelog:

[GENERAL] [BREAKING] - Turn off `babel/plugin-transform-destructuring` plugin

Pull Request resolved: react#58965

Test Plan:
1. Create a RN 0.88.x
2. Update `babel.config.js` to toggle `unstable_preserveDestructuring: true/false`
3. Apply the next code:

<details>
<summary>Click to view code</summary>

```tsx
// App.tsx
import React, {useCallback, useState} from 'react';
import {Pressable, ScrollView, StatusBar, StyleSheet, Text, View} from 'react-native';
import {SafeAreaProvider, SafeAreaView} from 'react-native-safe-area-context';

import {BENCH_NAMES, getSink, runBench, type BenchResult} from './destructuringBench';

const ITERATION_OPTIONS = [100_000, 1_000_000, 10_000_000];

function ResultRow({result}: {result: BenchResult}) {
  return (
    <View style={styles.card}>
      <Text style={styles.cardTitle}>{result.name}</Text>
      <Text style={styles.cardMeta}>
        {result.ms.toFixed(2)} ms · {Math.round(result.opsPerMs).toLocaleString()} ops/ms
      </Text>
    </View>
  );
}

function App() {
  const [iterations, setIterations] = useState(ITERATION_OPTIONS[1]);
  const [results, setResults] = useState<BenchResult[]>([]);
  const [running, setRunning] = useState(false);
  const [totalMs, setTotalMs] = useState<number | null>(null);

  const runAll = useCallback(() => {
    setRunning(true);
    setResults([]);
    setTotalMs(null);

    const collected: BenchResult[] = [];
    const runNext = (index: number) => {
      if (index >= BENCH_NAMES.length) {
        const total = collected.reduce((sum, r) => sum + r.ms, 0);
        console.log(
          `[destructuring-bench] iterations=${iterations} total=${total.toFixed(2)}ms sink=${getSink()}`,
        );
        console.log(
          collected.map(r => `${r.ms.toFixed(2).padStart(10)} ms  ${r.name}`).join('\n'),
        );
        setTotalMs(total);
        setRunning(false);
        return;
      }
      collected.push(runBench(index, iterations));
      setResults([...collected]);
      // Yield to the UI between benches so progress is visible.
      setTimeout(() => runNext(index + 1), 0);
    };
    setTimeout(() => runNext(0), 50);
  }, [iterations]);

  return (
    <SafeAreaProvider>
      <StatusBar barStyle="light-content" />
      <SafeAreaView style={styles.safeArea}>
        <ScrollView contentInsetAdjustmentBehavior="automatic" contentContainerStyle={styles.content}>
          <Text style={styles.title}>Hermes destructuring bench</Text>
          <Text style={styles.subtitle}>
            {BENCH_NAMES.length} cases · {iterations.toLocaleString()} iterations each
          </Text>

          <View style={styles.row}>
            {ITERATION_OPTIONS.map(option => (
              <Pressable
                key={option}
                disabled={running}
                onPress={() => setIterations(option)}
                style={[styles.chip, option === iterations && styles.chipActive]}>
                <Text style={styles.chipText}>{option.toLocaleString()}</Text>
              </Pressable>
            ))}
          </View>

          <Pressable
            disabled={running}
            onPress={runAll}
            style={[styles.button, running && styles.buttonDisabled]}>
            <Text style={styles.buttonText}>{running ? 'Running…' : 'Run all'}</Text>
          </Pressable>

          {totalMs !== null && <Text style={styles.total}>Total: {totalMs.toFixed(2)} ms</Text>}

          {results.map(result => (
            <ResultRow key={result.name} result={result} />
          ))}
        </ScrollView>
      </SafeAreaView>
    </SafeAreaProvider>
  );
}

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#0f172a',
  },
  content: {
    paddingHorizontal: 16,
    paddingTop: 16,
    paddingBottom: 16,
  },
  title: {
    color: '#e2e8f0',
    fontSize: 24,
    fontWeight: '700',
    marginBottom: 6,
  },
  subtitle: {
    color: '#94a3b8',
    fontSize: 14,
    marginBottom: 16,
  },
  row: {
    flexDirection: 'row',
    gap: 8,
    marginBottom: 12,
  },
  chip: {
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 999,
    backgroundColor: '#1e293b',
  },
  chipActive: {
    backgroundColor: '#2563eb',
  },
  chipText: {
    color: '#e2e8f0',
    fontSize: 13,
  },
  button: {
    backgroundColor: '#2563eb',
    borderRadius: 10,
    paddingVertical: 12,
    alignItems: 'center',
    marginBottom: 16,
  },
  buttonDisabled: {
    opacity: 0.5,
  },
  buttonText: {
    color: '#ffffff',
    fontSize: 16,
    fontWeight: '700',
  },
  total: {
    color: '#facc15',
    fontSize: 16,
    fontWeight: '700',
    marginBottom: 12,
  },
  card: {
    borderRadius: 14,
    marginBottom: 14,
    padding: 12,
    backgroundColor: '#111827',
    borderWidth: 1,
    borderColor: 'rgba(255,255,255,0.08)',
  },
  cardTitle: {
    color: '#ffffff',
    fontSize: 16,
    fontWeight: '700',
    marginBottom: 4,
  },
  cardMeta: {
    color: '#cbd5e1',
    fontSize: 12,
  },
});

export default App;

// destructuringBench
// Micro-benchmarks for destructuring in Hermes.
// Every case writes into `sink` so the engine can't drop the work as dead code.

export type BenchResult = {
  name: string;
  iterations: number;
  ms: number;
  opsPerMs: number;
};

type Bench = {
  name: string;
  run: (iterations: number) => number;
};

declare const performance: {now(): number} | undefined;

const now: () => number =
  typeof performance !== 'undefined' && performance.now
    ? () => performance.now()
    : () => Date.now();

const POOL_SIZE = 1024;
const MASK = POOL_SIZE - 1;

const objPool: Array<{x: number; y: number; z: number; w: number}> = [];
const nestedPool: Array<{pos: {x: number; y: number}; meta: {id: number}}> = [];
const arrPool: number[][] = [];
const shortArrPool: number[][] = [];

for (let i = 0; i < POOL_SIZE; i++) {
  objPool.push({x: i, y: i * 2, z: i * 3, w: i * 4});
  nestedPool.push({pos: {x: i, y: i + 1}, meta: {id: i}});
  arrPool.push([i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7]);
  shortArrPool.push(i % 2 === 0 ? [i] : [i, i + 1]);
}

function sumParams({x, y}: {x: number; y: number}) {
  return x + y;
}

function sumArrayParams([a, b]: number[]) {
  return a + b;
}

const benches: Bench[] = [
  {
    name: 'let {x, y} = obj',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        let {x, y} = objPool[i & MASK];
        sink += x + y;
      }
      return sink;
    },
  },
  {
    name: 'let {x, y, z, w} = obj',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        let {x, y, z, w} = objPool[i & MASK];
        sink += x + y + z + w;
      }
      return sink;
    },
  },
  {
    name: 'let {x, ...rest} = obj',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        let {x, ...rest} = objPool[i & MASK];
        sink += x + rest.y;
      }
      return sink;
    },
  },
  {
    name: 'let {pos: {x, y}, meta: {id}} = obj',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        let {
          pos: {x, y},
          meta: {id},
        } = nestedPool[i & MASK];
        sink += x + y + id;
      }
      return sink;
    },
  },
  {
    name: 'let {x, q = 5} = obj (default)',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        let {x, q = 5} = objPool[i & MASK] as {x: number; q?: number};
        sink += x + q;
      }
      return sink;
    },
  },
  {
    name: 'let [a, b] = arr',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        let [a, b] = arrPool[i & MASK];
        sink += a + b;
      }
      return sink;
    },
  },
  {
    name: 'let [a, b, ...rest] = arr',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        let [a, b, ...rest] = arrPool[i & MASK];
        sink += a + b + rest.length;
      }
      return sink;
    },
  },
  {
    name: 'let [a, , c] = arr (hole)',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        let [a, , c] = arrPool[i & MASK];
        sink += a + c;
      }
      return sink;
    },
  },
  {
    name: 'let [a, b = 7] = arr (default)',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        let [a, b = 7] = shortArrPool[i & MASK];
        sink += a + b;
      }
      return sink;
    },
  },
  {
    name: '[a, b] = [b, a] (swap)',
    run: n => {
      let a = 1;
      let b = 2;
      for (let i = 0; i < n; i++) {
        [a, b] = [b, a + i];
      }
      return a + b;
    },
  },
  {
    name: 'fn({x, y}) param',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        sink += sumParams(objPool[i & MASK]);
      }
      return sink;
    },
  },
  {
    name: 'fn([a, b]) param',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        sink += sumArrayParams(arrPool[i & MASK]);
      }
      return sink;
    },
  },
  {
    name: 'for (const {x, y} of objs)',
    run: n => {
      let sink = 0;
      const rounds = Math.max(1, Math.floor(n / POOL_SIZE));
      for (let r = 0; r < rounds; r++) {
        for (const {x, y} of objPool) {
          sink += x + y;
        }
      }
      return sink;
    },
  },
  {
    name: 'for (const [a, b] of arrs)',
    run: n => {
      let sink = 0;
      const rounds = Math.max(1, Math.floor(n / POOL_SIZE));
      for (let r = 0; r < rounds; r++) {
        for (const [a, b] of arrPool) {
          sink += a + b;
        }
      }
      return sink;
    },
  },
  {
    name: 'baseline: obj.x + obj.y (no destructuring)',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        const o = objPool[i & MASK];
        sink += o.x + o.y;
      }
      return sink;
    },
  },
  {
    name: 'baseline: arr[0] + arr[1] (no destructuring)',
    run: n => {
      let sink = 0;
      for (let i = 0; i < n; i++) {
        const a = arrPool[i & MASK];
        sink += a[0] + a[1];
      }
      return sink;
    },
  },
];

export const BENCH_NAMES = benches.map(b => b.name);

let globalSink = 0;

export function runBench(index: number, iterations: number): BenchResult {
  const bench = benches[index];
  // Warm up so the measurement isn't dominated by first-run costs.
  globalSink += bench.run(Math.min(10_000, iterations));
  const start = now();
  globalSink += bench.run(iterations);
  const ms = now() - start;
  return {
    name: bench.name,
    iterations,
    ms,
    opsPerMs: ms > 0 ? iterations / ms : Infinity,
  };
}

export function getSink() {
  return globalSink;
}
```

</details>

## Automated tests

- `js1 test xplat/js/tools/fb-metro-cli/__tests__/integration-tests/bundle-test.js xplat/js/tools/fb-metro-cli/__tests__/integration-tests/server-json-test.js xplat/js/tools/metro-buck-bundle-worker/__tests__/metro-buck-e2e-output-test.js xplat/js/react-native-github/packages/react-native-babel-preset/src/__tests__/transform-runtime-caller-test.js`
  - 6 suites, 27 tests, and 85 snapshots passed.
- `arc lint -a`
  - No lint issues.

Reviewed By: cipolleschi

Differential Revision: D126038210

Pulled By: javache

fbshipit-source-id: 4aeb9e89baeb65a1125bd301458b4c79820c57bc
…59170)

Summary:
`RCTViewComponentView` (`React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm`) applies a view's borders, shadow path, background colour and image layers, box shadows, outline, filters and clipping masks in `invalidateLayer`, which runs from `finalizeUpdates:` whenever `_needsInvalidateLayer` is set.

**Current behavior.** In `updateProps:oldProps:`, an opacity change sets the new value on the layer and also requests `invalidateLayer`. Inside `invalidateLayer`, the opacity is read only in the filter stage: it is re-applied to the layer and combined with `filter: opacity(...)` and `brightness(...)`. For a view without a filter, the invalidation therefore recomputes everything else unchanged: it resolves border metrics, recreates the shadow path, re-applies the layer properties, and removes and recreates the box-shadow, outline, background-image and mask sublayers.

Opacity animations make this per-frame work. With Animated's native driver, `-[RCTMountingManager synchronouslyUpdateViewOnUIThread:...]` calls `updateProps:` and `finalizeUpdates:` on every animated view on every frame. By default, Reanimated commits the animated props to the shadow tree on every frame, which mounts through the same `updateProps:` and `finalizeUpdates:`. Every styled view in a fade, a shimmer or a staggered entrance runs the full invalidation on every frame.

**With this change.** An opacity change requests the invalidation only when the view has a `filter`. The layer's opacity is still set in `updateProps:` as before. A change to `filter` itself already requests the invalidation in its own branch, so views with filters behave exactly as before, and so does every other prop. This follows the same reasoning as react#45620, which stopped `mixBlendMode` from invalidating the layer.

## Changelog:

[IOS] [FIXED] - Opacity changes no longer rebuild a View's borders, shadows and background layers, which made opacity animations over many styled Views drop frames

Pull Request resolved: react#59170

Test Plan:
- `node scripts/clang-format.js --check` on the changed file passes.
- RNTester built with `xcodebuild` in Release and run on a physical iPhone 15 Pro (iOS 26.6.1).
- In screenshots of a list of 1000 cards after a fade, with shadow props and with `boxShadow`, the list area is byte-identical before and after.
- **Measurement.** RNTester Playground with items in a `ScrollView`, each an `Animated.View` wrapping a `Text`, faded together from opacity 1 to 0.3 and back over one second. Three contents: plain (no border or shadow), card (`borderRadius: 8`, `borderWidth: 1`, solid `backgroundColor`, `shadowColor/Opacity/Radius/Offset`), and the same card with `boxShadow` instead. Timed per frame: a timer around `invalidateLayer`, the main-thread time applying the animated props (from entering `synchronouslyUpdateViewOnUIThread:` to after `finalizeUpdates:`), and the interval between frames from a run-loop observer after the CoreAnimation commit. The two arms are the same binary with the new check switched by an environment variable. Each arm ran 40 fades per content over two launches, in the order before, after, after, before, so drift over the session affects both arms equally (about 11,000 frames at 1000 items). The presses were driven by [Argent](https://github.com/software-mansion/argent) on the physical iPhone, with no accessibility polling during the fades. iPhone 15 Pro, Release, medians over all frames:

| Animated (`useNativeDriver: true`), 1000 items | before | this PR | change |
|---|--:|--:|--:|
| cards: frames per 1 s fade | 16 | 62 | |
| cards: interval between frames | 83.1 ms | 16.7 ms | −80% |
| cards: main-thread time per frame | 60.0 ms | 3.88 ms | −94% |
| `boxShadow` cards: frames per 1 s fade | 14 | 62 | |
| `boxShadow` cards: interval between frames | 99.6 ms | 16.7 ms | −83% |
| `boxShadow` cards: main-thread time per frame | 66.6 ms | 3.99 ms | −94% |

`invalidateLayer` accounted for 55.2 ms (cards) and 61.8 ms (`boxShadow`) of each frame before, and 0 after. Before the change, over 92% of frame intervals exceeded 25 ms for both card styles; after it, none did. Plain views already ran at 60 fps and change little (4.21 → 3.80 ms per frame).

With 20 items, closer to a typical skeleton or shimmer, both arms hold 60 fps and the change frees main-thread time in every frame (20 fades per content): 4.81 → 0.42 ms for cards (−91%), 5.08 → 0.68 ms for `boxShadow` (−87%).

**Reanimated.** The same comparison in a separate app on the latest releases: React Native 0.88.0 built from source (its opacity code in this file is identical), Reanimated 4.7.1 and react-native-worklets 0.13.0, in Reanimated's default mode, with the same protocol. Timed from the mount phase of each frame's transaction (`TransactionTelemetry`):

| Reanimated 4.7.1, `useAnimatedStyle` opacity | before | this PR | change |
|---|--:|--:|--:|
| 1000 cards: mount phase per frame | 57.6 ms | 0.67 ms | −99% |
| 1000 cards: frames per 1 s fade | 16 | 61 | |
| 1000 `boxShadow` cards: mount phase per frame | 64.8 ms | 0.76 ms | −99% |
| 1000 `boxShadow` cards: frames per 1 s fade | 12 | 60 | |
| 20 cards (120 Hz): mount phase per frame | 2.83 ms | 0.07 ms | −98% |
| 20 `boxShadow` cards (120 Hz): mount phase per frame | 2.71 ms | 0.09 ms | −97% |

Shorter runs with Reanimated 4.3.0 on React Native 0.84.1 gave the same result: 56 → 0.65 ms per frame for cards and 62 → 0.74 ms for `boxShadow`. Not exercised: Reanimated's opt-in `IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS` and `USE_ANIMATION_BACKEND` modes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed By: cipolleschi

Differential Revision: D126038713

Pulled By: javache

fbshipit-source-id: 44fab020f9019222a26ed98cedc8303ff06207d7
…#59168)

Summary:
`RCTViewComponentView` (`React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm`) applies a view's borders, shadow path, background colour and image layers, box shadows, outline, filters and clipping masks in `invalidateLayer`, which runs from `finalizeUpdates:` whenever `_needsInvalidateLayer` is set.

**Current behavior.** `updateLayoutMetrics:oldLayoutMetrics:` sets `_needsInvalidateLayer` unconditionally. The mounting layer calls it whenever any layout metric differs, including the frame origin alone, so a view that merely moved runs the whole invalidation: it resolves border metrics, recreates the shadow path, re-applies about ten layer properties, re-rasterizes the border image when borders are non-uniform, and removes and recreates the box-shadow, outline, background-image and mask sublayers. When the content above a list changes height, every card below it pays this although nothing it draws has changed.

**With this change.** The layout metrics are compared with the stored ones with the origin masked out. If only the origin changed, the invalidation is not requested; every other change (size, content insets, border widths, overflow inset, display type, layout direction, scale factor) still invalidates as before, and so does a recycled view, whose stored metrics are `EmptyLayoutMetrics`. Everything `invalidateLayer` derives is in the view's own coordinate space (`bounds`, `layer.bounds`, padding frame), with one exception: the iOS 17 hover shape is built from `self.frame` to work around a UIKit bug, so views with `cursor: pointer` keep invalidating on moves. The subview and sublayer frame updates in `updateLayoutMetrics:` are unchanged.

**Why.** Position-only layout updates are the common case in a reflow: inserting a row, expanding an accordion, a keyboard-avoiding shift, or a LayoutAnimation moving content. The cost scales with the number of styled views below the change.

## Changelog:

[IOS] [FIXED] - Views no longer rebuild their layer styling (borders, shadows, background and mask layers) when only their position changes

Pull Request resolved: react#59168

Test Plan:
- `node scripts/clang-format.js --check` on the changed file passes.
- RNTester built with `xcodebuild` in Release and run on a physical iPhone 15 Pro (iOS 26.6.1).
- In screenshots of a reflowed list of 1000 cards (border radius, 1pt border, solid background, shadow props) and of the same list with `boxShadow`, the list area is byte-identical before and after.
- **Measurement.** RNTester Playground with 1000 cards mounted in a `ScrollView`, each a `View` wrapping a `Text`; a "reflow" toggles a spacer above the list between 0 and 40pt, so every card moves and none changes size. Three contents: plain (unstyled wrapper, which Fabric flattens, so only the `Text` views remain), card (`borderRadius: 8`, `borderWidth: 1`, solid `backgroundColor`, `shadowColor/Opacity/Radius/Offset`) and the same card with `boxShadow` instead. Timed from Fabric's transaction telemetry (mount phase), a timer around `invalidateLayer`, a run-loop observer after the CoreAnimation commit (mount end to display), and the `touchend` Event Timing entry for the press (press to mount end). The two arms are the same binary with the new check switched by an environment variable; both include react#59155 so that text redraws do not dominate the reflow. The presses were driven by [Argent](https://github.com/software-mansion/argent) on the physical iPhone, with no accessibility polling between presses. iPhone 15 Pro, Release, medians of 5 reflows:

| reflow with 1000 cards (shadow props) | before | this PR | change |
|---|--:|--:|--:|
| layer invalidations | 1001 | 13 | |
| `invalidateLayer` total (main thread) | 59.2 ms | 1.5 ms | −97% |
| mount phase (main thread) | 61.5 ms | 3.8 ms | −94% |
| mount end to display | 5.3 ms | 1.4 ms | |
| press to mount end | 114.6 ms | 69.9 ms | −39% |

| reflow with 1000 cards (`boxShadow`) | before | this PR | change |
|---|--:|--:|--:|
| layer invalidations | 2001 | 10 | |
| `invalidateLayer` total (main thread) | 66.6 ms | 0.9 ms | −99% |
| mount phase (main thread) | 70.4 ms | 4.0 ms | −94% |
| mount end to display | 18.3 ms | 0.1 ms | −99% |
| press to mount end | 126.8 ms | 42.5 ms | −66% |

Each arm was run twice. The repeat of the before arm gave 114.1 ms and 132.1 ms press to mount end for the two card styles (58.2 ms and 66.9 ms of invalidation), the repeat of the after arm 52.3 ms and 53.2 ms (1.1 ms and 0.9 ms), so the saving is 39 to 68% of the press across both runs and both card styles. The remaining invalidations are views whose size or props do change in that commit, such as the harness's own status labels. The plain list is unchanged (0.8 ms of invalidation per reflow before, under 0.1 ms after, press to mount end within noise at 41 and 46 ms), as are mounting and unmounting: mounting the 1000 cards measures 141 ms in the mount phase in both arms, with the same 2001 invalidations.

Not exercised: the hover shape for `cursor: pointer`, which needs a pointer device; by design those views invalidate on moves exactly as before.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed By: cipolleschi

Differential Revision: D126038802

Pulled By: javache

fbshipit-source-id: d70780f522c85d90a077a46e3a3a31e433ab3cd5
…react#2037)

Summary:
* Round node dimensions in double so far-off nodes keep their full size

A node's rounded width and height are the difference of its two rounded
absolute edges. roundValueToPixelGrid returns float, so that difference was
taken in float; far from the origin a float has fewer bits than a pixel, and
when the two edges fall on either side of a power of two they are quantized
differently. A measured text node spanning y = 2048 on a 3x screen came out
43.9998779 tall instead of 44, one float step short of the two lines it was
measured at, and the text renderer then dropped the last line.

Round in double and subtract before narrowing to float. The public
roundValueToPixelGrid keeps its signature.

* Cover both fractions on either side of 1024, 2048 and 4096 in the rounding test

On the previous rounding each of these came out off by a float step
(44.00006, 43.99994, 43.99988, 43.99988 and 43.99976); all are exactly 44
now.

* Remove comments

Changelog: [Internal]

Reviewed By: cortinico

Differential Revision: D125427496

fbshipit-source-id: 4ba056bb164207d540032252633112358db80581

Co-authored-by: nishan (o^▽^o) <nishanbende@gmail.com>
… grid (react#2040)

Summary: When a text node's scaled size is a whole number of pixels, use that pixel count as its rounded size instead of the difference of its two rounded edges. The 0.0001 snap tolerance could snap one edge to a pixel and floor the other, which made the node one pixel smaller than its measured size and truncated its text.

Reviewed By: cortinico

Differential Revision: D125427541

fbshipit-source-id: 4fdd9223120938b35c38fc75df1602cbbc01820a
Summary:
copysign(x - trunc(x), x) is bit-identical to fmod(x, 1.0) for every
double but avoids the libm fmod loop, which is slow on Android's bionic.
roundValueToPixelGridDouble runs six times per node on every layout
pass.

Changelog: [Internal]

Reviewed By: cortinico

Differential Revision: D125600548

fbshipit-source-id: 7866bfb8619f7a6ec63bf799821a8c2270340a57

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
)

Summary:
Removes the `generate_changelog` job from the publish workflow, along with `generate-changelog.yml`, `generateChangelog.js` and its test.

The job opened a `changelog/v<version>` PR with every section present, empty or not, an `Unknown` bucket and the release machinery's own commits in it. Every one was then curated by hand. The release copilot ([reactwg/react-native-releases#1425](reactwg/react-native-releases#1425)) now opens that PR itself. It runs the same `rnx-kit/rn-changelog-generator` against the same base, then curates:

- **Empty sections are omitted** and the release machinery's own commits dropped.
- **Missing prefixes are added** where the entry's text names a scope the changelog already uses and the commit's files sit where that scope has lived. Trained without the release being checked, 111 of 133 inferred prefixes across 0.86.0 to 0.88.0 matched what the crew chose. Entries where the two signals disagree stay unprefixed and are listed rather than guessed.
- **Consistency is flagged**: a commit that removes or narrows a public API symbol filed outside Breaking, Removed or Deprecated, a `[BREAKING]` commit filed elsewhere, a Breaking entry with no API change or tag, a removal filed as a fix and a platform subsection that disagrees with the code. On the 0.88 consolidation it flagged 12, including the ArrayBuffer borrow contract. These are flags for the reviewer, not verdicts.

The PR it opens lists every prefix it added and every flag it raised. It also opens the `.0` consolidation, which the job always skipped (0.88's was [https://github.com/react/react-native/issues/59122](https://github.com/react/react-native/pull/59122), done by hand).

`create_draft_release` listed `generate_changelog` in `needs` and its `if`. It depended on it for ordering only: `createDraftRelease.js` runs `git checkout -b changelog/v<version>`, which creates a new local branch from the tag and never reads the bot's branch. The tag's own `CHANGELOG.md` never carries the release's section (checked for v0.88.0-rc.4, v0.88.0-rc.6 and v0.87.1), which is why the draft notes have always come out empty. It now depends on `publish_react_native` directly, so it still runs only after a successful publish.

The publish workflow runs from the tag's branch, so this takes effect from the first branch cut after it lands. Existing `*-stable` branches keep the job unless this is picked. The copilot defers to the bot wherever the job is still present, so the two never open the same branch.

This should land after [reactwg/react-native-releases#1425](reactwg/react-native-releases#1425), since that is where the replacement lives.

## Changelog:

[INTERNAL] [REMOVED] - Remove the generated changelog PR from the publish workflow

Pull Request resolved: react#59172

Test Plan:
- `yarn jest .github/workflow-scripts`: 10 suites, 61 tests pass.
- Every file in `.github/workflows` parses as YAML. In `publish-npm.yml`, every `needs` entry names an existing job and nothing references `generate_changelog`.
- `git grep` for `generate_changelog`, `generate-changelog` and `generateChangelog` finds nothing under `.github` after the change.
- The replacement was exercised as a dry run against v0.88.0-rc.6: it ran the generator, resolved the commits and pushed nothing.

Reviewed By: javache, cortinico

Differential Revision: D126044344

Pulled By: fabriziocucci

fbshipit-source-id: fd5b716d0018915baa389463d26ecf4e4361eae4
…eact#59144)

Summary:
Pull Request resolved: react#59144

`AppleEventBeat.h`, `RCTSurfacePresenter.h` and `RCTSurfacePresenterBridgeAdapter.h` still included `<ReactCommon/RuntimeExecutor.h>` directly. This diff switches it to umbrella include.

Changelog: [Internal]

___

landed-with-radar-review

Differential Revision: D124325477

fbshipit-source-id: d4f664400735e886fb4f3b097a03101bacf54610
…iOS (react#59177)

Summary:
iOS's Show Borders setting (called Button Shapes before iOS 26.1) outlines tappable controls. UIKit and SwiftUI buttons follow it automatically, but React Native apps can't detect it, so custom `Pressable` buttons can't match. It can also change layout (expo/expo#48058).

This adds `AccessibilityInfo.isShowBordersEnabled()` and a `showBordersChanged` event, following the existing `isDarkerSystemColorsEnabled` pattern.
- iOS 26.1 deprecated `UIAccessibilityButtonShapesEnabled()` in favour of `AXShowBordersEnabled()` in the Accessibility framework.
- The module uses the new API on 26.1 and later, guarded by `__IPHONE_OS_VERSION_MAX_ALLOWED` for older SDKs and by `available` at runtime, and the UIKit API below that.
- React-CoreModules now links the Accessibility framework, in both the podspec and `Package.swift`. The framework exists from iOS 14, below the 15.1 minimum, and the new symbol is weak-linked through `available`.

On Android, the method resolves to `false`.

Discussion: react-native-community/discussions-and-proposals#1026

## Changelog:

[IOS] [ADDED] - `AccessibilityInfo.isShowBordersEnabled()` and `showBordersChanged` (Show Borders, formerly Button Shapes)

Pull Request resolved: react#59177

Test Plan:
- `yarn jest packages/react-native/Libraries/Components/AccessibilityInfo`: all tests pass
- `yarn flow-check`: 0 errors
- `yarn lint`: passes
- `yarn build-types --validate`: API snapshot up to date
- `yarn test-generated-typescript` and `yarn test-typescript-legacy`: pass
- `yarn format-check-cpp`: passes
- RNTester builds for the iOS simulator: `xcodebuild -workspace RNTesterPods.xcworkspace -scheme RNTester -sdk iphonesimulator`, using Xcode 27.1

On an iPhone 18 Pro simulator running iOS 27.0, which uses the `AXShowBordersEnabled` path: I flipped the setting in the simulator's Settings app with a throwaway XCUITest, not part of this PR, and read RNTester's Accessibility → "Check if the display options are enabled" row. RNTester stayed running the whole time, so the row could only update through the change event.
- Before: "Show Borders is disabled."
- Show Borders turned on: "Show Borders is enabled."
- Turned off: "Show Borders is disabled."

Not run: iOS below 26.1, which uses the UIKit fallback. No older simulator runtime was available.

Reviewed By: cipolleschi

Differential Revision: D126037985

Pulled By: javache

fbshipit-source-id: 4fb84b25e5cfbc9c4793d26d9946957d42b38466
Summary:
Pull Request resolved: react#59145

`-[RCTInstance invalidate]` held `_invalidationMutex` while it synchronously called `[_surfacePresenter suspend]`. Stopping surfaces notifies mount observers on the calling thread, and any observer that reaches `callFunctionOnJSModule:` (e.g. `RCTLog` forwarding to JS in debug builds) blocks forever on the same non-recursive mutex.

Release the mutex after setting `_valid = false` and unregistering from the inspector, before `suspend`. Re-entrant calls now see `!_valid` and return.

Changelog:
[iOS][Fixed] - Fix deadlock in `RCTInstance` invalidation when a surface observer calls into JS

Reviewed By: javache

Differential Revision: D125706316

fbshipit-source-id: 6db052741415729e9e9dd14b04d6d2eaa08566ae
…t#59037)

Summary:
Pull Request resolved: react#59037

Let iOS apps that use CocoaPods enforce React Native's C++ stable API, and control its "for frameworks" warning, with two env vars read at `pod install`:

- `RN_STRICT_API=1` defines the `RN_STRICT_API` macro, which arms the `react/cxxstableapi` guards. A direct include of a public module header is an error pointing to the module's umbrella (`<React/<Module>.h>`), a private header is an error, and a "for frameworks" header is a warning.
- `RN_ALLOW_FRAMEWORKS=1` defines the `RN_ALLOW_FRAMEWORKS` macro, which silences that warning.

Each variable is named after the macro it defines, matching the Android opt-in, which passes `RN_STRICT_API` to CMake.

Both macros are added to the app project and the Pods project, so they reach the app, third-party pods and app codegen. React Native's own pods are unaffected because they define `RN_BUILDING`. Both are off by default; running `pod install` without a variable removes its define again. This mirrors the existing `RCT_REMOVE_LEGACY_*` opt-ins and the Android opt-in.

Who silences the "for frameworks" warning for which code:
- A library that uses for-frameworks headers declares `RN_ALLOW_FRAMEWORKS` for its own pod (`pod_target_xcconfig`). That covers its own build only and never reaches the app or other pods.
- The app author sets `RN_ALLOW_FRAMEWORKS=1` before `pod install` for the app's own code. It also silences libraries that use those headers without declaring it, which is on the library.
- The generated `ReactCodegen` and `ReactAppDependencyProvider` pods always define `RN_ALLOW_FRAMEWORKS`: nobody can act on a warning in generated code, and libraries are already held to the API in their own pods.

Changelog:
[iOS][Added] - Add `RN_STRICT_API` and `RN_ALLOW_FRAMEWORKS` CocoaPods opt-ins to enforce React Native's C++ stable API in app and library native code

Reviewed By: cipolleschi

Differential Revision: D124979668

fbshipit-source-id: 5fe97bbb564f99ad81d677f738d7ce946e81d35d
Summary:
For text that is not laid out as a `BoringLayout`, `TextLayoutManager.createLayout` (Kotlin) calls `Layout.getDesiredWidth(text, paint)` before building the `StaticLayout`. That call is a full measuring pass over the string, and its result is only needed to choose the layout width.

**Current behavior.** The pass runs even when its result cannot matter:

- When Yoga's width mode is `EXACTLY`, the result is discarded.
- When the text was "boring" but too wide for an `AT_MOST` width, the result is redundant. `boring.width` is already the unwrapped width of the same text, and it exceeded `floor(width)`, so `min(desiredWidth, floor(width))` is always `floor(width)`.

For right-to-left and complex scripts `isBoring` bails out early, so `getDesiredWidth` is the pass that pays for shaping, and its result is then thrown away.

**With this change.** The pass is skipped in those two cases, and the resulting layout widths are identical.

**Why.** Text measurement runs on the JS thread during commit. For Arabic text this pass was 37% of `createLayout`.

## Changelog:

[ANDROID] [CHANGED] - Skip an unnecessary measuring pass when laying out `Text`

Pull Request resolved: react#59064

Test Plan:
- `./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests "com.facebook.react.views.text.*"`: 60 tests, 0 failures.
- `yarn format-check-kotlin` and RNTester `assembleDebug` pass.
- Screenshots were pixel-identical before and after for long Latin paragraphs and Arabic text.

**Benchmarks.** RNTester release build with a text benchmark screen that is not part of this PR.

Per-function times from Perfetto with temporary trace sections. Emulator (Pixel 10 Pro AVD, API 37), the same logic on an earlier `main`, medians of 4 runs, mount phase. `measureContent` covers all text measurement on the JS thread:

| content | `getDesiredWidth` time removed | `createLayout` | `measureContent` |
|---|--:|--:|--:|
| 300 Arabic strings | 12.8 ms | 34.8 → 24.9 ms (−28%) | 39.8 → 30.8 ms (−23%) |
| 100 social-feed posts | 1.9 ms | 6.0 → 4.6 ms (−24%) | 11.6 → 10.2 ms (−13%) |
| 300 Chinese strings | 2.3 ms | 12.9 → 11.1 ms (−14%) | 18.6 → 17.2 ms (−7%) |
| 100 rich paragraphs (nested spans) | 2.0 ms | 9.8 → 8.4 ms (−14%) | 15.8 → 14.6 ms (−7%) |
| 100 long Latin paragraphs | 3.0 ms | 15.0 → 14.5 ms (−3%) | no change (`StaticLayout` then pays for shaping instead) |
| 1000 one-line labels | 0 (all `BoringLayout`) | – | – |

Untraced JS-thread CPU, current `main` vs. this branch, same emulator, 6 runs: long paragraphs −15% on mount and −29% on a text update. In the other scenarios the difference is within emulator run-to-run noise at the whole-thread level.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed By: mdvacca, javache

Differential Revision: D125730254

Pulled By: Abbondanzo

fbshipit-source-id: 5a762b82b4498f4a7b3b4aec9fb860a084872d11
Summary:
Pull Request resolved: react#59183

The JS-only `enableImperativeEvents_DEPRECATED` flag was kept so JS bundles delivered to older native builds, where the common `enableImperativeEvents` flag was unavailable, could still enable the feature. Those builds are gone, so remove the deprecated flag and gate `ReactNativeElement` and `ReadOnlyText` solely on the common flag.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D126047688

fbshipit-source-id: 7a35822b2fafb4535bd309677730c9b8c4d349cc
…graph (react#59162)

Summary:
Fixes react#59161.

When `LayoutAnimation` changes the height of a `<Text>` on iOS (for example a "read more" toggle from `numberOfLines={2}` to no limit), words jump between lines during the animation instead of the text unrolling with its final line breaks.

**Current behavior.** LayoutAnimation interpolates the paragraph's `LayoutMetrics` on every frame. `RCTParagraphComponentView` uses that intermediate content frame as its drawing frame, so `drawRect:` lays the text out again in a container of the in-between size. With no line limit, `RCTTextLayoutManager` sets the container's `lineBreakMode` to `NSLineBreakByClipping`, which applies to the last line that fits: that line isn't word-wrapped, it takes the rest of the text and clips it at the right edge. So on every frame the last visible line shows words that belong to the next line, cut off at the edge, and they jump down once the next line is revealed. Lines also appear only once they fit completely. Width animations have the same problem sideways: the text re-wraps on every frame.

The old architecture didn't have this problem. `RCTTextShadowView` laid the text out once, at the final frame size, and the animation only changed the view's frame. Android (with `enableLayoutAnimationsOnAndroid`) also unrolls height changes correctly.

**With this change.** The cxx `ParagraphState` (used outside of Android) stores the size of the content frame the paragraph was laid out with. `ParagraphShadowNode` sets it in `layout()`, where the layout metrics are final. `RCTParagraphComponentView` lays the text out in that size and lets the text view clip it to its current bounds. LayoutAnimation already builds every intermediate view from the final one and never interpolates state ("keep the mounting layer consistent with the final state"), so this needs no animation-specific code.

When nothing is animating, the stored size equals the current content frame size: layout metrics are only set together with a `layout()` call (`YogaLayoutableShadowNode::layout`, `layoutTree`). Static rendering is therefore unchanged, including text whose frame is shorter than its content.

Android keeps its current state handling (`#ifdef ANDROID`): its views don't use the size, and a state update there goes through JNI and resets the text.

I also tried the one-line alternative of using `NSLineBreakByWordWrapping` for unlimited lines. It stops the words from jumping, but lines still pop in only once they fit, and it changes the static rendering of text whose frame is shorter than its content.

**Cost.** A paragraph whose size changes without a content change now gets a new `ParagraphState` on iOS. Such a size change already re-measures and redraws the text, and on current `main` the attributed string comparison in `updateStateIfNeeded` fails after layout anyway (see react#59063), so iOS already creates a new state on most layouts.

## Changelog:

[IOS] [FIXED] - Fix words jumping between lines while `LayoutAnimation` resizes a `Text`

Pull Request resolved: react#59162

Test Plan:
**Before (left) and after (right)**, RNTester Playground, iPhone Air simulator (iOS 27.0), 3-second linear `LayoutAnimation`.

Expand from `numberOfLines={2}` to no limit. Before: the last visible line is clipped at the edge and there is a gap below it. After: the final lines are revealed, and the partially visible line is clipped mid-line.

<img width="1100" height="398" alt="expand-ba-frame" src="https://github.com/user-attachments/assets/a0ef8fad-5c70-4c67-a9a2-0e2cd2ee156f" />

Width 50% → 100%. Before: the text re-wraps on every frame. After: it keeps its final line breaks and is uncovered from the right.

<img width="1100" height="398" alt="width-ba-frame" src="https://github.com/user-attachments/assets/6c04b9e9-d03d-4751-894e-12443065af37" />

Videos of both are at the end of the test plan.

Repro used for the issue and the videos:

```tsx
const TEXT = Array.from({length: 40}, (_, i) => `word${i + 1}`).join(' ');

function Playground() {
  const [expanded, setExpanded] = useState(false);
  return (
    <View style={{padding: 20}}>
      <Pressable
        onPress={() => {
          LayoutAnimation.configureNext({
            duration: 3000,
            update: {type: LayoutAnimation.Types.linear},
          });
          setExpanded(value => !value);
        }}>
        <Text>{expanded ? 'Collapse' : 'Expand'}</Text>
      </Pressable>
      <Text style={{fontSize: 22}} numberOfLines={expanded ? undefined : 2}>
        {TEXT}
      </Text>
    </View>
  );
}
```

- **Static rendering.** Full-resolution screenshots of a screen with five static cases were pixel-identical before and after; only the status-bar clock differed. The cases were `height` shorter than the text, `numberOfLines={2}` with ellipsis, padding + border + `textAlign: 'center'` + `lineHeight`, nested styles with an inline view, and `flexShrink` in a fixed-height container.
- **Fantom.** `yarn fantom packages/react-native/Libraries/Text/__tests__/Text-itest.js packages/react-native/Libraries/LayoutAnimation/__tests__/LayoutAnimation-itest.js` gives 157 passed. Fantom builds the same non-Android `ParagraphState` path as iOS. To build the tester locally I had to add `-Wno-error=deprecated-literal-operator`, because the bundled `nlohmann/json.hpp` fails with Xcode 27's clang; that's unrelated to this change.
- **Cost.** I used a temporary Fantom benchmark, not part of this PR, that resizes a container of 100 or 1000 texts without changing their content. It ran in dev mode, since optimized mode isn't supported in OSS, 5 interleaved runs per variant. Medians were 4.07 ms → 3.44 ms for 100 texts and 50.9 ms → 42.2 ms for 1000 texts. Run-to-run noise (about ±30%) was larger than any difference.
- **Android.** `./gradlew :packages:rn-tester:android:app:installDebug -PreactNativeArchitectures=arm64-v8a` builds. With `enableLayoutAnimationsOnAndroid` forced on locally, the expand animation unrolls correctly before and after this change. I noticed separately that Android re-wraps text during width animations, because `SurfaceMountingManager.updateLayout` calls `measure()` on every frame and `TextView` rebuilds its layout there; the old architecture doesn't. This PR doesn't address that.
- **Formatting.** clang-format 21.1.2 (`--dry-run -Werror`) passes on the changed files.

Expand, before (left) and after (right):

https://github.com/user-attachments/assets/e33bd41a-b427-4e76-b936-219f03489685

Width animation, before (left) and after (right):

https://github.com/user-attachments/assets/439db994-47a0-4f61-8259-3d8bcbcff2d8

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed By: cipolleschi

Differential Revision: D126054764

Pulled By: javache

fbshipit-source-id: bc40ec40dcafc692c081c54736f8f40a08cb684c
…EWORKS (react#59043)

Summary:
Pull Request resolved: react#59043

Let iOS apps that use the Swift Package Manager setup enforce React Native's C++ stable API, matching the CocoaPods and Android opt-ins. Two env vars are read by `npx react-native spm add` / `update`:

- `RN_STRICT_API=1` defines the `RN_STRICT_API` macro, which arms the `react/cxxstableapi` guards.
- `RN_ALLOW_FRAMEWORKS=1` defines the `RN_ALLOW_FRAMEWORKS` macro, which silences their "for frameworks" warning.

Each variable is named after the macro it defines, as in the CocoaPods opt-in.

Each opted-in macro is defined for the app's own code:
- the app target (`GCC_PREPROCESSOR_DEFINITIONS`, seeded with `$(inherited)`),
- the packages the autolinker generates for the app's local `spm.modules`,
- the app codegen package (`ReactCodegen`, `ReactAppDependencyProvider`).

Generated code never warns: the codegen package always defines `RN_ALLOW_FRAMEWORKS`, because nobody can act on a warning in generated code and libraries are held to the API in their own builds.

Under SwiftPM every library builds from its own `Package.swift`, so a library declares `RN_ALLOW_FRAMEWORKS` there, and the app's opt-ins do not reach libraries. React Native itself ships prebuilt, so none of its code is compiled with these macros.

The Xcode build phase runs `spm sync` without the shell environment, so `add`/`update` record the chosen macros in `.spm-injected.json` and every later sync regenerates the packages from that record. As with `pod install`, each `add`/`update` re-reads the variables: running `update` without one turns its macro off, and `deinit` removes the app-target defines along with the rest of the injection.

Changelog:
[iOS][Added] - Add `RN_STRICT_API` and `RN_ALLOW_FRAMEWORKS` opt-ins for the Swift Package Manager setup to enforce React Native's C++ stable API in the app's native code

Reviewed By: cipolleschi

Differential Revision: D124985657

fbshipit-source-id: 5ec66f94d5e3bb9e75fa305487cb39d2bd7e9d95
…evived list can schedule updates again (react#59123)

Summary:
`VirtualizedList.componentWillUnmount` clears the pending `_updateCellsToRenderTimeoutID` timer but never resets the field to `null`. The low-priority branch of `_scheduleCellsToRenderUpdate` only schedules a batch when that id is `null`, so the stale id blocks every future low-priority update on the instance.

Unmount is not final for a class instance inside a Suspense boundary. When already-mounted content is hidden and later revealed — the react-freeze pattern that react-native-screens uses for `freezeOnBlur` — React calls `componentWillUnmount` and then `componentDidMount` on the **same** instance. If a batch timer was pending at the hide, the revived list can no longer schedule low-priority updates. When the list also had no items at the hide, `cellsAroundViewport` is `{first: 0, last: -1}`, so the high-priority path (`last >= 0`) cannot recover either and the render window stays at `initialNumToRender` regardless of how much data arrives afterwards.

Field impact: a POS handheld app (react-native-screens with `freezeOnBlur`) showed 12 of 28 order lines on a screen that was blurred while its list was still empty, and scrolling never rendered the rest.

The fix is one line: reset the timer id to `null` right after `clearTimeout` in `componentWillUnmount`.

## Changelog:

[General] [Fixed] - VirtualizedList: clear the pending batch-update timer id in componentWillUnmount so a list hidden and revived by Suspense (react-freeze / freezeOnBlur) resumes rendering new cells

Pull Request resolved: react#59123

Test Plan:
Added a regression test to `packages/virtualized-lists/Lists/__tests__/VirtualizedList-test.js`: `renders all cells after being hidden and revealed by Suspense with a pending batch update`. It renders a `VirtualizedList` with `data={[]}` and `initialNumToRender={12}` under a Suspense boundary on a concurrent root, simulates layout so a low-priority batch timer is pending, hides the subtree by suspending (never-resolving thenable, as react-freeze does), reveals it, updates to 28 items, simulates content layout and scroll, advances fake timers through several batching periods, and asserts that all 28 cells render.

- Without the fix: `Expected length: 28`, `Received length: 12` (the window never leaves the initial region).
- With the fix: 28 of 28 cells render and the test passes.

Verified against the published `react-native/virtualized-lists` 0.85.3 (same code paths) as well: 12/28 before, 28/28 after. `prettier --check` passes on both touched files.

Reviewed By: Abbondanzo

Differential Revision: D126055741

Pulled By: javache

fbshipit-source-id: a69f8c66c1c29dcfd3f543d3c24cb0cea0413f81
cortinico and others added 29 commits October 22, 2026 09:28
Summary:
Pull Request resolved: react#59343

Add a Maestro visual suite for `Border` using existing RNTester examples. Capture stable cropped checkpoints after opening each example and settling animations.

See `packages/rn-tester/visual-tests/suites/border.md` for the example and checkpoint inventory.

Changelog: [Internal]

Reviewed By: Abbondanzo

Differential Revision: D126723202

fbshipit-source-id: 4ced552dd8e9859097a4aafa2d0d814366eef5d7
Summary:
Pull Request resolved: react#59347

Add a Maestro visual suite for `Transform` using existing RNTester examples. Capture stable cropped checkpoints after opening each example and settling animations.

See `packages/rn-tester/visual-tests/suites/transform.md` for the example and checkpoint inventory.

Changelog: [Internal]

Reviewed By: Abbondanzo

Differential Revision: D126723203

fbshipit-source-id: f9241efa6c3f81d3e385681fb59c0116de931e4a
Summary:
Pull Request resolved: react#59341

Add a Maestro visual suite for `Image` using existing RNTester examples. Capture stable cropped checkpoints after opening each example and settling animations.

See `packages/rn-tester/visual-tests/suites/image.md` for the example and checkpoint inventory.

Changelog: [Internal]

Reviewed By: Abbondanzo

Differential Revision: D126723204

fbshipit-source-id: f785218fe708f146daa6d6c7b3a51f7a66906156
Summary:
Pull Request resolved: react#59342

Add a Maestro visual suite for `ScrollView` using existing RNTester examples. Capture stable cropped checkpoints after opening each example and settling animations.

See `packages/rn-tester/visual-tests/suites/scroll-view.md` for the example and checkpoint inventory.

Changelog: [Internal]

Reviewed By: Abbondanzo

Differential Revision: D126723205

fbshipit-source-id: 8619cb1c10e549fb8dcd898a6d7365969ba2754d
Summary:
Pull Request resolved: react#59346

Add a Maestro visual suite for `Switch` using existing RNTester examples. Capture stable cropped checkpoints after opening each example and settling animations.

See `packages/rn-tester/visual-tests/suites/switch.md` for the example and checkpoint inventory.

Changelog: [Internal]

Reviewed By: Abbondanzo

Differential Revision: D126723206

fbshipit-source-id: 949167abee74f74f983a1531879def6ebc867eaf
Summary:
Pull Request resolved: react#59345

Add a Maestro visual suite for `Button` using existing RNTester examples. Capture stable cropped checkpoints after opening each example and settling animations.

See `packages/rn-tester/visual-tests/suites/button.md` for the example and checkpoint inventory.

Changelog: [Internal]

Reviewed By: Abbondanzo

Differential Revision: D126723207

fbshipit-source-id: 5e0c480ab709b0e15eaf4884d711c20133cc5370
Summary:
Pull Request resolved: react#59338

Add a Maestro visual suite for `Touchable` using existing RNTester examples. Capture stable cropped checkpoints after opening each example and settling animations.

See `packages/rn-tester/visual-tests/suites/touchable.md` for the example and checkpoint inventory.

Changelog: [Internal]

Reviewed By: Abbondanzo

Differential Revision: D126723208

fbshipit-source-id: 386f4fbb9d8f89aaceb7e182509cc8a812f45b1a
…eact#59363)

Summary:
Pull Request resolved: react#59363

`Pressability._createEventHandlers` destructured `onHoverIn`/`onHoverOut` out of
`this._config` once, and `getEventHandlers` memoises the handler object for the
life of the instance. `configure` replaces the config on every update, but the
W3C-pointer hover closures kept calling the callbacks captured when the handlers
were first created, so a hover callback that closes over component state observed
that state as it was at mount, for the life of the view, however many times the
component re-rendered.

Every other handler in `_createEventHandlers` already reads `this._config` inside
its own body, and so does the legacy mouse branch below. This makes the W3C
pointer branch do the same. It also restores `reset`'s documented guarantee for
hover: after `reset` freezes `this._config`, a late `onPointerEnter` no longer
calls a configured callback.

Whether to attach `onPointerEnter`/`onPointerLeave` at all is still decided once,
from the config present when the handlers are created, because that presence is
what makes the host view listen for pointer enter/leave. A view that mounts
without a hover callback and is given one later still does not begin listening.
That behaviour is unchanged by this diff.

Only the branch behind `shouldPressibilityUseW3CPointerEventsForHover` is
touched. With that flag at its default the generated handlers are byte-identical
to before.

Changelog:
[General][Fixed] - `Pressability` now invokes the current `onHoverIn`/`onHoverOut` rather than the ones captured when its event handlers were created

Reviewed By: christophpurrer

Differential Revision: D126767220

fbshipit-source-id: 6bc1d2f663d503797c9c0762a79b6d35c7311fdf
…#59202)

Summary:
Pull Request resolved: react#59202

Add a Robolectric test that verifies `Layout.getDesiredWidth` is only called when necessary during text layout.

The test confirms the optimization that avoids expensive width measurements for `EXACTLY` mode and for `AT_MOST` mode when boring text doesn't fit. It uses `mockStatic` to intercept calls to `Layout.getDesiredWidth` and verifies the correct number of invocations across different measure modes.

Changelog: [Internal]

Reviewed By: andrewdacenko

Differential Revision: D125868474

fbshipit-source-id: 4b1f51eaa912554dcfed362080d3ba2a7b218006
Summary:
## Motivation

 - Dev/release consistency in correctness and performance shape.
 - ~10% faster dev cold builds
 - More responsive dev apps (~2x faster promise handling)
 - Improved debugging support

Since react#52651, Hermes profiles have lowered generators (with `enableBabelRuntime: false`), `for...of`, `?.`, `??` and optional catch binding in dev builds only, because Hermes couldn't show local variables when paused on a breakpoint in a native generator.

That turned out to be a Hermes bug: a breakpoint on that a `yield` line resolved to scope-less entry code before the body had even run. It was fixed in facebook/hermes@d1238c8 (hermes-v260318099.0.2), which main has used since react#58397.

This removes `enableRegenerator`, so Hermes-profile dev output matches release. It also fixes a dev-only divergence - loose `for-of` never calls `iterator.return()`, so breaking out of a `for...of` over a generator skipped its `finally` in dev but not release.

Note that with the default `enableBabelRuntime`, generators were already native in dev, so in practice the generator change mostly affects `enableBabelRuntime: false` setups, including RN's own Jest. (There's no good reason generator was ever conditional on `enableBabelRuntime === false` - it seems to date from react#44668 and probably a misunderstanding of what the `regenerator` argument to `babel/plugin-transform-runtime` did - it doesn't make that plugin transform generators, it just rewrites the helper import.)

Native generator debugging still isn't perfect on .0.2 - stepping through `try`/`catch` can stop on a `catch` line that doesn't run, and breakpoints on `yield*` lines or on a `throw` after a resume don't fire. Those already affect every default config though, so there's no regression in most cases. Regenerator has its own problems, eg breakpoints often stop a line early because statements merge into its `case` blocks. Overall, native is now a better experience.

Detailed comparison in https://robhogan.github.io/reports/react-native-regenerator-removal/

## Changelog

[GENERAL] [FIXED] - Babel preset: Hermes profiles no longer lower generators, `for...of`, optional chaining, nullish coalescing or optional catch binding in dev, matching release

Pull Request resolved: react#59303

Test Plan:
Reproduced the original bug through Hermes's `CDPAgent` test harness with `Debugger.setBreakpointByUrl`. On hermes-v250829098.0.16 and v260318099.0.1, a breakpoint on a generator's last `yield` line pauses at the generator call with only the global scope, and every local evaluates to `undefined`. On v260318099.0.2 and .0.4 it pauses at the real code with all locals.

AI-driven breakpoint sweep: this preset with `dev: true, enableBabelRuntime: false`, before and after this change, over 248 source lines of generator, async function, async generator, exception and loop fixtures. A breakpoint was set on every line, each pause was mapped back through the source map, and every local was evaluated. Same results with eager and lazy compilation.

| Hermes | Identical | Different, equivalent | Native better | Native worse | Mixed (loop headers) | Total |
| --- | --- | --- | --- | --- | --- | --- |
| 250829098.0.16 | 164 | 10 | 49 | 22 | 3 | 248 |
| 260318099.0.2 | 183 | 11 | 47 | 3 | 3 | 247 |
| 260318099.0.4 | 184 | 11 | 47 | 3 | 3 | 248 |

Reviewed By: christophpurrer

Differential Revision: D126904757

Pulled By: javache

fbshipit-source-id: f2d492ec6c326b9ec8b8101832c85b921836f5ce
Summary:
Pull Request resolved: react#59257

Extend RCTBridgeModule with optional launchOptions and performanceLogger properties, injected through RCTBridgeModuleDecorator from RCTInstance. This lets TurboModules read launch configuration and perf events without touching the deprecated bridge property.

Changelog:
[iOS][Added] - Add injectable launchOptions and performanceLogger module APIs

Reviewed By: shwanton

Differential Revision: D126567157

fbshipit-source-id: dca00a46c99cad9a0fd2e65a9fa71441941d411d
Summary:
Pull Request resolved: react#59282

Migrate `ExtraWindowEventListenerTest` from the deprecated
`createCatalystContextForTest` helper to `createTestReactApplicationContext`,
so the test builds a bridgeless context directly. Stub the feature flags
before context creation, since the bridgeless context reads them during init.

Changelog:
[Internal]

Reviewed By: mdvacca

Differential Revision: D126574365

fbshipit-source-id: 5175733861e61949d30fb76f2e9fbabfe9992db0
)

Summary:
Pull Request resolved: react#59367

Selectable text resolves to a distinct paragraph component when prepared text layout is enabled. iOS did not register that component, so it mounted an unimplemented view. Register a private paragraph subclass with the selectable descriptor and reuse the existing text drawing, selection, accessibility, layout and event implementation.

Changelog:
[iOS][Fixed] - Render selectable text when prepared text layout is enabled

Reviewed By: christophpurrer

Differential Revision: D126837680

fbshipit-source-id: 9ffd120f577e691840ad560af7ce6b1d3ec4d88d
…uilds

Differential Revision:
D126904757

Original commit changeset: f2d492ec6c32

Original Phabricator Diff: D126904757

fbshipit-source-id: 859ba1acbd13a9e084d3aed4075ee992359fa672
Summary:
Pull Request resolved: react#59259

Synthesize the injected launchOptions property and read getInitialURL state from it instead of self.bridge. Drops the last bridge read in this module along with its direct bridge header import.

Changelog:
[Internal]

Reviewed By: shwanton

Differential Revision: D126567470

fbshipit-source-id: c816046f23c7b9011a7729102b646af12ad2f113
Summary:
Pull Request resolved: react#59258

Route JS-thread dispatch through the injected dispatchToJSThread block and read the injected performanceLogger. Delete the Views counter, which can only show zero since RCTViewRegistry exposes no enumeration, and reflow the HUD bar.

Changelog:
[iOS][Removed] - Remove the non-functional Views counter from the PerfMonitor HUD

Reviewed By: shwanton

Differential Revision: D126567926

fbshipit-source-id: dddd4bd9d9e4b1842ed1f865e818b3ca8c9beaf8
Summary:
Pull Request resolved: react#59260

Delete the always-false _isBridgeMode helper and fold its call sites to the bridgeless paths. Remove the no-op profiling bridge calls with their synchronize cascade, and simplify setExecutorClass to persist locally plus reload.

Changelog:
[Internal]

Reviewed By: shwanton

Differential Revision: D126574662

fbshipit-source-id: 5c883e086e11c1d4c7dd8a75aa53c259e9147a16
Summary:
Pull Request resolved: react#59264

React Native only runs bridgeless now, so the bridge-mode code path is dead. Remove it, collapse the dual view-manager properties into a single strong reference, and route events through the already-injected dispatcher.

Changelog:
[Internal]

Reviewed By: shwanton

Differential Revision: D126575276

fbshipit-source-id: 36acc9039ca7eefcbf1286403efa45c85fc6406f
Summary:
Pull Request resolved: react#58756

Add a new RNTester example in ScrollViewExample for using the component with TextInputs. This reproduces the issue where TextInputs on Android absorb scroll gestures when textAlign is set to "center" or "right". For TextInputs inside a ScrollView, starting a scroll gesture on the TextInput would not scroll the ScrollView. As this is a common use case, the example acts as a reference to show intended behavior and prevent future regressions.

Changelog: [Internal]

Reviewed By: cortinico

Differential Revision: D122199421

fbshipit-source-id: 2e7df91887fd24f85e2d26633400ef4c168d14b1
Summary:
Pull Request resolved: react#58678

Fixes the issue described in [react#25594](react#25594) and [react#12167](react#12167), where TextInputs on Android absorb scroll gestures when textAlign is set to "center" or "right". For TextInputs inside a ScrollView, starting a scroll gesture on the TextInput would not scroll the ScrollView.

### Root Cause

The root cause is the `onTouchEvent()` function introduced in [commit 372d001](react@372d001). The smart scroll implementation checks if the TextInput can itself scroll in any direction. If it cannot, then the parent can intercept the gesture to apply normal scroll behavior.

However, when textAlign is set to "center" or "right", `canScrollHorizontally()` returns true, as Android treats the TextInput as capable of scrolling left/right. In these cases, the existing conditional logic blocks all scroll gestures, even if a vertical swipe is applied.

### This PR

Modify `ReactEditText` so that we only check to see if the TextInput can scroll in the direction of the provided touch gesture. If a TextInput can scroll horizontally but not vertically, vertical touch swipes will no longer interfere with the scroll gesture. Touch slop is also used to distinguish intentional swipe gestures from small micro-touch events.

Changelog: [Android][Fixed] - Prevent TextInput from preventing scroll gestures

Reviewed By: Abbondanzo

Differential Revision: D121519676

fbshipit-source-id: 23f78c99213dc0c34c2ae81cce11e706f79ec4e5
…e updates (react#57164)

Summary:
Currently we support [unstable_Immediate](https://github.com/facebook/react-native/blob/8915341694db15d97236758a0716215129221bfe/packages/react-native/ReactCommon/react/renderer/core/ConcreteState.h#L76) mode on `updateState` for iOS. This allows us to dispatch [synchronous](https://github.com/expo/expo/blob/9bb3eec52fd94da3c4ed18dc602e01000a3e8c7d/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm#L176) shadow node size updates in Expo UI SwiftUI (communicating SwiftUI children size to Yoga. [Host](https://docs.expo.dev/versions/latest/sdk/ui/swift-ui/host/) for more details).

Since it is not enabled on android, it leads to layout shift when mixing Compose and React Native components as Compose tree currently dispatches size information [asynchronously](https://github.com/expo/expo/blob/e1f6dc9c188ab057f31234f480487fe29835ced2/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt#L202) to the shadow node.

Here, the video demonstrates the asynchronous vs synchronous `updateState` usage (before and after applying the changes in this PR). Repro [link](https://github.com/intergalacticspacehighway/expo-ui-compose-flicker/)

https://github.com/user-attachments/assets/53d7471d-7a85-4487-be91-6a7e2e280fc6

## More:

After enabling the `update_immediate`, I noticed the flicker was still happening. The issue is that update layout instruction triggers a follow up instruction when we set the shadow node size from Expo UI which gets [skipped](https://github.com/facebook/react-native/blob/8915341694db15d97236758a0716215129221bfe/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.kt#L87) to the next frame. This is handled on iOS using `_followUpTransactionRequired` flag [here](https://github.com/facebook/react-native/blob/8915341694db15d97236758a0716215129221bfe/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm#L240). This seems to be added on iOS for similar reasons [here](react@5f0435f) when dispatching an instruction can lead to a follow-up instruction when synchronous update is triggered. So we also handle the sync follow up transaction in this PR using similar approach to iOS's.

## Changelog:

[ANDROID] [ADDED] - Add `unstable_Immediate` UpdateMode support in `updateState`.

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests

Pull Request resolved: react#57164

Test Plan:
- Tested the changes in the [repro](https://github.com/intergalacticspacehighway/expo-ui-compose-flicker/). Also tested RN Tester, it should be a non breaking change.
- Added unit tests to test the follow up re-entrancy event dispatch.

Reviewed By: javache

Differential Revision: D120962440

Pulled By: jasozh

fbshipit-source-id: 2f9387bbdd591745728256c4ed65ee40f7dc6f7c
Summary:
Pull Request resolved: react#59262

The interop layer only ever receives a bridge proxy, so delete the unused bridge ivar, init parameter, and bridge-only command paths. Keep the bridgeless flows and the proxy injection that legacy managers rely on.

Changelog:
[Internal]

Reviewed By: shwanton

Differential Revision: D126575534

fbshipit-source-id: de23006d517cc577b6623cb26d41cbe5a2162fad
Summary:
Pull Request resolved: react#59261

RCTSampleLegacyModule on iOS and macOS stored a bridge that nothing reads. Delete the ivar and setter from both sample files.

Changelog:
[Internal]

Reviewed By: shwanton

Differential Revision: D126575656

fbshipit-source-id: 24554aa151537a14afd4f57ef8de5b5e465933c4
Summary:
Pull Request resolved: react#59254

Replace the deprecated `BridgeReactContext` + mock `CatalystInstance` setup with `createTestReactApplicationContext` and add the `ShadowSoLoader` Robolectric shadow the bridgeless helper needs.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D126543566

fbshipit-source-id: bdaf6af63dbcb7ee0e020e5145a52d03720250dd
Summary:
Pull Request resolved: react#59321

RCTModuleInitTests and RCTModuleInitNotificationRaceTests skip every
test since they rely on the removed bridge. RCTAllocationTests keeps
only its live module-method deallocation test. Remove the dead files
from the Xcode project.

Changelog:
[Internal]

Reviewed By: shwanton

Differential Revision: D126581181

fbshipit-source-id: 8cae13ba423395285b96c4aec8eaeec606d3f756
Summary:
Pull Request resolved: react#59324

The view manager, accessibility manager, event dispatcher, packager
connection, and two Fabric component views imported the bridge headers
without using them. Each keeps the narrower header that provides what
it actually needs.

Changelog:
[Internal]

Reviewed By: cortinico

Differential Revision: D126580840

fbshipit-source-id: 081e025a2b185f4cadba657315d2cc47fb4ffa6e
Summary:
Pull Request resolved: react#59320

Nine React Base implementation files imported the bridge headers
without using any symbol they provide. RCTLog narrows to the module
protocol header, which declares the registry interfaces it messages.

Changelog:
[Internal]

Reviewed By: cortinico

Differential Revision: D126580392

fbshipit-source-id: 1324af388fb6a948d12ee3ac73fcddce951ccd38
Summary:
Native-driven animations (Animated with `useNativeDriver`, the shared
Animation Backend, and third-party libraries like Reanimated's synchronous
props path) apply non-layout props such as `transform` directly to the
mounted views through `synchronouslyUpdateViewOnUIThread`, without a
ShadowTree commit. The committed tree therefore lags behind the screen for
the duration of the animation.

Layout queries (`measure`, `measureInWindow`, `getBoundingClientRect`,
`findNodeAtPoint`, `UIManager::getRelativeLayoutMetrics`) read the committed
`transform` prop, while native hit-testing uses the real view hierarchy.
`Pressability` mixes the two: it measures the responder region with
`measure()` and compares it with the touch coordinates reported by the
platform, so a press with a slight finger movement on a view that is being
animated leaves the stale rectangle and `onPress` is silently dropped
(react#51621, react#36504, react#44768; the sticky header hack
`passthroughAnimatedPropExplicitValues` exists for the same reason).

This adds a `SynchronousTransformOverlay` to `UIManager`: a thread-safe map
from view tag to the last `transform` / `transformOrigin` written
synchronously, recorded at the platform entry points every synchronous
update funnels through (`FabricUIManager.synchronouslyUpdateViewOnUIThread`
and the batched `AnimatedProps` path on Android, `RCTSurfacePresenter` on
iOS, `SchedulerDelegateImpl` on ReactCxxPlatform). The overlay implements a
new `LayoutableShadowNode::TransformOverrideProvider` interface, which
`computeRelativeLayoutMetrics` and `findNodeAtPoint` consult (through
`LayoutInspectingPolicy`) before falling back to `getTransform()`. Entries
are resolved against the committed layout metrics exactly like
`ConcreteViewShadowNode::getTransform` resolves props, live until the view
is deleted (lazy pruning on commit) or until the writer removes them (the
Animation Backend does so when it clears its registry after the
`AnimationEndSync` commit). Libraries that bypass the platform entry points
can call `UIManager::recordSynchronousTransform` directly.

Everything is behind the new `enableSynchronousTransformOverlay` feature
flag (default off). See
`ReactCommon/react/renderer/uimanager/__docs__/SynchronousTransformOverlay.md`.

Changelog:
[GENERAL] [FIXED] - Layout queries and Pressable now take natively-driven `transform` animations into account (behind `enableSynchronousTransformOverlay`)

Test Plan:
- `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedSynchronousTransformOverlay-itest.js`: 16/16 passed (both flag values x `useSharedAnimatedBackend` x `animatedDeferStartOfTimingAnimations`).
- New gtest cases in `LayoutableShadowNodeTest`, `FindNodeAtPointTest` and `SynchronousTransformOverlayTest` (not run locally: no OSS C++ unit test runner).
- `yarn flow-check`, ESLint on the changed JS, `yarn test` for the feature flags, `yarn format`.
- RNTester, new "Pressable inside a natively animated view" example, with the flag enabled (see the follow-up commit): on a Samsung Galaxy A16 (Android 16) and the iPhone 18 Pro simulator, a press with finger drift on the moving button fires both `onPressIn` and `onPress`, and `measure()` reports the live position. On `main` the same press only fires `onPressIn`.
- Not run: `scripts/cxx-api` snapshot regeneration (requires doxygen).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Summary:
Verification-only commit: turns on `enableSynchronousTransformOverlay` in
RNTester on both platforms so the "Pressable inside a natively animated
view" example can be exercised with the overlay. Uses
`dangerouslyForceOverride` on top of the release-level overrides applied at
startup, and (on iOS) undefines `RN_STRICT_API` locally to reach the
private feature flags headers. Not meant to be merged.

Changelog:
[INTERNAL] - RNTester feature flag override for verification

Test Plan:
See the previous commit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@tomekzaw

Copy link
Copy Markdown
Owner Author

Device evidence (flag force-enabled in RNTester).

onPressIn 1, onPress 0 on main

onPressIn 2, onPress 2 with the overlay

onPressIn 2, onPress 2 on iOS

android-overlay-on.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.