Skip to content

Upgrade to React Native 0.86 / Expo SDK 57 (new architecture) - #6064

Draft
peachbits wants to merge 37 commits into
developfrom
phase2-rn-upgrade
Draft

Upgrade to React Native 0.86 / Expo SDK 57 (new architecture)#6064
peachbits wants to merge 37 commits into
developfrom
phase2-rn-upgrade

Conversation

@peachbits

@peachbits peachbits commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

A single hop from RN 0.79 / old architecture to RN 0.86 / Expo SDK 57 with the new
architecture enabled on both platforms. SDK 57 is the Expo release paired with RN 0.86,
so the two land together as one upgrade surface.

Android performance (release-build Flashlight benchmarks, OnePlus 5T): scroll jank
7.3% → 2.5%, worst-case scroll FPS 33 → 48, peak scroll CPU −38%, whole-journey CPU −42%
versus the new-arch baseline. Cost: RAM +45% (456 → 662 MB mean) — the expected price of
Fabric plus the modern ecosystem. Raw data and method in perf/RESULTS.md on the
workspace, headline numbers in the changelog entry.

iOS: Hermes V1 + React-Core built from source (the prebuilt 0.86 RNCore is compiled
against the non-V1 JSI ABI and dyld-crashes at launch), pinned in the Podfile. ShadowTree
commit-exhaustion protection via a 1-line patch-package flag flip.

Android: new architecture enabled; ShadowTree protection applied at runtime (the
prebuilt core ignores the iOS source patch); Fabric view recycling off (recycled views
arrive with stale visual state); blur dropped below API 31 in favor of solid colors
(RenderScript cannot snapshot Fabric content); drawer outside-tap overlay backported from
react-navigation 7 for devices that cannot animate it.

Fixes along the way: text truncates instead of shrinking illegibly (Fabric ignores
minimumFontScale), modals stay above the keyboard, amount-field clipping/jumping fixed,
fontSize-0 crash on animated icons clamped, YOLO auto-login race fixed (dev-only path).

Dependency modernization (same PR)

Replaces legacy-interop UI libraries with Fabric-native equivalents already in the tree,
in lockstep with the matching edge-login-ui-rn PR (the two share these dependencies —
login-ui declares them as devDependencies and relies on the app to install them).

  • expo-linear-gradient replaces react-native-linear-gradient (no codegen spec; every
    gradient rendered through the legacy view-manager interop). Same props; themes now
    declare gradient colors as a tuple of ≥ 2 stops, which the component requires. The old
    package is fully removed once login-ui migrates (its six scenes were the last imports).
  • keyboard-controller's KeyboardAwareScrollView replaces
    react-native-keyboard-aware-scroll-view (unmaintained since 2022; positions itself with
    findNodeHandle/UIManager measurement APIs that the new architecture only serves through
    a shim). extraScrollHeightbottomOffset; the Android opt-ins go away. One behavior
    change: SurveyModal's per-platform offset hack collapses to the app-standard 2.75rem —
    needs a QA look on both platforms (suppressed in dev builds).
  • One copy of airship and patina. login-ui's older ranges made npm install nested
    duplicates of two UI singletons (airship owns the modal portal host, patina caches
    styles). Aligned versions plus npm overrides so the dedupe holds until login-ui's
    next release.
  • Modal blur restored on Android 12+. The old blur backend snapshots the window in
    a way the new architecture renders as empty (gray wash below API 31, fully transparent
    above). Modals now blur via expo-blur (Dimezis 3 hardware snapshot, Fabric-native),
    sampling an explicit BlurTarget mounted around the app content; below Android 12 the
    solid sheet stays, per the accepted decision. Chrome (header, scene footer, tab bar,
    notification cards) blurs the focused scene's content via per-scene targets that
    SceneWrapper publishes while focused. iOS unchanged.
  • react-native-performance 6.0.0 — fixes new-architecture detection on RN 0.77+
    and an Android event-emitter init race; verified native launch marks still flow
    through to JS measures on device.
  • react-native-sound 0.13.0 and react-native-haptic-feedback 3.0.0 — both move from
    bridge interop to codegen; haptic 3.x is a TurboModule (login-ui bumps its range in
    lockstep, with an npm override holding one copy until its release).
  • Dead code: the write-only global.ReactNativeBlurView publication (login-ui stopped
    reading it in v2.15.0), deprecated-react-native-prop-types, @types/react-native.
  • The login-ui patch is regenerated against 3.37.2 (was misnamed 3.36.0, warning on every
    install).

