Status: Superseded Date: 2026-02-10 Deciders: Core team
This ADR has been superseded. The React Native app was removed in favor of a native Swift iOS app
(
apps/ios/).Context
The monorepo runs pnpm + Turborepo with strict TypeScript and workspace packages. The Expo (Expo Router) mobile app inapps/mobile was sensitive to:
- pnpm’s non-flattened
node_moduleslayout (symlinks + virtual store) - Monorepo workspace package boundaries and hoisting
- Metro resolver settings (watch folders, node module search paths)
- Babel plugin ordering requirements (notably
react-native-reanimated/pluginmust be last) - Expo Router route availability (e.g. OAuth callback path)
- EAS build profiles (dev-client vs production)
- pnpm remains the monorepo package manager.
- Avoid speculative infra or abstractions (0-user mode).
- Fast feedback when config drifts (tests over tribal knowledge).
Decision
Treat Expo mobile configuration as a contract and enforce it in-repo via tests and minimal explicit configuration:- Keep the Expo app in
apps/mobileusing Expo Router. - Commit an EAS configuration with a dev-client profile (
developmentClient: true) as the default for local device/simulator testing. - Add a Metro configuration compatible with pnpm workspaces that preserves hierarchical module lookup while explicitly setting workspace watch folders and node module search paths.
- Enforce critical invariants via lightweight config contract tests (Vitest), including:
app.jsonrequired fields and plugin configuration- Babel config includes
react-native-reanimated/pluginand is last - Metro config does not disable hierarchical lookup and includes required node module paths
- Required runtime dependencies exist
- OAuth callback route exists and redirect helper produces stable URLs
Alternatives considered
Alternative 1: Switch package manager for Expo compatibility
Switch the monorepo (or just mobile) to a package manager with a flatternode_modules layout.
Pros:
- Fewer Metro/pnpm edge cases.
- More “default path” Expo documentation applies without translation.
- Breaks repo-wide tooling and workflows built around pnpm.
- Introduces churn and risk across unrelated apps/packages.
Alternative 2: Metro with disableHierarchicalLookup = true
Force Metro to resolve only via explicit nodeModulesPaths.
Pros:
- Deterministic resolution when a single node_modules directory is authoritative.
- pnpm workspaces rely on hierarchical lookup to resolve from the virtual store.
- Increased risk of resolving the wrong version of a dependency or failing to resolve at all.
Alternative 3: No explicit contracts; rely on expo doctor / CI failures
Keep configs “best effort” and let runtime failures surface issues.
Pros:
- Less test code.
- Slow feedback loops; failures often appear late (CI/device-only).
- Harder code review; config intent is not explicit.
Consequences
Benefits:- Reduced Metro/EAS drift and “works on my machine” failures.
- Clear reviewable intent for critical Expo build/runtime requirements.
- Faster regression detection via cheap unit-style tests.
- Contract tests add maintenance overhead when intentional config changes occur.
- Some invariants are necessarily coupled to Expo/Metro implementation details.
- Contract tests could become brittle and block legitimate upgrades. Mitigation: keep tests focused on truly load-bearing invariants; update tests alongside deliberate upgrades.
- Metro config remains a subtle failure surface as pnpm/Expo evolves. Mitigation: keep configuration minimal and documented; validate with
expo exportin CI-like builds.

