Conventions
The rules a ReadyNative app expects you to follow: the @/lib shim contract, where code lives, the files setup owns, how to extend the graph
These are the rules the repo won't tell you itself. Follow them and your change looks like the
rest of the app; break one and the next bun run gen:graph, bun run lint or
bun run doctor tells on you.
The shim contract
Every capability is reached through @/lib/<category>: @/lib/auth, @/lib/payments,
@/lib/analytics, @/lib/push, @/lib/crash, @/lib/storage. The shim exists whether or not
a module was selected - when the category is none, the shim is a no-op with exactly the same
signature, so a screen written against it compiles and runs either way.
That single rule is what keeps screens portable:
- Import
@/lib/authfrom a screen, never@supabase/supabase-jsor another vendor SDK. - To swap a provider, rewrite the shim, not its call sites. That's the whole point.
- UI comes from
@/components/ui(Screen,Box,Text,Button,Row,List, …), not from rawreact-nativeviews: the primitives carry the theme tokens and dark mode. Styling has the pattern.
Where things live
In the finalized tree bun run setup leaves:
| Path | What belongs there |
|---|---|
src/app | routes only - every file is a route, _layout.tsx files are navigators |
src/screens | screen bodies; a route imports one and renders it |
src/components/ui | the primitive kit, reached as @/components/ui; types in types.ts |
src/components | your own shared components, next to the kit |
src/stores | zustand stores (or whatever the state category selected) |
src/lib | the shims, env.ts, the API client - the app's plumbing |
src/hooks | hooks shared across screens |
src/theme | tokens.ts, the design tokens every stack's theme is generated from |
docs | ARCHITECTURE.md and graph.json, both generated |
Before setup, or after setup --keep-modules, the kit is in src/ui/<stack> behind @/ui,
typed by the UI contract, and modules/ holds every option.
Keep non-route code out of src/app: expo-router turns every file there into a route, and a
helper next to a screen becomes a URL nobody asked for.
Generated - do not hand-edit
Each of these files has an owner that rewrites it:
| File | Owner |
|---|---|
src/providers.tsx | setup (from every selected module's providers[], sorted by order) |
src/lib/env.ts, .env.example | setup (from every module's env[]) |
.readynative.json | setup - the record of what was applied |
src/global.css, tailwind.config.js, tamagui.config.ts | bun run gen:theme, from src/theme/tokens.ts (whichever your stack uses) |
public/.well-known/* | bun run gen:links |
docs/graph.json, docs/ARCHITECTURE.md | bun run gen:graph |
Editing one of these isn't fatal, only temporary: the next generator run overwrites you. To add
an env key, add it to src/lib/env.ts and .env.example, then re-run bun run gen:graph;
bun run doctor is what tells a human the key is missing from .env.
setup runs once
In a finalized tree, bun run setup is a stub that prints "Already applied" - there's no "re-run
setup with a different option". Switching an option starts from a fresh clone; see Can I change a
module after setup?.
Extending the graph
gen:graph only knows about files. When you add something it can't see - a background job, a
webhook, an external service - say so where you wrote it, with a one-line annotation in any
src/** file:
// @graph node=job:digest kind=feature label="Nightly digest" edges=lib:api/client,env:EXPO_PUBLIC_API_URLOne node per line. node is the id, kind is one of module, service, provider, route,
screen, feature, hook, store, lib, env, script, custom (anything else becomes
custom), label is quoted and edges is a comma-separated list of node ids the new node
points at.
For anything with no natural home under src/, add it to docs/graph.extra.json, which is
merged verbatim and has the last word over both the generator and the annotations:
{
"nodes": [{ "id": "service:stripe", "kind": "service", "label": "Stripe" }],
"edges": [{ "from": "service:stripe", "to": "env:EXPO_PUBLIC_API_URL", "kind": "reads" }]
}An edge pointing at a node that doesn't exist is reported as a warning, not an error - it may point at work you haven't done yet.
Commands
bun run doctor # env keys, config placeholders, EAS login, SDK versions
bun run gen:graph # docs/graph.json + docs/ARCHITECTURE.md (--check fails when stale)
bun run gen:theme # src/theme/tokens.ts to src/global.css / tailwind.config.js / tamagui.config.ts
bun run gen:links # links config to public/.well-known/*
bun run gen:assets # assets/brand/icon.png to icons and splash
bun run typecheck && bun run lint && bun run testInstall packages with expo install, never a bare add - it resolves the version that matches
this SDK:
bunx expo install expo-hapticsUse whichever package manager's lockfile is in the repo (bun.lock → bun, package-lock.json
→ npm, and so on); the scripts themselves run under plain node, so all four work. A library
with native code needs a dev build (bunx expo run:ios), not Expo Go - see
Expo Go or a dev build.
Run lint and typecheck before you call a task done.