src/features/<name>. A feature owns its API calls, types, hooks, components and screens. Route files in src/app import one screen from a feature and nothing else.
Folders inside a feature
Not every feature has every folder.
branding is three files at the feature root. onboarding is one screen. Both store/ and stores/ exist in nutrition (store/nutritionSession.ts and stores/mealDraftStore.ts). New code should follow the folder the feature already uses.
Extra folders that exist for one feature only:
Dependency direction
Shared code sits below the features:src/components/ui, src/lib, src/theme and src/constants. Features do import from each other where a concept is shared. Examples in the code: auth reads branding, nutrition and workouts stores to clear them on sign out, home reads the nutrition session for its day hint, and app-update reads the workout session so it never interrupts a workout.
Worked example: notification preferences
src/features/notification-preferences is the smallest feature that shows the full path. It has three files.
1. Data
data/notificationPreferencesApi.ts declares the types and two functions. The client unwraps the data field of the response envelope, so the function returns the payload type directly.
2. Hooks
hooks/useNotificationPreferences.ts exports the query key and wraps the data functions.
onMutatecancels in-flight queries for the key and writes the new value into the cache withsetQueryData.scopewith a fixed id makes rapid toggles run one after another.onSettledinvalidates the query, but only when no other mutation with the same key is still running. That stops an early refetch from overwriting a later toggle.
3. Screen
presentation/NotificationPreferencesScreen.tsx composes UI kit primitives and the hooks. It handles the three states every data screen must handle:
It wraps everything in
Screen with scroll and onRefresh, uses enterAt from src/lib/motion.ts for staggered entrance, and uses useReplayAnimation so pull to refresh replays the entrance.
4. Route
src/app/(app)/notification-preferences.tsx is the whole route:
Adding a feature
1
Create the data layer
Add
src/features/<name>/data/<name>Api.ts and types.ts. Call apiClient with a /v1/trainee/... path. Type the return value as the unwrapped payload.2
Add hooks
Export a query key and a
use<Name> hook. Add mutations with cache updates where the UI should respond at once.3
Build the screen
Put the screen in
presentation/. Wrap it in Screen. Use AppText, Button and the other primitives from @/components/ui. Take colors from useColors() and useBrandPalette(), and build direction-aware styles with the helpers in @/lib/rtl.4
Add strings
Add every label to all six dictionaries in
src/constants/locales. The type of strings is typeof en, so a missing key in another locale is a type error.5
Add the route
Create the file under
src/app/(app)/ that renders the screen. Start the dev server once so typed routes pick it up.Conventions
- Imports use the
@/alias forsrc/and@/assets/forassets/. - Components and screens are PascalCase named exports. Hooks are
useX. Other files are camelCase. - No comments is the stated standard in
docs/CODING_STANDARDS.md. The current code does carry comments that explain platform constraints, so treat the rule as “explain why, never what”. - Never use
any. Narrow fromunknown, as theisDraftandisStartedSessionguards do. - Use
as constobjects with a derived union instead of TypeScriptenum.AuthStatusinauthStore.tsis the reference. - Preview mode must stay read-only. Anything that writes to device storage checks
IS_PREVIEWfromsrc/lib/preview.tsfirst. See Web preview export. - Native-only modules are loaded behind a guard. See Guarding native imports.