From 9a25e8fd83ee1180b54ef4ab653437575c05ece9 Mon Sep 17 00:00:00 2001 From: Oskar Pawica Date: Mon, 17 Aug 2026 10:58:32 +0200 Subject: [PATCH 1/2] Implement final-state-first mount delivery for native layout animations Layout code stores an already prepared, owned AnimationRequest while it intercepts a mounting transaction. A layout-owned pending-start store (PendingNativeLayoutStarts) submits it to the shared service only after the platform mount boundary reports the intended Fabric mount. The Apple boundary uses the synchronous RCTSurfacePresenterObserver signal, which fires after the mount instructions and before paint (D018). Before submission the store validates that the surface still runs, the handle is not cancelled or duplicated, and the mounted view still shows the expected final layout and final props. Surface teardown rejects queued records once and blocks later work until a new surface instance starts. Objective 07 will call the same enqueueNativeLayoutStart entry from production interception; a dev-only bench screen and common tests pass controlled prepared requests through it today. Co-Authored-By: Claude Fable 5 --- .../FinalStateFirstBenchExample.tsx | 97 ++++ .../src/apps/reanimated/examples/index.ts | 8 + apps/fabric-example/ios/Podfile.lock | 2 +- packages/react-native-reanimated/CHANGELOG.md | 1 + .../LayoutAnimationsProxyCommon.cpp | 68 +++ .../LayoutAnimationsProxyCommon.h | 24 +- .../LayoutAnimationsProxy_Experimental.h | 2 + .../LayoutAnimationsProxy_Legacy.cpp | 109 ++++ .../LayoutAnimationsProxy_Legacy.h | 10 + .../LayoutAnimations/LayoutMountBoundary.h | 38 ++ .../PendingNativeLayoutStarts.cpp | 195 ++++++++ .../PendingNativeLayoutStarts.h | 77 +++ .../NativeAnimations/NativeAnimationTrace.cpp | 4 +- .../NativeAnimations/NativeAnimationTrace.h | 6 +- .../NativeAnimations/tests/CMakeLists.txt | 8 +- .../tests/NativeAnimationServiceTest.cc | 3 + .../tests/PendingNativeLayoutStartsTest.cc | 470 ++++++++++++++++++ .../NativeModules/ReanimatedModuleProxy.cpp | 12 +- .../NativeModules/ReanimatedModuleProxy.h | 1 + .../Tools/PlatformDepMethodsHolder.h | 4 +- .../AppleLayoutMountBoundary.h | 15 + .../AppleLayoutMountBoundary.mm | 151 ++++++ .../native/PlatformDepMethodsHolderImpl.mm | 4 + 23 files changed, 1301 insertions(+), 8 deletions(-) create mode 100644 apps/common-app/src/apps/reanimated/examples/LayoutAnimations/FinalStateFirstBenchExample.tsx create mode 100644 packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutMountBoundary.h create mode 100644 packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/PendingNativeLayoutStarts.cpp create mode 100644 packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/PendingNativeLayoutStarts.h create mode 100644 packages/react-native-reanimated/Common/cpp/reanimated/NativeAnimations/tests/PendingNativeLayoutStartsTest.cc create mode 100644 packages/react-native-reanimated/apple/reanimated/apple/NativeAnimations/AppleLayoutMountBoundary.h create mode 100644 packages/react-native-reanimated/apple/reanimated/apple/NativeAnimations/AppleLayoutMountBoundary.mm diff --git a/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/FinalStateFirstBenchExample.tsx b/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/FinalStateFirstBenchExample.tsx new file mode 100644 index 000000000000..c22b40ba757a --- /dev/null +++ b/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/FinalStateFirstBenchExample.tsx @@ -0,0 +1,97 @@ +import { useState } from 'react'; +import { Alert, Button, Pressable, StyleSheet, Text, View } from 'react-native'; + +// Final-state-first bench (Objective 06). The nativeID markers below make the +// Legacy proxy enqueue controlled prepared native requests on example-app +// builds with IOS_LAYOUT_ANIMATIONS_CORE_ANIMATION on; no production Layout +// Animations run here. With the flag on, the box slides 5 s (taps land at the +// final frame) and the delayed view holds invisible for 1 s, then fades in. +// With the flag off, both views jump. + +const LAYOUT_BENCH_NATIVE_ID = 'fsf-bench-layout'; +const ENTERING_BENCH_NATIVE_ID = 'fsf-bench-entering'; + +export default function FinalStateFirstBenchExample() { + const [moved, setMoved] = useState(false); + const [entered, setEntered] = useState(false); + const [lastTap, setLastTap] = useState('none'); + + return ( + + Final-state-first bench (Objective 06) + + Layout: A → B over 5 s +