A modern GitHub mobile client built with Expo 55 (CNG), React Native 0.83, Expo Router, and Uniwind (Tailwind CSS for React Native). Uses bun as the package manager.
bun run start # Start Expo dev server
bun run android # Prebuild → run on Android
bun run ios # Run on iOS simulator
bun run web # Web preview
bun run native:sync # Regenerate android/ ios/ from app.json
bun run go # Format, lint, typecheck, then start dev serverbun run typecheck # TypeScript strict check (tsc --noEmit)
bun run lint # ESLint check
bun run lint:fix # ESLint with auto-fix
bun run format # Prettier (auto-sorts imports)
bun run prebuild # format && lint:fix && typecheckbun test # Run all tests
bun test --watch # Watch mode
bun test path/to/test # Run a single test file
bun test -t "test name" # Run tests matching a pattern
bun test --coverage # With coverage- Double quotes (
", not') - Trailing commas everywhere
- 2-space indent, 80-character print width
- singleAttributePerLine: true for JSX
- Run
bun run formatbefore committing — auto-sorts imports
- Strict mode enabled
- Use explicit types for function parameters and return values
- Use
interfacefor object shapes,typefor unions/intersections - ESLint allows
anyfor GitHub API responses (@typescript-eslint/no-explicit-any: warn)
- Auto-sorted by
prettier-plugin-sort-imports— don't manually order - Use barrel exports:
src/components/ui/index.ts,src/lib/api/hooks/index.ts
| Type | Convention | Examples |
|---|---|---|
| Components | PascalCase | Button.tsx, AuthContext.tsx |
| Hooks | camelCase with use |
useAuth.ts, useNotifications.ts |
| Utils/lib | camelCase | github.ts, theme.ts |
| Booleans | is/has/can/should |
isLoading, hasError |
| Types | PascalCase | ButtonProps, User |
export interface ButtonProps {
title: string;
variant?: ButtonVariant;
}
export function Button({ title, variant = "primary" }: ButtonProps) {
return <Pressable className={...}>{title}</Pressable>;
}- Use Uniwind (
classNameprop) for layout/colors - Use
StyleSheet.create()for complex styles and animations - Use
expo-image(<Image />), not React Native's built-inImage - Custom utilities:
bg-background,bg-card,bg-primary,text-primary, etc. - Use
dark:prefix for dark mode
- Always use
Ioniconsfromexpo-vector-icons - Type:
icon?: keyof typeof Ionicons.glyphMap
- Routes in
src/app/with file-based routing - Dynamic segments:
repo/[owner]/[repo]/ - Auth gating in root
_layout.tsx
- Hooks call
getOctokit()/getGraphQL()internally — never pass client as prop - TanStack Query:
staleTime: 5 min,retry: 2,refetchOnWindowFocus: false - Infinite scroll:
useInfiniteQuerywith page-based pagination (pageParam starts at 1, 30 items/page)
QueryClientProvider → ThemeProvider → ToastProvider → AuthProvider
- Token in
expo-secure-storeundergithub_access_token - Call
resetOctokit()after token changes
- Let TanStack Query handle loading/error states via
useQuery/useMutation - Use toast notifications for user-facing errors
- Use
unknownfor catch blocks, then narrow
- Preset:
jest-expo, Environment:jsdom - Path aliases:
@/*maps tosrc/* - Test files:
*.test.tsxor*.test.ts, co-locate with component - See
jest.setup.jsfor existing mocks
- Run
bun run prebuild(format, lint:fix, typecheck) - Run
bun testto ensure tests pass
android/andios/are git-ignored- Generated by
expo prebuild - Run
bun run native:syncafter changingapp.jsonor native dependencies