docs(mobile): roadmap + phase-0 deployment foundation design
Mobile shipping is decomposed into 5 phases:
0. Deployment Foundation — Netralax brand on a runnable dev build.
1. Auth + Chat MVP — magic-link login, conversation list, text send.
2. Messaging Features — attachments, voice messages, reactions.
3. Voice/Video Calls — LiveKit RN + CallKit/ConnectionService.
4. Polish + Store Submission — TestFlight, Play, signing.
Phase 0 spec lays out the concrete file changes:
* app.json rename to Netralax + cloud.netralax.app bundle/package.
* New eas.json with development/preview/production profiles.
* SafeAreaProvider + GestureHandlerRootView + ErrorBoundary in the
root layout, Netralax landing screen with runtime app version.
* sharedSmoke.ts runtime import to verify Metro can resolve
@chat-app/shared (which already has CryptoBackend/SecretStore
interfaces designed for mobile adapters).
* README quickstart for `eas init` + first dev client build.
No code changes here — just the planning surface. Implementation plan
follows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
# Mobile Phase 0 — Deployment Foundation
|
||||
|
||||
**Date:** 2026-05-13
|
||||
**Scope:** `apps/mobile`
|
||||
**Roadmap context:** `2026-05-13-mobile-deployment-roadmap.md`
|
||||
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
We can't even smoke-test a Netralax-branded build on a real device today. The Expo scaffold says "ChatApp" everywhere, the EAS project ID is a literal string `REPLACE_WITH_EAS_PROJECT_ID`, and the iOS bundle id / Android package id is the placeholder `com.meinname.chatapp`. Every later phase ships through this build pipeline, so it needs to work end-to-end before we touch features.
|
||||
|
||||
## Goal
|
||||
|
||||
After Phase 0, a developer (or the user) can run a single command from the repo root and get an installable `.ipa` or `.apk` carrying the Netralax brand. No real features — just a launch screen that renders, plus the harness underneath: SafeArea, navigation root, error boundary, the `@chat-app/shared` package compiling and importable from the mobile app, version readout, EAS config.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Auth, chat, calls, push wiring (Phases 1–3).
|
||||
- App Store / Play Store submission (Phase 4).
|
||||
- Resurrecting the legacy mobile sprints from the archived `chat-app` repo.
|
||||
- Code-signing certificates / provisioning profiles. Phase 0 produces development-signed builds via EAS managed credentials; production signing is Phase 4.
|
||||
|
||||
## Design
|
||||
|
||||
### 1. Branding rename in `apps/mobile/app.json`
|
||||
|
||||
Replace every `ChatApp`/`com.meinname.chatapp` reference with the Netralax brand. Final values:
|
||||
|
||||
```json
|
||||
{
|
||||
"expo": {
|
||||
"name": "Netralax",
|
||||
"slug": "netralax",
|
||||
"scheme": "netralax",
|
||||
"version": "0.1.0",
|
||||
"ios": {
|
||||
"bundleIdentifier": "cloud.netralax.app",
|
||||
"supportsTablet": true,
|
||||
"infoPlist": { "ITSAppUsesNonExemptEncryption": false }
|
||||
},
|
||||
"android": {
|
||||
"package": "cloud.netralax.app",
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#0b0b0f"
|
||||
}
|
||||
},
|
||||
"extra": { "eas": { "projectId": "REPLACE_WITH_EAS_PROJECT_ID" } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`extra.eas.projectId` stays as the placeholder string in source — the user runs `eas init` once locally, which fills it in and commits. Documented in the mobile README.
|
||||
|
||||
### 2. App icon + splash
|
||||
|
||||
Replace `assets/icon.png`, `assets/adaptive-icon.png`, `assets/splash.png` with placeholder Netralax variants (purple-on-dark `N` mark matching the sidebar avatar in the desktop). A single 1024×1024 master plus the 1024-Android-foreground and 1284×2778 splash is enough for Phase 0. Final art lives in `apps/mobile/assets/`.
|
||||
|
||||
### 3. EAS configuration
|
||||
|
||||
Create `apps/mobile/eas.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"cli": { "version": ">= 13.0.0" },
|
||||
"build": {
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"distribution": "internal",
|
||||
"ios": { "simulator": true },
|
||||
"android": { "buildType": "apk" }
|
||||
},
|
||||
"preview": {
|
||||
"distribution": "internal",
|
||||
"ios": { "simulator": false },
|
||||
"android": { "buildType": "apk" }
|
||||
},
|
||||
"production": {
|
||||
"ios": { "simulator": false },
|
||||
"android": { "buildType": "app-bundle" }
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {
|
||||
"ios": { "appleId": "REPLACE_WITH_APPLE_ID", "ascAppId": "REPLACE_WITH_ASC_APP_ID" },
|
||||
"android": { "serviceAccountKeyPath": "./play-service-account.json" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`production`-submit credentials stay as placeholders — Phase 4 fills them in. The `development` profile produces a custom dev client + APK that can be installed and used with `expo start --dev-client`.
|
||||
|
||||
### 4. Minimal app shell that doesn't crash
|
||||
|
||||
Four changes to verify the runtime is healthy:
|
||||
|
||||
- `app/_layout.tsx`: wrap the `<Stack>` in `<SafeAreaProvider>` from `react-native-safe-area-context`. Add `<GestureHandlerRootView style={{ flex: 1 }}>` outside it so future gesture-based UI is unblocked. Wrap everything in a top-level `<ErrorBoundary>` (new component, §4a) so an unhandled render error shows a readable fallback instead of a white-screen crash on a TestFlight build.
|
||||
- `app/index.tsx`: render "Netralax" (not "ChatApp"), display the app version read from `expo-application` (`Application.nativeApplicationVersion`), and link to `/(app)/chats` via a placeholder button so navigation is proved.
|
||||
- `app/(app)/chats.tsx`: same — render "Netralax — Chats coming soon" so the protected-stack mounts cleanly.
|
||||
|
||||
#### 4a. Error boundary
|
||||
|
||||
`apps/mobile/components/ErrorBoundary.tsx` is a small React class component that catches render errors and shows the error message + a "Reload" button. Same shape as the desktop's crash-recovery surface. Around 40 lines.
|
||||
|
||||
### 5. Shared package smoke import
|
||||
|
||||
Phase 0's gate is that `import { CryptoBackend } from '@chat-app/shared'` resolves and the Metro bundler doesn't choke on the shared package's exports. We don't *use* the import — just type it so a compile failure surfaces here, not later.
|
||||
|
||||
Concretely: add `apps/mobile/lib/sharedSmoke.ts` that pulls a **runtime** value (not a type-only import — type imports are erased by the TS compiler and never reach Metro). `@chat-app/shared` re-exports its modules as namespaces (`crypto`, `auth`, `chat`, `friends`, `rtc`, `supabase`, `i18n`, `admin`), so we go through one of those:
|
||||
|
||||
```ts
|
||||
// Force-imports a runtime symbol from @chat-app/shared so Metro
|
||||
// actually resolves the package on app boot. A type-only import would
|
||||
// be erased and never surface a packaging problem. This file goes away
|
||||
// once Phase 1's real crypto adapter lands and registers the backend
|
||||
// for real.
|
||||
import { crypto } from '@chat-app/shared';
|
||||
|
||||
// Pull the function into a runtime reference so the bundle keeps the
|
||||
// import. Calling it without a backend would throw, so we just retain
|
||||
// the reference.
|
||||
export const sharedSmoke = { register: crypto.setCryptoBackend };
|
||||
```
|
||||
|
||||
Imported from `_layout.tsx` once, value unused. Phase 1 removes it.
|
||||
|
||||
### 6. README + scripts
|
||||
|
||||
`apps/mobile/README.md` gets a "Phase 0 quickstart" section that walks through `eas init` + the first development build + installing the dev client on a device.
|
||||
|
||||
Root `package.json` gets convenience scripts only if they're missing: `mobile:dev`, `mobile:ios`, `mobile:android`, `mobile:typecheck` — all `pnpm --filter @chat-app/mobile <cmd>` wrappers.
|
||||
|
||||
### 7. Typecheck + lint plumbing
|
||||
|
||||
Verify `pnpm --filter @chat-app/mobile typecheck` already runs (the mobile `package.json` already declares it). If `eslint` is wired similarly, add it to a CI workflow stub — but actual CI integration is out of scope here.
|
||||
|
||||
## File structure (after Phase 0)
|
||||
|
||||
```
|
||||
apps/mobile/
|
||||
├── app/
|
||||
│ ├── _layout.tsx ← MODIFIED: SafeArea, GestureHandler, providers shell
|
||||
│ ├── index.tsx ← MODIFIED: Netralax landing with version + nav link
|
||||
│ └── (app)/
|
||||
│ ├── _layout.tsx ← unchanged
|
||||
│ └── chats.tsx ← MODIFIED: "coming soon" placeholder text
|
||||
├── assets/
|
||||
│ ├── icon.png ← REPLACED: Netralax 1024×1024
|
||||
│ ├── adaptive-icon.png ← REPLACED: foreground for Android adaptive
|
||||
│ └── splash.png ← REPLACED: Netralax splash
|
||||
├── lib/
|
||||
│ └── sharedSmoke.ts ← NEW: temporary import-canary
|
||||
├── app.json ← MODIFIED: Netralax branding, ids
|
||||
├── eas.json ← NEW: build profiles
|
||||
├── package.json ← unchanged (deps stay)
|
||||
└── README.md ← MODIFIED: Phase 0 quickstart
|
||||
```
|
||||
|
||||
## Risks
|
||||
|
||||
- **`react-native-libsodium` + New Architecture (Fabric / TurboModules).** `newArchEnabled: true` is in `app.json`. The library claims support, but past RN libsodium ports broke around the JSI migration. If the dev client crashes on require, fall back to disabling new arch in Phase 0 and re-enable in Phase 1 once the adapter is wired and tested.
|
||||
- **`@chat-app/shared` Node-only paths.** The package was authored against Node + browser; the supabase client uses `fetch` and `WebSocket` which RN polyfills. If any export pulls `node:crypto` directly the Metro resolver may fail. The smoke import (§5) is the canary for this.
|
||||
- **EAS account access.** `eas init` requires an Expo account. The user owns it; we treat the project ID slot as an external input.
|
||||
- **Apple Developer enrolment.** Phase 0 builds for iOS need an Apple ID; EAS can generate dev certs automatically the first time `eas build --platform ios` runs against the user's Apple ID. No paid enrolment needed for the development profile (free signing).
|
||||
|
||||
## Verification (run before declaring Phase 0 done)
|
||||
|
||||
1. `pnpm --filter @chat-app/mobile typecheck` → exit 0.
|
||||
2. `pnpm --filter @chat-app/mobile dev` (`expo start --dev-client` once a dev client is installed) → app boots on a real iPhone *and* a real Android device.
|
||||
3. Landing screen shows "Netralax" + a non-empty version string read at runtime.
|
||||
4. Tapping the placeholder nav link routes into `/(app)/chats` without crash.
|
||||
5. `eas build --platform android --profile development` produces an APK that installs and launches on a physical Android device.
|
||||
6. `eas build --platform ios --profile development` produces a `.tar.gz` (simulator build) **OR** an `.ipa` (device build, if free signing succeeded).
|
||||
7. Optional: visual check that the icon + splash render as Netralax (not Expo's default) on both platforms.
|
||||
|
||||
If any of those fail, Phase 0 isn't done — fix in this phase rather than rolling it into Phase 1.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Push notification token registration (Phase 1).
|
||||
- Linking to the Supabase URL / env-var injection (Phase 1).
|
||||
- Local SQLite schema (Phase 1).
|
||||
- Any screen beyond Landing + Chats-coming-soon.
|
||||
- Reanimated / gesture-based animations.
|
||||
- Dark / light mode polish — system default is fine.
|
||||
Reference in New Issue
Block a user