Deliberately NOT swapped: rn-id-blurview → expo-blur would lose real blur on Android 12+
(expo-blur's Android blur defaults to none; its opt-in method is the one already rejected
for performance and broken-in-modals reasons).

Verified on device (Android): gradients across home/assets/PIN-login, keyboard scroll on
the address form, auto-logoff modal through the portal host. iOS: build + smoke pending
pod install (expo-linear-gradient is a new pod).


Known open (details in the review talking points):

  • Amount input discards typing on Android until the field is tapped (Fabric IME-binding
    regression; four fixes attempted, likely needs upstream).
  • Tab-scene header overlap on Android (react-navigation v6 useHeaderHeight under
    Fabric; the v7 migration is the real fix).
  • Release-build accessibility-tree retest + TalkBack pass outstanding.
  • Modal sheets render without blur on all Android (see-through over busy scenes) — fixed
    in the stacked dependency-modernization PR: real blur on Android 12+ via expo-blur's
    Fabric-native backend, solid sheets below. Chrome (header/footer/tab bar/cards)
    blurs the focused scene again via per-scene blur targets.
  • Solid blur-fallback colors need a design opinion.
  • Jenkins iOS: the agent's Xcode upgrade surfaced a Swift ambiguity in
    expo-modules-jsi 57.0.3 (type of expression is ambiguous without a type annotation expo/expo#47957); fixed by bumping the lockfile to 57.0.6,
    which carries upstream's milliseconds.magnitude workaround.

@socket-security

socket-security Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedjest@​30.0.0 ⏵ 29.7.010010068 -193 +1100
Updated@​react-native/​metro-config@​0.79.2 ⏵ 0.86.0100 +11007297 +1100
Updated@​babel/​preset-typescript@​7.27.1 ⏵ 7.29.71001007289100
Updated@​react-native/​typescript-config@​0.79.2 ⏵ 0.86.010010073 +1097 +1100
Updated@​react-native-community/​cli@​18.0.0 ⏵ 20.1.097100 +7575 +194100
Addedexpo-linear-gradient@​57.0.1751008399100
Added@​react-native/​jest-preset@​0.86.0761008297100
Updatedexpo@​53.0.20 ⏵ 57.0.777100100 +23100 +1100
Updatedreact-native-safe-area-context@​5.6.1 ⏵ 5.7.0991007887100
Updated@​types/​react@​19.1.9 ⏵ 19.2.1710010079 -190 -5100
Updated@​babel/​core@​7.28.0 ⏵ 7.29.797100 +179 +190100
Updatedreact-native-gesture-handler@​2.28.0 ⏵ 2.32.08010094 +198100
Updatedreact-test-renderer@​19.0.0 ⏵ 19.2.380 +110086 +198100
Updated@​react-native-firebase/​app@​20.5.0 ⏵ 25.1.010010089 +197 -180
Updated@​react-native-firebase/​messaging@​20.5.0 ⏵ 25.1.010010090 +197 -180
Updatedreact-native-performance@​5.1.4 ⏵ 6.0.0100 +110010081100
Updated@​react-native/​babel-preset@​0.79.5 ⏵ 0.86.098 +110081 +197100
Updated@​react-native-picker/​picker@​2.11.2 ⏵ 2.11.410010010082100
Updatedreact-native-haptic-feedback@​1.14.0 ⏵ 3.0.0100100100 +183100
Addedexpo-blur@​57.0.210010083100100
Updatedreact@​19.0.0 ⏵ 19.2.31001008497100
Updatedreact-native-worklets@​0.6.1 ⏵ 0.10.099 +110085 +398100
Updatedreact-native-sound@​0.12.0 ⏵ 0.13.010010010086100
Updated@​react-native-community/​netinfo@​11.4.1 ⏵ 12.0.19910010086100
Updatedreact-native-keyboard-controller@​1.19.0 ⏵ 1.22.29910087 +196 +3100
Updated@​react-native-async-storage/​async-storage@​1.19.4 ⏵ 2.2.010010087 +1688 -3100
Updatedreact-native-svg@​15.14.0 ⏵ 15.15.499 +110010088100
Updatedreact-native-bootsplash@​6.3.8 ⏵ 6.3.1288 -1010010090100
Updatedreact-native-reanimated@​3.19.5 ⏵ 4.5.3100 +110090 +198100
Updatedtypescript@​5.0.4 ⏵ 5.8.3100 +110090 +1100 +590
Updated@​react-native-community/​datetimepicker@​8.4.2 ⏵ 9.1.09910010090 -4100
Updatedreact-native-vision-camera@​4.7.2 ⏵ 4.7.310010010093 +2100
See 6 more rows in the dashboard

View full report

@socket-security

socket-security Bot commented Jun 29, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @react-native/debugger-frontend is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/react-native@0.86.0npm/expo@57.0.7npm/@react-native/debugger-frontend@0.86.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@react-native/debugger-frontend@0.86.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @react-native/debugger-frontend is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/react-native@0.86.0npm/expo@57.0.7npm/@react-native/debugger-frontend@0.86.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@react-native/debugger-frontend@0.86.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @sentry/browser is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/@sentry/react-native@7.11.0npm/@sentry/browser@10.37.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@sentry/browser@10.37.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @sentry/core is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/@sentry/react-native@7.11.0npm/@sentry/core@10.37.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@sentry/core@10.37.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm react-native-bootsplash is 75.0% likely obfuscated

Confidence: 0.75

Location: Package overview

From: package-lock.jsonnpm/react-native-bootsplash@6.3.12

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/react-native-bootsplash@6.3.12. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Low adoption: npm fetch-nodeshim

Location: Package overview

From: package-lock.jsonnpm/expo@57.0.7npm/fetch-nodeshim@0.4.10

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/fetch-nodeshim@0.4.10. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@peachbits

Copy link
Copy Markdown
Contributor Author

History cleanup after a review question: commit 2 ("Upgrade to React Native 0.85.3") previously contained a spurious newArchEnabled true→false flip that commit 3 flipped back — an artifact of how the upgrade commit was reconstructed during the re-split, not a real setting change. Fixed: the flag now changes exactly once, in commit 1 (false→true), and commits 2–5 don't touch it. The Android commit keeps its legitimate jvmargs bump.

Audited the whole series for more of the same (blob-level revert detection across all commits + exact-line added-then-removed scan, lockfile excluded): this was the only instance. Final tree is bit-identical to the previously validated one, and all 5 commits re-passed the per-commit precommit gate (521/521 tests on commit 1's RN 0.79 base; 517/517 on 0.85).

@peachbits
peachbits force-pushed the phase2-rn-upgrade branch from 0a46981 to 30638ca Compare July 3, 2026 19:39
@peachbits

Copy link
Copy Markdown
Contributor Author

🔴→✅ Regression found in review and fixed: the PR was accidentally reverting develop PR #6044 ("Deprecate Botanix to keys-only mode on July 9, 2026") — the keysOnlyMode functions in WalletAndCurrencyConstants.ts, their test, the keys-only wiring in WalletListMenuModal/WalletListModal, and the CHANGELOG entries. Root cause: when this branch was originally rebuilt onto a newer develop base, the upgrade commit carried file-wholesale content from an older tree, stomping develop changes in files both change-sets touched.

Fixed by restoring develop's versions of all 5 files into the upgrade commit — the upgrade legitimately changed zero lines in them, so they now drop out of the PR diff entirely. Verification:

  • keysOnlyMode intact at every commit in the chain
  • Full sweep of every develop change in the base window vs the PR tip: no remaining reverts or divergences (test: add testIDs to wallet options menu for maestro selectors #6060 and the maestro-testID commit were never affected)
  • All 5 commits re-passed the per-commit precommit gate — now 521/521 tests at every commit (the previous 517-vs-521 gap between commits was itself the stomped Botanix test file)

The Botanix July 9 deprecation survives this PR.

@peachbits
peachbits force-pushed the phase2-rn-upgrade branch from 30638ca to 83b07b2 Compare July 7, 2026 18:07
@peachbits

Copy link
Copy Markdown
Contributor Author

🐛 On-device Android testing found (and fixed) a blocking crash — new commit 12195ddb1.

Symptom: release build on a physical device (OnePlus 5T) crashed the moment the password login scene mounted (tap "Sign in" from the welcome carousel).

Root cause: RN 0.85's Android Fabric text measurement throws IllegalArgumentException: FontSize should be a positive value for any Text with font size 0 — TextLayoutManager's letterSpacing.isNaN() check unconditionally invokes a getter that validates fontSize > 0 (the old architecture silently tolerated 0, which is why this never crashed pre-upgrade). FilledTextInput animates its side-icon sizes to exactly 0 (clear icon on an empty input), and those feed AnimatedFontIcon's fontSize — so any scene with a text input crashed on Android, most visibly login via edge-login-ui-rn (same component pattern).

Fix: clamp animated font sizes to ≥1 (visually identical — the icon containers already collapse to zero width) in the GUI's AnimatedFontIcon + FilledTextInput, plus a 1-line patches/edge-login-ui-rn+3.36.0.patch until the fix lands upstream in edge-login-ui-rn. The 9 snapshot updates in the commit are the fix's signature (fontSize: 0 → 1 on at-rest icons — the old snapshots had captured the crash-causing zero).

Verified: rebuilt release APK on the same device — password login scene renders, inputs + eye icon work, zero fatal exceptions. Commit gated: localize/warnings clean, tsc clean, eslint 0 errors, 521/521 tests.

Follow-up: edge-login-ui-rn needs the same clamp in its own repo (src/components/icons/ThemedIcons.tsx) so the patch can be dropped.

@peachbits
peachbits force-pushed the phase2-rn-upgrade branch from 12195dd to 5a23fed Compare July 21, 2026 19:15
@peachbits

Copy link
Copy Markdown
Contributor Author

♻️ Rebased onto current develop (was 67 commits behind and conflicting; now conflict-free). Same 6 commits, same content — mechanical notes:

  • Two conflicts, both in generated/dependency files: package.json (union: develop's react-native-piratechain ^0.6.1 + this branch's react-native-reanimated 4.3.1) and the lockfiles — regenerated, not hand-merged (npm install --package-lock-only for npm; a fresh pod install on the merged tree transplanted into the iOS commit).
  • Post-rebase audits all clean: 0 develop-window stomps (every file develop changed in the 67-commit window verified intact at the tip — includes the piratechain/monero/edge-core/accountbased/exchange-plugins bumps and the maestro testID work), 0 within-series reverts, newArchEnabled flips exactly once (commit 1).
  • Full per-commit precommit gate re-run: 6/6 PASS at 557/557 tests (suite grew with develop; every commit green under its own dependency set).

Also: the edge-login-ui-rn fontSize fix now exists as a proper branch in that repo (matthew/fabric-fontsize-clamp) pending review/PR — once released, the patches/edge-login-ui-rn+3.36.0.patch here can be dropped in favor of a version bump.

@peachbits
peachbits force-pushed the phase2-rn-upgrade branch from 5a23fed to a6c3123 Compare July 21, 2026 20:31
@peachbits peachbits changed the title Upgrade to React Native 0.85.3 / Expo SDK 56 (new architecture) Upgrade to React Native 0.86 / Expo SDK 57 (new architecture) Jul 21, 2026
@peachbits

Copy link
Copy Markdown
Contributor Author

🔁 Retargeted to React Native 0.86.0 / Expo SDK 57 — same 6-commit structure, now a single hop from 0.79.2 to current stable (no intermediate 0.85 in history).

What changed in the retarget:

  • Upgrade commit now pins RN 0.86.0 + Expo ^57.0.7 with the SDK 57 set (reanimated 4.5.0, worklets 0.10.0, gesture-handler ~2.32.0, keyboard-controller 1.21.9). TypeScript deliberately held at ~5.8.3 (SDK 57 suggests TS 6 — a major bump that belongs in its own PR).
  • reanimated 4.5 typing break fixed in EdgeAnim.tsx (ComplexAnimationBuilder went generic; presets' static delay declares the base-builder return — matched their declared signature with one documented cast).
  • Expo 57 factory-delegate change: customize now receives the new-arch UIView (was RCTRootView).
  • Podfile now pins the engine pairing (RCT_HERMES_V1_ENABLED=1, RCT_USE_PREBUILT_RNCORE=0): the 0.86.0 prebuilt React-Core binary is built against the non-V1 jsi ABI (jsi::Runtime vs V1's jsi::IRuntime) and dyld-crashes at launch when paired with the default V1 hermesvm — React-Core from source is the coherent pairing for the V1 engine Edge already ships. Deterministic regardless of shell environment.
  • ShadowTree patch regenerated as patches/react-native+0.86.0.patch (flag still default-off in 0.86; verified present).

Verification: audits clean (0 develop-window stomps, 0 within-series reverts, coherent prebuilt-free lock); per-commit gates 6/6 at 557/557 (JS surface; post-sweep amendments were iOS-native only, covered by builds); Android release APK builds; iOS builds from source and boots to login on the simulator. On-device Android smoke pending device reconnect.

edge-login-ui-rn's clamp branch (matthew/fabric-fontsize-clamp) is unaffected by the retarget.

peachbits and others added 10 commits September 1, 2026 15:05
Android ran reanimated 3 via the local `r3-hack` package on the old
architecture while iOS ran reanimated 4 on Fabric. Reanimated 4 requires the
new architecture, so route both platforms through reanimated 4 / worklets and
enable the new architecture on Android:

- babel.config.js: always use react-native-worklets/plugin; add api.cache(true)
  now that the config is static (no platform branch configuring the cache).
- metro.config.js: drop the r3-hack Android resolver.
- react-native.config.js: drop the reanimated sourceDir override and the
  worklets Android disable.
- package.json: remove the r3-hack dependency; delete scripts/r3-hack.
- android/gradle.properties: newArchEnabled=true.
- useCarouselGesture: unify the withSpring config to the reanimated-4 algorithm
  on both platforms (Android previously used the reanimated-3 damping).

Also dedupe yaob to ^0.4.0, fixing the nativeIo collision that crashed
ZEC/Pirate wallet creation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the app from React Native 0.79 to 0.86.0 / Expo SDK 57, with both platforms on reanimated v4 and the new architecture. Expo SDK 57 is the release paired with RN 0.86, with React at 19.2. Bumps the RN-ecosystem dependencies together (reanimated 4.5, worklets 0.10, gesture-handler 2.32 per the SDK 57 pin set; TypeScript deliberately held at 5.8.3) and updates the jest preset, worklets resolver, and test snapshots.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enable newArchEnabled, bump compile/target SDK 35->36, and rewrite settings.gradle for Expo SDK 56 autolinking. Unwrap MainApplication from the ReactNativeHostWrapper removed in SDK 56.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expo autolinking + new-architecture Podfile (deployment target 16.4 - Expo SDK 57's floor; React Native 0.86 itself only needs 15.1), the ExpoReactNativeFactory AppDelegate, and RCTNewArchEnabled in Info.plist. Includes the build fixes needed under Xcode 26.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
React Native 0.86's Android Fabric text measurement throws IllegalArgumentException ("FontSize should be a positive value") for any Text whose font size is 0 - TextLayoutManager's letterSpacing.isNaN() check unconditionally invokes a getter that validates fontSize > 0. FilledTextInput animates its side-icon sizes to exactly 0 (clear icon on an empty input, left icon when focused), and those sizes feed AnimatedFontIcon's fontSize, so mounting any scene with a text input crashed the app - most visibly the password login scene via edge-login-ui-rn, which shares the same component pattern.

Clamp animated font sizes to a minimum of 1 (visually equivalent: the icon containers already collapse to zero width) in the GUI's AnimatedFontIcon and FilledTextInput, and apply the same one-line clamp to edge-login-ui-rn via patch-package until the fix lands upstream.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
React Native 0.86 ships the equivalent of our old react-native+0.79.2.patch upstream - ShadowTree::commit() now caps optimistic retries at MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING and falls back to an exclusive recursive lock (ReactCommon/react/renderer/mounting/ShadowTree.cpp) - but only behind the preventShadowTreeCommitExhaustion feature flag, which defaults to false. Enable the flag with a 1-line patch so the RN upgrade doesn't silently drop the crash protection. Delete this patch when upstream flips the default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
These patch releases fix three Android new-architecture regressions found
on a physical Android 10 device: send-scene tiles and icon buttons stuck
at their initial animation size, a missing address-modal title, and the
app viewport staying collapsed after the keyboard was dismissed.
React Native ships this protection default-off. Without it, a runaway
ShadowTree commit loop aborts the app in native code with
"assertion failed (attempts < 1024)", which reproduced on an Android 10
device while interacting with text inputs.

The iOS side of this is a source patch, but Android loads the React
Native core from a prebuilt library, so the default cannot be patched
there. Override the flag at runtime instead. The override is
once-per-process and load() already consumes it, so reset first and
re-apply React Native's stable defaults alongside our one change.
The drawer's outside-tap overlay is an Animated.View whose dimming,
touchability and stacking are all driven from a reanimated shared value.
On Android below 12 under the new architecture those updates never
apply, so the overlay never painted its scrim and never captured
touches: taps fell through to the scene behind the open drawer and the
drawer could not be closed by tapping outside.

On those devices only, render a plain View driven by the drawer's
discrete open state, sized explicitly because an absolutely positioned
view sized only by insets measures 0x0 there. Every other platform keeps
the original animated overlay, verified unchanged on an Android 16
emulator. React Navigation 7 makes this same change upstream, so this
patch retires when we migrate.
On Android below 12 the blur library falls back to RenderScript, which
cannot snapshot content rendered by the new architecture. Instead of the
content behind it, it paints a flat gray wash, so modals, the tab bar,
scene headers and footers, notification cards and the keyboard-avoiding
buttons all rendered washed out or transparent.

There is no way to make that blur render correctly, so skip it on those
devices and have each host paint a solid background instead. The dark
theme's modal color is translucent because it normally sits on top of the
blur, so modals get a solid stand-in, and the QR modal gets a plain scrim
in place of its blurred underlay. The login-ui patch carries the same fix
for its own copies, plus a fix for a development-mode render error where
animation worklets captured a Date they cannot serialize.
The auto-login effect runs on mount and disarms itself immediately, but
on release builds this scene mounts before the core context reaches the
store, so the attempt ran against the placeholder object, threw, and
never retried. Bail out until the context is ready and let the effect
re-run. Development builds win the race, which is why this only ever
showed up in release builds.
View recycling only runs under the new architecture, so enabling it
turned this on for the first time. Recycled views arrive still carrying
visual properties from their previous use: a button that becomes enabled
stays dimmed, and labels render at a stale font size. Both were
reproduced on an Android 10 device, on the survey modal's Next button and
the wallet name modal's title.

Keep recycling off so views render the way they did before the
architecture switch. Re-enabling it is a performance follow-up once the
property-reset gaps are fixed upstream.
EdgeText asks Android to shrink text to fit its box, with a floor of 65%
of the intended size. Under the new architecture that floor is ignored:
labels render at a fraction of their size while the text beside them is
unaffected, which showed up on the wallet name modal's title and the
amount modal's header.

Let text truncate rather than shrink on Android, matching what a dozen
call sites already opt into individually. Other platforms are unchanged.
Modals deliberately bleed past the bottom of the screen so the sheet
never shows a seam above the navigation bar. That overhang sits behind
the keyboard, which cut off the bottom of the modal's own content - the
text input modal's submit button was only half visible.

Drop the overhang while the keyboard is open, the same way SceneWrapper
already drops its bottom inset. The drag slop stays, so swipe-to-dismiss
is unchanged.
Two problems with how the amount and its currency code were laid out:

The input carried a negative margin compensating for extra width that
the old architecture added for short values. That extra width is gone,
so the compensation pulled the currency code into the digits: '1USD'
with one digit, correct spacing from two digits on.

The input's width is also measured a render behind its contents, so a
value that grows by more than one character at once - 123 becoming
1,234, which also gains a separator - was drawn wider than its box and
the leading digits were clipped. Typing past it or deleting back to the
same value looked fine, which is what made it look intermittent.

Size the field with a matching Text instead, which measures in the same
pass as the value it draws, and drop the obsolete margin.
The field echoes a keystroke before it has re-measured, so for one frame
the text was wider than its box and slid left, taking the leading digits
out of view before settling. A rejected keystroke made it worse: the
value never changes, so the box never grows and the echo sat there until
it was reverted.

Reserve a character of width in the field so a keystroke always has
somewhere to land. That reserved space reads as a slightly wider gap
before the currency code, which is the trade: the alternative is letting
the currency code sit over the reserved space, and then a keystroke
briefly draws on top of it.
The Jenkins iOS agents' Xcode upgrade broke expo-modules-jsi 57.0.3 twice
over. First, `abs(milliseconds)` in JavaScriptCodable+Date.swift became
ambiguous (expo/expo#47957); upstream fixed it in 57.0.5 with
`milliseconds.magnitude`, and the existing ~57.0.3 range already allows it,
so the lockfile pin moves to 57.0.6 along with the matching pod.

Second, the newer Swift rejects SWIFT_RETURNS_RETAINED on the two
RuntimeScheduler constructors because the type is not imported as a shared
reference. Upstream removed both annotations on their SDK 58 line; the
patch mirrors that change exactly and is droppable once a 57.x release
picks it up (57.0.6 is currently the newest).
The Jenkins agent's newer Swift rejects EventEmitter's
`nonisolated(unsafe) weak let` captures being sent into the JavaScript
actor ("sending 'emitter' risks causing data races"). Upstream's SDK 58
line already fixed this by boxing the weak reference in the
NonisolatedUnsafeWeakVar wrapper that 57.0.6 ships but does not yet use
here; this patch mirrors that change exactly. No 57.x release carries it
(57.0.14 is byte-identical in this file), so the patch is droppable when
one does.
`deprecated-react-native-prop-types` exists to shim libraries that import
PropTypes from react-native; RN 0.86 removed those exports entirely and
nothing in the app imports it. `@types/react-native` is stale at 0.71 and
obsolete since react-native ships its own types.

`react-native-email-link` looks equally unused — it appears in no source
file — but it stays: edge-login-ui-rn imports it in PasswordRecoveryActions
while declaring it only as a devDependency, so the app has to supply it.
Removing it breaks the bundle at password recovery. Fourteen other packages
reach us the same way; `react-native-linear-gradient`, `rn-id-blurview`,
`react-native-vector-icons` and `react-native-permissions` are the ones that
matter for any future dependency cleanup.
react-native-linear-gradient has no codegen spec, so every gradient in the
app - cards, buttons, the header, the tab bar, the scene background - was
drawn through Fabric's legacy view-manager interop. expo-linear-gradient is
a real Fabric component and Expo is already a dependency, so this swap
removes a native dependency rather than adding one: edge-login-ui-rn's six
gradient scenes migrate through the regenerated patch, which lets
react-native-linear-gradient leave package.json entirely (verified on
device that removing it beforehand left PinLoginScene throwing "View config
not found for component BVLinearGradient"), and the BVLinearGradient pod
gives way to ExpoLinearGradient.

The props are the same (`colors`, `start`, `end`, `locations`); nothing
uses `useAngle`, the one prop Expo lacks. What differs is the typing: Expo
wants the color and location arrays to be tuples of two or more, since a
gradient with fewer stops has nothing to interpolate. The theme's gradient
fields become `GradientColors` to match, and the six scenes that darken the
first stop of the asset background build a new tuple instead of mutating a
copy.

react-native-gifted-charts also reaches for a gradient, but it tries
react-native-linear-gradient and falls back to expo-linear-gradient, with
both declared optional; the bundle builds and resolves to the Expo one.

expo-linear-gradient is mocked in jestSetup because importing it pulls in
expo-modules-core, which expects native globals the react-native jest
preset doesn't provide. Snapshots move from BVLinearGradient to that mock.
Touching these files takes them off the ESLint warnings list, so their
pre-existing lint errors are fixed here too.
edge-login-ui-rn asked for react-native-airship ^0.2.9 and patina ^0.1.6
while this app asks for ^0.3.0 and ^0.2.0, so npm installed both — nested
copies under edge-login-ui-rn alongside ours. Both packages are UI-layer
singletons: airship owns the portal host every modal mounts into, and
patina caches themed styles. Two module instances of either is the same
class of problem as the duplicate yaob that once cross-wired native IO.

edge-login-ui-rn now asks for the same versions, but npm resolves the
dependency tree from its published manifest, so that alone won't collapse
the duplicates until a release goes out. The overrides make it true today
and stay correct afterwards.

Both are pure JavaScript, so no native rebuild is involved. Verified the
auto-logoff modal still mounts and dismisses through the portal host.
react-native-keyboard-aware-scroll-view has had no release since 2022. It
is pure JavaScript, so it never appeared in a codegen audit, but it drives
its scrolling with findNodeHandle, UIManager.measureInWindow and
UIManager.viewIsDescendantOf - the legacy measurement APIs the new
architecture only serves through a compatibility shim. We already depend on
react-native-keyboard-controller, which ships an equivalent component.

`extraScrollHeight` becomes `bottomOffset`, and the `enableOnAndroid` /
`enableAutomaticScroll` opt-ins go away because the replacement always does
both. Everything else is plain ScrollView props. SendScene2 keeps a ref to
scroll to the end, which is now a plain `ref` rather than `innerRef`, and
takes React Native's `scrollToEnd({ animated })` signature instead of the
old library's positional one.

SurveyModal is the one behavior change: it passed an extraScrollHeight of
-16rem on iOS against +9rem on Android, a hack around the old library
measuring the two platforms differently. bottomOffset means the same thing
on both, so both now pass the 2.75rem every other scene uses.

edge-login-ui-rn makes the same migration in its three keyboard scenes, so
its patch is regenerated here - and renamed to 3.37.2, the version npm
actually resolves, silencing the version-mismatch warning every install
printed. Verified on Android: the address form scrolls a field near the
bottom clear of the keyboard on focus and accepts input. Touching these
files takes them off the ESLint warnings list, so their pre-existing lint
errors are fixed too.
LoginScene has published `global.ReactNativeBlurView` since 2020, when
edge-login-ui-rn could not add a native dependency without a breaking
release and instead feature-detected a blur component the host snuck to it
at runtime (falling back to a dim underlay without one).

The reading side was deleted from login-ui in September 2023 (v2.15.0),
when its modals started importing rn-id-blurview directly. Nothing in this
app, in the installed login-ui lib, or anywhere else in node_modules reads
the global today, so the assignment has been write-only for almost three
years. The rn-id-blurview import here existed only to feed it.
Under the new architecture the old blur path (rn-id-blurview, Dimezis 2)
snapshots the window with a software canvas that renders empty on every
Android version - the API < 31 grey wash and a silently transparent sheet
on 12+ are the same disease with different symptoms. Modal sheets were
see-through over busy scenes, and the chrome tint had been impersonating
blur all along. Dimezis 3 fixed the snapshot in hardware but changed the
contract: content must be wrapped in an explicit blur target, and
whole-window sampling is gone. expo-blur ships that backend as a
Fabric-native component, so this adds expo-blur (and its pod) rather than
another native dependency.

Two targets cover the two kinds of blur surface, shaped by one hard
constraint: a blur view must never sit inside its own target, or the
renderer recurses computing transforms and overflows the stack (a native
libhwui crash).

- Modals: the app tree gains one BlurTarget wrapping Main, inside the
  Airship layer, so ModalBlurBackground (EdgeModal) and the QR modal
  underlay sample the app content from outside it. login-ui mounts the
  equivalent target in LoginUiProvider for its own modals, delivered
  through the regenerated patch.
- Chrome: the header, scene footer, tab bar, and notification cards
  overlay the scene and live inside the app-level target, so each
  SceneWrapper wraps its content (background dots plus children, excluding
  the absolutely-positioned footer overlay) in its own target and
  publishes the ref through a registry while focused. ChromeBlurBackground
  samples whichever scene is focused, retargeting as navigation changes,
  and falls back to the long-standing tint when no scene publishes one
  (login, scenes outside SceneWrapper). The scene footer needs no special
  casing: with tabs it portals into MenuTabs and without them it is
  absolutely positioned, so it never sits inside what it samples.

Modal backgrounds return to translucent-over-blur on 12+, keeping the
solid color below API 31 where no blur implementation can render. iOS
keeps its existing whole-window blur, unchanged. Verified on device:
the sort modal frosts the wallet list, the security-alerts modal is
opaque and legible, the tab bar softly blurs list content scrolling
beneath it, and no recursion crash across cold start, tab switches, and
stack navigation.
Edge-to-edge draws the window under the status bar, and the Airship layer's
safe-area measurement is iOS-only (it measures through React Native's
SafeAreaView, a no-op on Android), so dropdown notifications - received
transactions, error alerts - slid in underneath the status bar and camera
cutout instead of below them.

Add the top inset to the dropdown's own padding on Android. iOS already
receives it from the Airship layer, so it is excluded there to avoid a
doubled gap. login-ui's error dropdown gets the same fix through the
regenerated patch.
Two fixes in 6.0.0 target exactly this app's setup: new-architecture
detection on React Native 0.77+ and an Android soft exception when the
event emitter is touched before initialization (plus Android listener-leak
fixes). The breaking changes do not reach us - Flipper support removal
(React Native itself dropped Flipper), Flow-to-TypeScript codegen
internals, and renames of old-bridge lifecycle APIs the app never calls.
Our entire usage is the standard performance mark/measure API in perf.ts.
Both upgrades take the module from bridge interop to codegen under the new
architecture. Sound 0.13.0 changes nothing in the API we touch
(setCategory, load, play). Haptic-feedback 3.0.0 keeps the default-export
trigger() call both this app and edge-login-ui-rn use; its breaking changes
are additions (hooks, patterns) and the TurboModule conversion, which is
why jest now needs a mock - the import itself demands a native binding.

login-ui pins ^1.14.0 until its next release, so an npm override keeps the
tree at one copy, the same arrangement airship and patina already use; the
login-ui branch bumps its own range in lockstep.
The disable animation's shared value always started at 0 (enabled), and a
mount effect then animated it toward the actual disabled prop. An input
mounted with disabled=true therefore flashed its enabled look for ~300ms
every time its scene appeared - most visibly the spending-limit amount
field, whose scene mounts it disabled whenever the limit is switched off.
Seed the shared value with the mounted state so the first frame is
already correct; later prop changes still animate as before.

Fix the same pattern in edge-login-ui-rn's copies of these inputs via the
login-ui patch, mirroring an identical commit in that repo.
peachbits and others added 7 commits September 2, 2026 21:23
Regenerate patches/edge-login-ui-rn+3.37.2.patch from the edge-login-ui-rn
branch (PR #301) so the app picks up the fixes mirrored there: EdgeText
truncating instead of shrinking on Android, EdgeModal dropping its bottom
overhang while the keyboard is open (seeded from Keyboard.isVisible), and
the animated font-size clamps in both text inputs.

This lands as one commit rather than fixups to the matching feature
commits because the 3.37.2 patch file postdates them - it was created by
the keyboard-controller commit and evolves forward from there. The patch
disappears entirely once login-ui releases and the dependency bumps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant