From dca02747d59da98dd1ef016533dd2dc0696588eb Mon Sep 17 00:00:00 2001 From: Eli Smargon Date: Mon, 1 Jun 2026 16:33:53 +0300 Subject: [PATCH 1/2] refactor: streamline loading states and animations across components --- app/(protected)/(tabs)/_layout.tsx | 2 +- app/(protected)/(tabs)/index.tsx | 37 +++++---- app/(protected)/(tabs)/savings.tsx | 5 +- app/_layout.tsx | 4 +- app/welcome.tsx | 2 +- components/Card/BorrowPositionCard.tsx | 4 +- components/CountUp.tsx | 76 +++++++++++++---- components/CustomTabBar.tsx | 40 +++++++-- .../Dashboard/DashboardHeaderMobile.tsx | 1 + components/SavingCountUp.tsx | 6 ++ components/Wallet/Card.tsx | 1 + components/Wallet/DesktopCards.tsx | 4 +- components/Wallet/MobileCards.tsx | 13 ++- components/Wallet/SavingCard.tsx | 1 + components/Wallet/WalletCard.tsx | 1 + hooks/useCardDetails.ts | 6 +- hooks/useSavingsYield.ts | 12 ++- hooks/useTotalSavingsUSD.ts | 10 ++- package-lock.json | 81 ------------------- 19 files changed, 166 insertions(+), 140 deletions(-) diff --git a/app/(protected)/(tabs)/_layout.tsx b/app/(protected)/(tabs)/_layout.tsx index e27450db6..db0e838e0 100644 --- a/app/(protected)/(tabs)/_layout.tsx +++ b/app/(protected)/(tabs)/_layout.tsx @@ -20,7 +20,7 @@ export default function TabLayout() { return ( state.updateUser); const openSpinWinModal = useSpinWinModalStore(state => state.setModal); const intercom = useIntercom(); - const { data: cardStatus } = useCardStatus(); - const { data: cardDetails } = useCardDetails(); + const { data: cardStatus, isLoading: isCardStatusLoading } = useCardStatus(); + const { data: cardDetails, isLoading: isCardDetailsLoading } = useCardDetails(); const { data: spinStatus } = useSpinStatus(); const { data: giveaway } = useCurrentGiveaway(); const countdown = useGiveawayCountdown(giveaway?.giveawayDate); @@ -88,8 +88,9 @@ export default function Home() { hasTriggeredInitialRefresh.current = false; }, [user?.safeAddress]); - const { data: userDepositTransactions, isLoading: isDepositsLoading } = - useUserTransactions(user?.safeAddress); + const { data: userDepositTransactions, isLoading: isDepositsLoading } = useUserTransactions( + user?.safeAddress, + ); const { data: totalSavingsUSD, isLoading: isTotalSavingsLoading } = useTotalSavingsUSD(); @@ -117,6 +118,14 @@ export default function Home() { }, [user, intercom]); const isInitialLoading = isBalanceLoading || isLoadingTokens || isDepositsLoading; + const isCardBalanceLoading = isCardStatusLoading || (userHasCard && isCardDetailsLoading); + const isHeadlineLoading = + isLoadingTokens || + isBalanceLoading || + isTotalSavingsLoading || + isCardBalanceLoading || + totalSavingsUSD === undefined; + const headlineBalance = totalUSDExcludingVaultTokens + (totalSavingsUSD ?? 0) + cardBalance; if (!isInitialLoading && !balance && !isDeposited && !hasTokens) { return ; @@ -132,17 +141,15 @@ export default function Home() { - {isLoadingTokens || - isBalanceLoading || - isTotalSavingsLoading || - totalSavingsUSD === undefined ? ( + {isHeadlineLoading ? ( ) : ( - ) : isLoadingTokens || - isBalanceLoading || - isTotalSavingsLoading || - totalSavingsUSD === undefined ? ( + ) : isHeadlineLoading ? ( @@ -181,16 +185,14 @@ export default function Home() { ) : ( - + )} {isScreenMedium ? ( @@ -199,6 +201,7 @@ export default function Home() { totalUSDExcludingVaultTokens={totalUSDExcludingVaultTokens} topThreeTokens={topThreeTokens} isLoadingTokens={isLoadingTokens} + isLoadingCard={isCardBalanceLoading} userHasCard={userHasCard} cardBalance={cardBalance} /> diff --git a/app/(protected)/(tabs)/savings.tsx b/app/(protected)/(tabs)/savings.tsx index c65bdafc3..cb77c8d02 100644 --- a/app/(protected)/(tabs)/savings.tsx +++ b/app/(protected)/(tabs)/savings.tsx @@ -149,7 +149,7 @@ export default function Savings() { const projectedEarnings = balance && vaultAPY ? balance * (exchangeRate ?? 1) * (vaultAPY / 100) : 0; - const stickyHeader = ( + const pageHeader = ( {isScreenMedium ? ( @@ -180,7 +180,8 @@ export default function Savings() { ); return ( - + + {pageHeader} {isScreenMedium ? ( diff --git a/app/_layout.tsx b/app/_layout.tsx index dda07540d..c4be878d4 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -320,14 +320,14 @@ export default Sentry.wrap(function RootLayout() { } return ( - + - + {Platform.OS === 'web' && ( diff --git a/app/welcome.tsx b/app/welcome.tsx index fbe09d496..4b2dd3b4f 100644 --- a/app/welcome.tsx +++ b/app/welcome.tsx @@ -132,7 +132,7 @@ export default function Welcome() { {/* Subtitle */} - + Please select your account to continue, you will be asked to login with your passkey. diff --git a/components/Card/BorrowPositionCard.tsx b/components/Card/BorrowPositionCard.tsx index 8693a48bb..7a985f9fc 100644 --- a/components/Card/BorrowPositionCard.tsx +++ b/components/Card/BorrowPositionCard.tsx @@ -140,7 +140,7 @@ export function BorrowPositionCard({ )} Net APY earned - + Net APY earned - + { const [isMounted, setIsMounted] = useState(false); - // On Android, delay showing AnimatedRollingNumber so we don't paint it with height=0 - // (stacked digits → white blob). Show static text for one frame then switch. - const [useRolling, setUseRolling] = useState(Platform.OS !== 'android'); + const [canAnimateAfterMount, setCanAnimateAfterMount] = useState(animateOnMount); + const [hasSwitchedToRolling, setHasSwitchedToRolling] = useState(animateOnMount); + // On Android, delay arming AnimatedRollingNumber so we don't paint it with height=0 + // (stacked digits → white blob). + const [useRolling, setUseRolling] = useState( + animated && animateOnMount && Platform.OS !== 'android', + ); useEffect(() => { setIsMounted(true); }, []); useEffect(() => { - if (Platform.OS === 'android' && isMounted) { + if (!animated) { + setUseRolling(false); + return; + } + if (Platform.OS !== 'android') { + setUseRolling(true); + return; + } + if (isMounted) { const t = requestAnimationFrame(() => { requestAnimationFrame(() => setUseRolling(true)); }); return () => cancelAnimationFrame(t); } - }, [isMounted]); + }, [animated, isMounted]); const safeCount = isFinite(count) && count >= 0 ? count : 0; const wholeNumber = Math.floor(safeCount); @@ -71,6 +87,8 @@ const CountUp = ({ const formattedText = isNaN(Number(trailingZero)) ? '0' : trailingZero; const formattedWhole = wholeNumber.toLocaleString('en-US'); + const displayKey = `${formattedWhole}.${formattedText}`; + const initialDisplayKeyRef = useRef(displayKey); const wholeStyle = useMemo( () => (Platform.OS === 'android' ? textStyleForAndroid(styles?.wholeText) : styles?.wholeText), @@ -82,7 +100,24 @@ const CountUp = ({ [styles?.decimalText], ); - const showRolling = isMounted && useRolling; + useEffect(() => { + if (!animated || animateOnMount || canAnimateAfterMount) return; + const t = requestAnimationFrame(() => setCanAnimateAfterMount(true)); + return () => cancelAnimationFrame(t); + }, [animated, animateOnMount, canAnimateAfterMount]); + + const hasChangedSinceInitial = displayKey !== initialDisplayKeyRef.current; + const showRolling = + animated && + isMounted && + useRolling && + (animateOnMount || hasSwitchedToRolling || (canAnimateAfterMount && hasChangedSinceInitial)); + + useEffect(() => { + if (showRolling && !hasSwitchedToRolling) { + setHasSwitchedToRolling(true); + } + }, [showRolling, hasSwitchedToRolling]); return ( @@ -93,31 +128,31 @@ const CountUp = ({ prefix ) ) : null} - {showRolling ? ( + {!showRolling ? {formattedWhole} : null} + {animated ? ( - ) : ( - {formattedWhole} - )} + ) : null} {decimalPlaces > 0 ? ( <> . - {showRolling ? ( + {!showRolling ? {formattedText} : null} + {animated ? ( - ) : ( - {formattedText} - )} + ) : null} ) : null} {suffix ? ( @@ -127,4 +162,13 @@ const CountUp = ({ ); }; +const countUpStyles = StyleSheet.create({ + hiddenRollingNumber: { + left: 0, + opacity: 0, + position: 'absolute', + top: 0, + }, +}); + export default CountUp; diff --git a/components/CustomTabBar.tsx b/components/CustomTabBar.tsx index 1f926d418..43a1e5b74 100644 --- a/components/CustomTabBar.tsx +++ b/components/CustomTabBar.tsx @@ -1,7 +1,8 @@ -import { useState } from 'react'; -import { Platform, Pressable, StyleSheet, View, type ViewStyle } from 'react-native'; +import { useEffect, useRef, useState } from 'react'; +import { Animated, Platform, Pressable, StyleSheet, View, type ViewStyle } from 'react-native'; import * as Haptics from 'expo-haptics'; import { BottomTabBarProps } from '@react-navigation/bottom-tabs'; +import { CommonActions } from '@react-navigation/native'; import TabBarBackground from '@/components/ui/TabBarBackground'; import { Text } from '@/components/ui/text'; @@ -14,8 +15,26 @@ type TabButtonProps = { onLongPress: () => void; }; +const ACTIVE_TAB_COLOR = 'white'; +const INACTIVE_TAB_COLOR = 'rgba(255, 255, 255, 0.5)'; + function TabButton({ label, icon, isFocused, onPress, onLongPress }: TabButtonProps) { const [pressed, setPressed] = useState(false); + const focusProgress = useRef(new Animated.Value(isFocused ? 1 : 0)).current; + const labelColor = isFocused ? ACTIVE_TAB_COLOR : INACTIVE_TAB_COLOR; + + useEffect(() => { + Animated.timing(focusProgress, { + toValue: isFocused ? 1 : 0, + duration: 140, + useNativeDriver: true, + }).start(); + }, [focusProgress, isFocused]); + + const nativeFocusOpacity = focusProgress.interpolate({ + inputRange: [0, 1], + outputRange: [0.5, 1], + }); const handlePressIn = () => { setPressed(true); @@ -56,22 +75,24 @@ function TabButton({ label, icon, isFocused, onPress, onLongPress }: TabButtonPr style={styles.tabButton} > - {icon} - + {label} @@ -106,7 +127,10 @@ export function CustomTabBar({ state, descriptors, navigation }: BottomTabBarPro }); if (!isFocused && !event.defaultPrevented) { - navigation.navigate(route.name); + navigation.dispatch({ + ...CommonActions.navigate(route), + target: state.key, + }); } }; @@ -120,7 +144,7 @@ export function CustomTabBar({ state, descriptors, navigation }: BottomTabBarPro // Get the icon const icon = options.tabBarIcon?.({ focused: isFocused, - color: isFocused ? 'white' : 'rgba(255, 255, 255, 1)', + color: ACTIVE_TAB_COLOR, size: Platform.OS === 'web' ? 36 : 40, }); diff --git a/components/Dashboard/DashboardHeaderMobile.tsx b/components/Dashboard/DashboardHeaderMobile.tsx index c116fe95b..812078826 100644 --- a/components/Dashboard/DashboardHeaderMobile.tsx +++ b/components/Dashboard/DashboardHeaderMobile.tsx @@ -32,6 +32,7 @@ const DashboardHeaderMobile = ({ lastTimestamp={lastTimestamp ?? 0} mode={mode} decimalPlaces={decimalPlaces} + animateOnMount={false} classNames={{ wrapper: 'text-foreground', }} diff --git a/components/SavingCountUp.tsx b/components/SavingCountUp.tsx index 80d22d709..cc62a9f81 100644 --- a/components/SavingCountUp.tsx +++ b/components/SavingCountUp.tsx @@ -30,6 +30,8 @@ interface SavingCountUpProps { styles?: Styles; prefix?: string | React.ReactNode; suffix?: string; + animated?: boolean; + animateOnMount?: boolean; userDepositTransactions?: GetUserTransactionsQuery; exchangeRate?: number; tokenAddress?: string; @@ -53,6 +55,8 @@ const SavingCountUp = memo( styles, prefix, suffix, + animated, + animateOnMount, userDepositTransactions, exchangeRate = 1, tokenAddress = ADDRESSES.fuse.vault, @@ -82,6 +86,8 @@ const SavingCountUp = memo( styles={styles} prefix={prefix} suffix={suffix} + animated={animated} + animateOnMount={animateOnMount} /> ); }, diff --git a/components/Wallet/Card.tsx b/components/Wallet/Card.tsx index f2a25cb1e..ab6cba824 100644 --- a/components/Wallet/Card.tsx +++ b/components/Wallet/Card.tsx @@ -43,6 +43,7 @@ const Card = ({ balance, className, tokens, isLoading, decimalPlaces }: CardProp count={balance ?? 0} isTrailingZero={false} decimalPlaces={decimalPlaces} + animateOnMount={false} classNames={{ wrapper: 'text-foreground', decimalSeparator: 'text-2xl md:text-3xl font-semibold', diff --git a/components/Wallet/DesktopCards.tsx b/components/Wallet/DesktopCards.tsx index 685e2d0b2..70e7ad8cb 100644 --- a/components/Wallet/DesktopCards.tsx +++ b/components/Wallet/DesktopCards.tsx @@ -9,6 +9,7 @@ type DesktopCardsProps = { totalUSDExcludingVaultTokens: number; topThreeTokens: TokenBalance[]; isLoadingTokens: boolean; + isLoadingCard?: boolean; userHasCard: boolean; cardBalance: number; }; @@ -17,6 +18,7 @@ export default function DesktopCards({ totalUSDExcludingVaultTokens, topThreeTokens, isLoadingTokens, + isLoadingCard, userHasCard, cardBalance, }: DesktopCardsProps) { @@ -34,7 +36,7 @@ export default function DesktopCards({ balance={cardBalance} className="flex-1" tokens={[USDC_TOKEN_BALANCE]} - isLoading={isLoadingTokens} + isLoading={isLoadingCard} decimalPlaces={2} /> )} diff --git a/components/Wallet/MobileCards.tsx b/components/Wallet/MobileCards.tsx index 7dd646e4e..999cbbd71 100644 --- a/components/Wallet/MobileCards.tsx +++ b/components/Wallet/MobileCards.tsx @@ -10,6 +10,7 @@ type MobileCardsProps = { totalUSDExcludingVaultTokens: number; topThreeTokens: TokenBalance[]; isLoadingTokens: boolean; + isLoadingCard?: boolean; userHasCard: boolean; cardBalance: number; }; @@ -18,6 +19,7 @@ export default function MobileCards({ totalUSDExcludingVaultTokens, topThreeTokens, isLoadingTokens, + isLoadingCard, userHasCard, cardBalance, }: MobileCardsProps) { @@ -47,13 +49,20 @@ export default function MobileCards({ balance={cardBalance} className="h-full w-full" tokens={[USDC_TOKEN_BALANCE]} - isLoading={isLoadingTokens} + isLoading={isLoadingCard} decimalPlaces={2} /> ) : null, , ].filter(Boolean), - [totalUSDExcludingVaultTokens, topThreeTokens, isLoadingTokens, userHasCard, cardBalance], + [ + totalUSDExcludingVaultTokens, + topThreeTokens, + isLoadingTokens, + isLoadingCard, + userHasCard, + cardBalance, + ], ); const totalCards = cards.length; diff --git a/components/Wallet/SavingCard.tsx b/components/Wallet/SavingCard.tsx index 6663df41b..dd1d5167f 100644 --- a/components/Wallet/SavingCard.tsx +++ b/components/Wallet/SavingCard.tsx @@ -140,6 +140,7 @@ const SavingCard = memo(({ className, decimalPlaces = 2 }: SavingCardProps) => { prefix="$" count={totalSavingsUSD} decimalPlaces={decimalPlaces} + animateOnMount={false} classNames={{ wrapper: 'text-foreground', decimalSeparator: 'text-2xl md:text-3xl font-semibold', diff --git a/components/Wallet/WalletCard.tsx b/components/Wallet/WalletCard.tsx index c3d74bebe..f5c1c98e7 100644 --- a/components/Wallet/WalletCard.tsx +++ b/components/Wallet/WalletCard.tsx @@ -40,6 +40,7 @@ const WalletCard = ({ balance, className, tokens, isLoading, decimalPlaces }: Wa count={balance ?? 0} isTrailingZero={false} decimalPlaces={decimalPlaces} + animateOnMount={false} classNames={{ wrapper: 'text-foreground', decimalSeparator: 'text-2xl md:text-3xl font-semibold', diff --git a/hooks/useCardDetails.ts b/hooks/useCardDetails.ts index 607a91f47..5bf5abc17 100644 --- a/hooks/useCardDetails.ts +++ b/hooks/useCardDetails.ts @@ -49,8 +49,10 @@ export const useCardDetails = () => { () => ({ ...detailsQuery, data: mergedData, - isLoading: detailsQuery.isLoading, + isLoading: + detailsQuery.isLoading || + (provider === CardProvider.RAIN && !!detailsQuery.data && balanceQuery.isLoading), }), - [detailsQuery, mergedData], + [detailsQuery, mergedData, provider, balanceQuery.isLoading], ); }; diff --git a/hooks/useSavingsYield.ts b/hooks/useSavingsYield.ts index 9fc993506..7644a7b31 100644 --- a/hooks/useSavingsYield.ts +++ b/hooks/useSavingsYield.ts @@ -58,7 +58,12 @@ export function useSavingsYield({ summary, vault, }: UseSavingsYieldParams): number { - const [liveYield, setLiveYield] = useState(0); + const [liveYield, setLiveYield] = useState(() => { + if (balance <= 0 || !isFinite(balance)) return 0; + if (mode === SavingMode.BALANCE_ONLY) return balance; + if (mode === SavingMode.TOTAL_USD) return balance * exchangeRate; + return 0; + }); const [animation, setAnimation] = useState(0); const [anchor, setAnchor] = useState<{ value: number; time: number } | null>(null); const { user } = useUser(); @@ -78,6 +83,11 @@ export function useSavingsYield({ setAnchor(null); return; } + if (mode === SavingMode.BALANCE_ONLY) { + setLiveYield(balance); + setAnchor(null); + return; + } if (mode === SavingMode.TOTAL_USD) { setLiveYield(balance * exchangeRate); return; diff --git a/hooks/useTotalSavingsUSD.ts b/hooks/useTotalSavingsUSD.ts index b14d6a428..a5bdc886e 100644 --- a/hooks/useTotalSavingsUSD.ts +++ b/hooks/useTotalSavingsUSD.ts @@ -49,9 +49,11 @@ export const useTotalSavingsUSD = (): { data: number | undefined; isLoading: boo const { data: exchangeRateFuse, isLoading: isLoadingRateFuse } = useVaultExchangeRate( fuseVault?.name ?? usdcVault.name, ); + const hasFuseBalance = !!fuseVault && (balanceFuse ?? 0) > 0; const { data: fusePriceUsd, isLoading: isLoadingFusePrice } = useQuery({ queryKey: ['fusePriceUsd'], queryFn: fetchFusePrice, + enabled: hasFuseBalance, staleTime: 5_000, refetchInterval: 5_000, }); @@ -60,19 +62,19 @@ export const useTotalSavingsUSD = (): { data: number | undefined; isLoading: boo isLoadingBalanceUsdc || isLoadingBalanceFuse || isLoadingRateUsdc || - isLoadingRateFuse || - isLoadingFusePrice; + (hasFuseBalance && (isLoadingRateFuse || isLoadingFusePrice)); const data = useMemo(() => { if (isLoading) return undefined; const rateUsdc = exchangeRateUsdc ?? 1; const rateFuse = exchangeRateFuse ?? 1; - const fusePrice = Number(fusePriceUsd) || 0; + const fusePrice = hasFuseBalance ? Number(fusePriceUsd) || 0 : 0; const redeemableUsdc = (balanceUsdc ?? 0) * rateUsdc; - const redeemableFuse = fuseVault ? (balanceFuse ?? 0) * rateFuse * fusePrice : 0; + const redeemableFuse = hasFuseBalance ? (balanceFuse ?? 0) * rateFuse * fusePrice : 0; return redeemableUsdc + redeemableFuse; }, [ isLoading, + hasFuseBalance, balanceUsdc, balanceFuse, exchangeRateUsdc, diff --git a/package-lock.json b/package-lock.json index 9d369c6ed..7a7f51fdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11181,17 +11181,6 @@ } } }, - "node_modules/@reown/appkit-controllers/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@reown/appkit-pay": { "version": "1.7.8", "resolved": "https://registry.npmjs.org/@reown/appkit-pay/-/appkit-pay-1.7.8.tgz", @@ -11815,17 +11804,6 @@ } } }, - "node_modules/@reown/appkit-utils/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@reown/appkit-wallet": { "version": "1.7.8", "resolved": "https://registry.npmjs.org/@reown/appkit-wallet/-/appkit-wallet-1.7.8.tgz", @@ -12334,17 +12312,6 @@ } } }, - "node_modules/@reown/appkit/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@repeaterjs/repeater": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.6.tgz", @@ -16938,17 +16905,6 @@ } } }, - "node_modules/@walletconnect/ethereum-provider/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@walletconnect/events": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@walletconnect/events/-/events-1.0.1.tgz", @@ -17587,17 +17543,6 @@ } } }, - "node_modules/@walletconnect/universal-provider/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@walletconnect/utils": { "version": "2.23.9", "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.23.9.tgz", @@ -36035,7 +35980,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36052,7 +35996,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36069,7 +36012,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36086,7 +36028,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36103,7 +36044,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36120,7 +36060,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36137,7 +36076,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36154,7 +36092,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36171,7 +36108,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36188,7 +36124,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36205,7 +36140,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36222,7 +36156,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36239,7 +36172,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36256,7 +36188,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36273,7 +36204,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36290,7 +36220,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36307,7 +36236,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36324,7 +36252,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36341,7 +36268,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36358,7 +36284,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36375,7 +36300,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36392,7 +36316,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36409,7 +36332,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36426,7 +36348,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36443,7 +36364,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36460,7 +36380,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ From 4ddca0d0b99bbf7fe47314e5a51bfa4cf116e61d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 26 May 2026 09:31:30 +0000 Subject: [PATCH 2/2] fix(lockfile): restore nested zod 3.25.76 entries needed by npm ci MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 7270042 dropped 11 nested zod@3.25.76 entries from package-lock.json (under @reown/appkit-*, @walletconnect/*, etc.) as "unused", but they aren't — they're required peer-deps that npm's lock file resolver records. Since 7270042 only touched JS, no fresh native iOS build ran (the EAS fingerprint stayed the same and the workflow reused the cached build). PR #2093 added a file under patches/, bumping the fingerprint and triggering the first fresh build since May 20. That build now fails at "npm ci --include=dev" with "Missing: zod@3.25.76 from lock file". Regenerate with "npm install --package-lock-only" to put the entries back. Purely additive diff; no package versions change. --- package-lock.json | 81 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/package-lock.json b/package-lock.json index 7a7f51fdd..9d369c6ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11181,6 +11181,17 @@ } } }, + "node_modules/@reown/appkit-controllers/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@reown/appkit-pay": { "version": "1.7.8", "resolved": "https://registry.npmjs.org/@reown/appkit-pay/-/appkit-pay-1.7.8.tgz", @@ -11804,6 +11815,17 @@ } } }, + "node_modules/@reown/appkit-utils/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@reown/appkit-wallet": { "version": "1.7.8", "resolved": "https://registry.npmjs.org/@reown/appkit-wallet/-/appkit-wallet-1.7.8.tgz", @@ -12312,6 +12334,17 @@ } } }, + "node_modules/@reown/appkit/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@repeaterjs/repeater": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.6.tgz", @@ -16905,6 +16938,17 @@ } } }, + "node_modules/@walletconnect/ethereum-provider/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@walletconnect/events": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@walletconnect/events/-/events-1.0.1.tgz", @@ -17543,6 +17587,17 @@ } } }, + "node_modules/@walletconnect/universal-provider/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@walletconnect/utils": { "version": "2.23.9", "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.23.9.tgz", @@ -35980,6 +36035,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -35996,6 +36052,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36012,6 +36069,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36028,6 +36086,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36044,6 +36103,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36060,6 +36120,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36076,6 +36137,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36092,6 +36154,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36108,6 +36171,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36124,6 +36188,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36140,6 +36205,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36156,6 +36222,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36172,6 +36239,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36188,6 +36256,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36204,6 +36273,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36220,6 +36290,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36236,6 +36307,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36252,6 +36324,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36268,6 +36341,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36284,6 +36358,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36300,6 +36375,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36316,6 +36392,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36332,6 +36409,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36348,6 +36426,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36364,6 +36443,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -36380,6 +36460,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [