Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The local Supabase stack still uses shared default ports and container names. In
- `src/` is the React 18 and TypeScript client. Routes and lazy page entry points live in `src/App.tsx`.
- `src/lib/stores/` owns browser state and Supabase hydration. `src/lib/ai/client.ts` is the client AI boundary.
- `packages/core/` (`@debatelab/core`, npm workspace) holds platform-neutral shared code: Zod schemas, validation, languages, Crossfire and Sparring logic. No DOM globals, React, or `import.meta` there; old `src/lib/` paths re-export it. Stores and i18n catalogs stay in `src/`.
- `apps/mobile/` (`@debatelab/mobile`, npm workspace) is the Expo iOS app (React 19, Expo Router). It imports `@debatelab/core`, the `src/locales` catalogs and `src/lib/supabase/projects.ts`; milestone 1 runs in Expo Go, so only Expo Go's native modules. Read [apps/mobile/AGENTS.md](apps/mobile/AGENTS.md) before changing it.
- `supabase/functions/` contains Deno Edge Functions. Shared auth, CORS, research, prompts, cost, and validation code lives in `supabase/functions/_shared/`.
- `supabase/migrations/` is the database history and schema source of truth.
- `src/lib/stores/debate-store.ts` owns the Current Debate: every source and saved result belongs to one debate or to one-off work, and nothing crosses debates.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Key entry points:
```text
src/ React client, stores, hooks, and unit tests
packages/core/ Platform-neutral shared code: schemas, validation, languages
apps/mobile/ Expo iOS app (Expo Router, React 19)
supabase/ Edge Functions, migrations, pgTAP tests, local config
e2e/ Playwright browser, full-stack, live, staging, and canary tests
services/ Collaboration service for the hidden Workspace
Expand Down
43 changes: 43 additions & 0 deletions apps/mobile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files

# dependencies
node_modules/

# Expo
.expo/
dist/
web-build/
expo-env.d.ts

# Native
.kotlin/
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision

# Metro
.metro-health-check*

# debug
npm-debug.*
yarn-debug.*
yarn-error.*

# macOS
.DS_Store
*.pem

# local env files
.env*.local

# typescript
*.tsbuildinfo

example

# generated native folders
/ios
/android
37 changes: 37 additions & 0 deletions apps/mobile/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# DebateLab mobile app

The iOS app (`@debatelab/mobile`): Expo SDK 57, React Native 0.86, React 19, Expo Router. The plan is [plans/2026-10-08-native-ios-app.md](../../plans/2026-10-08-native-ios-app.md). Read the root [AGENTS.md](../../AGENTS.md) first; this file adds only what is specific to the app.

## Rules

- Milestone 1 runs in the App Store's Expo Go, with no EAS builds. Every native module must be listed in `node_modules/expo/bundledNativeModules.json`. Check before adding a dependency, and install with `npx expo install <package>` from this folder so versions match the SDK.
- Expo changes between SDKs. Read the docs for SDK 57 (`https://docs.expo.dev/versions/v57.0.0/`), not `latest`.
- React 18 (website) and React 19 (app) coexist in one workspace. Do not add a root `overrides` entry for react.
- Shared logic comes from `@debatelab/core` (`packages/core`). Stores, the AI client and other `src/` wiring stay in the website.
- Strings come from the website's catalogs in `src/locales/{en,es,hu}.json`. App-only strings go under `mobile.*`, in all three languages.
- The Supabase project URLs and anon keys live once, in `src/lib/supabase/projects.ts` at the repo root; `src/lib/supabase-config.ts` imports them. The app uses staging unless a build sets `EXPO_PUBLIC_SUPABASE_ENV=production`.
- Google sign-in needs a development build, because Expo Go cannot receive OAuth redirects. Until then, sign-in is email code or demo code.
- Native code has no DOM. eslint rejects `window`, `document`, `localStorage`, `sessionStorage` and `location` outside `*.web.ts(x)` files. A `.web.tsx` file exists only for the web preview.
- `ios/` and `android/` are generated and gitignored. Configure native behaviour in `app.json`.

## Layout

- `src/app/`: routes. `sign-in.tsx`, and `(tabs)/` with one route group per tab: Debate, Prepare, Practice, Library, You.
- `src/components/`: shared UI. `app-tabs.tsx` uses native tabs; `app-tabs.web.tsx` draws the mock's tab bar for the web preview.
- `src/lib/`: auth (`auth.tsx`), the Supabase client, session storage (AES key in SecureStore, ciphertext in AsyncStorage) and i18n.
- `src/theme/`: colours from the mocks and the website's dark palette, Inter fonts.
- `scripts/screenshots.ts`: web-export screenshots. `scripts/render-tab-icons.ts` redraws the tab bar PNGs.

## Checks

Root `npm run lint` and `npm run typecheck` cover the app. From the repo root:

```bash
npm run -w @debatelab/mobile typecheck
npm run -w @debatelab/mobile export:web
PORT=<free port> npx tsx apps/mobile/scripts/screenshots.ts
```

