Each product area is a folder under 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.
The mutation hook shows the optimistic update pattern used across the app:
  • onMutate cancels in-flight queries for the key and writes the new value into the cache with setQueryData.
  • scope with a fixed id makes rapid toggles run one after another.
  • onSettled invalidates 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 for src/ and @/assets/ for assets/.
  • 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 from unknown, as the isDraft and isStartedSession guards do.
  • Use as const objects with a derived union instead of TypeScript enum. AuthStatus in authStore.ts is the reference.
  • Preview mode must stay read-only. Anything that writes to device storage checks IS_PREVIEW from src/lib/preview.ts first. See Web preview export.
  • Native-only modules are loaded behind a guard. See Guarding native imports.