- 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
389 lines
10 KiB
Markdown
389 lines
10 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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.**
|
|
|
|
1. **Start with Expo Go**: Run `npx expo start` and scan the QR code with Expo Go
|
|
2. **Check if features work**: Test your app thoroughly in Expo Go
|
|
3. **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
|
|
|
|
```bash
|
|
# 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.json` instead of relative imports
|
|
- Prefer aliases over relative imports for refactors
|
|
- Always use import statements at the top of the file
|
|
|
|
#### Library Preferences
|
|
- ✅ `expo-audio` not `expo-av`
|
|
- ✅ `expo-video` not `expo-av`
|
|
- ✅ `expo-image` with `source="sf:name"` for SF Symbols
|
|
- ✅ `react-native-safe-area-context` not react-native SafeAreaView
|
|
- ✅ `process.env.EXPO_OS` not `Platform.OS`
|
|
- ✅ `React.use` not `React.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 `app` directory
|
|
- Never co-locate components, types, or utilities in the app directory
|
|
- Ensure the app always has a route that matches "/"
|
|
- Always use `_layout.tsx` files 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 `useWindowDimensions` over `Dimensions.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 `contentContainerStyle` padding
|
|
- Add entering and exiting animations for state changes
|
|
|
|
#### Shadows
|
|
Use CSS `boxShadow` style prop. NEVER use legacy React Native shadow or elevation styles.
|
|
|
|
```tsx
|
|
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
|
|
```
|
|
|
|
#### Text Styling
|
|
- Add the `selectable` prop 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
|
|
|
|
```tsx
|
|
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
|
|
|
|
```tsx
|
|
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
|
|
|
|
```tsx
|
|
// 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 `.env` files
|
|
|
|
```bash
|
|
# .env
|
|
EXPO_PUBLIC_API_URL=https://api.example.com
|
|
EXPO_PUBLIC_SUPABASE_URL=your-supabase-url
|
|
```
|
|
|
|
#### API Client Pattern
|
|
|
|
```tsx
|
|
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
|
|
|
|
```tsx
|
|
// 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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
1. **Start simple**: Always test in Expo Go before creating custom builds
|
|
2. **Follow conventions**: Use Expo Router patterns, kebab-case filenames
|
|
3. **Use modern APIs**: Prefer `expo-audio`, `expo-video`, `expo-image`
|
|
4. **Handle safe areas**: Use `contentInsetAdjustmentBehavior="automatic"`
|
|
5. **Style with CSS**: Use `boxShadow` instead of legacy shadow props
|
|
6. **Type everything**: Use TypeScript strict mode, no `any`
|
|
7. **Secure tokens**: Use `expo-secure-store` for sensitive data
|
|
8. **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
|
|
|
|
```typescript
|
|
BACKGROUND: '#000000' // Pure black
|
|
SURFACE: '#1C1C1E' // Charcoal
|
|
BRAND: '#FF6B35' // Flame orange
|
|
REST: '#5AC8FA' // Ice blue
|
|
SUCCESS: '#30D158' // Energy green
|
|
```
|
|
|
|
### Phase Colors (Critical)
|
|
|
|
```typescript
|
|
PREP: '#FF9500' // Orange-yellow
|
|
WORK: '#FF6B35' // Flame orange
|
|
REST: '#5AC8FA' // Ice blue
|
|
COMPLETE: '#30D158' // Green
|
|
```
|
|
|
|
---
|
|
|
|
*Last updated: March 14, 2026*
|