ποΈ Architecture β
Routing β
The app uses Expo Router β the filesystem maps directly to screens. Route groups (parenthesized folders) never appear in URLs.
app/
βββ _layout.tsx # Root Stack: Sentry init, providers
βββ (auth)/ # Unauthenticated routes
β βββ _layout.tsx # Redirects β / if already authenticated
β βββ login.tsx # Email/password login
β βββ register.tsx # Email/password signup
β βββ forgot-password.tsx # Password reset
βββ (app)/ # Protected routes (requires auth)
β βββ _layout.tsx # Redirects β /login if not authenticated
β βββ (tabs)/ # Bottom tab navigation
β β βββ _layout.tsx # 3-tab layout: Map, Chat, Settings
β β βββ index.tsx # Map tab
β β βββ chat.tsx # LLM chat (streaming SSE)
β β βββ settings.tsx # Settings + sign-out
β βββ modal.tsx # Modal screenTyped routes are enabled (experiments.typedRoutes in app.json) β use the href prop with string literals and TypeScript will catch invalid paths.
Authentication β
Firebase Authentication via @react-native-firebase/auth (native modules β not the JS SDK).
auth/
βββ auth-context.tsx # AuthProvider + useAuth() hook
βββ mutations.ts # signIn, signUp, signOut, resetPassword
βββ map-auth-error.ts # Firebase error codes β user-friendly messages
βββ index.ts # Barrel re-exportAuth state drives the routing guards in (auth)/_layout.tsx and (app)/_layout.tsx:
// (app)/_layout.tsx β redirect to login if no user
const { user, isLoading } = useAuth();
if (!isLoading && !user) return <Redirect href="/login" />;Firebase ID tokens are obtained via lib/firebase/auth.ts β getFirebaseToken(), which is called automatically by the API client before every request.
Styling: Uniwind + CVA β
All styling uses Uniwind β Tailwind CSS classes compiled to React Native StyleSheet at build time via Metro. There is no StyleSheet.create() in most components.
// Use className like on the web
<View className="flex-1 bg-background px-4">
<Text className="text-foreground font-semibold text-lg">Hello</Text>
</View>Design tokens (colors, spacing, radii, dark mode) are defined in packages/tailwind-config/global.css and re-exported in apps/mobile/global.css:
@import '@tellia-solutions/tailwind-config/global.css';
@source '../../packages/ui/src';Merging Classes β
Use cn() from @tellia-solutions/ui to conditionally merge classes (like clsx + tailwind-merge):
import { cn } from '@tellia-solutions/ui';
<View className={cn('p-4', isActive && 'bg-primary')} />;Component Variants with CVA β
For components with multiple visual states, use CVA (Class Variance Authority):
import { cva } from 'class-variance-authority';
const buttonVariants = cva('rounded-lg px-4 py-2', {
variants: {
variant: {
primary: 'bg-primary text-primary-foreground',
destructive: 'bg-destructive text-destructive-foreground',
},
size: {
sm: 'text-sm',
lg: 'text-lg',
},
},
defaultVariants: { variant: 'primary', size: 'sm' },
});See packages/ui/CLAUDE.md for the full CVA patterns used in the shared UI package.
Bottom Sheets β
Uses react-native-actions-sheet. Sheets are registered in sheets/registry.ts and typed via module augmentation in sheets.d.ts.
Available Sheets β
| Sheet ID | Purpose |
|---|---|
confirm-sheet | Generic yes/no confirmation |
create-note-sheet | Create a geo-located note |
note-detail-sheet | View/edit an existing note |
field-action-sheet | Actions on a map field |
field-save-action-sheet | Save a drawn field |
parcel-action-sheet | Actions on a parcel |
Opening a Sheet β
import SheetManager from 'react-native-actions-sheet';
await SheetManager.show('confirm-sheet', {
payload: { title: 'Delete field?', onConfirm: handleDelete },
});SheetProvider must wrap the app (it does, in the root _layout.tsx).
Path Alias β
@/* maps to the apps/mobile/ root (set in tsconfig.json). Always use the alias in imports:
// β
import { useAuth } from '@/auth/auth-context';
// β
import { useAuth } from '../../auth/auth-context';Platform-Specific Files β
Append .ios.tsx or .android.tsx to override a file per platform. Metro resolves these automatically:
components/
βββ icon-symbol.tsx # Fallback
βββ icon-symbol.ios.tsx # iOS-specific (SF Symbols)
βββ icon-symbol.android.tsx # Android-specific (if needed)