GenerateSaaS

Architecture

How apps/mobile is laid out - router groups, app-core seams, storage, the theme, and the import rules.

apps/mobile is a thin view layer over the shared packages the web app uses. Screens are React Native; shared behavior lives in @repo/app-core and reaches the app through a seam.

Layout

PathHolds
app/Expo Router file routes: (auth) signed-out, (app) signed-in tabs, modals/ the modal stack.
components/ui/React Native Reusables primitives, styled with Uniwind.
components/<feature>/One folder per surface: app shell, auth, home, chat, automations, notifications, settings, billing.
lib/Platform adapters: API client, auth, storage, push, theme.
providers/Root providers and the app-core seam mount.
hooks/Workspace, auth guard, deep-link session, chat, billing status.
i18n/Locale loader and generated message map.

What apps/mobile may import

apps/mobile/eslint.config.mjs enforces these in bundle files; tests/, e2e/ and the root configs are exempt.

SpecifierRule
@repo/app-coreAllowed. DOM-free, Node-free, framework-free headless behavior.
@repo/config, @repo/i18n, @repo/utilsAllowed. Pure data and pure functions.
@repo/config subpathsA module on a startup path imports @repo/config/mobile, @repo/config/pricing or @repo/config/notification-types, never the root barrel.
@repo/ai/* subpathsAllowed, e.g. @repo/ai/ui-message. The bare barrel pulls every provider SDK into the bundle.
@repo/api, @repo/authType imports only; a value import drags the server into Metro's graph.
@repo/ui, @repo/database, @repo/payments, next, node:*Banned.
Buffer, __dirname, every process member but process.envBanned globals; Hermes has none.

Shared LOGIC crosses into this app; shared JSX does not - @repo/ui draws DOM elements React Native cannot render. See client architecture. One primitive adds a rule: components/ui/dialog.tsx needs an intl provider above it, and throws without one.

Seams

@repo/app-core cannot import Expo Router or the auth client, so providers/app-core.tsx mounts this app's slice of each seam once.

SeamWhat the app supplies
navigationExpo Router's usePathname() and router.push()
identityThe signed-in user from the auth client
notificationsThe mobile API client and a native toast sink. Mounted only when config.notifications?.enabled is true, an optional block unlike config.mobile

Storage

KindWhere
Session and tokensexpo-secure-store, namespaced by config.mobile.slug
Preferences and cached readsexpo-sqlite/kv-store, through app-core's KeyValueStorage seam
Pending writesNone. No offline mutation queue: a write needs the network.

Theme

The shared theme compiles into the generated apps/mobile/global.css, so app and website read one palette. Edit tooling/styles/theme.css, then run pnpm --filter @repo/styles mobile-css. See Theming.

Config

@repo/config/mobile is plain JS with no env reads, because app.config.ts, EAS, Metro and the bundle import it directly.

ExportCarries
mobileConfigApp identity (appName, slug, bundleId, scheme), the universal-link host, store URLs, and the push and appLock switches.
UNIVERSAL_LINK_PATHS, HOME_PATH … SETTINGS_ABOUT_PATHThe web paths the app claims and its own route table - also the allowlist a tapped push is matched against. A route without a constant is no deep-link target.

On this page