Skip to content

feat(ReJest): record native updates from platform mutations on new arch#9653

Draft
piaskowyk wants to merge 1 commit into
mainfrom
rejest-new-arch-compatibility
Draft

feat(ReJest): record native updates from platform mutations on new arch#9653
piaskowyk wants to merge 1 commit into
mainfrom
rejest-new-arch-compatibility

Conversation

@piaskowyk

Copy link
Copy Markdown
Member

Summary

ReJest's recording of native animation updates broke on the new
architecture. It read native prop values synchronously from the shadow node
(_obtainPropgetNewestCloneOfShadowNode), which on the new arch
deadlocks against in-flight Fabric commits and returns stale values once
animations are applied via commits / the animation backend / Core Animation.
This is why several toMatchNativeSnapshots / getNativeSnapshots tests were
disabled with // TODO: ... it hangs and // tag is not passed to _updateProps.

This PR records native updates the way the user suggested — by listening to
the ShadowViewMutations Reanimated sends to the platform, reusing the same
pullTransaction funnel the layout-animation delegate already owns — and feeds
them into the ReJest matchers.

How it works

JS channel (values the worklet computes) is still captured via the existing
_updateProps / _notifyAboutProgress interception.

Native channel (values sent to the platform) is now captured in C++:

  • New NativeMutationsRegistry (Common/cpp/reanimated/Fabric/updates/) — a
    thread-safe recorder of the ShadowViewMutations sent to the platform, plus
    Core Animation transition descriptors (from/to/duration) routed to the
    iOS CALayer (which emit no per-frame mutation).
  • record(filteredMutations) is called at the end of both
    LayoutAnimationsProxy_Experimental::pullTransaction and _Legacy::pullTransaction,
    so it captures animated styles, layout animations and shared element
    transitions through one chokepoint. CSS routed to Core Animation is captured
    at the CSSPlatformTransitionProxy::run boundary.
  • Exposed to the UI runtime via new globals (_startRecordingNativeMutations,
    _stopRecordingNativeMutations, _clearRecordedNativeMutations,
    _getRecordedNativeMutations, _obtainLatestRecordedProp).
  • UpdatesContainer.getNativeSnapshots now reconstructs native snapshots from
    the recorded mutation stream
    instead of reading the shadow node per frame —
    which is what removes the deadlock — while preserving the existing matcher /
    frame-lag contract.

All native code is gated behind the existing RUNTIME_TEST_FLAG static feature
flag
(if constexpr + a runtime atomic), so it is compiled out / a no-op in
production builds.

Also fixes a latent bug: _updateProps operations were missing their tag, so
ReJest could not group recorded updates per component (the // tag is not passed
TODOs).

Verification

Built and run on the iOS simulator (fabric-example, new arch, experimental
proxy + IOS_CSS_CORE_ANIMATION). The new native updates suite passes:

Native mutations recording (new architecture)
 ✔ records the native opacity mutations sent to the platform   (20 mutations, opacity 1 → 0)
 ✔ native snapshots track the JS-computed values of an animation
🧮 Tests summary: 2 passed, 0 failed, 0 skipped
✅ All tests passed!

The C++ recorder captured the real per-frame native opacity values (1 → 0) and
toMatchNativeSnapshots confirmed they match the JS-computed values — the
cross-check the framework lost on the new arch.

Still draft — follow-ups

  • Layout animations & shared element transitions: covered by the same
    recording call site (the experimental proxy's filteredMutations); dedicated
    tests are blocked by a pre-existing harness hang on entering/exiting
    animations (the entering and exiting / layout transitions suites are
    already disabled).
  • CSS animations (iOS): in this config they don't surface as shadow
    mutations (totalRecorded == 0) and the CSS-animation component's unmount
    hangs the harness — needs investigation of the iOS CSS apply path.
  • CSS + Core Animation descriptor: the recording hook is wired at
    CSSPlatformTransitionProxy::run; the iOS opacity transition did not surface a
    descriptor in this run — needs a closer look at the routing.
  • _obtainLatestRecordedProp is installed but currently unused by the JS side
    (kept as a usable API); could be removed.

🤖 Generated with Claude Code

ReJest's native-update recording broke on the new architecture. It read
native prop values synchronously from the shadow node
(getNewestCloneOfShadowNode), which deadlocks against in-flight Fabric
commits and returns stale values once animations are applied via commits,
the animation backend, or Core Animation.

This adds a C++ NativeMutationsRegistry that records the ShadowViewMutations
Reanimated sends to the platform - tapping the same pullTransaction funnel
the layout-animations delegate owns - plus the Core Animation transition
descriptors routed to the iOS CALayer. ReJest reads them back through new
UI-runtime globals and reconstructs native snapshots from them, so
toMatchNativeSnapshots works again without any per-frame native reads. All
native code is gated behind the existing RUNTIME_TEST_FLAG static feature
flag (zero cost in production builds).

Also fixes the missing `tag` on `_updateProps` operations that prevented
ReJest from grouping recorded updates per component.

Verified on the iOS simulator (fabric-example): the new
nativeMutationsRecording suite passes - native opacity mutations are
recorded (1 -> 0) and match the JS-computed values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant