Skip to content

feat(mobile): turn on the React Native New Architecture - #14657

Draft
dylanjeffers wants to merge 1 commit into
chore/mobile-native-release-1.5.189from
feat/mobile-new-architecture
Draft

dylanjeffers wants to merge 1 commit into
chore/mobile-native-release-1.5.189from
feat/mobile-new-architecture

Conversation

@dylanjeffers

@dylanjeffers dylanjeffers commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Turns on React Native's New Architecture (Fabric + bridgeless) for native release 1.5.189, with nothing else changing. It's based on #14656 (the version bump). Everything the flip needs shipped on the old architecture in 1.5.188: RN 0.81.6, the RNTP bridgeless patch (#14647), native-driver progress (#14651), react-native-svg 15.15.1 + Lottie shouldBeRecycled NO (#14652), the SwiftAudioEx Podfile patch (#14650), notifee (#14640) and UIScene (#14626). This supersedes the spike (#14648), whose findings matrix still applies.

Requires #14658 first. On new arch, about half of iOS cold launches stayed on the splash screen. This is a JS race in App.tsx's preload gate, and it isn't new-arch code. Details are below and in #14658.

What changed

  • Android packages/mobile/android/gradle.properties: newArchEnabled=true
  • iOS RCTNewArchEnabled true in every Info.plist that has the key: AudiusReactNative/Info.plist and RichPushExtension/Info.plist
  • RCT_NEW_ARCH_ENABLED=0 → 1 in every place git grep finds it: both pod install steps in .github/workflows/mobile.yml and scripts/postinstall.sh. Set to 1, not removed. RN 0.81's CocoaPods default is already new arch when the variable is unset (ENV["RCT_NEW_ARCH_ENABLED"] == '0' ? false : true), but with an explicit 1, a stale RCT_NEW_ARCH_ENABLED=0 exported in someone's shell or a CI env can't silently build an old-arch binary. It also matches the explicit Info.plist keys.
  • Podfile.lock regenerated with RCT_NEW_ARCH_ENABLED=1 bundle exec pod install: Fabric/codegen subspecs and checksums. The Podfile checksum is unchanged.
  • MainApplication.kt: reactHost is now by lazy { getDefaultReactHost(applicationContext, reactNativeHost) }. It was a getter, and the ReactNativeHost overload of getDefaultReactHost evaluates reactNativeHost.jsBundleFile (so CodePush.getJSBundleFile()) on every access, even though the host itself is cached. With the lazy property, logcat shows one [CodePush] Loading JS bundle line per session. The spike saw 33.
  • Not changed: android:enableOnBackInvokedCallback="false" stays in AndroidManifest.xml. Predictive back is a separate change.
  • Also not changed: android/build.gradle's dr.pogodin_react-native-fs block. It only applies when newArchEnabled != 'true', so it goes inert.

Checks

Check Result
npx tsc --noEmit -p packages/mobile pass
eslint on touched files no JS/TS touched. plutil -lint on both plists, bash -n scripts/postinstall.sh and a YAML parse of mobile.yml pass. #14658's App.tsx passes eslint
cd packages/mobile && npm test 9 suites, 34 tests pass
iOS Release, RCT_NEW_ARCH_ENABLED=1 bundle exec pod install, ENVFILE=.env.prod, generic/platform=iOS Simulator, ARCHS=arm64, worktree-local DerivedData builds. Info.plist 1.1.202, RCTNewArchEnabled=true, bundle carries 1.5.189
Android assembleProdRelease -PreactNativeArchitectures=arm64-v8a, debug keystore builds. versionName 1.1.538, libappmodules.so present, signed CN=Android Debug

iOS smoke test (RN081 iOS 26.5 simulator, signed in, Release)

Installed over the signed-in 1.5.188 app. The CodePush folder was moved aside before every launch. Old-arch numbers are the 1.5.188 Release build on the same simulator and account, measured just before.

