docs: add AGENTS.md with project best practices and workflows
This commit is contained in:
362
AGENTS.md
Normal file
362
AGENTS.md
Normal file
@@ -0,0 +1,362 @@
|
||||
# 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 tests
|
||||
npm test
|
||||
|
||||
# Lint
|
||||
npx eslint .
|
||||
```
|
||||
|
||||
### 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*
|
||||
Reference in New Issue
Block a user