From fefc021f5f9f3fc0784ff68a5179a4b78e10d1be Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Thu, 20 Aug 2026 14:45:33 +0200 Subject: [PATCH] Fix "Tried to synchronously call a Remote Function" crash Capture-free shared value updaters like sv.set(() => withSpring(...)) inside worklets get outlined to module scope by React Compiler. The outlined function is not workletized, so it is captured into the worklet closure as a remote function, and set() invoking it synchronously on the UI runtime crashes fatally (reproduced via the Home pager on Android, also reachable via DM swipe gestures). Marking the updaters as worklets keeps them callable on the UI runtime. Co-Authored-By: Claude Fable 5 --- src/components/dms/SwipeToReply.tsx | 9 +++++---- src/lib/custom-animations/GestureActionView.tsx | 14 +++++++++----- src/state/shell/minimal-mode.tsx | 9 +++++---- src/view/com/util/MainScrollProvider.tsx | 9 +++++---- src/view/screens/Home.tsx | 9 +++++---- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/components/dms/SwipeToReply.tsx b/src/components/dms/SwipeToReply.tsx index 7cdd256aa9..6ab13d2b98 100644 --- a/src/components/dms/SwipeToReply.tsx +++ b/src/components/dms/SwipeToReply.tsx @@ -78,12 +78,13 @@ export function SwipeToReply({ const runPop = () => { 'worklet' if (isReducedMotion) return - iconScale.set(() => - withSequence( + iconScale.set(() => { + 'worklet' + return withSequence( withTiming(1.2, {duration: 175}), withTiming(1, {duration: 100}), - ), - ) + ) + }) } return ( diff --git a/src/lib/custom-animations/GestureActionView.tsx b/src/lib/custom-animations/GestureActionView.tsx index de4dd5d0fa..b9c01239c5 100644 --- a/src/lib/custom-animations/GestureActionView.tsx +++ b/src/lib/custom-animations/GestureActionView.tsx @@ -63,12 +63,13 @@ export function GestureActionView({ return } - iconScale.set(() => - withSequence( + iconScale.set(() => { + 'worklet' + return withSequence( withTiming(1.2, {duration: 175}), withTiming(1, {duration: 100}), - ), - ) + ) + }) } useAnimatedReaction( @@ -205,7 +206,10 @@ export function GestureActionView({ scheduleOnRN(actions.rightFirst.action) } } - transX.set(() => withTiming(0, {duration: 200})) + transX.set(() => { + 'worklet' + return withTiming(0, {duration: 200}) + }) hitFirst.set(false) hitSecond.set(false) isActive.set(false) diff --git a/src/state/shell/minimal-mode.tsx b/src/state/shell/minimal-mode.tsx index 2309b6fa1b..ff5ef8b83d 100644 --- a/src/state/shell/minimal-mode.tsx +++ b/src/state/shell/minimal-mode.tsx @@ -33,12 +33,13 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const setModeWorklet = useCallback( (v: boolean) => { 'worklet' - footerMode.set(() => - withSpring(v ? 1 : 0, { + footerMode.set(() => { + 'worklet' + return withSpring(v ? 1 : 0, { ...Reanimated3DefaultSpringConfig, overshootClamping: true, - }), - ) + }) + }) }, [footerMode], ) diff --git a/src/view/com/util/MainScrollProvider.tsx b/src/view/com/util/MainScrollProvider.tsx index ccab7cb992..f2d491b59a 100644 --- a/src/view/com/util/MainScrollProvider.tsx +++ b/src/view/com/util/MainScrollProvider.tsx @@ -104,12 +104,13 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { const setMode = useCallback( (v: boolean) => { 'worklet' - headerMode.set(() => - withSpring(v ? 1 : 0, { + headerMode.set(() => { + 'worklet' + return withSpring(v ? 1 : 0, { ...Reanimated3DefaultSpringConfig, overshootClamping: true, - }), - ) + }) + }) }, [headerMode], ) diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index d3495bbd04..6aaef56ed5 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -150,12 +150,13 @@ function HomeScreenReady({ const headerMode = useHomeHeaderMode() const showHeader = useCallback(() => { 'worklet' - headerMode.set(() => - withSpring(0, { + headerMode.set(() => { + 'worklet' + return withSpring(0, { ...Reanimated3DefaultSpringConfig, overshootClamping: true, - }), - ) + }) + }) }, [headerMode]) useFocusEffect(