Item Result
Runtime is new arch ✅ sample shows the com.facebook.react.runtime.JavaScript thread (old arch: com.facebook.react.JavaScript)
Cold launch, as built ❌ 5 of 6 launches stuck on the splash. JS was idle, and RCTRootComponentView had no children (lldb recursiveDescription). Instrumented runs logged App render preloaded=false → preload resolved → no re-render. Fixed by #14658
Cold launch with #14658's JS ✅ 8 of 8 reach the feed
Feed: render, scroll, pull ✅
First play after a cold launch (Lottie) ✅ still playing at 1 s, 5 s and 10 s. The track-page button and play bar both show pause, and the drawer clock advanced 0:22 → 0:27 (one try)
Auto-advance ✅ from the feed: track 1 seeked to the end → track 2 "The Cat Came Back". From a track page: advanced to the next recommended track
Now playing drawer: open, scrubber, tap-to-seek, pause ✅
Profile: collapsible header collapse/expand, tab swipe Tracks → Albums, tap Reposts ✅
Static own profile CPU, 30 s top -l average, two runs each old 13.6% / 13.3%, new 17.0% / 16.7% → 1.25× (target ≤ ~1.3×). The new-arch run had a paused track in the play bar and the old-arch run had none

Android (emulator, Release189_API_36, clone of Audius_API_36, swiftshader, 4 GB)

  • pm clear, then launch: logcat shows [CodePush] Loading JS bundle from "assets://index.android.bundle" once. No "Legacy Architecture" warning. libfabricjni / libreact_newarchdefaults load, with no FATAL EXCEPTION.
  • Sign-up and Sign In screens render. The APK on the emulator includes fix(mobile): don't leave the splash up when the preload beats the first effect #14658's JS.
  • The guest showed system ANR dialogs (Digital Wellbeing, Pixel Launcher, System UI) under load (guest load avg ~40, 4 vCPU). The app itself didn't ANR.

Not verified

  • Everything in the spike's physical-device checklist: lock-screen and notification controls, Bluetooth, CarPlay/Android Auto, background audio for more than 2 min with CPU-kill watch, pushes, Chromecast connect, video, scrypt timing on device, and a Debug build for the ShadowTree::commit assert.
  • Android signed in: the emulator is left on the Sign In screen for that pass.
  • Whether the splash race also hits Android before fix(mobile): don't leave the splash up when the preload beats the first effect #14658. The emulator run already had the fix.

🤖 Generated with Claude Code

- Android newArchEnabled=true
- iOS RCTNewArchEnabled true in both Info.plists
- pod install with RCT_NEW_ARCH_ENABLED=1 in CI and postinstall
- Podfile.lock regenerated for the Fabric/codegen pods
- MainApplication.reactHost built once (lazy) so CodePush.getJSBundleFile()
  isn't re-read on every access

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 9, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 920917b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@socket-security

Copy link
Copy Markdown

Dependency limit exceeded — report not shown.

This pull request scan exceeded the 10,000-dependency limit applied to this scan, so the results are incomplete and may be inaccurate. To avoid reporting false positives, Socket has not posted a report.

Upgrade your plan to raise the dependency limit and get complete reports, or view the partial scan in the dashboard.

Socket is always free for open source. If this is a non-commercial open source project, contact us to request a free Team account.

dylanjeffers added a commit that referenced this pull request Oct 9, 2026
…st effect (#14658)

## Summary

Cold launches can stay on the splash screen forever. `App` renders
`null` until the AsyncStorage preload finishes, and its `useEffectOnce`
only subscribed to `localStoragePreloadPromise` when the module-level
`localStoragePreloaded` flag was still false. If the preload resolves
after the first render but before the effect runs, the flag is already
true, the effect skips the subscription, and nothing ever sets
`preloaded`. `App` keeps returning `null`, `RootScreen` never mounts,
and `BootSplash.hide` is never called.

The fix subscribes unconditionally. `setPreloaded(true)` is idempotent,
so the extra `.then` on an already-resolved promise costs nothing.

## Why now

Found while smoke-testing the New Architecture flip (#14657). On the new
architecture, passive effects run later relative to the first render,
which opens the window. On the iOS 26.5 simulator with the 1.5.189
new-arch Release build:
- without this fix, 5 of 6 cold launches of the as-built bundle stuck on
the splash (JS idle, `RCTRootComponentView` with no children).
Instrumented runs logged `App render preloaded=false`, then `preload
resolved`, and no further render.
- with this fix, 8 of 8 cold launches reached the feed.

The same race exists on the old architecture (introduced in #14259). It
just loses it less often. 3 of 3 old-arch 1.5.188 launches were fine.

## Rollout

JS-only. It's safe to OTA to 1.5.188. It has to be on main before #14656
/ #14657 merge, so the 1.5.189 binary ships with it. Rebase or merge
main into `chore/mobile-native-release-1.5.189` after this lands.

## Checks
- eslint `packages/mobile/src/app/App.tsx`: pass
- `npx tsc --noEmit -p packages/mobile`: pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant