This file is for engineers and coding agents working in this repository.
react-native-airborne is an opinionated Bun workspace starter for mobile apps:
- Expo + Expo Router client
- Expo Router Native Tabs (SDK 55)
- Clerk auth
- Uniwind styling (Tailwind v4)
- Convex backend
- Zustand + MMKV for local non-sensitive preferences
- Expo notifications
The repo also ships a scaffolder package: tooling/create-react-native-airborne.
client/: Expo app (mobile only: iOS + Android)server/: Convex functions, schema, teststooling/create-react-native-airborne/: published create packageuniwind/: local integration docs used for setup decisionsJustfile: top-level task runner.github/workflows/ci.yml: CI pipeline.github/workflows/publish-create-react-native-airborne.yml: npm publish + GitHub release on tags
- Package manager: Bun (
bun@1.3.4) - Workspace management: Bun workspaces (root
package.json) - Task runner:
just - Formatting: Prettier (root
.prettierrc.json) - Client lint: Expo ESLint config (flat config)
- Server lint: strict ESLint 9 +
typescript-eslint+@convex-dev/eslint-plugin - Tests: Vitest (client and server), plus
convex-teston server
From repository root:
just install: install all workspace dependenciesjust dev: run client and server togetherjust dev-client: run Expo onlyjust dev-server: run Convex onlyjust prebuild: generate native iOS/Android projects locallyjust ios: run iOS app (supports extra Expo CLI args, for examplejust ios --device "iPhone 16")just android: run Android app (supports extra Expo CLI args, for examplejust android --device "Pixel_8_API_35")just fmt: format client/server with Prettierjust lint: lint both workspacesjust typecheck: typecheck both workspacesjust test: run all testsjust ci: lint + typecheck + tests
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY(required)EXPO_PUBLIC_CONVEX_URL(required)EXPO_PUBLIC_EAS_PROJECT_ID(optional; used for push token registration when needed)
Validation is in client/src/lib/env-schema.ts.
CLERK_JWT_ISSUER_DOMAIN(required in real environments)EXPO_PUSH_ENDPOINT(optional, default Expo endpoint)EXPO_ACCESS_TOKEN(optional)
Validation is in server/convex/env.ts.
- Root providers live in
client/app/_layout.tsx:ClerkProviderwithtokenCachefrom@clerk/clerk-expo/token-cache(Expo Secure Store, backed by iOS Keychain / Android Keystore)ConvexProviderWithClerkSafeAreaProvider
- Route guards:
client/app/(auth)/_layout.tsxredirects signed-in users to/(app)client/app/(app)/_layout.tsxredirects signed-out users to/(auth)/sign-in
- App shell:
client/app/(app)/_layout.tsxusesNativeTabsfromexpo-router/unstable-native-tabs- tabs are explicitly registered for
index,push, andsettings
- Auth screens are custom flows in:
client/app/(auth)/sign-in.tsxclient/app/(auth)/sign-up.tsx
- Social auth is included in custom auth screens via Clerk SSO:
- Google on iOS + Android (
oauth_google) - Apple on iOS only (
oauth_apple)
- Google on iOS + Android (
Important: never store auth/session tokens in MMKV.
Uniwind integration is intentionally specific. Keep these rules:
client/global.cssmust contain:@import "tailwindcss";@import "uniwind";
- Import
../global.cssinclient/app/_layout.tsx. - Keep Metro wrapped by
withUniwindConfig(...)inclient/metro.config.jswith:cssEntryFile: "./global.css"dtsFile: "./uniwind-types.d.ts"
- For third-party components without
classNamesupport (for exampleSafeAreaView), wrap withwithUniwind(...). - Theming defaults to
system; explicitlight/dark/systemis handled by preferences store + theme sync hook:client/src/store/preferences-store.tsclient/src/hooks/use-theme-sync.ts
- MMKV + Zustand is used for app preferences, not secrets.
- Convex API imports in client use the alias path:
@convex/_generated/api
- Alias is configured in
client/tsconfig.json.
- Convex directory:
server/convex/ - Auth provider config:
server/convex/auth.config.ts - Main starter functions:
users.bootstrappush.registerTokenpush.unregisterTokenpush.sendTestNotification
- Schema:
server/convex/schema.ts - Regenerate generated types after Convex setup:
cd server && bun run codegen
just prebuildis supported for local native runs.client/iosandclient/androidare intentionally gitignored.- Do not commit generated native folders in this starter.
- Run
just cibefore handing off significant changes. - Server tests use
convex-test; keep tests deterministic and isolated. - If changing auth/push flows, verify both happy path and obvious edge cases.
This repo doubles as the source for the published scaffolder template.
When you change files that should be part of generated projects (root/client/server), sync template:
cd tooling/create-react-native-airborne
bun run sync-templateThe sync script copies selected repo paths into tooling/create-react-native-airborne/template and rewrites placeholder metadata.
It uses explicit exclude paths so repo-only files (like publish workflow) are not copied into generated app templates.
CI (.github/workflows/ci.yml) runs:
- install (
bun install --workspaces) - validate (lint + typecheck + tests for client and server)
- native Android build on Linux (
arm64-v8a) - native iOS simulator build on
macos-26(arm64)
Publish CI (.github/workflows/publish-create-react-native-airborne.yml) runs on tag push, publishes tooling/create-react-native-airborne to npm with trusted publishing (GitHub OIDC) when tag version matches package version, and creates a GitHub Release via gh release create.
Keep local changes compatible with these checks.
- Convex asks for missing env var on startup:
- set required variable in Convex dashboard deployment environment variables.
- Clerk says user is already signed in on auth pages:
- ensure route guards are active and auth pages redirect signed-in users.
- if simulator state is stale, clear app data/reinstall app and retry.
- Social login fails to return to app:
- verify Google/Apple providers are enabled in Clerk and OAuth redirect URLs are configured for your app scheme.
- Uniwind styles missing:
- verify
global.cssimports andwithUniwindConfigmetro wrapper.
- verify