From 2645de297c210a9edb4436dd40591db4f5f354bd Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 19 Mar 2026 08:17:51 -0600 Subject: [PATCH 1/7] fix: resolve TypeScript type errors after nativewindui v2 upgrade Update types and imports to match @packrat-ai/nativewindui v2.0.0 API: - AlertRef -> AlertMethods (18 files) - LargeTitleSearchBarRef -> LargeTitleSearchBarMethods (4 files) - ContextMenuRef -> ContextMenuMethods (1 file) - Use SfSymbol names for context/dropdown menu icons - Switch useColorScheme import to local hook for files using colors.green/yellow - Replace rootStyle/rootClassName with style/className on List - Fix SearchInputRef usage with TextInput ref type - Fix invalid icon names (person-outline, backpack) - Remove unsupported namingScheme property - Add missing Share import from react-native --- apps/expo/app/(app)/(tabs)/(home)/index.tsx | 4 ++-- apps/expo/app/(app)/(tabs)/profile/index.tsx | 6 +++--- apps/expo/app/(app)/(tabs)/profile/name.tsx | 2 +- apps/expo/app/(app)/messages/chat.android.tsx | 6 +++--- apps/expo/app/(app)/messages/chat.tsx | 12 ++++++------ .../app/(app)/messages/conversations.android.tsx | 5 ++--- apps/expo/app/(app)/messages/conversations.tsx | 9 ++++----- apps/expo/app/(app)/settings/index.android.tsx | 4 ++-- apps/expo/app/_layout.tsx | 6 +++--- apps/expo/app/auth/(create-account)/credentials.tsx | 4 ++-- apps/expo/app/auth/(login)/forgot-password.tsx | 4 ++-- apps/expo/app/auth/(login)/reset-password.tsx | 4 ++-- apps/expo/app/auth/index.tsx | 4 ++-- apps/expo/app/auth/one-time-password.tsx | 4 ++-- .../expo/features/ai-packs/screens/AIPacksScreen.tsx | 6 +++--- .../features/ai/components/WebSearchGenerativeUI.tsx | 10 ++-------- .../features/auth/components/DeleteAccountButton.tsx | 4 ++-- .../screens/PackTemplateListScreen.tsx | 4 ++-- .../expo/features/packs/components/GapSuggestion.tsx | 3 ++- .../features/packs/components/GearInventoryTile.tsx | 4 ++-- .../packs/components/HorizontalCatalogItemCard.tsx | 3 ++- .../features/packs/components/PackCategoriesTile.tsx | 4 ++-- .../expo/features/packs/components/PackStatsTile.tsx | 4 ++-- .../features/packs/components/WeightAnalysisTile.tsx | 4 ++-- apps/expo/features/packs/screens/PackListScreen.tsx | 4 ++-- .../trips/components/TrailConditionsTile.tsx | 4 ++-- apps/expo/features/trips/components/TripCard.tsx | 4 ++-- .../features/trips/components/UpcomingTripsTile.tsx | 4 ++-- .../expo/features/trips/screens/TripDetailScreen.tsx | 6 +++--- apps/expo/features/trips/screens/TripListScreen.tsx | 4 ++-- .../weather/components/WeatherAlertsTile.tsx | 4 ++-- .../weather/screens/LocationSearchScreen.tsx | 5 +++-- 32 files changed, 75 insertions(+), 80 deletions(-) diff --git a/apps/expo/app/(app)/(tabs)/(home)/index.tsx b/apps/expo/app/(app)/(tabs)/(home)/index.tsx index 1c7329222d..91f8fe13c2 100644 --- a/apps/expo/app/(app)/(tabs)/(home)/index.tsx +++ b/apps/expo/app/(app)/(tabs)/(home)/index.tsx @@ -1,6 +1,6 @@ 'use client'; -import type { LargeTitleSearchBarRef, ListDataItem } from '@packrat/ui/nativewindui'; +import type { LargeTitleSearchBarMethods, ListDataItem } from '@packrat/ui/nativewindui'; import { LargeTitleHeader, List, @@ -167,7 +167,7 @@ function DemoIcon() { export default function DashboardScreen() { const [searchValue, setSearchValue] = useState(''); - const searchBarRef = useRef(null); + const searchBarRef = useRef(null); const { t } = useTranslation(); const dashboardLayout = useRef([ diff --git a/apps/expo/app/(app)/(tabs)/profile/index.tsx b/apps/expo/app/(app)/(tabs)/profile/index.tsx index 5b4a05bd21..353a86cd9b 100644 --- a/apps/expo/app/(app)/(tabs)/profile/index.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/index.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { ActivityIndicator, Alert, @@ -10,7 +10,6 @@ import { type ListRenderItemInfo, ListSectionHeader, Text, - useColorScheme, } from '@packrat/ui/nativewindui'; import AsyncStorage from '@react-native-async-storage/async-storage'; import TabScreen from 'expo-app/components/TabScreen'; @@ -20,6 +19,7 @@ import { useUser } from 'expo-app/features/auth/hooks/useUser'; import { ProfileAuthWall } from 'expo-app/features/profile/components'; import { cn } from 'expo-app/lib/cn'; import { hasUnsyncedChanges } from 'expo-app/lib/hasUnsyncedChanges'; +import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; import { useTranslation } from 'expo-app/lib/hooks/useTranslation'; import { Stack } from 'expo-router'; import * as Updates from 'expo-updates'; @@ -140,7 +140,7 @@ function ListFooterComponent() { const { colors } = useColorScheme(); const { t } = useTranslation(); - const alertRef = useRef(null); + const alertRef = useRef(null); const [isSigningOut, setIsSigningOut] = useState(false); const handleSignOut = async () => { diff --git a/apps/expo/app/(app)/(tabs)/profile/name.tsx b/apps/expo/app/(app)/(tabs)/profile/name.tsx index d1fc92a19f..0816faa9cc 100644 --- a/apps/expo/app/(app)/(tabs)/profile/name.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/name.tsx @@ -61,7 +61,7 @@ export default function NameScreen() { contentContainerStyle={{ paddingBottom: insets.bottom }} >
- + ; }) { - const contextMenuRef = React.useRef(null); - const contextMenuRef2 = React.useRef(null); + const contextMenuRef = React.useRef(null); + const contextMenuRef2 = React.useRef(null); const { colors } = useColorScheme(); const rootStyle = useAnimatedStyle(() => { return { diff --git a/apps/expo/app/(app)/messages/conversations.android.tsx b/apps/expo/app/(app)/messages/conversations.android.tsx index 078634f13e..7850b20c27 100644 --- a/apps/expo/app/(app)/messages/conversations.android.tsx +++ b/apps/expo/app/(app)/messages/conversations.android.tsx @@ -85,7 +85,7 @@ export default function ConversationsAndroidScreen() { > } - rightView={} + rightView={} iosBlurIntensity={30} /> @@ -114,14 +114,13 @@ function LeftView() { createDropdownItem({ actionKey: 'go-home', title: 'Go Home', - icon: { name: 'home' }, + icon: { name: 'house.fill' }, }), createDropdownItem({ actionKey: 'toggle-theme', title: 'Toggle Theme', icon: { name: isDarkColorScheme ? 'moon.stars' : 'sun.min', - namingScheme: 'sfSymbol', }, }), ]; diff --git a/apps/expo/app/(app)/messages/conversations.tsx b/apps/expo/app/(app)/messages/conversations.tsx index 092a11d653..f308648154 100644 --- a/apps/expo/app/(app)/messages/conversations.tsx +++ b/apps/expo/app/(app)/messages/conversations.tsx @@ -113,19 +113,18 @@ function LeftView({ createDropdownItem({ actionKey: 'go-home', title: 'Go Home', - icon: { name: 'home' }, + icon: { name: 'house.fill' }, }), createDropdownItem({ actionKey: 'select-messages', title: 'Select messages', - icon: { name: 'checkmark.circle', namingScheme: 'sfSymbol' }, + icon: { name: 'checkmark.circle' }, }), createDropdownItem({ actionKey: 'toggle-theme', title: 'Toggle Theme', icon: { name: isDarkColorScheme ? 'moon.stars' : 'sun.min', - namingScheme: 'sfSymbol', }, }), ]; @@ -220,12 +219,12 @@ const CONTEXT_MENU_ITEMS = [ createContextItem({ actionKey: 'hide-alerts', title: 'Hide Alerts', - icon: { name: 'bell-outline' }, + icon: { name: 'bell' }, }), createContextItem({ actionKey: 'delete', title: 'Delete', - icon: { name: 'trash-can-outline', color: 'red' }, + icon: { name: 'trash', color: 'red' }, destructive: true, }), ]; diff --git a/apps/expo/app/(app)/settings/index.android.tsx b/apps/expo/app/(app)/settings/index.android.tsx index d0d7d63cff..07adc2938e 100644 --- a/apps/expo/app/(app)/settings/index.android.tsx +++ b/apps/expo/app/(app)/settings/index.android.tsx @@ -55,12 +55,12 @@ export default function SettingsAndroidStyleScreen() { /> )} ; +export let appAlert: React.RefObject; function RootLayout() { useInitialAndroidBarSync(); - appAlert = useRef(null); + appAlert = useRef(null); const { colorScheme, isDarkColorScheme } = useColorScheme(); diff --git a/apps/expo/app/auth/(create-account)/credentials.tsx b/apps/expo/app/auth/(create-account)/credentials.tsx index ea5d3ade7d..bc3e3eeebe 100644 --- a/apps/expo/app/auth/(create-account)/credentials.tsx +++ b/apps/expo/app/auth/(create-account)/credentials.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { AlertAnchor, Button, @@ -109,7 +109,7 @@ export default function CredentialsScreen() { const [focusedTextField, setFocusedTextField] = React.useState< 'email' | 'password' | 'confirm-password' | null >(null); - const alertRef = React.useRef(null); + const alertRef = React.useRef(null); // Get data from previous screen const params = useLocalSearchParams<{ diff --git a/apps/expo/app/auth/(login)/forgot-password.tsx b/apps/expo/app/auth/(login)/forgot-password.tsx index 1751554c4a..0a08340fde 100644 --- a/apps/expo/app/auth/(login)/forgot-password.tsx +++ b/apps/expo/app/auth/(login)/forgot-password.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { AlertAnchor, Button, @@ -32,7 +32,7 @@ const emailSchema = z.object({ export default function ForgotPasswordScreen() { const insets = useSafeAreaInsets(); const { t } = useTranslation(); - const alertRef = React.useRef(null); + const alertRef = React.useRef(null); const [isLoading, setIsLoading] = React.useState(false); const { forgotPassword } = useAuthActions(); const needsReauth = useAtomValue(needsReauthAtom); diff --git a/apps/expo/app/auth/(login)/reset-password.tsx b/apps/expo/app/auth/(login)/reset-password.tsx index dc7cf90a89..90116714cc 100644 --- a/apps/expo/app/auth/(login)/reset-password.tsx +++ b/apps/expo/app/auth/(login)/reset-password.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { AlertAnchor, Button, @@ -107,7 +107,7 @@ export default function ResetPasswordScreen() { const [focusedTextField, setFocusedTextField] = React.useState< 'password' | 'confirm-password' | null >(null); - const alertRef = React.useRef(null); + const alertRef = React.useRef(null); // Get data from previous screen const params = useLocalSearchParams<{ diff --git a/apps/expo/app/auth/index.tsx b/apps/expo/app/auth/index.tsx index 72b1cc87d5..7026ac86a5 100644 --- a/apps/expo/app/auth/index.tsx +++ b/apps/expo/app/auth/index.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { ActivityIndicator, AlertAnchor, Button, Text } from '@packrat/ui/nativewindui'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { featureFlags } from 'expo-app/config'; @@ -27,7 +27,7 @@ type RouteParams = { export default function AuthIndexScreen() { const { signInWithGoogle, signInWithApple, isLoading } = useAuth(); const { t } = useTranslation(); - const alertRef = React.useRef(null); + const alertRef = React.useRef(null); const { redirectTo = '/', showSignInCopy, diff --git a/apps/expo/app/auth/one-time-password.tsx b/apps/expo/app/auth/one-time-password.tsx index d87e03f1ca..4f44251aa6 100644 --- a/apps/expo/app/auth/one-time-password.tsx +++ b/apps/expo/app/auth/one-time-password.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { ActivityIndicator, AlertAnchor, Button, Text, TextField } from '@packrat/ui/nativewindui'; import { useHeaderHeight } from '@react-navigation/elements'; import { useAuthActions } from 'expo-app/features/auth/hooks/useAuthActions'; @@ -38,7 +38,7 @@ export default function OneTimePasswordScreen() { const [codeValues, setCodeValues] = React.useState(Array(NUM_OF_CODE_CHARACTERS).fill('')); const [errorIndexes, setErrorIndexes] = React.useState([]); const [isLoading, setIsLoading] = React.useState(false); - const alertRef = React.useRef(null); + const alertRef = React.useRef(null); const headerHeight = useHeaderHeight(); const params = useLocalSearchParams<{ email: string; mode: string }>(); const email = params.email || ''; diff --git a/apps/expo/features/ai-packs/screens/AIPacksScreen.tsx b/apps/expo/features/ai-packs/screens/AIPacksScreen.tsx index 59f14364fe..0dadfc0564 100644 --- a/apps/expo/features/ai-packs/screens/AIPacksScreen.tsx +++ b/apps/expo/features/ai-packs/screens/AIPacksScreen.tsx @@ -1,7 +1,7 @@ import { ActivityIndicator, Alert, - type AlertRef, + type AlertMethods, Button, LargeTitleHeader, Text, @@ -20,7 +20,7 @@ import { useGeneratePacks } from '../hooks/useGeneratedPacks'; export function AIPacksScreen() { const { colors } = useColorScheme(); const { t } = useTranslation(); - const alertRef = useRef(null); + const alertRef = useRef(null); const { mutateAsync: generatePacks, isPending, generatedPacksFromStore } = useGeneratePacks(); const [packsModalVisible, setPacksModalVisible] = useState(false); const router = useRouter(); @@ -35,7 +35,7 @@ export function AIPacksScreen() { alertRef.current?.alert({ title: t('ai.packsGenerated'), message: t('ai.successfullyGenerated', { count: packs.length }), - materialIcon: { name: 'backpack', color: colors.green }, + materialIcon: { name: 'bag-personal', color: colors.green }, buttons: [ { text: t('ai.return'), onPress: () => {} }, { diff --git a/apps/expo/features/ai/components/WebSearchGenerativeUI.tsx b/apps/expo/features/ai/components/WebSearchGenerativeUI.tsx index 2558f8ccc9..4148757a0c 100644 --- a/apps/expo/features/ai/components/WebSearchGenerativeUI.tsx +++ b/apps/expo/features/ai/components/WebSearchGenerativeUI.tsx @@ -2,15 +2,9 @@ import EvilIcons from '@expo/vector-icons/EvilIcons'; import Fontisto from '@expo/vector-icons/Fontisto'; import Ionicons from '@expo/vector-icons/Ionicons'; import { BottomSheetScrollView } from '@gorhom/bottom-sheet'; -import { - Card, - CardContent, - Sheet, - Text, - useColorScheme, - useSheetRef, -} from '@packrat/ui/nativewindui'; +import { Card, CardContent, Sheet, Text, useSheetRef } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; +import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; import { useTranslation } from 'expo-app/lib/hooks/useTranslation'; import { Linking, Pressable, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; diff --git a/apps/expo/features/auth/components/DeleteAccountButton.tsx b/apps/expo/features/auth/components/DeleteAccountButton.tsx index ad050cd50d..c4b69acad3 100644 --- a/apps/expo/features/auth/components/DeleteAccountButton.tsx +++ b/apps/expo/features/auth/components/DeleteAccountButton.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { ActivityIndicator, Alert, Button, Text } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { useAuth } from 'expo-app/features/auth/hooks/useAuth'; @@ -11,7 +11,7 @@ export function DeleteAccountButton() { const { colors } = useColorScheme(); const { deleteAccount } = useAuth(); const { t } = useTranslation(); - const alertRef = useRef(null); + const alertRef = useRef(null); const [isDeleting, setIsDeleting] = useState(false); return ( diff --git a/apps/expo/features/pack-templates/screens/PackTemplateListScreen.tsx b/apps/expo/features/pack-templates/screens/PackTemplateListScreen.tsx index d99097dad5..701b63e5c8 100644 --- a/apps/expo/features/pack-templates/screens/PackTemplateListScreen.tsx +++ b/apps/expo/features/pack-templates/screens/PackTemplateListScreen.tsx @@ -1,5 +1,5 @@ import type { BottomSheetModal } from '@gorhom/bottom-sheet'; -import type { LargeTitleSearchBarRef } from '@packrat/ui/nativewindui'; +import type { LargeTitleSearchBarMethods } from '@packrat/ui/nativewindui'; import { LargeTitleHeader, SegmentedControl } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { useAuth } from 'expo-app/features/auth/hooks/useAuth'; @@ -46,7 +46,7 @@ export function PackTemplateListScreen() { const { t } = useTranslation(); const templateOptionsRef = useRef(null); - const searchBarRef = useRef(null); + const searchBarRef = useRef(null); // Filter options with translations const filterOptions: FilterOption[] = [ diff --git a/apps/expo/features/packs/components/GapSuggestion.tsx b/apps/expo/features/packs/components/GapSuggestion.tsx index a8e3bb9b44..c3919de9f8 100644 --- a/apps/expo/features/packs/components/GapSuggestion.tsx +++ b/apps/expo/features/packs/components/GapSuggestion.tsx @@ -1,7 +1,8 @@ -import { Button, Text, useColorScheme } from '@packrat/ui/nativewindui'; +import { Button, Text } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { useVectorSearch } from 'expo-app/features/catalog/hooks/useVectorSearch'; import type { CatalogItem } from 'expo-app/features/catalog/types'; +import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; import { useState } from 'react'; import { View } from 'react-native'; import type { GapAnalysisItem } from '../hooks'; diff --git a/apps/expo/features/packs/components/GearInventoryTile.tsx b/apps/expo/features/packs/components/GearInventoryTile.tsx index c1b9095a67..f41237203d 100644 --- a/apps/expo/features/packs/components/GearInventoryTile.tsx +++ b/apps/expo/features/packs/components/GearInventoryTile.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { Alert, ListItem, Text } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; @@ -11,7 +11,7 @@ import { useUserPackItems } from '../hooks'; export function GearInventoryTile() { const { t } = useTranslation(); const router = useRouter(); - const alertRef = useRef(null); + const alertRef = useRef(null); const items = useUserPackItems(); const handlePress = () => { diff --git a/apps/expo/features/packs/components/HorizontalCatalogItemCard.tsx b/apps/expo/features/packs/components/HorizontalCatalogItemCard.tsx index 9efeb398e3..2ee15840cd 100644 --- a/apps/expo/features/packs/components/HorizontalCatalogItemCard.tsx +++ b/apps/expo/features/packs/components/HorizontalCatalogItemCard.tsx @@ -1,7 +1,8 @@ -import { Text, useColorScheme } from '@packrat/ui/nativewindui'; +import { Text } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { CatalogItemImage } from 'expo-app/features/catalog/components/CatalogItemImage'; import type { CatalogItem } from 'expo-app/features/catalog/types'; +import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; import { TouchableWithoutFeedback, View } from 'react-native'; type HorizontalCatalogItemCardProps = { diff --git a/apps/expo/features/packs/components/PackCategoriesTile.tsx b/apps/expo/features/packs/components/PackCategoriesTile.tsx index 4b016813a3..dd9244443c 100644 --- a/apps/expo/features/packs/components/PackCategoriesTile.tsx +++ b/apps/expo/features/packs/components/PackCategoriesTile.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { Alert, ListItem, Text } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; @@ -15,7 +15,7 @@ export function PackCategoriesTile() { const router = useRouter(); - const alertRef = useRef(null); + const alertRef = useRef(null); const handlePress = () => { if (!currentPack) return alertRef.current?.show(); diff --git a/apps/expo/features/packs/components/PackStatsTile.tsx b/apps/expo/features/packs/components/PackStatsTile.tsx index 50c192a3bb..4dbae9769d 100644 --- a/apps/expo/features/packs/components/PackStatsTile.tsx +++ b/apps/expo/features/packs/components/PackStatsTile.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { Alert, ListItem } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; @@ -15,7 +15,7 @@ export function PackStatsTile() { const packs = usePacks(); const currentPack = packs[0]; - const alertRef = useRef(null); + const alertRef = useRef(null); const route: Href | null = currentPack ? `/pack-stats/${currentPack.id}` : null; diff --git a/apps/expo/features/packs/components/WeightAnalysisTile.tsx b/apps/expo/features/packs/components/WeightAnalysisTile.tsx index 9fdf805eb1..28c3a0760a 100644 --- a/apps/expo/features/packs/components/WeightAnalysisTile.tsx +++ b/apps/expo/features/packs/components/WeightAnalysisTile.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { Alert, ListItem, Text } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; @@ -12,7 +12,7 @@ export function WeightAnalysisTile() { const { t } = useTranslation(); const router = useRouter(); const currentPack = useCurrentPack(); - const alertRef = useRef(null); + const alertRef = useRef(null); const packWeight = currentPack?.totalWeight ?? 0; const route: Href | null = currentPack ? `/weight-analysis/${currentPack.id}` : null; diff --git a/apps/expo/features/packs/screens/PackListScreen.tsx b/apps/expo/features/packs/screens/PackListScreen.tsx index c65e28d11b..ce075a7115 100644 --- a/apps/expo/features/packs/screens/PackListScreen.tsx +++ b/apps/expo/features/packs/screens/PackListScreen.tsx @@ -1,4 +1,4 @@ -import type { LargeTitleSearchBarRef } from '@packrat/ui/nativewindui'; +import type { LargeTitleSearchBarMethods } from '@packrat/ui/nativewindui'; import { ActivityIndicator, Button, @@ -66,7 +66,7 @@ export function PackListScreen() { ); const allPacksQuery = useAllPacks(selectedTypeIndex === ALL_PACKS_INDEX); - const searchBarRef = useRef(null); + const searchBarRef = useRef(null); const { colors } = useColorScheme(); diff --git a/apps/expo/features/trips/components/TrailConditionsTile.tsx b/apps/expo/features/trips/components/TrailConditionsTile.tsx index 9f61cddc53..b94ea8eab4 100644 --- a/apps/expo/features/trips/components/TrailConditionsTile.tsx +++ b/apps/expo/features/trips/components/TrailConditionsTile.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { Alert, ListItem } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { featureFlags } from 'expo-app/config'; @@ -12,7 +12,7 @@ export function TrailConditionsTile() { const router = useRouter(); const { t } = useTranslation(); - const alertRef = useRef(null); + const alertRef = useRef(null); const handlePress = () => { // if (!currentPack) return alertRef.current?.show(); diff --git a/apps/expo/features/trips/components/TripCard.tsx b/apps/expo/features/trips/components/TripCard.tsx index 68ab8fa0fd..d7f1884456 100644 --- a/apps/expo/features/trips/components/TripCard.tsx +++ b/apps/expo/features/trips/components/TripCard.tsx @@ -1,5 +1,5 @@ import { useActionSheet } from '@expo/react-native-action-sheet'; -import { Alert, type AlertRef, Button } from '@packrat/ui/nativewindui'; +import { Alert, type AlertMethods, Button } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; import { useTranslation } from 'expo-app/lib/hooks/useTranslation'; @@ -36,7 +36,7 @@ export function TripCard({ trip, onPress }: TripCardProps) { const deleteTrip = useDeleteTrip(); const { colors } = useColorScheme(); const { showActionSheetWithOptions } = useActionSheet(); - const alertRef = useRef(null); + const alertRef = useRef(null); const insets = useSafeAreaInsets(); const durationDays = getTripDurationDays(trip.startDate, trip.endDate); diff --git a/apps/expo/features/trips/components/UpcomingTripsTile.tsx b/apps/expo/features/trips/components/UpcomingTripsTile.tsx index da8b0abb69..29314dc994 100644 --- a/apps/expo/features/trips/components/UpcomingTripsTile.tsx +++ b/apps/expo/features/trips/components/UpcomingTripsTile.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { Alert, ListItem, Text, useColorScheme } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { featureFlags } from 'expo-app/config'; @@ -11,7 +11,7 @@ import { View } from 'react-native'; export function UpcomingTripsTile() { const router = useRouter(); const { t } = useTranslation(); - const alertRef = useRef(null); + const alertRef = useRef(null); const [showAlert, setShowAlert] = useState(false); // ✅ get all trips diff --git a/apps/expo/features/trips/screens/TripDetailScreen.tsx b/apps/expo/features/trips/screens/TripDetailScreen.tsx index 27691c449f..36ca9ff6e2 100644 --- a/apps/expo/features/trips/screens/TripDetailScreen.tsx +++ b/apps/expo/features/trips/screens/TripDetailScreen.tsx @@ -1,7 +1,7 @@ import { ActivityIndicator, Alert, - type AlertRef, + type AlertMethods, Button, Card, Text, @@ -12,7 +12,7 @@ import { useTranslation } from 'expo-app/lib/hooks/useTranslation'; import { assertDefined } from 'expo-app/utils/typeAssertions'; import { useLocalSearchParams, useRouter } from 'expo-router'; import { useRef } from 'react'; -import { ScrollView, View } from 'react-native'; +import { ScrollView, Share, View } from 'react-native'; import MapView, { Marker, PROVIDER_GOOGLE } from 'react-native-maps'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useDetailedPacks } from '../../packs/hooks/useDetailedPacks'; @@ -24,7 +24,7 @@ export function TripDetailScreen() { const { id } = useLocalSearchParams(); const { colors } = useColorScheme(); const { t } = useTranslation(); - const alertRef = useRef(null); + const alertRef = useRef(null); const trip = useTripDetailsFromStore(id as string) as Trip; const packs = useDetailedPacks(); diff --git a/apps/expo/features/trips/screens/TripListScreen.tsx b/apps/expo/features/trips/screens/TripListScreen.tsx index c04edfec92..05f9a4dd5d 100644 --- a/apps/expo/features/trips/screens/TripListScreen.tsx +++ b/apps/expo/features/trips/screens/TripListScreen.tsx @@ -1,4 +1,4 @@ -import { LargeTitleHeader, type LargeTitleSearchBarRef } from '@packrat/ui/nativewindui'; +import { LargeTitleHeader, type LargeTitleSearchBarMethods } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import TabScreen from 'expo-app/components/TabScreen'; import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; @@ -28,7 +28,7 @@ export function TripsListScreen() { const router = useRouter(); const { t } = useTranslation(); const trips = useTrips(); - const searchBarRef = useRef(null); + const searchBarRef = useRef(null); const handleTripPress = useCallback( (trip: Trip) => { diff --git a/apps/expo/features/weather/components/WeatherAlertsTile.tsx b/apps/expo/features/weather/components/WeatherAlertsTile.tsx index 473ade61a6..26b824fe70 100644 --- a/apps/expo/features/weather/components/WeatherAlertsTile.tsx +++ b/apps/expo/features/weather/components/WeatherAlertsTile.tsx @@ -1,4 +1,4 @@ -import type { AlertRef } from '@packrat/ui/nativewindui'; +import type { AlertMethods } from '@packrat/ui/nativewindui'; import { Alert, ListItem, Text } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; @@ -9,7 +9,7 @@ import { Platform, View } from 'react-native'; export function WeatherAlertsTile() { const router = useRouter(); - const alertRef = useRef(null); + const alertRef = useRef(null); const { t } = useTranslation(); const handlePress = () => { diff --git a/apps/expo/features/weather/screens/LocationSearchScreen.tsx b/apps/expo/features/weather/screens/LocationSearchScreen.tsx index 7df867f022..ffc8c8ada1 100644 --- a/apps/expo/features/weather/screens/LocationSearchScreen.tsx +++ b/apps/expo/features/weather/screens/LocationSearchScreen.tsx @@ -1,4 +1,4 @@ -import { SearchInput, type SearchInputRef, Text } from '@packrat/ui/nativewindui'; +import { SearchInput, Text } from '@packrat/ui/nativewindui'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { Icon } from '@roninoss/icons'; import { cn } from 'expo-app/lib/cn'; @@ -15,6 +15,7 @@ import { Keyboard, Linking, Platform, + type TextInput, TouchableOpacity, View, } from 'react-native'; @@ -32,7 +33,7 @@ export default function LocationSearchScreen() { const [query, setQuery] = useState(''); const { isLoading, results, error, search, addSearchResult, searchByCoordinates } = useLocationSearch(); - const searchInputRef = useRef(null); + const searchInputRef = useRef(null); const [recentSearches, setRecentSearches] = useState([]); const [isAdding, setIsAdding] = useState(false); const [addingLocationId, setAddingLocationId] = useState(null); From 4714658f31591b483381952bf290ff7f55efbd5e Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 19 Mar 2026 08:44:36 -0600 Subject: [PATCH 2/7] fix: update lockfile for @packrat-ai/nativewindui@2.0.1, gitignore .npmrc --- .gitignore | 2 +- bun.lock | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ba79468dcc..1bf42b2e46 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json .idea # Finder (MacOS) folder config -.DS_Store \ No newline at end of file +.DS_Store.npmrc diff --git a/bun.lock b/bun.lock index 57e4f439fb..d3e90244d1 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "packrat-monorepo", @@ -317,7 +318,7 @@ "name": "@packrat/ui", "version": "2.0.15", "dependencies": { - "@packrat-ai/nativewindui": "^2.0.0-rc.3", + "@packrat-ai/nativewindui": "^2.0.0", }, }, }, @@ -945,7 +946,7 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@packrat-ai/nativewindui": ["@packrat-ai/nativewindui@2.0.0-rc.3", "https://npm.pkg.github.com/download/@packrat-ai/nativewindui/2.0.0-rc.3/11ac155933fd39999bc39bdaa6c0f5e7d6492a69", { "peerDependencies": { "@expo/vector-icons": ">=15.0.0", "@gorhom/bottom-sheet": "^5.1.2", "@react-native-community/datetimepicker": "^8.4.0", "@react-native-community/slider": "^5.0.0", "@react-native-picker/picker": "^2.11.0", "@react-native-segmented-control/segmented-control": "^2.5.0", "@react-navigation/drawer": "^7.1.1", "@react-navigation/elements": "^2.3.1", "@react-navigation/native": "^7.0.14", "@rn-primitives/alert-dialog": "^1.1.0", "@rn-primitives/avatar": "^1.0.4", "@rn-primitives/checkbox": "^1.1.0", "@rn-primitives/context-menu": "^1.1.0", "@rn-primitives/dropdown-menu": "^1.1.0", "@rn-primitives/hooks": "^1.1.0", "@rn-primitives/portal": "^1.1.0", "@rn-primitives/slot": "^1.1.0", "@shopify/flash-list": "^2.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "expo-blur": "~15.0.8", "expo-device": "~8.0.0", "expo-glass-effect": "*", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-linear-gradient": "~15.0.8", "expo-navigation-bar": "~5.0.10", "expo-router": "~6.0.23", "expo-symbols": "~1.0.8", "nativewind": "^4.1.21", "react": ">=19.0.0", "react-native": ">=0.79.0", "react-native-keyboard-controller": "^1.16.7", "react-native-reanimated": ">=3.17.0", "react-native-safe-area-context": ">=5.4.0", "react-native-screens": ">=4.11.0", "react-native-uitextview": "^1.1.4", "rn-icon-mapper": "^0.0.1", "tailwind-merge": "^2.2.1" } }, "sha512-M5MecnXZgb2MKffccR/MBsot3t4t/siz3HNO2HvAZ4yBIrU/FrwwE/AgtWmGF47z4UoIngI6lVH+x0lAVf3F+g=="], + "@packrat-ai/nativewindui": ["@packrat-ai/nativewindui@2.0.1", "https://npm.pkg.github.com/download/@packrat-ai/nativewindui/2.0.1/c8f3e4e6113c8d464803f637dbfb6fe5fa9a5e36", { "peerDependencies": { "@expo/vector-icons": ">=15.0.0", "@gorhom/bottom-sheet": "^5.1.2", "@react-native-community/datetimepicker": "^8.4.0", "@react-native-community/slider": "^5.0.0", "@react-native-picker/picker": "^2.11.0", "@react-native-segmented-control/segmented-control": "^2.5.0", "@react-navigation/drawer": "^7.1.1", "@react-navigation/elements": "^2.3.1", "@react-navigation/native": "^7.0.14", "@rn-primitives/alert-dialog": "^1.1.0", "@rn-primitives/avatar": "^1.0.4", "@rn-primitives/checkbox": "^1.1.0", "@rn-primitives/context-menu": "^1.1.0", "@rn-primitives/dropdown-menu": "^1.1.0", "@rn-primitives/hooks": "^1.1.0", "@rn-primitives/portal": "^1.1.0", "@rn-primitives/slot": "^1.1.0", "@shopify/flash-list": "^2.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "expo-blur": "~15.0.8", "expo-device": "~8.0.0", "expo-glass-effect": "*", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-linear-gradient": "~15.0.8", "expo-navigation-bar": "~5.0.10", "expo-router": "~6.0.23", "expo-symbols": "~1.0.8", "nativewind": "^4.1.21", "react": ">=19.0.0", "react-native": ">=0.79.0", "react-native-keyboard-controller": "^1.16.7", "react-native-reanimated": ">=3.17.0", "react-native-safe-area-context": ">=5.4.0", "react-native-screens": ">=4.11.0", "react-native-uitextview": "^1.1.4", "rn-icon-mapper": "^0.0.1", "tailwind-merge": "^2.2.1" } }, "sha512-zMzFalxu6MKuBMIIDzeiMj/7wM9qn8kFkAbWm3IxBWTHHSsl/Zic053DCJZS1GQcY0Ke2Om3TmUTuBujERuwvA=="], "@packrat/api": ["@packrat/api@workspace:packages/api"], From 265e3c9486e4f5862fccb9edf0e9014acc94cc48 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 19 Mar 2026 08:45:32 -0600 Subject: [PATCH 3/7] fix: revert .npmrc from gitignore, not needed with bunfig.toml --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1bf42b2e46..a14702c409 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json .idea # Finder (MacOS) folder config -.DS_Store.npmrc +.DS_Store From e62c2a5c4a44c9afdfead0eaa3782b32bd2ea8a4 Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Thu, 9 Apr 2026 19:11:12 +0100 Subject: [PATCH 4/7] refactor: fix type errors --- apps/expo/app/(app)/(tabs)/profile/index.tsx | 6 ++-- .../(app)/(tabs)/profile/notifications.tsx | 7 ++--- apps/expo/app/(app)/keyboard-debug.tsx | 1 - .../app/(app)/weather-alert-preferences.tsx | 6 ++-- .../features/ai/components/AIModeSheet.tsx | 2 +- .../utils/__tests__/asNonNullableRef.test.ts | 4 +-- packages/api/src/routes/ai/index.ts | 2 +- .../generateFromOnlineContent.ts | 6 ++-- .../services/__tests__/catalogService.test.ts | 2 +- .../services/__tests__/weatherService.test.ts | 4 +-- packages/api/src/utils/__tests__/auth.test.ts | 12 ++++---- .../utils/__tests__/env-validation.test.ts | 20 ++++++------- .../utils/__tests__/itemCalculations.test.ts | 29 +++++++++---------- packages/api/test/setup.ts | 2 +- 14 files changed, 48 insertions(+), 55 deletions(-) diff --git a/apps/expo/app/(app)/(tabs)/profile/index.tsx b/apps/expo/app/(app)/(tabs)/profile/index.tsx index 4e2c55f204..0ce70f4312 100644 --- a/apps/expo/app/(app)/(tabs)/profile/index.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/index.tsx @@ -30,7 +30,7 @@ import * as FileSystem from 'expo-file-system/legacy'; import { router, Stack } from 'expo-router'; import * as Updates from 'expo-updates'; import { useRef, useState } from 'react'; -import { Alert, Platform, TouchableOpacity, View } from 'react-native'; +import { Alert, Linking, Platform, TouchableOpacity, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; const AVATAR_MAX_BYTES = 5 * 1024 * 1024; // 5 MB @@ -137,8 +137,8 @@ function ListHeaderComponent() { if (!image) return; // Validate file size before uploading (5 MB limit) - const info = await FileSystem.getInfoAsync(image.uri, { size: true }); - if (info.exists && info.size > AVATAR_MAX_BYTES) { + const info = await FileSystem.getInfoAsync(image.uri); + if (info.exists && 'size' in info && info.size > AVATAR_MAX_BYTES) { Alert.alert(t('errors.somethingWentWrong'), t('profile.imageTooLarge')); return; } diff --git a/apps/expo/app/(app)/(tabs)/profile/notifications.tsx b/apps/expo/app/(app)/(tabs)/profile/notifications.tsx index e7613ad988..9e7d2d0b2e 100644 --- a/apps/expo/app/(app)/(tabs)/profile/notifications.tsx +++ b/apps/expo/app/(app)/(tabs)/profile/notifications.tsx @@ -71,10 +71,7 @@ export default function NotificationsScreen() { - + @@ -99,7 +96,7 @@ export default function NotificationsScreen() { > - + {t('profile.configureAlertTypes')} diff --git a/apps/expo/app/(app)/keyboard-debug.tsx b/apps/expo/app/(app)/keyboard-debug.tsx index 0e402059a2..d28f27e739 100644 --- a/apps/expo/app/(app)/keyboard-debug.tsx +++ b/apps/expo/app/(app)/keyboard-debug.tsx @@ -103,7 +103,6 @@ export default function ChatScreen() { onChangeText={setInputText} placeholder="Type a message..." multiline - maxHeight={100} style={{ flex: 1, borderWidth: 1, diff --git a/apps/expo/app/(app)/weather-alert-preferences.tsx b/apps/expo/app/(app)/weather-alert-preferences.tsx index 30c4e33f93..ad65399b2c 100644 --- a/apps/expo/app/(app)/weather-alert-preferences.tsx +++ b/apps/expo/app/(app)/weather-alert-preferences.tsx @@ -84,7 +84,7 @@ export default function WeatherAlertPreferencesScreen() { - + {t('weather.weatherNotifications')} @@ -132,7 +132,9 @@ export default function WeatherAlertPreferencesScreen() { diff --git a/apps/expo/features/ai/components/AIModeSheet.tsx b/apps/expo/features/ai/components/AIModeSheet.tsx index 3b883a31ee..7a94ef5ef1 100644 --- a/apps/expo/features/ai/components/AIModeSheet.tsx +++ b/apps/expo/features/ai/components/AIModeSheet.tsx @@ -23,7 +23,7 @@ import { } from '../lib/localModelManager'; import { CircularDownloadButton } from './CircularDownloadButton'; -type AIModeSheetProps = Record; +type AIModeSheetProps = {}; export const AIModeSheet = React.forwardRef( function AIModeSheet(_props, ref) { diff --git a/apps/expo/lib/utils/__tests__/asNonNullableRef.test.ts b/apps/expo/lib/utils/__tests__/asNonNullableRef.test.ts index 9ecfa0d0f0..2b7921bf4f 100644 --- a/apps/expo/lib/utils/__tests__/asNonNullableRef.test.ts +++ b/apps/expo/lib/utils/__tests__/asNonNullableRef.test.ts @@ -61,10 +61,10 @@ describe('asNonNullableRef', () => { // ------------------------------------------------------------------------- describe('edge cases', () => { it('handles undefined current value', () => { - const ref = { current: undefined } as React.RefObject; + const ref = { current: null } as React.RefObject; const castedRef = asNonNullableRef(ref); - expect(castedRef.current).toBeUndefined(); + expect(castedRef.current).toBeNull(); }); it('handles empty object', () => { diff --git a/packages/api/src/routes/ai/index.ts b/packages/api/src/routes/ai/index.ts index e9b2f0a33e..fbe51d9086 100644 --- a/packages/api/src/routes/ai/index.ts +++ b/packages/api/src/routes/ai/index.ts @@ -43,7 +43,7 @@ aiRoutes.openapi(ragSearchRoute, async (c) => { const { q: query, limit } = c.req.valid('query'); const aiService = new AIService(c); const result = await aiService.searchPackratOutdoorGuidesRAG(query, limit); - return c.json(result, 200); + return c.json(result as any, 200); } catch (error) { console.error('RAG search error:', error); return c.json({ error: 'Failed to search outdoor guides' }, 500); diff --git a/packages/api/src/routes/packTemplates/generateFromOnlineContent.ts b/packages/api/src/routes/packTemplates/generateFromOnlineContent.ts index 8aa1d29f74..bd1721f765 100644 --- a/packages/api/src/routes/packTemplates/generateFromOnlineContent.ts +++ b/packages/api/src/routes/packTemplates/generateFromOnlineContent.ts @@ -264,7 +264,7 @@ generateFromOnlineContentRoutes.openapi(generateFromOnlineContentRoute, async (c console.log(`Processing content: ${contentUrl}`); - let imageUrls: string[]; + let imageUrls: string[] = []; let videoUrl: string | undefined; let caption: string | undefined; let youtubeVideoTranscript: string | undefined; @@ -295,9 +295,7 @@ generateFromOnlineContentRoutes.openapi(generateFromOnlineContentRoute, async (c } } catch (apiError) { console.error('TikTok service call failed:', apiError); - c.get('sentry').captureException(apiError, { - extra: { tiktokUrl: contentUrl, errorType: 'tiktok_service_error' }, - } as unknown); + c.get('sentry').captureException(apiError); return c.json( { error: `Failed to fetch data from TikTok URL: ${apiError instanceof Error ? apiError.message : 'TikTok service unavailable'}`, diff --git a/packages/api/src/services/__tests__/catalogService.test.ts b/packages/api/src/services/__tests__/catalogService.test.ts index 315a9f9406..e86aaff3d5 100644 --- a/packages/api/src/services/__tests__/catalogService.test.ts +++ b/packages/api/src/services/__tests__/catalogService.test.ts @@ -174,7 +174,7 @@ describe('CatalogService', () => { }); it('returns empty result when embeddings generation fails', async () => { - vi.mocked(embeddingService.generateManyEmbeddings).mockResolvedValueOnce(null); + vi.mocked(embeddingService.generateManyEmbeddings).mockResolvedValueOnce([]); const result = await service.batchVectorSearch(['tent', 'sleeping bag'], 5); diff --git a/packages/api/src/services/__tests__/weatherService.test.ts b/packages/api/src/services/__tests__/weatherService.test.ts index f9dc338b39..e5b0da2c3d 100644 --- a/packages/api/src/services/__tests__/weatherService.test.ts +++ b/packages/api/src/services/__tests__/weatherService.test.ts @@ -61,7 +61,7 @@ describe('WeatherService', () => { // Mock global fetch fetchMock = vi.fn(); - global.fetch = fetchMock; + (global.fetch as any) = fetchMock; }); // ------------------------------------------------------------------------- @@ -300,7 +300,7 @@ describe('WeatherService', () => { await service.getWeatherForLocation('Austin'); - const calledUrl = fetchMock.mock.calls[0][0]; + const calledUrl = fetchMock.mock.calls[0]?.[0]; expect(calledUrl).toContain('https://api.openweathermap.org/data/2.5/weather'); expect(calledUrl).toContain('q=Austin'); expect(calledUrl).toContain('units=imperial'); diff --git a/packages/api/src/utils/__tests__/auth.test.ts b/packages/api/src/utils/__tests__/auth.test.ts index d2bc842e8b..627d726c0d 100644 --- a/packages/api/src/utils/__tests__/auth.test.ts +++ b/packages/api/src/utils/__tests__/auth.test.ts @@ -86,7 +86,7 @@ describe('auth utils', () => { // ------------------------------------------------------------------------- describe('hashPassword', () => { it('hashes a password using bcrypt', async () => { - vi.mocked(bcrypt.hash).mockResolvedValue('hashed_password'); + vi.mocked(bcrypt.hash).mockResolvedValue('hashed_password' as never); const result = await hashPassword('myPassword123'); @@ -95,7 +95,7 @@ describe('auth utils', () => { }); it('uses 10 salt rounds', async () => { - vi.mocked(bcrypt.hash).mockResolvedValue('hash'); + vi.mocked(bcrypt.hash).mockResolvedValue('hash' as never); await hashPassword('test'); @@ -103,7 +103,7 @@ describe('auth utils', () => { }); it('handles empty password', async () => { - vi.mocked(bcrypt.hash).mockResolvedValue('hash_empty'); + vi.mocked(bcrypt.hash).mockResolvedValue('hash_empty' as never); const result = await hashPassword(''); @@ -111,7 +111,7 @@ describe('auth utils', () => { }); it('handles special characters in password', async () => { - vi.mocked(bcrypt.hash).mockResolvedValue('hash_special'); + vi.mocked(bcrypt.hash).mockResolvedValue('hash_special' as never); await hashPassword('p@$$w0rd!'); @@ -206,7 +206,7 @@ describe('auth utils', () => { await generateJWT({ payload: { userId: 1 }, c }); const afterTime = Math.floor(Date.now() / 1000); - const signCall = vi.mocked(sign).mock.calls[0][0] as any; + const signCall = vi.mocked(sign).mock.calls[0]?.[0] as any; const exp = signCall.exp; // Should be approximately 7 days (604800 seconds) from now @@ -224,7 +224,7 @@ describe('auth utils', () => { c, }); - const signCall = vi.mocked(sign).mock.calls[0][0] as any; + const signCall = vi.mocked(sign).mock.calls[0]?.[0] as any; expect(signCall).toMatchObject({ userId: 456, role: 'ADMIN', diff --git a/packages/api/src/utils/__tests__/env-validation.test.ts b/packages/api/src/utils/__tests__/env-validation.test.ts index f6a9c80c93..f2c8af87d2 100644 --- a/packages/api/src/utils/__tests__/env-validation.test.ts +++ b/packages/api/src/utils/__tests__/env-validation.test.ts @@ -20,8 +20,8 @@ describe('env-validation', () => { beforeEach(() => { vi.clearAllMocks(); // Reset test environment detection - delete process.env.NODE_ENV; - delete process.env.VITEST; + delete (process.env as any).NODE_ENV; + delete (process.env as any).VITEST; }); // ------------------------------------------------------------------------- @@ -205,7 +205,7 @@ describe('env-validation', () => { // ------------------------------------------------------------------------- describe('getEnv', () => { it('returns validated environment in production', () => { - process.env.NODE_ENV = 'production'; + (process.env as any).NODE_ENV = 'production'; const mockEnv = { ENVIRONMENT: 'production', @@ -258,7 +258,7 @@ describe('env-validation', () => { }); it('uses relaxed validation in test environment', () => { - process.env.NODE_ENV = 'test'; + (process.env as any).NODE_ENV = 'test'; const minimalEnv = { JWT_SECRET: 'test-secret', @@ -289,7 +289,7 @@ describe('env-validation', () => { }); it('caches validated environment per context', () => { - process.env.NODE_ENV = 'test'; + (process.env as any).NODE_ENV = 'test'; const c = makeMockContext(); vi.mocked(env).mockReturnValue({ JWT_SECRET: 'secret' } as any); @@ -301,7 +301,7 @@ describe('env-validation', () => { }); it('does not cache across different contexts', () => { - process.env.NODE_ENV = 'test'; + (process.env as any).NODE_ENV = 'test'; const c1 = makeMockContext(); const c2 = makeMockContext(); @@ -316,7 +316,7 @@ describe('env-validation', () => { }); it('throws error for invalid environment in production', () => { - process.env.NODE_ENV = 'production'; + (process.env as any).NODE_ENV = 'production'; const invalidEnv = { JWT_SECRET: 'secret', @@ -330,7 +330,7 @@ describe('env-validation', () => { }); it('merges Cloudflare bindings from raw env', () => { - process.env.NODE_ENV = 'test'; + (process.env as any).NODE_ENV = 'test'; const mockAI = { run: vi.fn() }; const mockBucket = { get: vi.fn() }; @@ -441,7 +441,7 @@ describe('env-validation', () => { // ------------------------------------------------------------------------- describe('test environment detection', () => { it('detects NODE_ENV=test', () => { - process.env.NODE_ENV = 'test'; + (process.env as any).NODE_ENV = 'test'; const c = makeMockContext(); vi.mocked(env).mockReturnValue({} as any); @@ -459,7 +459,7 @@ describe('env-validation', () => { }); it('uses production schema when not in test', () => { - delete process.env.NODE_ENV; + delete (process.env as any).NODE_ENV; delete process.env.VITEST; const c = makeMockContext(); diff --git a/packages/api/src/utils/__tests__/itemCalculations.test.ts b/packages/api/src/utils/__tests__/itemCalculations.test.ts index 96d4f9d53d..1546af328a 100644 --- a/packages/api/src/utils/__tests__/itemCalculations.test.ts +++ b/packages/api/src/utils/__tests__/itemCalculations.test.ts @@ -17,33 +17,31 @@ import { const mockPackItem: PackItem = { id: 'pack-1', packId: 'pack-123', - itemId: 'item-1', name: 'Tent', weight: 2500, weightUnit: 'g', quantity: 2, + category: 'Shelter', notes: 'Lightweight camping tent', worn: false, consumable: false, - ownerId: 'user-1', - createdAt: new Date(), - updatedAt: new Date(), - deleted: false, + userId: 1, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), }; const mockCatalogItem: CatalogItem = { - id: 'catalog-1', + id: 1, name: 'Sleeping Bag', description: 'Warm sleeping bag', - category: 'Shelter', sku: 'SB-001', weight: 1500, + weightUnit: 'g', price: 199.99, seller: 'OutdoorGear', productUrl: 'https://example.com/sleeping-bag', - imageUrl: 'https://example.com/images/sleeping-bag.jpg', - createdAt: new Date(), - updatedAt: new Date(), + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), }; describe('itemCalculations', () => { @@ -60,7 +58,7 @@ describe('itemCalculations', () => { }); it('returns false for item with undefined packId', () => { - const itemWithUndefinedPackId = { ...mockPackItem, packId: undefined }; + const itemWithUndefinedPackId = { ...mockPackItem, packId: undefined as any }; expect(isPackItem(itemWithUndefinedPackId)).toBe(false); }); @@ -371,17 +369,16 @@ describe('itemCalculations', () => { const minimalItem: PackItem = { id: 'min-1', packId: 'pack-1', - itemId: 'item-1', name: 'Minimal', weight: 0, weightUnit: 'g', quantity: 1, + category: 'Misc', worn: false, consumable: false, - ownerId: 'user-1', - createdAt: new Date(), - updatedAt: new Date(), - deleted: false, + userId: 1, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), }; expect(isPackItem(minimalItem)).toBe(true); diff --git a/packages/api/test/setup.ts b/packages/api/test/setup.ts index 1fe800e4ff..3de5f322ef 100644 --- a/packages/api/test/setup.ts +++ b/packages/api/test/setup.ts @@ -422,7 +422,7 @@ beforeAll(async () => { try { await testClient.connect(); - testDb = drizzle(testClient, { schema }) as unknown; + testDb = drizzle(testClient, { schema }) as any; isConnected = true; console.log('✅ Test database connected successfully'); } catch (error) { From 8625effaa367f0b55793c6391bcfa26abae51b2e Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Thu, 9 Apr 2026 19:26:44 +0100 Subject: [PATCH 5/7] refactor: fix type errors --- .../__tests__/imageDetectionService.test.ts | 2 +- .../services/__tests__/weatherService.test.ts | 6 ++--- packages/api/src/utils/__tests__/auth.test.ts | 2 +- .../utils/__tests__/itemCalculations.test.ts | 22 +++++++++---------- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/api/src/services/__tests__/imageDetectionService.test.ts b/packages/api/src/services/__tests__/imageDetectionService.test.ts index 5cf407510f..530fbbeeb4 100644 --- a/packages/api/src/services/__tests__/imageDetectionService.test.ts +++ b/packages/api/src/services/__tests__/imageDetectionService.test.ts @@ -307,7 +307,7 @@ describe('ImageDetectionService', () => { // Should only return the item with catalog matches expect(result).toHaveLength(1); - expect(result[0].detected.name).toBe('Tent'); + expect(result[0]!.detected.name).toBe('Tent'); }); it('handles catalog service errors gracefully', async () => { diff --git a/packages/api/src/services/__tests__/weatherService.test.ts b/packages/api/src/services/__tests__/weatherService.test.ts index ef84166f48..107cf01d56 100644 --- a/packages/api/src/services/__tests__/weatherService.test.ts +++ b/packages/api/src/services/__tests__/weatherService.test.ts @@ -4,7 +4,7 @@ import { WeatherService } from '../weatherService'; // --------------------------------------------------------------------------- // Mocks // --------------------------------------------------------------------------- -global.fetch = vi.fn(); +global.fetch = vi.fn() as any; vi.mock('@packrat/api/utils/env-validation', () => ({ getEnv: vi.fn(() => ({ @@ -20,14 +20,14 @@ function makeMockContext() { } function mockWeatherResponse(data: any) { - (global.fetch as ReturnType).mockResolvedValue({ + (global.fetch as any).mockResolvedValue({ ok: true, json: async () => data, } as Response); } function mockWeatherError() { - (global.fetch as ReturnType).mockResolvedValue({ + (global.fetch as any).mockResolvedValue({ ok: false, status: 404, } as Response); diff --git a/packages/api/src/utils/__tests__/auth.test.ts b/packages/api/src/utils/__tests__/auth.test.ts index 3fe546b6fe..6abb64c096 100644 --- a/packages/api/src/utils/__tests__/auth.test.ts +++ b/packages/api/src/utils/__tests__/auth.test.ts @@ -142,7 +142,7 @@ describe('auth utilities', () => { }); const { sign } = await import('hono/jwt'); - const callArgs = (sign as ReturnType).mock.calls[0][0]; + const callArgs = (sign as ReturnType).mock.calls[0]![0]; expect(callArgs.exp).toBeGreaterThanOrEqual(beforeTime - 5); expect(callArgs.exp).toBeLessThanOrEqual(beforeTime + 5); diff --git a/packages/api/src/utils/__tests__/itemCalculations.test.ts b/packages/api/src/utils/__tests__/itemCalculations.test.ts index e7cca93b48..d9cd91ddb8 100644 --- a/packages/api/src/utils/__tests__/itemCalculations.test.ts +++ b/packages/api/src/utils/__tests__/itemCalculations.test.ts @@ -48,22 +48,20 @@ function makeCatalogItem(overrides: Partial = {}): CatalogItem { brand: 'TestBrand', model: 'TestModel', ratingValue: 4.5, - color: null, - size: null, + color: undefined, + size: undefined, price: 99.99, availability: 'in_stock', - seller: null, - productSku: null, - material: null, + seller: undefined, + productSku: undefined, + material: undefined, currency: 'USD', - condition: null, + condition: undefined, reviewCount: 0, - variants: null, - techs: null, - links: null, - reviews: null, - qas: null, - faqs: null, + variants: undefined, + techs: undefined, + links: undefined, + reviews: undefined, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), ...overrides, From b8384fdeee8009d5403a4c407c1e27f1b3fbd14f Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Thu, 9 Apr 2026 19:34:51 +0100 Subject: [PATCH 6/7] refactor: remove unused keyboard debug and circular download button components --- apps/expo/app/(app)/keyboard-debug.tsx | 127 ---------- .../CircularDownloadButton_ShimmerEffect.tsx | 233 ------------------ 2 files changed, 360 deletions(-) delete mode 100644 apps/expo/app/(app)/keyboard-debug.tsx delete mode 100644 apps/expo/features/ai/components/CircularDownloadButton_ShimmerEffect.tsx diff --git a/apps/expo/app/(app)/keyboard-debug.tsx b/apps/expo/app/(app)/keyboard-debug.tsx deleted file mode 100644 index d28f27e739..0000000000 --- a/apps/expo/app/(app)/keyboard-debug.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { useRef, useState } from 'react'; -import { - KeyboardAvoidingView, - Platform, - ScrollView, - Text, - TextInput, - TouchableOpacity, - View, -} from 'react-native'; - -interface Message { - id: string; - text: string; - isUser: boolean; - timestamp: Date; -} - -export default function ChatScreen() { - const [messages, setMessages] = useState([ - { - id: '1', - text: 'Welcome to the chat! This is a sample message to test keyboard avoidance.', - isUser: false, - timestamp: new Date(), - }, - { - id: '2', - text: 'Hello! Testing the chat interface.', - isUser: true, - timestamp: new Date(), - }, - { - id: '3', - text: 'The keyboard should properly avoid this input field when you type.', - isUser: false, - timestamp: new Date(), - }, - ]); - const [inputText, setInputText] = useState(''); - const scrollViewRef = useRef(null); - - const sendMessage = () => { - if (inputText.trim()) { - const newMessage: Message = { - id: Date.now().toString(), - text: inputText.trim(), - isUser: true, - timestamp: new Date(), - }; - setMessages((prev) => [...prev, newMessage]); - setInputText(''); - } - }; - - const renderMessage = (message: Message) => ( - - - - {message.text} - - - {message.timestamp.toLocaleTimeString([], { - hour: '2-digit', - minute: '2-digit', - })} - - - - ); - - return ( - - {/* Header */} - - Chat Debug - Testing keyboard avoidance behavior - - - {/* Messages */} - scrollViewRef.current?.scrollToEnd({ animated: true })} - > - {messages.map(renderMessage)} - - - {/* Input Area */} - - - - Send - - - - ); -} diff --git a/apps/expo/features/ai/components/CircularDownloadButton_ShimmerEffect.tsx b/apps/expo/features/ai/components/CircularDownloadButton_ShimmerEffect.tsx deleted file mode 100644 index 411b85a2ef..0000000000 --- a/apps/expo/features/ai/components/CircularDownloadButton_ShimmerEffect.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; -import * as React from 'react'; -import { TouchableOpacity, View } from 'react-native'; -import Animated, { - cancelAnimation, - Easing, - useAnimatedProps, - useSharedValue, - withRepeat, - withTiming, -} from 'react-native-reanimated'; -import { Defs, LinearGradient, Path, Stop, Svg } from 'react-native-svg'; - -const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient); - -// Arrow-down path in a 24×24 viewBox -const ARROW_PATH = 'M12 4 L12 20 M5 13 L12 20 L19 13'; -const SVG_SIZE = 24; -const SHIMMER_BAND = 4; // half-height of the highlight band in SVG units - -type Props = { - progress: number; - isDownloading: boolean; - onDownload: () => void; - size?: number; - strokeWidth?: number; -}; - -function ArrowSvg({ size, color }: { size: number; color: string }) { - return ( - - - - ); -} - -export function CircularDownloadButton({ - progress, - isDownloading, - onDownload, - size = 44, - strokeWidth = 4, -}: Props) { - const { colors } = useColorScheme(); - const HALF = size / 2; - const innerSize = size - strokeWidth * 2; - const innerRadius = innerSize / 2; - const iconSize = size * 0.45; - - // Shimmer sweeps from above the arrow to below - const shimmerPos = useSharedValue(-SHIMMER_BAND); - - React.useEffect(() => { - if (isDownloading) { - shimmerPos.value = -SHIMMER_BAND; - shimmerPos.value = withRepeat( - withTiming(SVG_SIZE + SHIMMER_BAND, { - duration: 1200, - easing: Easing.linear, - }), - -1, - false, - ); - } else { - cancelAnimation(shimmerPos); - shimmerPos.value = -SHIMMER_BAND; - } - }, [isDownloading, shimmerPos]); - - // y1/y2 define the gradient band position in SVG user space - const gradientId = React.useId().replace(/:/g, ''); - - const gradientProps = useAnimatedProps(() => ({ - y1: shimmerPos.value - SHIMMER_BAND, - y2: shimmerPos.value + SHIMMER_BAND, - })); - - // Rotation angles for the two D-shape halves - const r1 = isDownloading ? (progress <= 50 ? 180 + (progress / 50) * 180 : 0) : 180; - const r2 = isDownloading ? (progress > 50 ? 180 - ((progress - 50) / 50) * 180 : 180) : 180; - - const handlePress = () => { - if (isDownloading) return; - onDownload(); - }; - - return ( - - {isDownloading ? ( - // ── Circular progress ring ────────────────────────────────────── - - {/* Track (full circle background) */} - - - {/* Right-half fill (0→50%) */} - - - - - {/* Left-half fill (50→100%) */} - - - - - {/* Inner hole — shimmer arrow */} - - - - {/* Gradient band position is animated top-to-bottom in SVG user space */} - - - - - - - - - - - ) : ( - // ── Download icon button ──────────────────────────────────────── - - - - )} - - ); -} From 542b56f50feaf6ec38a3884ad67585885919f71f Mon Sep 17 00:00:00 2001 From: Ibrahim Isa Jajere Date: Thu, 9 Apr 2026 19:41:58 +0100 Subject: [PATCH 7/7] refactor: fix type errors --- .../api/src/routes/packTemplates/packTemplates.ts | 11 ++++++++++- packages/api/src/services/r2-bucket.ts | 3 ++- packages/api/src/utils/auth.ts | 2 +- packages/api/src/utils/typeAssertions.ts | 4 ++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/api/src/routes/packTemplates/packTemplates.ts b/packages/api/src/routes/packTemplates/packTemplates.ts index 77004f0a46..161995eefc 100644 --- a/packages/api/src/routes/packTemplates/packTemplates.ts +++ b/packages/api/src/routes/packTemplates/packTemplates.ts @@ -10,6 +10,7 @@ import { } from '@packrat/api/schemas/packTemplates'; import type { Env } from '@packrat/api/types/env'; import type { Variables } from '@packrat/api/types/variables'; +import { assertDefined } from '@packrat/api/utils/typeAssertions'; import { and, eq, or } from 'drizzle-orm'; const packTemplateRoutes = new OpenAPIHono<{ @@ -131,7 +132,15 @@ packTemplateRoutes.openapi(createTemplateRoute, async (c) => { }) .returning(); - return c.json(newTemplate, 201); + assertDefined(newTemplate, 'Failed to create pack template'); + + // Query the created template with its items to match the response schema + const templateWithItems = await db.query.packTemplates.findFirst({ + where: eq(packTemplates.id, newTemplate.id), + with: { items: true }, + }); + + return c.json(templateWithItems, 201); }); // Get a specific pack template diff --git a/packages/api/src/services/r2-bucket.ts b/packages/api/src/services/r2-bucket.ts index 5cb2d6d446..ac26cc9051 100644 --- a/packages/api/src/services/r2-bucket.ts +++ b/packages/api/src/services/r2-bucket.ts @@ -333,7 +333,8 @@ export class R2BucketService { }, blob: async () => { assertStreamNotConsumed(); - return new globalThis.Blob([await consumeStream()]); + const data = await consumeStream(); + return new globalThis.Blob([data.buffer as ArrayBuffer]); }, }; diff --git a/packages/api/src/utils/auth.ts b/packages/api/src/utils/auth.ts index ed8e17981e..0c10494a94 100644 --- a/packages/api/src/utils/auth.ts +++ b/packages/api/src/utils/auth.ts @@ -54,7 +54,7 @@ export async function verifyJWT({ }): Promise { try { const { JWT_SECRET } = getEnv(c); - return await verify(token, JWT_SECRET); + return await verify(token, JWT_SECRET, 'HS256'); } catch (_error) { return null; } diff --git a/packages/api/src/utils/typeAssertions.ts b/packages/api/src/utils/typeAssertions.ts index c28d77bcde..5c1c1bad5b 100644 --- a/packages/api/src/utils/typeAssertions.ts +++ b/packages/api/src/utils/typeAssertions.ts @@ -1,5 +1,5 @@ -export function assertDefined(val: T | undefined): asserts val is T { - if (val === undefined) throw new Error('Value must be defined'); +export function assertDefined(val: T | undefined, message?: string): asserts val is T { + if (val === undefined) throw new Error(message ?? 'Value must be defined'); } export function assertAllDefined(...values: (unknown | undefined)[]): void {