From 318bb2176aee16d9c55d6b5742571f54da313b00 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 29 Jun 2026 04:07:52 -0700 Subject: [PATCH 1/2] Fix missing VERSION_NATIVE_FB in commit artifacts (#36889) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: ## Summary The `runtime_commit_artifacts` workflow has been failing in the "move relevant files for react in fbsource into compiled-rn" step with: ``` mv: cannot stat 'build/facebook-react-native/VERSION_NATIVE_FB': No such file or directory ``` `VERSION_NATIVE_FB` was only written in `processExperimental`, *inside* `build/facebook-react-native/`. That directory is not part of the allowlist in the cleanup loop at the end of `processExperimental`, so the experimental workers wrote the version file and then immediately deleted it. This was masked until #36859. The cleanup loop previously used `rm -rm` (an invalid flag), so `rm` errored out and deleted nothing — the loop was effectively a no-op. Once it was changed to `fs.rmSync`, the deletion actually took effect. After CI merges the per-worker artifacts, `facebook-react-native/*/cjs` still survives (the **stable** workers produce it and `processStable` has no cleanup loop), but `VERSION_NATIVE_FB` is gone because stable never wrote it. ## Fix The `build/facebook-react-native` artifacts are identical across release channels: - RN packages resolve to the generic `.fb.js` entry fork (no `.modern.fb.js` / `.classic.fb.js` split). - The `native-fb` feature-flag forks don't reference `__EXPERIMENTAL__`. - The version string is identical (`-native-fb--`). So this writes `VERSION_NATIVE_FB` from `processStable`, where `facebook-react-native` is preserved, and drops the now-dead write from `processExperimental`. ## Test Plan Verified locally by building the `react` package through each code path: - **Stable channel** (`yarn build --r=stable react/index`): `build/facebook-react-native/VERSION_NATIVE_FB` is written with the correct `19.3.0-native-fb--` version string. ✅ - **Experimental channel** (`yarn build --r=experimental react/index`): `build/facebook-react-native` is deleted by the cleanup loop, leaving only `oss-experimental` and `facebook-www` — confirming the version file cannot come from this channel. ✅ - **Local merged build** (`yarn build react/index`, builds both channels and merges): the final `build/facebook-react-native/` contains `VERSION_NATIVE_FB`, matching what CI produces after merging per-worker artifacts. ✅ DiffTrain build for [68631c0453b08e2c7c96a40910f4c91db1f66d5a](https://github.com/react/react/commit/68631c0453b08e2c7c96a40910f4c91db1f66d5a) Reviewed By: rickhanlonii, zeyap Differential Revision: D109860005 --- .../Renderer/shims/ReactNativeTypes.js | 72 +++++++++---------- .../shims/ReactNativeViewConfigRegistry.js | 8 +-- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/packages/react-native/Libraries/Renderer/shims/ReactNativeTypes.js b/packages/react-native/Libraries/Renderer/shims/ReactNativeTypes.js index 24cc37e31e76..6473a5f5be13 100644 --- a/packages/react-native/Libraries/Renderer/shims/ReactNativeTypes.js +++ b/packages/react-native/Libraries/Renderer/shims/ReactNativeTypes.js @@ -7,7 +7,7 @@ * @noformat * @nolint * @flow strict - * @generated SignedSource<<4ab83fd2606d6a4d374ef914f231d9c1>> + * @generated SignedSource<<92e869744dd405a4d74b1b6f50fa8d52>> */ import type { @@ -25,33 +25,33 @@ import * as React from 'react'; export type AttributeType = | true - | $ReadOnly<{ + | Readonly<{ diff?: (arg1: T, arg2: T) => boolean, process?: (arg1: V) => T, }>; -// We either force that `diff` and `process` always use mixed, +// We either force that `diff` and `process` always use unknown, // or we allow them to define specific types and use this hack export type AnyAttributeType = AttributeType<$FlowFixMe, $FlowFixMe>; -export type AttributeConfiguration = $ReadOnly<{ +export type AttributeConfiguration = Readonly<{ [propName: string]: AnyAttributeType | void, - style?: $ReadOnly<{ + style?: Readonly<{ [propName: string]: AnyAttributeType, ... }>, ... }>; -export type ViewConfig = $ReadOnly<{ - Commands?: $ReadOnly<{[commandName: string]: number, ...}>, - Constants?: $ReadOnly<{[name: string]: mixed, ...}>, +export type ViewConfig = Readonly<{ + Commands?: Readonly<{[commandName: string]: number, ...}>, + Constants?: Readonly<{[name: string]: unknown, ...}>, Manager?: string, - NativeProps?: $ReadOnly<{[propName: string]: string, ...}>, + NativeProps?: Readonly<{[propName: string]: string, ...}>, baseModuleName?: ?string, - bubblingEventTypes?: $ReadOnly<{ - [eventName: string]: $ReadOnly<{ - phasedRegistrationNames: $ReadOnly<{ + bubblingEventTypes?: Readonly<{ + [eventName: string]: Readonly<{ + phasedRegistrationNames: Readonly<{ captured: string, bubbled: string, skipBubbling?: ?boolean, @@ -59,8 +59,8 @@ export type ViewConfig = $ReadOnly<{ }>, ... }>, - directEventTypes?: $ReadOnly<{ - [eventName: string]: $ReadOnly<{ + directEventTypes?: Readonly<{ + [eventName: string]: Readonly<{ registrationName: string, }>, ... @@ -70,7 +70,7 @@ export type ViewConfig = $ReadOnly<{ validAttributes: AttributeConfiguration, }>; -export type PartialViewConfig = $ReadOnly<{ +export type PartialViewConfig = Readonly<{ bubblingEventTypes?: ViewConfig['bubblingEventTypes'], directEventTypes?: ViewConfig['directEventTypes'], supportsRawText?: boolean, @@ -78,22 +78,22 @@ export type PartialViewConfig = $ReadOnly<{ validAttributes?: AttributeConfiguration, }>; -type InspectorDataProps = $ReadOnly<{ +type InspectorDataProps = Readonly<{ [propName: string]: string, ... }>; type InspectorDataGetter = ( - ( + ( componentOrHandle: React.ElementRef | number, ) => ?number, -) => $ReadOnly<{ +) => Readonly<{ measure: (callback: MeasureOnSuccessCallback) => void, props: InspectorDataProps, }>; -export type InspectorData = $ReadOnly<{ - closestInstance?: mixed, +export type InspectorData = Readonly<{ + closestInstance?: unknown, hierarchy: Array<{ name: ?string, getInspectorData: InspectorDataGetter, @@ -103,11 +103,11 @@ export type InspectorData = $ReadOnly<{ componentStack: string, }>; -export type TouchedViewDataAtPoint = $ReadOnly< +export type TouchedViewDataAtPoint = Readonly< { pointerY: number, touchedViewTag?: number, - frame: $ReadOnly<{ + frame: Readonly<{ top: number, left: number, width: number, @@ -119,39 +119,39 @@ export type TouchedViewDataAtPoint = $ReadOnly< export type RenderRootOptions = { onUncaughtError?: ( - error: mixed, - errorInfo: {+componentStack?: ?string}, + error: unknown, + errorInfo: {readonly componentStack?: ?string}, ) => void, onCaughtError?: ( - error: mixed, + error: unknown, errorInfo: { - +componentStack?: ?string, + readonly componentStack?: ?string, // $FlowFixMe[unclear-type] unknown props and state. // $FlowFixMe[value-as-type] Component in react repo is any-typed, but it will be well typed externally. - +errorBoundary?: ?React.Component, + readonly errorBoundary?: ?React.Component, }, ) => void, onRecoverableError?: ( - error: mixed, - errorInfo: {+componentStack?: ?string}, + error: unknown, + errorInfo: {readonly componentStack?: ?string}, ) => void, onDefaultTransitionIndicator?: () => void | (() => void), }; -export opaque type Node = mixed; -export opaque type InternalInstanceHandle = mixed; +export opaque type Node = unknown; +export opaque type InternalInstanceHandle = unknown; export type ReactFabricType = { - findHostInstance_DEPRECATED( + findHostInstance_DEPRECATED( componentOrHandle: ?(React.ElementRef | number), ): ?PublicInstance, - findNodeHandle( + findNodeHandle( componentOrHandle: ?(React.ElementRef | number), ): ?number, dispatchCommand( handle: PublicInstance, command: string, - args: Array, + args: Array, ): void, isChildPublicInstance(parent: PublicInstance, child: PublicInstance): boolean, sendAccessibilityEvent(handle: PublicInstance, eventType: string): void, @@ -211,7 +211,7 @@ export type LayoutAnimationProperty = | 'scaleY' | 'scaleXY'; -export type LayoutAnimationAnimationConfig = $ReadOnly<{ +export type LayoutAnimationAnimationConfig = Readonly<{ duration?: number, delay?: number, springDamping?: number, @@ -220,7 +220,7 @@ export type LayoutAnimationAnimationConfig = $ReadOnly<{ property?: LayoutAnimationProperty, }>; -export type LayoutAnimationConfig = $ReadOnly<{ +export type LayoutAnimationConfig = Readonly<{ duration: number, create?: LayoutAnimationAnimationConfig, update?: LayoutAnimationAnimationConfig, diff --git a/packages/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js b/packages/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js index 436b74da7674..0eb4d2505ae3 100644 --- a/packages/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js +++ b/packages/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js @@ -7,7 +7,7 @@ * @noformat * @nolint * @flow strict-local - * @generated SignedSource<<1f7876c0dc0b05685a730513dc410236>> + * @generated SignedSource<<260b8a01781c54c31a520f00cb1bde7f>> */ 'use strict'; @@ -17,8 +17,8 @@ import invariant from 'invariant'; // Event configs export const customBubblingEventTypes: { - [eventName: string]: $ReadOnly<{ - phasedRegistrationNames: $ReadOnly<{ + [eventName: string]: Readonly<{ + phasedRegistrationNames: Readonly<{ captured: string, bubbled: string, skipBubbling?: ?boolean, @@ -26,7 +26,7 @@ export const customBubblingEventTypes: { }>, } = {}; export const customDirectEventTypes: { - [eventName: string]: $ReadOnly<{ + [eventName: string]: Readonly<{ registrationName: string, }>, } = {}; From ba3354826cdfe9d514e010a534633a55cbf82e86 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 29 Jun 2026 04:07:52 -0700 Subject: [PATCH 2/2] Clean up `enableEagerAlternateStateNodeCleanup` Summary: The `enableEagerAlternateStateNodeCleanup` flag is no longer read by the renderer, so the feature-flag definition and the two prelude overrides that forced it on become dead code. Drop the flag entry from `ReactNativeInternalFeatureFlags` (incl. its test mirror and the Fantom mock) and remove the `= true` assignments from the Airwave and FB4TV preludes. The dedicated `VirtualView-enableEagerAlternateStateNodeCleanup-itest.js` is folded into `VirtualView-itest.js`'s `memory management` describe so the shadow-node retention regression test continues to run, just no longer keyed on a non-existent flag. Vendored React (`react-devtools-facade`), the append-only mobileconfig stable_id, and the generated `react-devtools-core` bundles are intentionally untouched. Changelog: [Internal] Differential Revision: D110043719 --- ...bleEagerAlternateStateNodeCleanup-itest.js | 47 ------------------- .../__tests__/VirtualView-itest.js | 23 +++++++++ .../mocks/ReactNativeInternalFeatureFlags.js | 6 --- 3 files changed, 23 insertions(+), 53 deletions(-) delete mode 100644 packages/react-native/src/private/components/virtualview/__tests__/VirtualView-enableEagerAlternateStateNodeCleanup-itest.js diff --git a/packages/react-native/src/private/components/virtualview/__tests__/VirtualView-enableEagerAlternateStateNodeCleanup-itest.js b/packages/react-native/src/private/components/virtualview/__tests__/VirtualView-enableEagerAlternateStateNodeCleanup-itest.js deleted file mode 100644 index 06a87fadbf2e..000000000000 --- a/packages/react-native/src/private/components/virtualview/__tests__/VirtualView-enableEagerAlternateStateNodeCleanup-itest.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - * @format - */ - -import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; - -import {createShadowNodeReferenceCountingRef} from '../../../__tests__/utilities/ShadowNodeReferenceCounter'; -import VirtualView, {_logs, VirtualViewMode} from '../VirtualView'; -import {dispatchModeChangeEvent} from './VirtualView-itest'; -import * as Fantom from '@react-native/fantom'; -import * as React from 'react'; -import {createRef} from 'react'; -import {Text} from 'react-native'; - -beforeEach(() => { - _logs.states = []; -}); - -describe('VirtualView with enableEagerAlternateStateNodeCleanup flag', () => { - test('does not retain shadow node after becoming hidden', () => { - const root = Fantom.createRoot(); - - const [getReferenceCount, childRef] = - createShadowNodeReferenceCountingRef(); - const viewRef = createRef>(); - - Fantom.runTask(() => { - root.render( - - Child - , - ); - }); - - expect(getReferenceCount()).toBeGreaterThan(0); - - dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Hidden); - - expect(getReferenceCount()).toBe(0); - }); -}); diff --git a/packages/react-native/src/private/components/virtualview/__tests__/VirtualView-itest.js b/packages/react-native/src/private/components/virtualview/__tests__/VirtualView-itest.js index bcc17b87f054..06436e9e4175 100644 --- a/packages/react-native/src/private/components/virtualview/__tests__/VirtualView-itest.js +++ b/packages/react-native/src/private/components/virtualview/__tests__/VirtualView-itest.js @@ -15,6 +15,7 @@ import type {NativeModeChangeEvent} from '../VirtualViewNativeComponent'; import ensureInstance from '../../../__tests__/utilities/ensureInstance'; import isUnreachable from '../../../__tests__/utilities/isUnreachable'; +import {createShadowNodeReferenceCountingRef} from '../../../__tests__/utilities/ShadowNodeReferenceCounter'; import {getNodeFromPublicInstance} from '../../../../../Libraries/ReactPrivate/ReactNativePrivateInterface'; import ReactNativeElement from '../../../webapis/dom/nodes/ReactNativeElement'; import VirtualView, {_logs, VirtualViewMode} from '../VirtualView'; @@ -291,6 +292,28 @@ describe('memory management', () => { expect(isUnreachable(nullthrows(weakRef))).toBe(true); }); + + test('does not retain shadow node after becoming hidden', () => { + const root = Fantom.createRoot(); + + const [getReferenceCount, childRef] = + createShadowNodeReferenceCountingRef(); + const viewRef = createRef>(); + + Fantom.runTask(() => { + root.render( + + Child + , + ); + }); + + expect(getReferenceCount()).toBeGreaterThan(0); + + dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Hidden); + + expect(getReferenceCount()).toBe(0); + }); }); /** diff --git a/private/react-native-fantom/runtime/mocks/ReactNativeInternalFeatureFlags.js b/private/react-native-fantom/runtime/mocks/ReactNativeInternalFeatureFlags.js index 133a0bee1baf..140add8e8685 100644 --- a/private/react-native-fantom/runtime/mocks/ReactNativeInternalFeatureFlags.js +++ b/private/react-native-fantom/runtime/mocks/ReactNativeInternalFeatureFlags.js @@ -9,12 +9,6 @@ */ module.exports = { - // When enableEagerAlternateStateNodeCleanup is enabled, alternate.stateNode is proactively - // pointed towards finishedWork's stateNode, releasing resources sooner. - // With enableEagerAlternateStateNodeCleanup enabled, we can remove workarounds in tests - // and have predictable memory model. - // See https://github.com/facebook/react/pull/33161 for details. - enableEagerAlternateStateNodeCleanup: true, enableFragmentRefs: true, enableFragmentRefsInstanceHandles: true, enableFragmentRefsTextNodes: true,