Screenshots go to `.verify/mobile-shots/`. For signed-in screens, see the header of `scripts/screenshots.ts`. To load the app on a phone, see [docs/development.md](../../docs/development.md#mobile-app).

knip ignores `expo-updates`: its Expo plugin expects the package whenever `app.json` does not disable updates, and EAS Update is not set up yet.
12 changes: 12 additions & 0 deletions apps/mobile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# DebateLab mobile

The DebateLab iOS app, built with Expo and Expo Router. It shares `@debatelab/core` and the translation catalogs with the website and talks to the staging Supabase project by default.

Agent and contributor rules are in [AGENTS.md](AGENTS.md). Running it on an iPhone with Expo Go is described in [docs/development.md](../../docs/development.md#mobile-app).

```bash
npm ci # from the repo root; installs every workspace
npm run -w @debatelab/mobile start # Metro dev server for Expo Go
npm run -w @debatelab/mobile typecheck
npm run -w @debatelab/mobile export:web # static web preview in apps/mobile/dist
```
44 changes: 44 additions & 0 deletions apps/mobile/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"expo": {
"name": "DebateLab",
"slug": "debatelab",
"version": "1.0.0",
"orientation": "default",
"icon": "./assets/images/icon.png",
"scheme": "debatelab",
"userInterfaceStyle": "automatic",
"ios": {
"bundleIdentifier": "com.adam.debatelab",
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"foregroundImage": "./assets/images/android-icon-foreground.png",
"backgroundImage": "./assets/images/android-icon-background.png",
"monochromeImage": "./assets/images/android-icon-monochrome.png"
},
"predictiveBackGestureEnabled": false
},
"web": {
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router",
[
"expo-splash-screen",
{
"backgroundColor": "#eef4fa",
"image": "./assets/images/splash-icon.png",
"imageWidth": 76,
"dark": { "backgroundColor": "#0f172a" }
}
]
],
"experiments": {
"typedRoutes": true,
"reactCompiler": true
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/images/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/images/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/images/splash-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/library.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/library@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/library@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/sparkles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/sparkles@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/sparkles@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/swords.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/swords@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/swords@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/target.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/target@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/target@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/user.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/user@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/mobile/assets/tab-icons/user@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@debatelab/mobile",
"private": true,
"version": "1.0.0",
"main": "expo-router/entry",
"scripts": {
"start": "expo start",
"export:web": "expo export --platform web",
"typecheck": "tsc --noEmit && tsc --noEmit -p scripts"
},
"dependencies": {
"@debatelab/core": "*",
"@expo-google-fonts/inter": "^0.4.2",
"@react-native-async-storage/async-storage": "2.2.0",
"@supabase/supabase-js": "^2.49.1",
"aes-js": "^3.1.2",
"expo": "~57.0.27",
"expo-constants": "~57.0.21",
"expo-crypto": "~57.0.3",
"expo-font": "~57.0.4",
"expo-linear-gradient": "~57.0.2",
"expo-linking": "~57.0.12",
"expo-localization": "~57.0.2",
"expo-router": "~57.0.25",
"expo-secure-store": "~57.0.4",
"expo-splash-screen": "~57.0.9",
"expo-status-bar": "~57.0.1",
"expo-system-ui": "~57.0.4",
"i18next": "^23.16.8",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-i18next": "^15.4.1",
"react-native": "0.86.3",
"react-native-gesture-handler": "~2.32.0",
"react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "~4.26.0",
"react-native-svg": "15.15.4",
"react-native-url-polyfill": "^4.0.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.10.1"
},
"devDependencies": {
"@playwright/test": "^1.63.0",
"@types/aes-js": "^3.1.4",
"@types/node": "^22.16.5",
"@types/react": "~19.2.2",
"typescript": "~6.0.3"
}
}
38 changes: 38 additions & 0 deletions apps/mobile/scripts/render-tab-icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Renders the tab icons in src/components/icon-paths.ts to template PNGs at
* 1x, 2x and 3x for the native tab bar. iOS tints template images, so they
* are drawn in black on transparent. Run from the repository root after
* changing a tab icon:
*
* npx tsx apps/mobile/scripts/render-tab-icons.ts
*/
import { chromium } from '@playwright/test';
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';

import { iconSvg, type IconName } from '../src/components/icon-paths';

const TAB_ICONS: IconName[] = ['target', 'sparkles', 'swords', 'library', 'user'];
// The mock's tab icons are 22pt in a 320pt-wide frame; iOS tab bar glyphs are about 25pt.
const POINTS = 25;
const outDir = join(__dirname, '../assets/tab-icons');

async function main() {
const browser = await chromium.launch();
try {
await mkdir(outDir, { recursive: true });
for (const scale of [1, 2, 3]) {
const page = await browser.newPage({ deviceScaleFactor: scale, viewport: { width: POINTS, height: POINTS } });
for (const name of TAB_ICONS) {
await page.setContent(`<body style="margin:0;background:transparent">${iconSvg(name, '#000', POINTS, 1.8)}</body>`);
const png = await page.locator('svg').screenshot({ omitBackground: true });
await writeFile(join(outDir, `${name}${scale === 1 ? '' : `@${scale}x`}.png`), png);
}
await page.close();
}
} finally {
await browser.close();
}
}

void main();
Loading
Loading