- Replace browse tab with Supabase-connected explore tab with filters - Add React Query for data fetching with loading states - Add 3 structured programs with weekly progression - Add Supabase anonymous auth sync service - Add PostHog analytics with screen tracking and events - Add comprehensive test strategy (Vitest + Maestro E2E) - Add RevenueCat subscription system with DEV simulation - Add i18n translations for new screens (EN/FR/DE/ES) - Add data deletion modal, sync consent modal - Add assessment screen and program routes - Add GitHub Actions CI workflow - Update activity store with sync integration
10 KiB
10 KiB
AGENTS.md
This file contains optimizations and best practices for working with this TabataFit Expo project.
🚀 Command Optimizations
Use the following rtk commands for optimal performance:
| Command | rtk Equivalent | Speedup | Tokens Before | Tokens After | Savings |
|---|---|---|---|---|---|
ls / tree |
rtk ls |
10x | 2,000 | 400 | -80% |
cat / read |
rtk read |
20x | 40,000 | 12,000 | -70% |
grep / rg |
rtk grep |
8x | 16,000 | 3,200 | -80% |
git status |
rtk git status |
10x | 3,000 | 600 | -80% |
git diff |
rtk git diff |
5x | 10,000 | 2,500 | -75% |
git log |
rtk git log |
5x | 2,500 | 500 | -80% |
git add/commit/push |
rtk git |
8x | 1,600 | 120 | -92% |
cargo test / npm test |
rtk test |
5x | 25,000 | 2,500 | -90% |
ruff check |
rtk lint |
3x | 3,000 | 600 | -80% |
pytest |
rtk pytest |
4x | 8,000 | 800 | -90% |
go test |
rtk gotest |
3x | 6,000 | 600 | -90% |
docker ps |
rtk docker |
- | - | - | - |
Usage Examples
# Instead of: ls -la src/
rtk ls src/
# Instead of: cat package.json
rtk read package.json
# Instead of: grep -r "useEffect" src/
rtk grep "useEffect" src/
# Instead of: git status
rtk git status
# Instead of: npm test
rtk test
# Instead of: ruff check .
rtk lint
📱 Expo Best Practices
Development Workflow
CRITICAL: Always try Expo Go first before creating custom builds.
- Start with Expo Go: Run
npx expo startand scan the QR code with Expo Go - Check if features work: Test your app thoroughly in Expo Go
- Only create custom builds when required
When Custom Builds Are Required
Use npx expo run:ios/android or eas build ONLY when using:
- Local Expo modules (custom native code in
modules/) - Apple targets (widgets, app clips, extensions via
@bacons/apple-targets) - Third-party native modules not included in Expo Go
- Custom native configuration that can't be expressed in
app.json
When Expo Go Works
Expo Go supports a huge range of features out of the box:
- All
expo-*packages (camera, location, notifications, etc.) - Expo Router navigation
- Most UI libraries (reanimated, gesture handler, etc.)
- Push notifications, deep links, and more
Common Commands
# Development
npx expo start # Start development server
npx expo start --tunnel # If network issues
npx expo start --clear # Clear cache
npx tsc --noEmit # Type check
npx expo install <pkg> # Install Expo-compatible packages
# Building
npx eas-cli@latest build --profile development # Dev build
npx eas-cli@latest build -p ios --profile production --submit # Build and submit iOS
npx testflight # Quick TestFlight shortcut
# Development Client (when native code changes)
npx expo start --dev-client
Code Style Rules
File Naming
- Use kebab-case for file names (e.g.,
workout-card.tsx) - Never use special characters in file names
- Always remove old route files when moving or restructuring navigation
Imports
- Use path aliases from
tsconfig.jsoninstead of relative imports - Prefer aliases over relative imports for refactors
- Always use import statements at the top of the file
Library Preferences
- ✅
expo-audionotexpo-av - ✅
expo-videonotexpo-av - ✅
expo-imagewithsource="sf:name"for SF Symbols - ✅
react-native-safe-area-contextnot react-native SafeAreaView - ✅
process.env.EXPO_OSnotPlatform.OS - ✅
React.usenotReact.useContext - ❌ Never use modules removed from React Native: Picker, WebView, SafeAreaView, AsyncStorage
- ❌ Never use legacy expo-permissions
- ❌ Never use intrinsic elements like 'img' or 'div' unless in webview
Route Structure
app/
_layout.tsx # Root layout with tabs
(tabs)/ # Group routes for tabs
_layout.tsx # Tabs layout
index.tsx # Home tab
workouts.tsx # Workouts tab
activity.tsx # Activity tab
browse.tsx # Browse tab
profile.tsx # Profile tab
player/
[id].tsx # Workout player
Route Rules
- Routes belong in the
appdirectory - Never co-locate components, types, or utilities in the app directory
- Ensure the app always has a route that matches "/"
- Always use
_layout.tsxfiles to define stacks
UI Guidelines
Responsiveness
- Always wrap root component in a scroll view for responsiveness
- Use
<ScrollView contentInsetAdjustmentBehavior="automatic" />instead of<SafeAreaView> contentInsetAdjustmentBehavior="automatic"should be applied to FlatList and SectionList- Use flexbox instead of Dimensions API
- ALWAYS prefer
useWindowDimensionsoverDimensions.get()
Styling
- Prefer flex gap over margin and padding styles
- Prefer padding over margin where possible
- Inline styles not StyleSheet.create unless reusing styles is faster
- Use
{ borderCurve: 'continuous' }for rounded corners - ALWAYS use a navigation stack title instead of a custom text element
- When padding a ScrollView, use
contentContainerStylepadding - Add entering and exiting animations for state changes
Shadows
Use CSS boxShadow style prop. NEVER use legacy React Native shadow or elevation styles.
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
Text Styling
- Add the
selectableprop to every<Text/>element displaying important data - Counters should use
{ fontVariant: 'tabular-nums' }for alignment
Safe Area
- Always account for safe area with stack headers, tabs, or ScrollView/FlatList
contentInsetAdjustmentBehavior="automatic" - Ensure both top and bottom safe area insets are accounted for
Navigation Patterns
Stack Configuration
import { Stack } from 'expo-router/stack';
<Stack
screenOptions={{
headerTransparent: true,
headerShadowVisible: false,
headerLargeTitleShadowVisible: false,
headerLargeStyle: { backgroundColor: "transparent" },
headerTitleStyle: { color: PlatformColor("label") },
headerLargeTitle: true,
headerBlurEffect: "none",
headerBackButtonDisplayMode: "minimal",
}}
>
<Stack.Screen name="index" options={{ title: "Home" }} />
</Stack>
Links with Previews
import { Link } from 'expo-router';
<Link href="/settings">
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Preview />
<Link.Menu>
<Link.MenuAction title="Share" icon="square.and.arrow.up" />
<Link.MenuAction title="Delete" icon="trash" destructive />
</Link.Menu>
</Link>
Modal/Sheet Presentations
// Modal
<Stack.Screen name="modal" options={{ presentation: "modal" }} />
// Form Sheet
<Stack.Screen
name="sheet"
options={{
presentation: "formSheet",
sheetGrabberVisible: true,
sheetAllowedDetents: [0.5, 1.0],
contentStyle: { backgroundColor: "transparent" },
}}
/>
Data Fetching
Environment Variables
- Use
EXPO_PUBLIC_prefix for client-side environment variables - Never put secrets in
EXPO_PUBLIC_variables (visible in built app) - Restart the dev server after changing
.envfiles
# .env
EXPO_PUBLIC_API_URL=https://api.example.com
EXPO_PUBLIC_SUPABASE_URL=your-supabase-url
API Client Pattern
const BASE_URL = process.env.EXPO_PUBLIC_API_URL;
export const apiClient = {
get: async <T,>(path: string): Promise<T> => {
const response = await fetch(`${BASE_URL}${path}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
},
};
React Query Setup
// app/_layout.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
retry: 2,
},
},
});
EAS Build Configuration
{
"cli": {
"version": ">= 16.0.1",
"appVersionSource": "remote"
},
"build": {
"production": {
"autoIncrement": true,
"ios": {
"resourceClass": "m-medium"
}
},
"development": {
"developmentClient": true,
"distribution": "internal"
}
},
"submit": {
"production": {}
}
}
Testing & Type Checking
# TypeScript
npx tsc --noEmit
# Run unit tests (Vitest)
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage report
npm run test:coverage
# Run Maestro E2E tests
npm run test:maestro
# Lint
npx eslint .
Test Structure
src/__tests__/
setup.ts # Mocks and test configuration
stores/ # Zustand store tests
hooks/ # React hooks tests
services/ # Service layer tests
components/ # Component logic tests
data/ # Data validation tests
Coverage Goals
- Stores: 80%+ (business logic)
- Services: 80%+ (API integration)
- Hooks: 70%+ (timer, purchases)
- Components: 50%+ (critical UI)
Key Takeaways
- Start simple: Always test in Expo Go before creating custom builds
- Follow conventions: Use Expo Router patterns, kebab-case filenames
- Use modern APIs: Prefer
expo-audio,expo-video,expo-image - Handle safe areas: Use
contentInsetAdjustmentBehavior="automatic" - Style with CSS: Use
boxShadowinstead of legacy shadow props - Type everything: Use TypeScript strict mode, no
any - Secure tokens: Use
expo-secure-storefor sensitive data - Environment config: Use
EXPO_PUBLIC_prefix for client env vars
📝 Project Context
TabataFit — "Apple Fitness+ for Tabata"
- Framework: Expo SDK 52 (managed)
- Navigation: Expo Router v3
- State: Zustand + AsyncStorage
- Video: expo-av → HLS streaming
- Audio: expo-av (coaching + music)
- Animations: React Native Animated
- Payments: RevenueCat
- Analytics: PostHog
Design System
BACKGROUND: '#000000' // Pure black
SURFACE: '#1C1C1E' // Charcoal
BRAND: '#FF6B35' // Flame orange
REST: '#5AC8FA' // Ice blue
SUCCESS: '#30D158' // Energy green
Phase Colors (Critical)
PREP: '#FF9500' // Orange-yellow
WORK: '#FF6B35' // Flame orange
REST: '#5AC8FA' // Ice blue
COMPLETE: '#30D158' // Green
Last updated: March 14, 2026