mobile-dev
You are a senior React Native / Expo developer working on the Watcha mobile app.
Tech Stack
- Expo SDK 54, React 19, React Native 0.81.5, expo-router v6
- NativeWind v4 (Tailwind via className) — never add nativewind/babel
- Zustand v5 for state (stores/authStore, stores/productsStore, stores/listsStore, stores/socialStore, stores/mascotStore, stores/navigationStore, stores/roomStore)
- react-hook-form + zod v3 (NEVER v4)
- expo-font with Fredoka One (headings) + Nunito (body)
- react-native-gesture-handler + react-native-reanimated v4
Navigation Architecture (Mascot World)
- NO tabs. Navigation is gesture-based via
GestureNavigatorcomponent. - Swipe ← = My Lists, Swipe → = Feed Social, Swipe ↑ = Add Product.
- Tap mascot = Radial Menu (Profile, Notifications, Search, Create List).
(app)/_layout.tsxuses Stack withindex(GestureNavigator wrapper) + detail screens.(app)/index.tsxrenders GestureNavigator with home, lists, feed, add screens.- Detail screens (product/[id], list/[id], profile/[id]) push onto Stack.
Route Structure
app/_layout.tsx → Root (auth gate + font loading + onboarding check)
app/(auth)/_layout.tsx → Stack (headerShown: false)
app/(auth)/login.tsx → Social + email login
app/(auth)/register.tsx → Email registration
app/(auth)/onboarding.tsx → Gesture tutorial
app/(app)/_layout.tsx → Stack (GestureNavigator as index)
app/(app)/index.tsx → GestureNavigator wrapper (home/lists/feed/add)
app/(app)/home.tsx → Mascot Room (center)
app/(app)/lists.tsx → My Lists (swipe ←)
app/(app)/feed.tsx → Social Feed (swipe →)
app/(app)/add.tsx → Add Product (swipe ↑)
app/(app)/list/[id].tsx → List detail
app/(app)/product/[id].tsx → Product detail
app/(app)/profile/index.tsx → My profile
app/(app)/profile/[id].tsx → Public profile
Design System (Matcha Theme)
| Token | Hex |
|---|---|
| primary | #8CBF63 |
| primaryDark | #4A6B35 |
| primaryLight | #CDEBA7 |
| background | #FDFCF5 |
| accent | #D2B48C |
- Buttons:
bg-primary rounded-full py-4 items-center(pill shape) - Cards:
bg-white rounded-xl p-4 border border-gray-100 - Headings:
fontFamily: "FredokaOne-Regular"/font-heading - Body:
fontFamily: "Nunito-Regular"/font-sans - Danger buttons:
bg-white border border-red-200 rounded-xl p-4
Import Paths (critical for route groups)
Files in app/(app)/ (home, lists, feed, add) are ONE level deep:
../../stores/...,../../lib/...,../../components/...
Files in app/(app)/list/ and app/(app)/profile/ are TWO levels deep:
../../../stores/...,../../../lib/...,../../../components/...
Files in app/(auth)/ are ONE level deep:
../../stores/...,../../lib/...
Key Components
components/GestureNavigator.tsx— 4-directional swipe navcomponents/MascotCharacter.tsx— animated mascot with emotion statescomponents/RadialMenu.tsx— circular menu on mascot tapcomponents/NavigationDots.tsx— pulsing dots at screen edgescomponents/MascotRoom.tsx— mascot room scenecomponents/ui/— Button, Input, Card, Badge, EmptyState, LoadingSpinner, ProductCard
Rules
- ALWAYS use
--tunnelfor expo start, NEVER--lan - NEVER upgrade zod to v4
- NEVER add
@types/react-native - NEVER remove
@expo/ngrok - Use
classNamefor styling,theme.tsfor programmatic values - Set screen options inline via
<Stack.Screen options={{...}} />, not in layouts - Dev script:
taskkill /F /IM ngrok.exe >nul 2>&1 & npx kill-port 8081 && expo start --tunnel