Architecture
Understand the runtime architecture, providers, gates, and navigation.
The Expo template keeps architecture visible in a few files instead of hiding it
behind a framework layer. app/_layout.tsx is the runtime composition root,
Expo Router owns navigation, TanStack Query owns server state, and provider
hooks are the dependency-injection surface.
Routing
Expo Router maps files to routes and app.config.ts enables
experiments.typedRoutes.
| Pattern | File or example | Notes |
|---|---|---|
| Route groups | app/(auth), app/(tabs) | Group files without changing the URL path |
| Tabs | app/(tabs)/_layout.tsx | Four visible tabs plus hidden tab-context screens with href: null |
| Pushed screens | app/todos.tsx, app/profile.tsx, app/permissions.tsx | Opened from drawer rows, cards, or deep links |
| Dynamic routes | app/component/[slug].tsx | Reads slug with useLocalSearchParams() |
| Modals | Stack.Screen name="paywall" and name="redeem-code" | Root stack sets presentation: 'modal' |
| Fallback | app/+not-found.tsx | Handles unknown paths |
| Web shell | app/+html.tsx | Used by pnpm web and Metro static output |
Deep-linkable paths are intentionally allow-listed in lib/deep-links.ts.
Adding a route does not automatically make it safe for push notifications,
referral links, or external URLs.
Provider stack
RootLayout is wrapped by Sentry.wrap(...) and renders this order:
| Order | Provider/component | Why it is there |
|---|---|---|
| 1 | RootErrorBoundary | Branded runtime fallback and Sentry capture |
| 2 | GestureHandlerRootView, KeyboardProvider, SafeAreaProvider | Required native infrastructure for gestures, keyboard, and insets |
| 3 | QueryProvider | TanStack Query cache, persistence, focus/online managers |
| 4 | AuthProvider | Supabase session and auth actions |
| 5 | AnalyticsProvider | PostHog capture, identify/reset, consent-aware queueing |
| 6 | ConsentProvider, OnboardingProvider | Persisted legal and onboarding preferences |
| 7 | EntitlementProvider, PurchasesProvider | Supabase entitlement read-model plus RevenueCat purchase mechanism |
| 8 | I18nProvider | t, locale, and setLocale |
| 9 | ThemePreferenceProvider, React Navigation ThemeProvider | NativeWind preference plus navigation colors |
| 10 | NotificationsProvider, BiometricProvider, ToastProvider, BottomSheetModalProvider | App services and global UI surfaces |
The gates are not wrapper components around the navigator. They are siblings
inside the provider tree: OnboardingGate, AuthGate, and VersionGate render
before the root Stack; ConsentGate and BiometricGate render after it as
overlay-style gates.
Use hooks as the injection surface: useAuth(), useI18n(),
useEntitlement(), usePurchases(), useToast(), useNotifications(),
useThemePreference(), and service-specific helpers.
State conventions
| State kind | Owner |
|---|---|
| Server state | TanStack Query in lib/query.tsx and screen hooks |
| Supabase session | AuthProvider, backed by SecureStore chunking on native |
| Entitlement | EntitlementProvider, reading the subscriptions table |
| Purchases | PurchasesProvider, RevenueCat SDK state and purchase actions |
| App preferences | Small providers backed by AsyncStorage/SecureStore/localStorage |
| UI notifications | ToastProvider, NotificationsProvider, bottom sheets/dialogs |
There is no Redux or Zustand store. Add one only if a future feature has global client state that is not a server cache, auth/session state, or a simple persisted preference.
Styling
Styling is split across three connected layers:
| Layer | File | Role |
|---|---|---|
| NativeWind classes | Screens and components | className for layout, type, spacing, and tokens |
| CSS variables | global.css | Light/dark shadcn-style and iOS grouped color tokens |
| JS tokens | lib/theme.ts | THEME, IOS_COLORS, getIOSColor(), and NAV_THEME |
Reusable variants use CVA, for example buttonVariants,
buttonTextVariants, and textVariants. Platform fallbacks use the normal
React Native file pattern: store-review.web.ts and
tracking-permissions.web.ts no-op where native APIs do not exist.
Code style and checks
The template uses strict TypeScript, Prettier with Tailwind class sorting, Expo's ESLint flat config, Husky, and lint-staged.
pnpm typecheck
pnpm lint
pnpm format:checkThere is no Jest/Vitest setup in the template today. The shipped automated checks are TypeScript, Expo lint, formatting, and the Deno test for the RevenueCat webhook mapper:
deno test supabase/functions/_shared/revenuecat.test.ts