# ***This second part of this patch is load bearing, do not remove.*** ## UIManager.cpp Patch - Fabric focus navigation use-after-free Fixes Sentry issue APP-T4H9: a SIGSEGV in `FabricUIManagerBinding::findNextFocusableElement` during focus navigation. React Native 0.86 contains the safe implementation behind `fixFindShadowNodeByTagRaceCondition`, but the public default is false. The fallback captures a raw root shadow-node pointer in `tryCommit` and dereferences it after the lock is released, allowing a concurrent commit or surface stop to free the node first. This backports the final upstream implementation, which holds the current revision's `shared_ptr` for the entire traversal. **TODO: Remove after bumping React Native to a release containing facebook/react-native#56850.** ## RefreshControl Patch - iOS 17.4 Haptic Regression Patching `RCTRefreshControl.mm` temporarily to play an impact haptic on refresh when using iOS 17.4 or higher. Since 17.4, there has been a regression somewhere causing haptics to not play on iOS on refresh. Should monitor for an update in the RN repo: https://github.com/facebook/react-native/issues/43388 ## RCTPullToRefreshViewComponentView.mm Patch - iOS 17.4+ haptic regression and iOS 26 progressViewOffset cancellation on New Arch Both bugs share one root cause, established by instrumented frame-logging runs on the iOS 26 simulator (Aug 2026): **writes to a detached `UIRefreshControl` are hazardous, because the control's `_UIRefreshControlModernContentView` bakes in the state it observes at its own creation.** Facts proven by the logs: - `scrollView.refreshControl` assignment inserts the control and creates its content view **synchronously** on iOS 26 (the "UIKit inserts lazily on a later layout pass" folklore is false there). - The content view can also be materialized **earlier** by a pre-attach property write (observed with `tintColor`) while the control is still detached. - The content view positions itself at whatever `bounds.origin` exists at its creation and keeps that y forever - width tracks on later layouts, y never re-pins. Consequences: **1. progressViewOffset.** Stock Fabric writes the offset as a `bounds.origin` shift in `updateProps`, pre-attach. The content view is then created (at insertion) already inside the shifted bounds, pins to it, and cancels the shift exactly - spinner hidden behind the floating home header (home is the only screen passing a non-zero offset). Stock RN appeared to work only by accident: its own pre-attach `tintColor` write materialized the content view at origin 0 *before* the offset write. Possibly related upstream: react-native#54183. **2. Haptic (react-native#43388).** The Paper fix above does not cover Fabric: `updateProps` writes `tintColor` pre-attach, and a tint write on a detached control materializes the content view outside the scroll view, permanently suppressing the trigger haptic on iOS 17.4+ (the creation-time-state story likely explains this too, though the haptic wiring itself is not observable in logs). **The fix**: both `tintColor` and `progressViewOffset` are parked in the component view (`_pendingTintColor` / `_pendingProgressViewOffset`, no `UIRefreshControl` subclass) and applied only once `_refreshControl.superview` is the scroll view - by then the content view exists, was created at origin 0, and a bounds shift lands visibly. Application points: immediately in `_updateX` for runtime changes while attached; in `_attach` right after the assignment (insertion is synchronous); and from `layoutSubviews` with a `setNeedsLayout` re-arm as a fallback should insertion ever be deferred. Supporting changes: - `shouldBeRecycled = NO`: recycled instances get all props force-applied in `updateProps` before the new control is attached, which would re-trigger the pre-attach hazards; opting out keeps every mount on the untouched-before-attach path. - `_updateTitle` no longer writes `attributedTitle = nil` when there is nothing to clear - even a nil write before attach suppresses the haptic. History: an earlier iteration fixed the offset by porting Paper's frame-offset trick into an `RCTHapticCompatibleRefreshControl` subclass (worked, verified on device) - replaced by the deferral once the root cause was understood. The control's `didMoveToSuperview` appeared broken as a tint application point in early non-rigorous testing; unproven, not disproven. Upstream issue #43388 still open as of Aug 2026. Haptics cannot be verified on the simulator - physical device only. Spinner position verified via frame logs; haptic on this variant NOT yet device-verified. Opened issue in RN repo: https://github.com/react/react-native/issues/57843 ## RCTEnhancedScrollView.mm / RCTScrollViewComponentView.mm Patch - centerContent insets stale after content resize on New Arch **TODO: Remove after bumping React Native to 0.87+** (fixed upstream by facebook/react-native#56832, commit d50c1b5207; first shipped in 0.87.0-rc.0). On Fabric, `centerContent` centers by computing `contentInset` in `centerContentIfNeeded`, but that recompute only ran on `setFrame`/`didAddSubview`/`scrollViewDidZoom` - not when a state update assigns a new `contentSize` in `updateState`. Any content that resizes after mount inside a `centerContent` ScrollView (e.g. the lightbox image crop view getting its real aspect ratio from `onLoad` when the embed has no aspectRatio metadata) keeps the old insets: content rests off-center and the excess inset creates phantom scroll range, so the image can be dragged and parked off-center and the native scroll steals the swipe-down-to-dismiss pan. The old architecture paired every `contentSize` update with re-centering in `RCTScrollView.updateContentSizeIfNeeded`; Fabric dropped that link. Backport of the upstream fix: `setContentSize:`/`setCenterContent:` overrides on `RCTEnhancedScrollView` that call `centerContentIfNeeded`, plus the `updateProps` guards so the `contentInset` prop does not fight the computed centering inset. Issue: https://github.com/facebook/react-native/issues/55090 ## RCTScrollViewComponentView.mm Patch - ScrollView pinch/pan ignored outside content area on New Arch **TODO: Remove after bumping React Native to 0.87+** (fixed upstream by facebook/react-native#56747, commit efcab20908; first shipped in 0.87.0-rc.0). On Fabric, `betterHitTest` in `RCTScrollViewComponentView` deliberately skips the `_containerView` and hit-tests its grandchildren, returning `self` (the wrapper component view) when the touch lands inside the scroll view bounds but outside any content. UIKit only delivers touches to a gesture recognizer when the hit view is the recognizer's view or a descendant of it, and the `UIScrollView` is a *child* of the wrapper - so its native pinch/pan recognizers never see those touches. In the lightbox this means pinch-to-zoom and pan-while-zoomed only respond when the fingers are over the image itself, not over the letterbox bars above/below it. On the old architecture, default UIKit hit-testing returns the `UIScrollView` itself for those touches, so everything works. Backport of the upstream one-liner: return `_scrollView` instead of `self` so touches in the content-less area are attributed to the `UIScrollView`. Issue: https://github.com/facebook/react-native/issues/54123 PR: https://github.com/react/react-native/pull/56747 ## ReactViewGroup.kt Patch - Fatal "Required value was null" during subview clipping on Android Fixes Sentry issue APP-T20Q: `IllegalStateException: Required value was null` thrown by `checkNotNull(allChildren?.get(idx))` in `updateSubviewClipStatus`, reached from `ReactScrollView.onScrollChanged -> updateClippingRect` during an animated smooth scroll (New Architecture, `removeClippedSubviews`). The clipping loop in `updateClippingToRect` captures its bound once, but clipping a view (`removeViewsInLayout`) can synchronously trigger reentrant child removal (layout-change listeners, animation-end callbacks, Fabric mounting on the UI thread), which compacts `allChildren` and nulls the tail mid-loop. Upstream already catches the `IndexOutOfBoundsException` variant of this corruption with diagnostics, but the null-child variant throws `IllegalStateException` and escapes as a fatal crash. A null entry means the view is already detached, so we skip it and count it as clipped to keep index math aligned. Not fixed upstream as of July 2026 (identical `checkNotNull` on `main`); the sibling fix attempt facebook/react-native#57365 for the same bookkeeping corruption (different stack) was abandoned. Re-check when bumping React Native. Note on build modes: production Android builds compile react-android from source (`buildReactNativeFromSource: IS_PRODUCTION` via expo-build-properties in app.config.js injects the includeBuild/dependency-substitution block at prebuild), so this hunk IS active in production releases. Local dev builds prebuilt in a non-production env consume the prebuilt AAR from Maven Central instead, where this hunk (like any ReactAndroid source change) has no effect - do not expect to see the fix in a local debug build unless you prebuild with EXPO_PUBLIC_ENV=production or add the substitution block manually. ## RCTFontUtils.mm Patch - Custom font weights render as the heaviest face on New Arch **TODO: Remove after bumping React Native to a release that contains facebook/react-native#57483** (commit 918fb15bfe5f, on `main`; not in 0.86 and not yet released). Backport of the upstream one-liner: use a real ternary so the numeric weight is returned instead of the boolean. For a double, `(A != 0.0) ? A : B` is exactly equivalent to the original `A ?: B`. PR: https://github.com/facebook/react-native/pull/57483 ## RCTTextLayoutManager.mm Patch - Text overflows instead of wrapping on the last line Issue: https://github.com/react/react-native/issues/53450#issuecomment-3298157830 Bandaid fix taken from: https://github.com/react/react-native/commit/581d643a9e59fd88f93757f80194e1efd11bd0e5 ## RCTViewComponentView.mm Patch - Hairline border strokes dropped at certain subpixel Y offsets on New Arch Symptom: dividers built as `borderTopWidth: hairlineWidth` vanish on some screens and not others, deterministically by the view's absolute subpixel Y. A background fill of the same geometry always renders. Cause: Fabric draws borders as a stretched 9-slice image. The consumer (`RCTAddContourEffectToLayer`) hardcodes the stretchable middle as a 1pt band, which matched the image the producer built until RN 0.81. facebook/react-native#54237 changed the image size to `ceil(insets) + 1 + ceil(insets)` without updating that formula, so for fractional (hairline) insets the labels no longer match the image: transparent filler is treated as a rigid cap, and when squeezed into a one-pixel-tall layer the sampling can land on it instead of the stroke - no line. Fix: compute the middle from the cap insets (`size - caps`) instead of assuming 1pt. Upstream issue: https://github.com/react/react-native/issues/58054 (repro: https://github.com/abulenok/HairlineBorderRepro, fails identically on 0.86.0 and 0.87.0).