| Tool | Version | Install |
|---|---|---|
| Node.js | 20+ | nodejs.org |
| pnpm | 8+ | npm install -g pnpm |
| Docker + Docker Compose | any recent | docker.com |
| Xcode | 15+ | Mac App Store (iOS/simulator only) |
| Rust toolchain | stable | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh (Tauri only) |
git clone <your-repo-url> ordrctrl
cd ordrctrl
pnpm install # installs all workspaces (root, backend, frontend)PostgreSQL 16 and Redis 7 run in Docker. One command starts both:
docker compose up -dStop them:
docker compose down # stop but keep data
docker compose down -v # stop and delete all datacp backend/.env.example backend/.env
cp frontend/.env.example frontend/.envThese two vars must be unique random values — never use the placeholders:
# SESSION_SECRET (32 random bytes → hex)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# TOKEN_ENCRYPTION_KEY (same command — run twice, use different values)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Paste the output into backend/.env:
SESSION_SECRET="<output of first command>"
TOKEN_ENCRYPTION_KEY="<output of second command>"| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql://ordrctrl:ordrctrl@localhost:5432/ordrctrl_dev |
Pre-filled — works with Docker Compose |
REDIS_URL |
redis://localhost:6379 |
Pre-filled — works with Docker Compose |
SESSION_SECRET |
(generate) | 64-char hex string — see above |
TOKEN_ENCRYPTION_KEY |
(generate) | 64-char hex string — see above |
RESEND_API_KEY |
re_test_... |
Resend API key — see integration setup below |
EMAIL_FROM |
noreply@ordrctrl.local |
Sender address for transactional email |
GOOGLE_CLIENT_ID |
(required) | Google OAuth client ID — see integration setup below |
GOOGLE_CLIENT_SECRET |
(required) | Google OAuth client secret |
APPLE_CLIENT_ID |
(required for Apple auth) | Apple Service ID (e.g. com.ordrctrl.signin) |
APPLE_TEAM_ID |
(required for Apple auth) | 10-character Apple Developer team ID |
APPLE_KEY_ID |
(required for Apple auth) | Key ID for your Sign In with Apple private key |
APPLE_PRIVATE_KEY |
(required for Apple auth) | Contents of the .p8 file — newlines as \n |
MICROSOFT_CLIENT_ID |
(optional) | Azure AD app client ID — for Microsoft Tasks |
MICROSOFT_CLIENT_SECRET |
(optional) | Azure AD app client secret |
MICROSOFT_TENANT_ID |
common |
Azure AD tenant ID — defaults to multi-tenant |
APP_URL |
http://localhost:3000 |
Frontend URL |
API_URL |
http://localhost:4000 |
Backend URL |
NATIVE_APP_ORIGINS |
capacitor://localhost,tauri://localhost,http://tauri.localhost |
Allowed CORS origins for native webviews. For Android emulator live-reload, the VS Code tasks automatically append http://10.0.2.2:3000 at runtime. |
NODE_ENV |
development |
|
PORT |
4000 |
Backend port |
LOG_LEVEL |
info |
Log verbosity — trace | debug | info | warn | error | fatal |
NGROK_AUTHTOKEN |
(optional) | ngrok auth token — physical device testing only |
NGROK_DOMAIN |
(optional) | Your static ngrok domain — physical device testing only |
| Variable | Default | Description |
|---|---|---|
VITE_API_URL |
http://localhost:4000 |
Backend URL |
NODE_ENV |
development |
|
VITE_DEV_APPLE_USERNAME |
(optional) | Pre-fills Apple CalDAV credential form locally |
VITE_DEV_APPLE_APP_SPECIFIC_PASSWORD |
(optional) | Pre-fills Apple CalDAV credential form locally |
E2E_SESSION_COOKIE |
(optional) | Session token for authenticated Playwright e2e tests — see E2E testing |
Each platform has its own env file loaded on top of frontend/.env. Copy the example and fill in values once:
| Platform | Script | Example file | Local file (gitignored) | Notes |
|---|---|---|---|---|
| iOS simulator | pnpm dev:ios |
.env.ios.example |
.env.ios.local |
Uses localhost — iOS simulator shares Mac's network stack |
| Android emulator | pnpm dev:android |
.env.android.example |
.env.android.local |
Uses 10.0.2.2 — Android's alias for host loopback |
| Physical device | pnpm dev:device |
.env.device.example |
.env.device.local |
Uses ngrok HTTPS URL |
# One-time setup for iOS simulator
cp frontend/.env.ios.example frontend/.env.ios.local
# No edits needed — localhost works out of the box
# One-time setup for Android emulator
cp frontend/.env.android.example frontend/.env.android.local
# No edits needed — 10.0.2.2 is the standard Android host aliasYou only need to do this once per developer machine. These credentials don't change unless you rotate them.
- Go to Google Cloud Console → APIs & Services → Credentials
- Create an OAuth 2.0 Client ID (type: Web application)
- Under Authorized redirect URIs, add both:
http://localhost:4000/api/auth/google/callbackhttp://localhost:4000/api/integrations/gmail/callback
- On the OAuth consent screen, add yourself as a test user (while the app is in Testing mode)
- Copy Client ID and Client Secret into
backend/.env:GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com" GOOGLE_CLIENT_SECRET="GOCSPX-your-secret"
- Log into Apple Developer Portal
- Create a Services ID (e.g.
com.ordrctrl.signin) - Enable Sign In with Apple on that Service ID and configure:
- Return URL:
http://localhost:4000/api/auth/apple/callback
- Return URL:
- Go to Keys, create a key with Sign In with Apple enabled, download the
.p8file - Note your Team ID (top-right of the portal), Key ID, and Service ID
- Inline the private key into
backend/.env(replace literal newlines with\n):awk 'NF {printf "%s\\n", $0}' ~/Downloads/AuthKey_XXXXXXXXXX.p8
- Set in
backend/.env:APPLE_CLIENT_ID="com.ordrctrl.signin" APPLE_TEAM_ID="XXXXXXXXXX" APPLE_KEY_ID="XXXXXXXXXX" APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIGT...\n-----END PRIVATE KEY-----\n"
- Go to Azure portal → Azure Active Directory → App registrations → New registration
- Set redirect URI to
http://localhost:4000/api/integrations/microsoft/callback - Under Certificates & secrets, create a new client secret
- Set in
backend/.env:MICROSOFT_CLIENT_ID="your-client-id" MICROSOFT_CLIENT_SECRET="your-client-secret"
- Sign up at resend.com and create an API key
- Set in
backend/.env:RESEND_API_KEY="re_xxxxxxxxxxxxxxxxxxxx" EMAIL_FROM="noreply@yourdomain.com"
Skip for now: Without a Resend key the app still runs — email sends are no-ops in development when
RESEND_API_KEYis unset or set totest.
cd backend
pnpm prisma migrate dev --name init
pnpm prisma generate
cd ..The quickest way is to use the VS Code task (see VS Code tasks below):
Cmd+Shift+B→ opens backend + frontend in a split terminal view
Or from the terminal:
pnpm devThis starts both backend (:4000) and frontend (:3000) concurrently.
Or start them individually:
cd backend && pnpm dev # backend only
cd frontend && pnpm dev # frontend onlyThe project ships with a full set of VS Code tasks (.vscode/tasks.json) that open split-terminal views and manage process lifecycle automatically. They are the recommended way to run dev servers and tests during active development.
Command Palette (recommended): Cmd+Shift+P → Tasks: Run Task → pick a task
Keyboard shortcut: Cmd+Shift+B runs the default build task (▶ Web: Dev)
Each scenario task stops any running dev servers, then opens a split-terminal view with everything needed for that platform.
| Task | Terminals opened | Use when |
|---|---|---|
▶ Web: Dev |
Backend · Frontend | Building or testing in a browser |
▶ Web: Dev + E2E |
Backend · Frontend · Playwright | Running Playwright tests against a live dev server |
▶ iOS: Simulator Dev |
Backend · Frontend · iOS Simulator | Testing the Capacitor iOS app in the Xcode simulator |
▶ iOS: Device Dev |
Backend (device) · Frontend (device) · ngrok | Testing on a physical iOS device |
▶ Android: Emulator Dev |
Backend · Frontend · Android Emulator | Testing the Capacitor Android app in an emulator |
▶ Android: Device Dev |
Backend (device) · Frontend (device) · ngrok | Testing on a physical Android device |
▶ Desktop: Dev |
Backend · Tauri | Working on the Tauri desktop app |
Switching scenarios: just run the new scenario task — the stop step runs automatically, previous terminals close, and the new split view opens.
Manually stopping everything: run ⚡ Stop: All from the task picker.
These do not stop running dev servers first (test runners are independent).
| Task | Terminals opened | Use when |
|---|---|---|
▶ Test: All |
Backend unit · Backend contract · Frontend unit · Frontend E2E | Full pre-commit test pass (terminals auto-close) |
▶ Test: All (keep) |
same | Same, but terminals stay open to review results |
▶ Test: Unit Watch |
Backend watch · Frontend watch | TDD / watching tests while editing (terminals auto-close) |
▶ Test: Unit Watch (keep) |
same | Same, but terminals stay open |
▶ iOS: E2E |
Backend · Frontend · iOS Simulator · Maestro | iOS Maestro flow tests — boots simulator, waits for app, then runs tests (terminals auto-close) |
▶ iOS: E2E (keep) |
same | Same, but terminals stay open |
▶ Android: E2E |
Backend · Frontend · Android Emulator · Maestro | Android Maestro flow tests — boots emulator, waits for app, then runs tests (terminals auto-close) |
▶ Android: E2E (keep) |
same | Same, but terminals stay open |
Every command is also available as a standalone task for one-off use (e.g. running a single test suite, syncing Capacitor, or building the Tauri app without opening a full scenario). Find them in the task picker under their platform prefix: Backend:, Frontend:, iOS:, Android:, Desktop:.
With pnpm dev running, open http://localhost:3000. Sign in with email/password, Google, or Apple (if credentials are configured).
Prerequisites: Xcode 15+, Capacitor iOS project initialized (run once: cd frontend && pnpm cap add ios)
The VS Code task ▶ iOS: Simulator Dev handles everything automatically. To run manually:
# 1. Sync Capacitor
cd frontend && pnpm cap sync ios
# 2. Run with live-reload (connects simulator to local Vite dev server)
pnpm cap run ios --live-reload --host localhostThe iOS simulator shares the Mac's network stack — localhost resolves to your Mac directly, so no ngrok or LAN IP setup is needed.
Sign in with Apple on simulator: Ensure you have an Apple ID configured in the simulator's Settings → Sign in to your iPhone before testing.
Physical devices cannot reach localhost. A public HTTPS tunnel is required. Apple also requires HTTPS redirect URIs.
One-time setup (do once, never again):
- Sign up for a paid ngrok account to get a static domain
- Get your authtoken and static domain from dashboard.ngrok.com
- Copy the device env templates:
cp backend/.env.device.example backend/.env.device.local cp frontend/.env.device.example frontend/.env.device.local
- Fill in
backend/.env.device.local:API_URL=https://your-domain.ngrok-free.app NGROK_AUTHTOKEN=your_authtoken NGROK_DOMAIN=your-domain.ngrok-free.app
- Fill in
frontend/.env.device.local:VITE_API_URL=https://your-domain.ngrok-free.app
- Register your static domain in Apple Developer Portal → Service ID → Sign In with Apple → Return URL:
https://your-domain.ngrok-free.app/api/auth/apple/callback - Register it in Google Cloud Console → OAuth Client → Authorized redirect URIs:
https://your-domain.ngrok-free.app/api/auth/google/callback(keeplocalhosttoo — don't remove it)
Daily device workflow:
# Terminal 1 — infrastructure
docker compose up -d
# Terminal 2 — backend with device overrides
cd backend && pnpm dev:device
# Terminal 3 — ngrok tunnel
cd backend && pnpm dev:ngrok
# Terminal 4 — frontend with device overrides
cd frontend && pnpm dev:deviceThen in Xcode, select your physical device and hit Run.
Additional prerequisites:
# macOS
xcode-select --install
brew install create-dmg # for DMG packaging only
# Rust (both platforms)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shDevelopment (hot-reload):
# Terminal 1 — backend
cd backend && pnpm dev
# Terminal 2 — Tauri desktop app
cd frontend && pnpm exec tauri dev --config desktop/tauri.conf.jsonTip: If
tauri devexits immediately with no error, a previous.appbuild is running in the system tray. Quit it from the tray menu first.
Sign in with Google / Apple (Tauri):
- Click Continue with Google — your system browser opens
- After sign-in, Google redirects to the backend callback
- Backend redirects to
ordrctrl://auth/callback?status=success - macOS routes the
ordrctrl://URL back to the Tauri app → navigates to/feed
Production build:
cd frontend
pnpm exec tauri build --config desktop/tauri.conf.json
# macOS app: desktop/target/release/bundle/macos/ordrctrl.app
# macOS DMG: desktop/target/release/bundle/dmg/ordrctrl_*.dmg
# Windows MSI: desktop/target/release/bundle/msi/ordrctrl_*.msiAdded in spec 018.
With pnpm dev running:
# Unauthenticated tests only (auth flows, redirects)
cd frontend && pnpm test:e2e
# Authenticated tests (feed interactions — requires a session cookie)
# 1. Log in via the browser and grab the sessionId cookie value from DevTools
# 2. Run:
E2E_SESSION_COOKIE=<your-session-token> pnpm --filter frontend test:e2e
# Run only the feed test suite
E2E_SESSION_COOKIE=<token> pnpm exec playwright test --grep "Feed interactions"
# View the HTML report after a run
cd frontend && pnpm exec playwright show-reportUnauthenticated tests skip (not fail) when E2E_SESSION_COOKIE is absent.
Prerequisites: Maestro CLI ≥ v1.38 (curl -Ls "https://get.maestro.mobile.dev" | bash), a stable Java LTS (Java 21 recommended — see below), and a running iOS simulator or Android emulator with the app installed.
One-time setup — credentials:
cp .maestro/.env.example .maestro/.env.local
# Edit .maestro/.env.local with your test account credentialsThe ▶ iOS: E2E and ▶ Android: E2E VS Code tasks load .maestro/.env.local automatically.
# Run all flows manually (auth → feed-load → task-complete)
maestro test --env MAESTRO_TEST_EMAIL=<email> --env MAESTRO_TEST_PASSWORD=<password> .maestro/flows/
# Run a single flow
maestro test --env MAESTRO_TEST_EMAIL=<email> --env MAESTRO_TEST_PASSWORD=<password> .maestro/flows/auth.yamlJava version: Maestro 2.3.0 fails to parse Java EA version strings (e.g. 27-ea). Install a stable LTS via SDKMAN:
sdk install java 21.0.10-tem
sdk default java 21.0.10-tem| Symptom | Likely cause | Fix |
|---|---|---|
ECONNREFUSED on backend start |
PostgreSQL or Redis not running | docker compose up -d |
invalid_state on Apple sign-in |
Redis state expired or API_URL mismatch |
Confirm API_URL matches your actual backend URL; clear stale state: redis-cli keys "oauth:state:*" | xargs redis-cli del |
| Blank page after Apple auth | Deep link not firing | Confirm Info.plist has CFBundleURLSchemes = [ordrctrl]; re-run pnpm cap sync ios |
invalid_client from Apple |
Wrong Apple credentials | Check APPLE_CLIENT_ID, APPLE_TEAM_ID, APPLE_KEY_ID, APPLE_PRIVATE_KEY in .env |
invalid_redirect_uri from Apple |
ngrok URL not registered in Apple Portal | Complete one-time ngrok setup step 6 above |
| Tauri exits immediately | Previous .app build running in system tray |
Quit from tray menu; or kill $(pgrep -f "ordrctrl.app") |
| Physical device can't reach backend | Using localhost URL on a physical device |
Use pnpm dev:device + pnpm dev:ngrok workflow |
You may see the following error in the developer console during a normal session:
content.js:264 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'id')
This is not an ordrctrl bug. The filename content.js is the conventional name for browser extension content scripts — scripts injected into every page the browser visits. Vite's build output never produces a file called content.js.
Verification: Open an incognito window with all extensions disabled and reload the app. The error will not appear.
Resolution: The error originates from one of your installed browser extensions, not from ordrctrl's code. No fix is needed; the issue was investigated and closed in spec 017.
| Spec | Change |
|---|---|
| 001-mvp-core | Initial setup guide |
| 018-e2e-testing | Added E2E testing section (Playwright + Maestro); E2E_SESSION_COOKIE env var |