AGENTS.md
Must-follow constraints
- This is a React Native / Expo SDK 55 app (not a web app). Do not use DOM APIs, CSS files, or web-only libraries.
- Styling uses NativeWind v4 (TailwindCSS 3 for RN) via
babel-preset-expo + nativewind/babel. Inline style={} objects are also used extensively — match surrounding code style.
- Navigation uses
@react-navigation/native (stack + material-top-tabs). There is no file-based routing via expo-router despite it being installed — all routing is in App.tsx.
- All user-facing strings must go through
react-i18next (useTranslation / t()). Never hardcode display text. Fallback language is tr (Turkish).
- Six locale files exist:
src/locales/{en,tr,es,fr,zh,ja}.json. When adding a new string key, add it to all six files.
- Firebase Auth persistence uses
getReactNativePersistence(AsyncStorage). Do not use firebase/auth/react-native or setPersistence — it will crash.
- Environment variables must use
EXPO_PUBLIC_ prefix to be accessible at runtime.
- The app entry point is
App.tsx at root (not app/ directory). main in package.json points to node_modules/expo/AppEntry.js.
- RevenueCat simulation is driven by
EXPO_PUBLIC_IAP_SIMULATION ("1"/unset = simulation, "0" = real). Production builds set it to "0" in eas.json.
- AdMob initialization is guarded by
Constants.appOwnership !== 'expo'. It will be skipped in Expo Go — this is intentional.
- Safe Area management uses
react-native-safe-area-context exclusively. Never use the legacy SafeAreaView from react-native. Screens with headerShown: false must use useSafeAreaInsets() for top/bottom padding — never use hardcoded platform-specific values (e.g., paddingTop: 60). Full-screen gates rendered outside the navigation tree (loading, banned, email verification) must be wrapped in SafeAreaProvider + SafeAreaView.
Validation before finishing
npx expo lint — ESLint check
npx tsc --noEmit — TypeScript type check
npx expo start — verify Metro bundler starts without errors
Repo-specific conventions
- Context providers wrap the app in this order (outermost → innermost):
GestureHandlerRootView → SafeAreaProvider → ThemeProvider → LanguageProvider → TokenProvider
TokenContext is the central state manager for tokens, premium status, admin, banned, badges, athlete mode, and daily scan economy. Access via useToken().
- Premium is derived from three sources: Firestore
premiumUntil timestamp, RevenueCat entitlement, or active trial. Never set a boolean isPremium field in Firestore — use premiumUntil (a Firestore Timestamp).
- Product analysis uses Gemini API (
gemini-2.5-flash) via src/services/analyze.ts. The prompt enforces JSON-only responses with responseMimeType: "application/json".
- Category values are Turkish strings:
"Gıda", "Kozmetik", "Temizlik Ürünleri", "Vitaminler", "Takviyeler", "Takviye / Vitamin".
Important locations
App.tsx — all navigation, auth gate, email verification, banned gate, provider tree
src/config/firebase.ts — Firebase init (single source of truth)
src/context/TokenContext.tsx — token/premium/admin/ban/badge/scan economy state
src/services/analyze.ts — Gemini AI product analysis with streaming support
src/services/revenuecat.ts — IAP with simulation mode toggle
src/locales/*.json — i18n translation files
firestore.rules — Firestore security rules (admin helper, per-collection ACLs)
Change safety rules
- Do not modify
firestore.rules without understanding the isAdmin() helper and global deny rule at the bottom.
- Do not remove the
@ts-ignore above getReactNativePersistence import in firebase.ts — it is required.
- Do not change the Metro config’s
unstable_enablePackageExports — it fixes AdMob @iabtcf/core resolution.
.env is gitignored. Never put secrets in eas.json — the Gemini key lives ONLY as a Firebase Functions secret (GEMINI_API_KEY).
Known gotchas
expo-router is installed but not used for routing. All navigation is manual in App.tsx. Do not create an app/ directory expecting file-based routing.
- The
google-services.json is committed (Android Firebase config). Do not delete it.