Skip to content

revert(session-replay-react-native): remove SRMaskView / AmpMask / AmpUnmask stack#1885

Merged
aliaksandr-kazarez merged 3 commits into
mainfrom
revert/srmaskview-ampmask-stack
Jul 15, 2026
Merged

revert(session-replay-react-native): remove SRMaskView / AmpMask / AmpUnmask stack#1885
aliaksandr-kazarez merged 3 commits into
mainfrom
revert/srmaskview-ampmask-stack

Conversation

@aliaksandr-kazarez

@aliaksandr-kazarez aliaksandr-kazarez commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Reverts the display:contents masking stack from @amplitude/session-replay-react-native:

Why

Customer diagnostics on RN 0.83.6 (2026-07-15) proved a rendering defect in the SRMaskView design: the display:contents shadow node always hands the host view zero-size layout metrics, and on Fabric-recycled component views a (0,0,0,0) metrics frame is never applied to the UIKit view — so the widened frame (including origin) from the pointer's previous incarnation persists and masks render at stale screen positions (19/19 zero-metric recycle events in the customer trace kept stale frames; e.g. hosts stuck at x=227 that should be at x=0). The shadow tree stays correct, so JS-side measurement cannot detect it.

A plain-layout-node Fabric mask component tested in the same customer run self-heals in the identical situations. That replacement (a Fabric-native AmpMaskView) lands in a follow-up PR stacked on this one.

Impact

🤖 Generated with Claude Code


Note

High Risk
Breaking removal of beta public APIs and large native/build changes; setup no longer serializes masking registration with capture start, which could reintroduce subtle init races.

Overview
Reverts the experimental layout-transparent masking stack (<AmpMask> / <AmpUnmask>, Fabric SRMaskView, C++ display:contents shadow nodes, and the SRMaskingRegistry seam) from @amplitude/session-replay-react-native, returning masking to the legacy AmpMaskView wrapper only.

The public package no longer exports AmpMask/AmpUnmask or Fabric component codegen (codegenConfig goes from type: "all" to modules only). iOS/Android builds drop Fabric-specific sources, RN ≥ 0.77 floor gates, and Android codegen CMake/header patching for SRMaskView. Native setup()/invalidate() no longer defer work on the UI/main thread to register masking primitives before start().

README, example app Mask demo, and the associated unit/instrumented tests for the removed APIs are deleted; example lockfile bumps react-native-screens.

Reviewed by Cursor Bugbot for commit 12e2c16. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

Copy link
Copy Markdown

size-limit report 📦

Path Size
packages/analytics-browser/lib/scripts/amplitude-min.js.gz 60.95 KB (0%)
packages/session-replay-browser/lib/scripts/session-replay-browser-min.js.gz 134.34 KB (0%)
packages/unified/lib/scripts/amplitude-min.umd.js.gz 214.82 KB (0%)
@amplitude/element-selector (gzipped esm) 2.67 KB (0%)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Invalidate races native setup
    • Added invalidation flag with thread-safe checks in both iOS and Android to serialize setup and invalidate, preventing race conditions during Fast Refresh or bridge teardown.

Create PR

Or push these changes by commenting:

