# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ### Mobile App (Expo) ```bash npm install # Install dependencies npm run start # Start Expo dev server (offline mode) npm run android # Start on Android npm run ios # Start on iOS npm run test # Run Jest tests ``` ### Server (Express) ```bash cd server npm install npm run start # Start Express server npm run rebuild:batches # Rebuild plant catalog from batch constants npm run diagnostics # Check duplicates and import audits ``` ### Production Builds (EAS) ```bash npx eas-cli build:version:set -p ios # Bump iOS build number npx eas-cli build -p ios --profile production npx eas-cli submit -p ios --latest # Submit to TestFlight ``` ## Architecture ### Mobile App Expo Router with file-based routing. Entry point is `app/_layout.tsx`. - **`app/(tabs)/`** — Tab navigation: Home (`index.tsx`), Search, Profile - **`app/scanner.tsx`** — Plant scan modal - **`app/lexicon.tsx`** — Plant encyclopedia - **`app/plant/`** — Plant detail screens - **`app/auth/`** — Login / Signup screens - **`app/onboarding.tsx`** — First-launch onboarding Global state lives in `context/AppContext.tsx` (plants, user, billing, language). ### Services Layer (Mobile) - `services/storageService.ts` — AsyncStorage persistence for user plants - `services/plantRecognitionService.ts` — Calls `/v1/scan` on backend - `services/plantDatabaseService.ts` — Local static plant data - `services/authService.ts` — JWT auth against backend - `services/backend/backendApiClient.ts` — HTTP client for all `/v1/*` calls - `services/backend/mockBackendService.ts` — In-app mock if `EXPO_PUBLIC_BACKEND_URL` is not set ### Backend (Express — `server/`) Single `server/index.js` with all routes. Libs in `server/lib/`: - `sqlite.js` — SQLite wrapper (`openDatabase`, `run`, `get`, `all`) - `plants.js` — Plant catalog CRUD + semantic search - `auth.js` — JWT-based signup/login - `billing.js` — Credits, idempotency, Stripe webhooks - `openai.js` — Plant identification + health analysis via OpenAI - `storage.js` — MinIO/S3 image upload (`uploadImage`, `ensureStorageBucket`) Key env vars for server: ``` PLANT_DB_PATH # SQLite file path (default: server/data/greenlns.sqlite) OPENAI_API_KEY STRIPE_SECRET_KEY JWT_SECRET MINIO_ENDPOINT / MINIO_ACCESS_KEY / MINIO_SECRET_KEY / MINIO_BUCKET / MINIO_PUBLIC_URL ``` ### Landing Page (`greenlns-landing/`) Next.js 16 app with `output: 'standalone'` for Docker. Runs independently from the mobile app. Has its own `docker-compose.yml` that spins up: - Next.js app (Landing Page) - PostgreSQL 16 (persistent DB for the backend) - MinIO (persistent image storage) - Nginx (reverse proxy + SSL) ### Infrastructure Plan **Current state:** Server runs on Railway with SQLite (ephemeral). **Target state (not yet migrated):** - Express Server moves OFF Railway → runs on the landing page server via `docker-compose.yml` - PostgreSQL + MinIO replace SQLite + Railway hosting entirely **When migrating to PostgreSQL (do all of these together):** 1. Remove `server/lib/sqlite.js` and `server/data/` entirely 2. Remove Railway service for the Express server (no longer needed) 3. Add `pg` package to `server/package.json` 4. Replace all SQLite calls with `pg` and `DATABASE_URL` env var 5. Change all SQL placeholders from `?` to `$1, $2, ...` (SQLite → PostgreSQL syntax) 6. Add Express server as a service in `greenlens-landing/docker-compose.yml` 7. Use `JSONB` columns in PostgreSQL for nested data (e.g. `careInfo`, `categories`) instead of serialized strings — enables fast querying and filtering directly on JSON fields ### Key Patterns - SQL placeholders: SQLite uses `?`, PostgreSQL uses `$1, $2, ...` — important when migrating - Translations: `utils/translations.ts` supports `de` / `en` / `es` - Colors: `constants/Colors.ts` with light/dark mode tokens - Image URIs: App sends base64 to `/v1/upload/image`, gets back a public MinIO URL