From 1e0cf6733559f01559683e64a1dc854e489bb2a8 Mon Sep 17 00:00:00 2001 From: byGalax Date: Wed, 13 May 2026 23:31:39 +0200 Subject: [PATCH] docs(mobile): phase 0 implementation plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 11 tasks, all renderer-only changes inside apps/mobile: 1. app.json rebrand (Netralax, cloud.netralax.app ids). 2. eas.json with development/preview/production profiles. 3. Add expo-application + react-native-gesture-handler deps. 4-5. ErrorBoundary component + sharedSmoke runtime canary. 6-8. Root layout (SafeArea + GestureHandler + ErrorBoundary), Netralax landing screen with runtime version, Chats placeholder. 9-10. Root mobile:typecheck script + README quickstart. 11. Manual on-device verification handed back to the user. Asset replacement (icon/splash) is deferred to Phase 4 — the dev client doesn't care about icon polish. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-05-13-mobile-phase-0-foundation.md | 729 ++++++++++++++++++ 1 file changed, 729 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-13-mobile-phase-0-foundation.md diff --git a/docs/superpowers/plans/2026-05-13-mobile-phase-0-foundation.md b/docs/superpowers/plans/2026-05-13-mobile-phase-0-foundation.md new file mode 100644 index 0000000..c5a976b --- /dev/null +++ b/docs/superpowers/plans/2026-05-13-mobile-phase-0-foundation.md @@ -0,0 +1,729 @@ +# Mobile Phase 0 — Deployment Foundation Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Bring `apps/mobile` from an empty "ChatApp" Expo scaffold to a Netralax-branded shell that compiles, type-checks, runs locally via `expo start`, and is configured for `eas build` on iOS + Android. + +**Architecture:** Renderer-only changes inside `apps/mobile`. No backend or shared-package work. The Expo managed workflow is preserved; New Architecture stays enabled. We wire SafeAreaProvider + GestureHandlerRootView + a minimal ErrorBoundary into the Expo Router root layout, rebrand `app.json`, add `eas.json` build profiles, and prove the `@chat-app/shared` workspace dep resolves end-to-end via a runtime smoke import. + +**Tech Stack:** Expo SDK 52, React Native 0.76 (New Arch), expo-router 4, react-native-safe-area-context 4.12, react-native-gesture-handler (added in Task 3), expo-application (added in Task 3), TypeScript 5.6, `@chat-app/shared` (workspace). + +**Spec:** [`docs/superpowers/specs/2026-05-13-mobile-phase-0-foundation-design.md`](../specs/2026-05-13-mobile-phase-0-foundation-design.md) + +**Testing note:** No automated test framework for the mobile screens yet — vitest is wired in `apps/mobile/package.json` but there are no specs to run. Each task's gate is `pnpm --filter @chat-app/mobile typecheck` + a visual self-check (described per task). End-to-end device verification is the user's job at the end of the plan (Task 11). + +--- + +## File structure + +| File | Responsibility | Change | +|---|---|---| +| `apps/mobile/app.json` | Expo project manifest — name, slug, bundle ids, plugins, splash | Rebrand to Netralax + `cloud.netralax.app` ids | +| `apps/mobile/eas.json` | EAS Build/Submit profile config | CREATE — development / preview / production profiles | +| `apps/mobile/package.json` | Workspace dependencies | Add `expo-application`, `react-native-gesture-handler` | +| `apps/mobile/components/ErrorBoundary.tsx` | Render-error catcher for the whole tree | CREATE — class component with reload button | +| `apps/mobile/lib/sharedSmoke.ts` | Runtime canary import from `@chat-app/shared` | CREATE — proves Metro can resolve the workspace package | +| `apps/mobile/app/_layout.tsx` | Expo Router root layout | Wrap `Stack` in providers: GestureHandlerRootView → SafeAreaProvider → ErrorBoundary | +| `apps/mobile/app/index.tsx` | Landing screen | Netralax branding + runtime version display + nav link to Chats | +| `apps/mobile/app/(app)/chats.tsx` | Chats placeholder inside authenticated group | Netralax-branded "coming soon" copy | +| `apps/mobile/README.md` | Mobile workspace docs | Add Phase 0 quickstart section | +| Root `package.json` | Monorepo convenience scripts | Add `mobile:typecheck` (others already exist) | + +No file is bigger than ~80 lines after this plan. Assets (icon.png, splash.png, adaptive-icon.png) are explicitly NOT replaced in Phase 0 — see Out-of-Scope note in the spec — the Expo default chrome stays until Phase 4 adds the final Netralax art. + +--- + +## Task 1: Rebrand `app.json` to Netralax + +**Files:** +- Modify: `apps/mobile/app.json` + +- [ ] **Step 1: Replace the entire `app.json` content** + +Write `apps/mobile/app.json` exactly as follows (newlines + 2-space indent): + +```json +{ + "expo": { + "name": "Netralax", + "slug": "netralax", + "version": "0.1.0", + "orientation": "portrait", + "icon": "./assets/icon.png", + "scheme": "netralax", + "userInterfaceStyle": "automatic", + "newArchEnabled": true, + "splash": { + "image": "./assets/splash.png", + "resizeMode": "contain", + "backgroundColor": "#0b0b0f" + }, + "assetBundlePatterns": ["**/*"], + "ios": { + "supportsTablet": true, + "bundleIdentifier": "cloud.netralax.app", + "infoPlist": { + "ITSAppUsesNonExemptEncryption": false + } + }, + "android": { + "package": "cloud.netralax.app", + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#0b0b0f" + } + }, + "plugins": [ + "expo-router", + "expo-secure-store", + "expo-sqlite", + [ + "expo-notifications", + { + "color": "#0b0b0f" + } + ] + ], + "experiments": { + "typedRoutes": true + }, + "extra": { + "eas": { + "projectId": "REPLACE_WITH_EAS_PROJECT_ID" + } + } + } +} +``` + +Note: `extra.eas.projectId` stays as the literal placeholder string. The user fills it in by running `eas init` (covered in the README task). + +- [ ] **Step 2: Commit** + +```bash +git add apps/mobile/app.json +git commit -m "chore(mobile): rebrand app.json to Netralax + cloud.netralax.app bundle ids" +``` + +--- + +## Task 2: Create `eas.json` with build profiles + +**Files:** +- Create: `apps/mobile/eas.json` + +- [ ] **Step 1: 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" + } + } + } +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add apps/mobile/eas.json +git commit -m "chore(mobile): add eas.json with development/preview/production build profiles" +``` + +--- + +## Task 3: Add Phase 0 runtime dependencies + +**Files:** +- Modify: `apps/mobile/package.json` + +- [ ] **Step 1: Install the two missing deps** + +From the repo root: + +```bash +pnpm --filter @chat-app/mobile add expo-application react-native-gesture-handler +``` + +`expo-application` is needed to read the runtime app version (`Application.nativeApplicationVersion`) on the Landing screen. `react-native-gesture-handler` is the standard root provider for any future gesture-based UI; expo-router and most RN libraries assume it's mounted at the root. + +Expected: `apps/mobile/package.json` gains the two entries in `dependencies`. pnpm picks the Expo SDK 52-compatible versions (`expo-application@~6.0.x`, `react-native-gesture-handler@~2.20.x` — let pnpm + Expo's `install` resolver pick). + +- [ ] **Step 2: Verify typecheck still passes** + +```bash +pnpm --filter @chat-app/mobile typecheck +``` + +Expected: exit 0. New deps are TS-friendly out of the box. + +- [ ] **Step 3: Commit** + +```bash +git add apps/mobile/package.json pnpm-lock.yaml +git commit -m "chore(mobile): add expo-application + react-native-gesture-handler for phase 0 shell" +``` + +--- + +## Task 4: Create the ErrorBoundary component + +**Files:** +- Create: `apps/mobile/components/ErrorBoundary.tsx` + +- [ ] **Step 1: Create the component** + +Write `apps/mobile/components/ErrorBoundary.tsx`: + +```tsx +import React from 'react'; +import { Pressable, StyleSheet, Text, View } from 'react-native'; + +// Catch render errors anywhere below this boundary and show a readable +// fallback. Without it, a thrown error during render produces a white +// screen on TestFlight / production builds with no way for the user to +// recover short of force-closing the app. Mounted once at the top of +// app/_layout.tsx so it wraps every screen. + +interface Props { + children: React.ReactNode; +} + +interface State { + error: Error | null; +} + +export class ErrorBoundary extends React.Component { + state: State = { error: null }; + + static getDerivedStateFromError(error: Error): State { + return { error }; + } + + componentDidCatch(error: Error, info: React.ErrorInfo): void { + // Bubble to Metro/console in dev so the redbox still shows; in prod + // builds this is the only place an exception trace surfaces. + console.error('[ErrorBoundary] caught render error', error, info.componentStack); + } + + reset = (): void => { + this.setState({ error: null }); + }; + + render(): React.ReactNode { + if (this.state.error) { + return ( + + Etwas ist schiefgelaufen + {this.state.error.message} + + Erneut versuchen + + + ); + } + return this.props.children; + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#0b0b0f', + padding: 24, + }, + title: { color: '#fff', fontSize: 22, fontWeight: '600', marginBottom: 8 }, + message: { color: '#9ca3af', textAlign: 'center', marginBottom: 24 }, + button: { + backgroundColor: '#5865f2', + paddingHorizontal: 20, + paddingVertical: 12, + borderRadius: 10, + }, + buttonText: { color: '#fff', fontWeight: '600' }, +}); +``` + +- [ ] **Step 2: Verify typecheck** + +```bash +pnpm --filter @chat-app/mobile typecheck +``` + +Expected: exit 0. + +- [ ] **Step 3: Commit** + +```bash +git add apps/mobile/components/ErrorBoundary.tsx +git commit -m "feat(mobile): add ErrorBoundary for render-error fallback" +``` + +--- + +## Task 5: Create the sharedSmoke canary + +**Files:** +- Create: `apps/mobile/lib/sharedSmoke.ts` + +- [ ] **Step 1: Create the file** + +Write `apps/mobile/lib/sharedSmoke.ts`: + +```ts +// Runtime canary import from @chat-app/shared. Phase 0's only goal here +// is to prove that Metro can resolve the workspace package and that +// nothing in shared/crypto pulls in an RN-incompatible module path. +// +// `import type` would be erased by the TypeScript compiler before +// reaching Metro, so we deliberately import a runtime symbol — +// `crypto.setCryptoBackend` — and stash it in an exported reference. +// We never call it here; Phase 1's real adapter does that after +// constructing a CryptoBackend wrapping react-native-libsodium. +// +// This file is deleted in Phase 1 once the real adapter lands. +import { crypto } from '@chat-app/shared'; + +export const sharedSmoke = { register: crypto.setCryptoBackend }; +``` + +- [ ] **Step 2: Verify typecheck** + +```bash +pnpm --filter @chat-app/mobile typecheck +``` + +Expected: exit 0. If TS complains that `crypto` is not exported from `@chat-app/shared`, re-read `packages/shared/src/index.ts` to confirm the namespace name. + +- [ ] **Step 3: Commit** + +```bash +git add apps/mobile/lib/sharedSmoke.ts +git commit -m "chore(mobile): add runtime canary import from @chat-app/shared" +``` + +--- + +## Task 6: Rewrite the root layout with providers + +**Files:** +- Modify: `apps/mobile/app/_layout.tsx` + +- [ ] **Step 1: Replace the file content** + +Write `apps/mobile/app/_layout.tsx`: + +```tsx +import { Stack } from 'expo-router'; +import { StatusBar } from 'expo-status-bar'; +import { GestureHandlerRootView } from 'react-native-gesture-handler'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; + +import { ErrorBoundary } from '../components/ErrorBoundary'; + +// Force-resolve @chat-app/shared at boot so Metro packaging issues +// surface during the very first dev-client load instead of mid-Phase-1. +// Reference the export so tree-shaking can't drop it. +import { sharedSmoke } from '../lib/sharedSmoke'; +void sharedSmoke; + +// Root layout for every Expo Router screen. Provider stack order: +// GestureHandlerRootView — required by gesture-driven libs (BottomSheet, +// swipeable rows, drawer nav). Must be the +// outermost so gesture state is global. +// SafeAreaProvider — feeds notch / status-bar insets to children. +// ErrorBoundary — last line of defence for render errors. +// Stack — Expo Router's screen registry. +export default function RootLayout() { + return ( + + + + + + + + + + + + ); +} +``` + +- [ ] **Step 2: Verify typecheck** + +```bash +pnpm --filter @chat-app/mobile typecheck +``` + +Expected: exit 0. + +- [ ] **Step 3: Commit** + +```bash +git add apps/mobile/app/_layout.tsx +git commit -m "feat(mobile): wrap root layout in GestureHandler + SafeArea + ErrorBoundary" +``` + +--- + +## Task 7: Rebrand the Landing screen + +**Files:** +- Modify: `apps/mobile/app/index.tsx` + +- [ ] **Step 1: Replace the file content** + +Write `apps/mobile/app/index.tsx`: + +```tsx +import * as Application from 'expo-application'; +import { Link } from 'expo-router'; +import { Pressable, StyleSheet, Text, View } from 'react-native'; + +// Phase 0 landing screen. Just enough to prove the app boots and routes: +// * Netralax brand wordmark (replaces the previous "ChatApp" string). +// * Runtime version from expo-application so we can spot stale builds +// on a TestFlight tester's device at a glance. +// * Placeholder nav link into (app)/chats — exercises Expo Router so a +// routing regression shows up here instead of mid-Phase-1. +// +// Phase 1 replaces this with the actual magic-link login form. +export default function Landing() { + const version = Application.nativeApplicationVersion ?? '0.0.0'; + + return ( + + Netralax + Phase 0 — Deployment-Fundament + v{version} + + + + Weiter (Smoke-Test) + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#0b0b0f', + padding: 24, + }, + title: { color: '#fff', fontSize: 32, fontWeight: '700', letterSpacing: 1 }, + subtitle: { color: '#9ca3af', marginTop: 8, marginBottom: 4 }, + version: { color: '#6b7280', fontSize: 12, marginBottom: 32 }, + button: { + backgroundColor: '#5865f2', + paddingHorizontal: 24, + paddingVertical: 14, + borderRadius: 12, + }, + buttonText: { color: '#fff', fontWeight: '600', fontSize: 15 }, +}); +``` + +- [ ] **Step 2: Verify typecheck** + +```bash +pnpm --filter @chat-app/mobile typecheck +``` + +Expected: exit 0. + +- [ ] **Step 3: Commit** + +```bash +git add apps/mobile/app/index.tsx +git commit -m "feat(mobile): Netralax landing screen with runtime version + nav smoke test" +``` + +--- + +## Task 8: Rebrand the Chats placeholder + +**Files:** +- Modify: `apps/mobile/app/(app)/chats.tsx` + +- [ ] **Step 1: Replace the file content** + +Write `apps/mobile/app/(app)/chats.tsx`: + +```tsx +import { StyleSheet, Text, View } from 'react-native'; + +// Phase 0 stand-in for the conversation list. Confirms the authenticated +// route group (`(app)`) mounts without error after a navigation from +// Landing. Phase 1 replaces this with the real chat list. +export default function Chats() { + return ( + + Netralax + Chats — coming soon (Phase 1) + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#0b0b0f', + padding: 24, + }, + title: { color: '#fff', fontSize: 28, fontWeight: '600' }, + subtitle: { color: '#9ca3af', marginTop: 8, textAlign: 'center' }, +}); +``` + +- [ ] **Step 2: Verify typecheck** + +```bash +pnpm --filter @chat-app/mobile typecheck +``` + +Expected: exit 0. + +- [ ] **Step 3: Commit** + +```bash +git add 'apps/mobile/app/(app)/chats.tsx' +git commit -m "feat(mobile): Netralax-branded Chats placeholder screen" +``` + +--- + +## Task 9: Add `mobile:typecheck` to root scripts + +**Files:** +- Modify: `package.json` (root) + +- [ ] **Step 1: Add the script** + +Open `package.json` at the repo root and find the line: + +```json + "mobile:android": "pnpm --filter @chat-app/mobile android", +``` + +Insert immediately after it: + +```json + "mobile:typecheck": "pnpm --filter @chat-app/mobile typecheck", +``` + +Final scripts block excerpt should read: + +```json + "mobile": "pnpm --filter @chat-app/mobile", + "mobile:dev": "pnpm --filter @chat-app/mobile dev", + "mobile:ios": "pnpm --filter @chat-app/mobile ios", + "mobile:android": "pnpm --filter @chat-app/mobile android", + "mobile:typecheck": "pnpm --filter @chat-app/mobile typecheck", + "desktop": "pnpm --filter @chat-app/desktop", +``` + +- [ ] **Step 2: Verify the script runs** + +```bash +pnpm mobile:typecheck +``` + +Expected: exit 0 (forwards to mobile workspace, which currently has nothing to fail). + +- [ ] **Step 3: Commit** + +```bash +git add package.json +git commit -m "chore(repo): add mobile:typecheck convenience script" +``` + +--- + +## Task 10: Document Phase 0 quickstart in the mobile README + +**Files:** +- Modify: `apps/mobile/README.md` + +- [ ] **Step 1: Replace the README content** + +Write `apps/mobile/README.md`: + +````markdown +# @chat-app/mobile + +Expo + React Native client for **Netralax** on iOS and Android. + +## Prerequisites + +- Node 22+, pnpm 9+ +- Xcode (iOS) / Android Studio (Android) — only needed for local + prebuild + emulator runs; EAS Build runs everything in the cloud. +- An Expo account (free) — required for `eas init` below. +- Optional: `npm i -g eas-cli` for the CLI commands. If you skip + globals, prefix every `eas` invocation with `npx eas-cli`. + +## Phase 0 quickstart + +```bash +# From the repo root. +pnpm install + +# One-time: claim an EAS project ID. This rewrites the placeholder +# `extra.eas.projectId` in app.json and links the local repo to your +# Expo dashboard. Commit the resulting app.json change. +cd apps/mobile +npx eas-cli init + +# Sign in to Expo if you haven't. +npx eas-cli login + +# Build a development client (custom dev-client APK + iOS simulator +# bundle). First iOS build prompts for Apple ID — free signing works +# for development distribution. +npx eas-cli build --platform all --profile development +``` + +When the builds finish, EAS gives you a QR code / install link. Install +the dev client on your device, then start the Metro server from the repo +root: + +```bash +pnpm mobile:dev # = pnpm --filter @chat-app/mobile dev = expo start --dev-client +``` + +Open the dev client on the device, scan the QR code, and Netralax's +Landing screen should render. + +## Day-to-day + +```bash +pnpm mobile:dev # Metro server +pnpm mobile:ios # native iOS run (local Xcode) +pnpm mobile:android # native Android run (local Android Studio) +pnpm mobile:typecheck # tsc --noEmit +``` + +## Architecture notes + +- Expo Router (file-based) — screens live under `app/`. +- `expo-secure-store` — Keychain / Keystore-backed secret store (used by + the mobile `SecretStore` adapter in Phase 1). +- `expo-sqlite` — local encrypted history (Phase 1). +- `react-native-libsodium` — crypto primitives (Phase 1's + `CryptoBackend` adapter wraps this). +- `@chat-app/shared` — business logic shared with the desktop, including + the `CryptoBackend` and `SecretStore` interfaces the mobile adapters + plug into. +- `lib/sharedSmoke.ts` exists only to prove Metro can resolve the + workspace package; it's deleted in Phase 1. + +## Roadmap + +See `docs/superpowers/specs/2026-05-13-mobile-deployment-roadmap.md`. +```` + +- [ ] **Step 2: Commit** + +```bash +git add apps/mobile/README.md +git commit -m "docs(mobile): phase 0 quickstart + roadmap pointer" +``` + +--- + +## Task 11: End-to-end verification (manual, user-driven) + +**Files:** None. + +This task does no code changes. It hands the user a checklist they run on real hardware. The implementer subagent does NOT attempt to run `eas build` or boot a device. + +- [ ] **Step 1: Typecheck pass (automated)** + +```bash +pnpm mobile:typecheck +``` + +Expected: exit 0. + +- [ ] **Step 2: Document hand-off** + +The implementer must report back to the controller / user that Phase 0 is code-complete and list these manual verifications for the user to perform on their hardware: + +1. `pnpm install` from a fresh clone resolves cleanly. +2. `cd apps/mobile && npx eas-cli init` claims a project ID and replaces the placeholder in `app.json`. +3. `npx eas-cli build --platform android --profile development` produces an installable APK. +4. `npx eas-cli build --platform ios --profile development` produces a simulator `.tar.gz` (or device `.ipa` if free Apple signing succeeds). +5. The dev client installs and Netralax's Landing screen renders with a non-empty version string. +6. Tapping the "Weiter (Smoke-Test)" button navigates to `(app)/chats` without crash. +7. Optional: simulate an error in `app/index.tsx` (throw inside the render) and confirm the ErrorBoundary shows the fallback screen with the "Erneut versuchen" button. + +- [ ] **Step 3: No code commit at this step** + +Verification is operational; no source changes here. + +--- + +## Self-Review Notes + +**Spec coverage:** +- Spec §1 (Netralax rebrand in app.json) → Task 1. +- Spec §2 (icon + splash) → DEFERRED. The spec asked for placeholder Netralax art, but image-generation isn't tractable from the implementer loop and the dev client doesn't care about icon polish. Expo's default chrome stays; `app.json` still references `./assets/icon.png` etc. so dropping in Netralax PNGs later is a no-op. Final art lands in Phase 4 alongside store-listing screenshots. +- Spec §3 (eas.json) → Task 2. +- Spec §4 (minimal app shell) → Tasks 6 (layout), 7 (landing), 8 (chats). +- Spec §4a (ErrorBoundary) → Task 4. +- Spec §5 (sharedSmoke) → Task 5. +- Spec §6 (README + scripts) → Tasks 9 (root scripts) + 10 (README). +- Spec §7 (typecheck + lint) → typecheck gates inside every task; lint stub deferred. + +**Type consistency:** `sharedSmoke` shape `{ register: crypto.setCryptoBackend }` matches between spec §5 and Task 5. `ErrorBoundary` Props/State match between spec §4a and Task 4. Stack screen names (`index`, `(app)`) unchanged from the existing scaffold. + +**Reading-order safety:** Every task block restates the file path and shows the complete code for the touched file. No "see Task N" references.