React Native + Expo application using TypeScript. All code must be type-safe with zero any types outside test files. Theme system is mandatory for all styling. Server state uses React Query exclusively. Navigation is centralized in a single router file.
- File names: kebab-case for components (
user-profile.tsx), kebab-case for utilities (format-date.ts), UPPER_SNAKE_CASE for constants (API_BASE_URL.ts) - Component files must export exactly one default export: the component itself
- Hook files start with
use-prefix and export named function:export function useUserData() {} - Utility files export named functions only, never default exports
- Variable names: camelCase for all variables, functions, and object keys
- Boolean variables must start with
is,has,can, orshould:isLoading,hasError,canSubmit - Private functions in files start with underscore:
_formatInternalDate() - Type names: PascalCase for all types, interfaces, and enums
- Enum values: UPPER_SNAKE_CASE inside enums
- Never use
anytype. Useunknownwith type guards if needed - Never use
astype assertions outside.test.tsfiles. Use type guards or Zod validation instead - All function parameters must have explicit types. No implicit
any - All function return types must be explicit. No implicit inference
- Use
satisfiesoperator for object literals that must match a type - All object literals in code must have explicit type annotations:
const config: AppConfig = {} - Discriminated unions required for error states:
type Result = { status: 'success'; data: T } | { status: 'error'; error: Error }
- Max line length: 100 characters
- Indent: 2 spaces
- Trailing commas in multi-line objects, arrays, and function parameters
- No semicolons at end of statements
- Use single quotes for strings
- Use template literals for any string with variables
src/
├── app/ # Navigation and app entry
│ └── router.tsx # ONLY file with navigation logic
├── components/ # Reusable UI components
│ ├── buttons/
│ ├── inputs/
│ ├── layouts/
│ └── [component-name].tsx
├── hooks/ # Custom React hooks
│ └── use-[hook-name].ts
├── screens/ # Full-screen components
│ └── [screen-name].tsx
├── services/ # API clients and external services
│ ├── api-client.ts
│ └── [service-name].ts
├── store/ # React Query and state management
│ ├── queries.ts
│ └── mutations.ts
├── theme/ # Design tokens and styling
│ ├── colors.ts
│ ├── spacing.ts
│ ├── typography.ts
│ └── theme-context.tsx
├── types/ # Shared TypeScript types
│ └── [domain].types.ts
├── utils/ # Utility functions
│ └── [utility-name].ts
└── constants/ # App-wide constants
└── [constant-name].ts
- No nested component directories deeper than 2 levels
- No component files in
utils/orservices/ - No utility functions in
components/ - All theme values must live in
theme/directory only
- Absolute imports only:
import { Button } from '@/components/buttons/button' - No relative imports (
../../../) - Group imports: React/Expo first, then third-party, then local (blank line between groups)
- Import types with
import type:import type { User } from '@/types/user.types'
- All colors must come from
useTheme()hook, never hardcoded hex values - All spacing must use
theme.spacingvalues, never arbitrary numbers - All font sizes must use
theme.typography, never arbitrary sizes - StyleSheet.create() must reference theme values via closure:
const styles = (theme: Theme) => StyleSheet.create({...}) - Every screen component must wrap content in
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} /> - Platform-specific code must use
Platform.select()orPlatform.OS === 'ios'checks, never assume iOS behavior
- All API calls must use React Query hooks from
store/queries.ts - Never use
useStatefor server data. UseuseQueryinstead - Mutations must use
useMutationfromstore/mutations.ts - Query keys must be defined in
store/queries.tsas constants:export const userKeys = { all: ['users'], detail: (id: string) => ['users', id] } - All queries must have explicit
staleTimeandcacheTimeset - Infinite queries must use
useInfiniteQuerywithgetNextPageParamdefined
- All navigation logic must live in
app/router.tsxonly - No
useNavigation()calls in components for navigation decisions - Components receive navigation callbacks as props:
onPress={() => navigation.navigate('Detail', { id })} - Screen components must not import navigation types directly
- Deep linking configuration must be in
app/router.tsxwith explicit type-safe route params
- Create typed error class:
class AppError extends Error { constructor(public code: string, message: string) {} } - Every async function must have try/catch with typed error handling
- Caught errors must be checked:
catch (err) { if (err instanceof AppError) { ... } else if (err instanceof NetworkError) { ... } } - Never catch with
catch (e: any)or barecatch - API responses must validate with Zod before use:
const user = userSchema.parse(response.data) - Error messages shown to users must come from
constants/messages.ts, never inline strings - Network errors must be caught separately and mapped to user-friendly messages
- All form inputs must validate with Zod schemas defined in
types/[domain].types.ts - Validation must happen at component boundary before API calls
- API responses must validate with Zod before storing in React Query cache
- Never trust API response shape — always parse with schema
- All async operations in components must use
useEffectwith cleanup - Async operations in
useEffectmust be wrapped in named function:useEffect(() => { async function load() {} load() }, []) - Never use
asyncdirectly onuseEffectcallback - All promises must be awaited or explicitly handled with
.catch() - Abort controllers required for fetch calls:
const controller = new AbortController(); fetch(url, { signal: controller.signal })
- Functional components only, no class components
- Props interface must be defined above component:
interface UserCardProps { userId: string; onPress: () => void } - Component must destructure props in signature:
export default function UserCard({ userId, onPress }: UserCardProps) - Hooks must be called at top level before any conditional logic
- Component must return JSX directly, no intermediate variables for JSX
- Max component file size: 300 lines (split into smaller components if larger)
- Test files must be colocated with source:
src/components/button.tsx→src/components/button.test.tsx - Test file naming:
[filename].test.tsor[filename].test.tsx - No separate
__tests__directories - Integration tests in
src/__tests__/integration/with[feature].integration.test.tsnaming
- Every utility function must have unit tests covering happy path and error cases
- Every custom hook must have tests using
@testing-library/react-native - Every screen component must have at least one integration test
- API integration points must have tests mocking React Query
- Error handling code must have explicit tests for each error type
- Navigation logic in
app/router.tsxmust have tests for all route transitions
- Use
describe()blocks to group related tests - Test names must start with
should:it('should return user data when API succeeds') - Mock React Query with
QueryClientin test setup - Mock navigation with
@react-navigation/nativetest utilities - Mock theme with
ThemeProviderwrapper in test setup - All async tests must use
waitFor()from testing library - Never use
act()directly — usewaitFor()instead - Mock API calls with
msw(Mock Service Worker), never inline mocks
- Minimum 80% line coverage for
utils/,hooks/, andservices/ - Minimum 60% coverage for components and screens
- 100% coverage for error handling paths
- All error branches must be explicitly tested
Source: Codelibrium — the marketplace for AI behaviour files. Browse multiple rulesets at codelibrium.com or install via CLI:
npx codelibrium-cli install <ruleset-name>