@cursor push 9ce08b488f
Preview (9ce08b488f)
diff --git a/packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativeModule.kt b/packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativeModule.kt
--- a/packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativeModule.kt
+++ b/packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativeModule.kt
@@ -18,6 +18,7 @@
 class SessionReplayReactNativeModule(private val reactContext: ReactApplicationContext) :
   SessionReplayReactNativeSpec(reactContext) {
   private lateinit var sessionReplay: SessionReplay
+  @Volatile private var invalidated = false
 
   override fun getName(): String {
     return NAME
@@ -82,6 +83,12 @@
         privacyConfig = PrivacyConfig(maskLevel = maskLevel),
       )
       
+      if (invalidated) {
+        sessionReplay.shutdown()
+        promise.reject("MODULE_INVALIDATED", "Module was invalidated during setup")
+        return
+      }
+      
       promise.resolve(null)
     } catch (e: Exception) {
       promise.reject("SETUP_ERROR", e.message, e)
@@ -177,6 +184,7 @@
   }
 
   override fun invalidate() {
+    invalidated = true
     if (::sessionReplay.isInitialized) {
       sessionReplay.shutdown()
     }

diff --git a/packages/session-replay-react-native/ios/NativeSessionReplay.swift b/packages/session-replay-react-native/ios/NativeSessionReplay.swift
--- a/packages/session-replay-react-native/ios/NativeSessionReplay.swift
+++ b/packages/session-replay-react-native/ios/NativeSessionReplay.swift
@@ -10,13 +10,14 @@
     
     var sessionReplay: SessionReplay!
     var logger: CoreLogger!
+    private var invalidated = false
     
     override init() {
         print("NativeSessionReplay init")
     }
     
     @objc(setup:resolve:reject:)
-    func setup(_ config: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
+    func setup(_ config: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
         guard let apiKey = config["apiKey"] as? String,
               let sessionId = config["sessionId"] as? NSNumber,
               let serverZone = config["serverZone"] as? String,
@@ -62,10 +63,17 @@
             enableRemoteConfig: enableRemoteConfig
         )
         
-        if (autoStart) {
-            sessionReplay.start()
+        DispatchQueue.main.async { [weak self] in
+            guard let self = self, !self.invalidated else {
+                reject("MODULE_INVALIDATED", "Module was invalidated during setup", nil)
+                return
+            }
+            
+            if (autoStart) {
+                self.sessionReplay?.start()
+            }
+            resolve(nil)
         }
-        resolve(nil)
     }
     
     @objc(setSessionId:resolve:reject:)
@@ -128,6 +136,7 @@
     @objc(invalidate)
     func invalidate() {
         print("invalidate")
+        invalidated = true
         // could be nil here
         sessionReplay?.stop()
         sessionReplay = nil

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 12e2c16. Configure here.

Comment thread packages/session-replay-react-native/ios/NativeSessionReplay.swift

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Reverts the experimental layout-transparent masking stack from @amplitude/session-replay-react-native (Fabric SRMaskView, SRMaskingRegistry seam, and the public <AmpMask>/<AmpUnmask> API), returning the package to the legacy AmpMaskView-only approach.

Changes:

  • Removes the public AmpMask/AmpUnmask API and associated JS/Fabric/native implementations.
  • Removes the masking registry/primitive seam and associated native tests/canaries.
  • Simplifies codegen and build wiring back to TurboModule-only (codegenConfig.type = "modules"), and updates docs/example accordingly.

Reviewed changes

Copilot reviewed 37 out of 39 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/session-replay-react-native/test/specs/SRMaskViewNativeComponent.test.ts Removes codegen-component registration test for SRMaskView.
packages/session-replay-react-native/test/paper/MaskPaper.test.tsx Removes old-arch fallback behavior tests for AmpMask/AmpUnmask.
packages/session-replay-react-native/test/Mask.types.test.ts Removes tests for experimental mask prop/types exports.
packages/session-replay-react-native/test/index.arch.test.tsx Removes tests for index new-arch vs old-arch AmpMask selection/export behavior.
packages/session-replay-react-native/test/fabric/MaskFabric.test.tsx Removes Fabric masking component behavior tests and availability probing tests.
packages/session-replay-react-native/test/mocks/react-native.ts Simplifies React Native Jest mock (removes UIManager mock).
packages/session-replay-react-native/src/specs/SRMaskViewNativeComponent.ts Removes SRMaskView codegen native component spec.
packages/session-replay-react-native/src/paper/MaskPaper.tsx Removes old-arch AmpMask/AmpUnmask guard + fallback implementation.
packages/session-replay-react-native/src/Mask.types.ts Removes experimental mask/unmask prop types and helpers.
packages/session-replay-react-native/src/mask-fallback.tsx Removes shared fallback/warn-once helpers for AmpMask/AmpUnmask.
packages/session-replay-react-native/src/index.tsx Stops exporting AmpMask/AmpUnmask and related types; keeps AmpMaskView export.
packages/session-replay-react-native/src/fabric/MaskFabric.tsx Removes Fabric implementation and fail-closed probing logic.
packages/session-replay-react-native/README.md Removes documentation for experimental layout-transparent masking.
packages/session-replay-react-native/package.json Switches codegenConfig back to TurboModule-only; updates lint/fix globs.
packages/session-replay-react-native/ios/SRMaskingRegistry.m Removes iOS masking registry implementation.
packages/session-replay-react-native/ios/SRMaskingPrimitive.h Removes iOS masking primitive protocol + registry header.
packages/session-replay-react-native/ios/SRDefaultMaskingPrimitive.swift Removes iOS default masking primitive bridge.
packages/session-replay-react-native/ios/NativeSessionReplay.swift Removes deferred main-thread setup/teardown sequencing tied to masking seam.
packages/session-replay-react-native/ios/fabric/SRMaskView.mm Removes iOS Fabric SRMaskView host view implementation.
packages/session-replay-react-native/ios/fabric/SRMaskView.h Removes iOS Fabric SRMaskView header.
packages/session-replay-react-native/example/yarn.lock Updates example lockfile dependencies after removing masking demo stack.
packages/session-replay-react-native/example/package.json Loosens react-native-screens version range in the example app.
packages/session-replay-react-native/example/ios/Podfile.lock Updates pods resolution in example after dependency changes.
packages/session-replay-react-native/example/ios/exampleTests/SRMaskViewTests.mm Removes iOS XCTest canaries for SRMaskView + masking seam.
packages/session-replay-react-native/example/ios/example.xcodeproj/project.pbxproj Removes SRMaskViewTests.mm from Xcode test target.
packages/session-replay-react-native/example/App.tsx Removes the Mask demo screen and AmpMask/AmpUnmask usage from example app.
packages/session-replay-react-native/cpp/SRMaskViewShadowNode.h Removes C++ ShadowNode for display:contents SRMaskView.
packages/session-replay-react-native/cpp/SRMaskViewShadowNode.cpp Removes C++ ShadowNode implementation details/workarounds.
packages/session-replay-react-native/cpp/SRMaskViewComponentDescriptor.h Removes custom ComponentDescriptor binding for SRMaskView shadow node.
packages/session-replay-react-native/android/src/newarch/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativePackage.kt Stops registering the SRMaskView view manager in new-arch package.
packages/session-replay-react-native/android/src/newarch/java/com/amplitude/sessionreplayreactnative/fabric/SRMaskViewPointerEvents.java Removes cross-version pointerEvents shim (SRMaskView removed).
packages/session-replay-react-native/android/src/newarch/java/com/amplitude/sessionreplayreactnative/fabric/SRMaskViewManager.kt Removes Fabric SRMaskView manager.
packages/session-replay-react-native/android/src/newarch/java/com/amplitude/sessionreplayreactnative/fabric/SRMaskView.kt Removes Fabric SRMaskView host implementation.
packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SRMaskingPrimitive.kt Removes Android masking primitive + registry seam.
packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SRDefaultMaskingPrimitive.kt Removes Android default masking primitive bridge.
packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativeModule.kt Removes UI-thread setup serialization and primitive registration logic.
packages/session-replay-react-native/android/src/androidTest/java/com/amplitude/sessionreplayreactnative/SRMaskViewTest.kt Removes Android instrumented tests for SRMaskView + masking seam.
packages/session-replay-react-native/android/build.gradle Removes RN-floor gating/patch tasks and Android test/buildConfig wiring for SRMaskView.
packages/session-replay-react-native/AmplitudeSessionReplayReactNative.podspec Removes Fabric gating and cpp/ios-fabric conditional compilation; ships iOS sources only.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/session-replay-react-native/package.json
@aliaksandr-kazarez
aliaksandr-kazarez merged commit b681b43 into main Jul 15, 2026
17 checks passed
@aliaksandr-kazarez
aliaksandr-kazarez deleted the revert/srmaskview-ampmask-stack branch July 15, 2026 22:24
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.

3 participants