This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
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.
Tech Stack: Expo 55, React Native 0.83, TypeScript 5.9, Expo Router, Uniwind, TanStack Query v5, @octokit/rest, @octokit/graphql, expo-auth-session, expo-secure-store, react-native-reanimated 4.
bun run start # Start Expo dev server
bun run android # Prebuild (format, lint:fix, typecheck) → run on Android
bun run ios # Run on iOS simulator
bun run web # Web preview
bun run native:sync # Regenerate android/ and ios/ from app.json
bun run build # Export for production (expo export)
bun run typecheck # tsc --noEmit
bun run lint # ESLint check
bun run lint:fix # ESLint with auto-fix
bun run format # Prettier (auto-sorts imports)
bun run doctor # Run expo-doctor health checksNo test suite exists in this project.
The prebuild script runs format && lint:fix && typecheck — these run automatically before bun run android.
All routes live under src/app/. The entry point is expo-router/entry (set in package.json).
(auth)/— Unauthenticated screens (login, OAuth callback)(tabs)/— 5-tab main app: feed, explore, notifications, repos, profilerepo/[owner]/[repo]/— Repo detail, settings, pages, workflowsuser/[login].tsx— Public user profile
Auth gating is done in src/app/_layout.tsx: if isAuthenticated, render (tabs); otherwise render (auth).
src/app/_layout.tsx wraps the app in this order (outermost first):
QueryClientProvider → ThemeProvider → ToastProvider → AuthProvider
- REST API:
getOctokit()fromsrc/lib/api/github.tsreturns a lazily-cached@octokit/restinstance authenticated fromexpo-secure-store. - GraphQL API:
getGraphQL()fromsrc/lib/api/graphql.tsreturns a@octokit/graphqlinstance with the same token. - All data-fetching hooks are in
src/lib/api/hooks/and barrel-exported fromsrc/lib/api/hooks/index.ts. Hooks callgetOctokit()/getGraphQL()internally — never pass the client as a prop. - TanStack Query v5 is configured with
staleTime: 5 min,retry: 2,refetchOnWindowFocus: false. - Infinite scroll lists use
useInfiniteQuerywith page-based pagination (pageParamstarting at 1, 30 items per page).
Available hooks: useActivity, useTrending, useRepo, useRepoTopics, useUpdateRepo, useUpdateTopics, useNotifications, useMarkNotificationRead, useMarkAllRead, useSearch, useMyRepos, useWorkflows, useWorkflowRuns, useDispatchWorkflow, useCancelRun, useContributions.
OAuth flow uses expo-auth-session + expo-web-browser. Two GitHub OAuth Apps are used:
| Platform | Client ID env var | Redirect URI |
|---|---|---|
| Native (Android/iOS) | GITHUB_CLIENT_ID |
awesomegithubapp://oauth/callback |
| Web | GITHUB_CLIENT_ID_WEB |
http://localhost:8081/oauth/callback |
All token exchanges go through the Cloudflare Worker (workers/oauth-token-exchange), which holds the client secrets. The app never has direct access to client_secret.
After the OAuth redirect, src/app/oauth/callback.tsx handles the deep link. It is registered in the root Stack in _layout.tsx so Expo Router can navigate to it regardless of auth state.
The GitHub access token is stored in expo-secure-store under the key github_access_token. resetOctokit() must be called after token changes (handled by setToken / clearToken).
Client IDs are configured in app.json under extra.oauth.githubClientId (native) and extra.oauth.webGithubClientId (web), populated from env vars by app.config.js.
This project uses Uniwind (Tailwind CSS for React Native). Apply styles via the className prop on any React Native element:
<View className="flex-1 bg-background p-4">
<Text className="text-lg font-bold text-primary">Hello</Text>
</View>Dark mode uses the dark: prefix. ThemeProvider adds/removes the dark class on the root element for web; on native Uniwind handles it automatically.
Custom CSS utility classes are defined in src/global.css and map to CSS variables:
bg-background,bg-card,bg-primary,bg-secondary,bg-accenttext-primary,text-secondary,text-accent
- Uniwind
className— Preferred for component layout and colors using the custom CSS variables above. useAppTheme()fromsrc/lib/theme.ts— Returns a JS object of GitHub-palette color tokens. Used whereclassNameisn't applicable (e.g., tab barbackgroundColor, animated style values).
Reusable primitives live in src/components/ui/ and are barrel-exported from src/components/ui/index.ts. Always import from ../components/ui (not individual files).
Key components: ThemedView, ThemedText, Button, Card, Input, Avatar, Badge, Skeleton, SkeletonCard, ChipFilter, LanguageDot, EmptyState, Section, SettingsRow.
- Icons: Always use
Ioniconsfromexpo-vector-icons. - Images: Use
expo-image(import { Image } from "expo-image"), not React Native's built-inImage.
Prettier config enforces: double quotes, trailing commas, 2-space indent, 80-char print width, singleAttributePerLine: true for JSX. Imports are auto-sorted by prettier-plugin-sort-imports on every format run — don't manually order imports.
android/ and ios/ are git-ignored and generated by expo prebuild. To regenerate after changing app.json or native dependencies, run bun run native:sync.
The ESLint config allows any types for GitHub API responses (the API types are extensive and not worth maintaining locally).