This guide covers getting the ABStrack monorepo running on a new machine and onboarding a teammate. The project is an Nx workspace with Next.js apps (web, practitioner), an Expo app (mobile), and shared packages under packages/.
| Requirement | Notes |
|---|---|
| Git | For clone and version control. |
| Node.js ≥ 24.16.0 | Matches CI and engines in the repo root package.json. Use nodejs.org or a version manager (see below). |
| pnpm 10.29.2 | Matches CI. Install via pnpm.io/installation or Corepack (below). |
Optional but common:
| Tool | When you need it |
|---|---|
| Android Studio (with SDK + emulator) | Running the mobile app on Android. |
| Xcode (macOS only) | Running the mobile app on the iOS Simulator. |
| Watchman (macOS/Linux) | Can improve Metro file-watching for React Native; not strictly required. |
-
Install Node.js 24.16 or newer (24.x LTS line) — e.g. nvm with
.nvmrcin the repo root, fnm, mise, or your distro’s packages. -
Enable Corepack (ships with Node) and activate the repo’s pnpm version:
corepack enable corepack prepare pnpm@10.29.2 --activateAlternatively install pnpm globally as documented on pnpm.io.
-
Confirm versions:
node -v # expect v24.16.0 or newer (see root package.json engines) pnpm -v # expect 10.29.2
-
Install Node.js 24.16+ LTS from nodejs.org (includes npm).
-
Open PowerShell or Command Prompt as appropriate and enable Corepack, then pnpm:
corepack enable corepack prepare pnpm@10.29.2 --activate
If
corepackis not recognized, use an elevated shell or install pnpm with npm:npm install -g pnpm@10.29.2
-
Confirm:
node -v pnpm -v
Windows notes
- Prefer PowerShell or Git Bash for commands in this doc. Adjust paths if you use
cmd.exe(copy commands for env files are in §3). - Long paths: if installs fail with path-length errors, enable long paths in Windows or clone the repo closer to
C:\dev. - Line endings: Git’s
core.autocrlfmay change newlines in checked-in files;.env.localand.envare not committed—if a tool misreads env files, save them with LF line endings.
From the directory where you keep projects:
git clone https://github.com/sarahpoulin/ABStrack.git ABStrack
cd ABStrack
pnpm install --frozen-lockfileUse pnpm install without --frozen-lockfile only when you intentionally change dependencies.
To add a dev dependency to the workspace root (shared tooling), run from the repo root with pnpm’s explicit root flag, e.g. pnpm add -D <package>@latest -w. Without -w / --workspace-root, pnpm refuses root installs so you do not add packages to the root by mistake.
git clone https://github.com/sarahpoulin/ABStrack.git ABStrack
cd ABStrack
pnpm install --frozen-lockfileSecrets must never be committed. Each app has its own template under apps/<app>/.env.example (safe to commit). The repo root .env.example is an index only — it has no variables. Each runtime loads env files from inside the app folder, not from the monorepo root.
| App | Template | File (create locally) | Purpose |
|---|---|---|---|
User web (apps/web) |
apps/web/.env.example |
apps/web/.env.local |
Next.js: URL + publishable key (sb_publishable_…). |
Practitioner web (apps/practitioner) |
apps/practitioner/.env.example |
apps/practitioner/.env.local |
Same pattern as web. |
Mobile (apps/mobile) |
apps/mobile/.env.example |
apps/mobile/.env |
Expo / Metro: EXPO_PUBLIC_* variables only. |
Next.js documents .env.local in each app directory. Expo picks up .env under apps/mobile/. See also packages/supabase/README.md for dashboard ↔ variable mapping.
Run these from the repository root (ABStrack/), after clone.
cp apps/web/.env.example apps/web/.env.local
cp apps/practitioner/.env.example apps/practitioner/.env.local
cp apps/mobile/.env.example apps/mobile/.envCopy-Item apps/web/.env.example apps/web/.env.local
Copy-Item apps/practitioner/.env.example apps/practitioner/.env.local
Copy-Item apps/mobile/.env.example apps/mobile/.envcopy apps\web\.env.example apps\web\.env.local
copy apps\practitioner\.env.example apps\practitioner\.env.local
copy apps\mobile\.env.example apps\mobile\.envExpo embeds only names starting with EXPO_PUBLIC_ into the Metro bundle (Expo env docs). The mobile template already lists those variables — use the same Supabase URL and publishable key as web, with the EXPO_PUBLIC_ prefix. Do not add NEXT_PUBLIC_* to apps/mobile/.env.
- Open each of the three new files in an editor.
- Remove or comment out variables you do not use yet; uncomment and set at minimum:
- Next apps:
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY. - Mobile:
EXPO_PUBLIC_SUPABASE_URL,EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY(andEXPO_PUBLIC_POWERSYNC_URLfor offline replication).
- Next apps:
- Optional server-only
SUPABASE_SECRET_KEY(sb_secret_...) belongs only in server contexts (e.g. Next Route Handlers), never inEXPO_PUBLIC_*or client bundles.@abstrack/supabase/admindoes not use legacy JWTservice_roleenv vars. In the Supabase dashboard, the secret key is listed under Settings → API Keys (secret key, not publishable).
Get URLs and publishable keys from the Supabase dashboard: Settings → API Keys, or Integrations → Data API for the API URL. For Email/password auth (PRD), enable Authentication → Providers → Email.
.gitignorealready excludes.env,.env.local, and.env.*.local; your copies should not appear ingit statusas new tracked files (if Git proposes adding them, stop and check paths).- The per-app templates contain comments; it is normal for
webandpractitionerto include the sameNEXT_PUBLIC_*values, andmobilethe same logical values underEXPO_PUBLIC_*.
Supabase workflow (cloud + CLI + GitHub Actions): SUPABASE_CLOUD_DEVELOPER.md — Actions db push on merge to dev (Cloud) and main (self-hosted); PR checks via /ci / /infra-ci (§4 → PR comment commands). For a single PR with migrations + types, run db push and gen types --linked from your laptop before merge. AI assistants: AGENTS.md.
Caretaker email invites (Edge secrets ABSTRACK_CARETAKER_INVITE_*, Supabase Auth redirect URLs, apps/mobile EXPO_PUBLIC_USER_WEB_ORIGIN, production /.well-known env on user web): Caretaker deploy checklist in the same doc (section Patient caretaker Edge Function).
App environment variables (NEXT_PUBLIC_SUPABASE_URL, keys) let clients call the Data API; they do not apply SQL from the repo. Schema changes live in supabase/migrations/ and must be applied to your hosted Postgres with the Supabase CLI (or an equivalent process).
Official reference: Managing Environments (CLI + GitHub Actions).
From the repo root (or install the CLI globally per Supabase CLI docs):
pnpm dlx supabase --version
pnpm dlx supabase loginsupabase login opens a browser or accepts a personal access token from the dashboard: Account → Access Tokens. The CLI stores credentials for local use; it is not the same key as your app’s publishable Data API key.
You need your project ref (the id in the dashboard URL: https://supabase.com/dashboard/project/<project-ref>).
pnpm dlx supabase link --project-ref <project-ref>The CLI will prompt for the database password unless you set it in the environment (useful for scripts and CI):
export SUPABASE_DB_PASSWORD='<database-password>'
pnpm dlx supabase link --project-ref <project-ref>Find or reset the database password in the Supabase dashboard under Database → Settings for your project, or open Connect at the top of the project page to view connection strings and the postgres password. It is not the same as API Keys or JWT Keys under Project Settings.
Linking writes local metadata (under supabase/, gitignored where appropriate) so commands like db push know which remote database to target.
After new files exist in supabase/migrations/, apply them to the linked project:
pnpm dlx supabase db pushRecommended with this repo: run db push from your laptop before merging a migration PR into dev, then gen types typescript --linked and update packages/supabase/src/lib/database.types.ts (see SUPABASE_CLOUD_DEVELOPER.md). GitHub Actions still runs db push on merge to dev / main as a backstop.
Useful variants:
pnpm dlx supabase db push --dry-run— prints which migrations would run without applying them (good for checks before merge).pnpm dlx supabase migration list— compare local and remote migration history.
supabase db reset (recreate local DB + run migrations + seed.sql) is for local development with Docker and the Supabase stack; it does not run against your online project. seed.sql is not applied to production unless you intentionally use a seeding path (the default cloud workflow is migrations only).
Default branch is dev. Feature work opens a PR into dev; when dev is stable, promote with a PR into main.
Environments: development (Supabase Cloud + PowerSync Cloud) vs production (self-hosted Supabase + PowerSync on the same VM). Secrets live under the GitHub Environments development and production, not only as repo-level secrets.
| Stage | What happens |
|---|---|
Feature branch → PR into dev |
No automatic CI/infra on every push (saves Actions minutes). Comment to run checks (table below). |
Merge into dev |
Real deploy when paths change: Supabase Cloud db push, PowerSync Cloud deploy sync-config, Edge Functions functions deploy. |
PR dev → main |
Automatic lightweight checks (fine to run without a comment). |
Merge into main |
Real prod deploy when paths change: self-hosted Supabase migrations + PowerSync + Edge Functions via Tailscale. |
Post the comment as the entire PR comment body (exact match — GitHub Actions has no trim() in if: expressions, so no leading/trailing spaces or extra lines). You need write access on the repo. Workflows react to the comment and post a commit status on the PR head SHA (for branch protection).
| Comment | When to use | What runs | Commit status context(s) |
|---|---|---|---|
/ci |
Before merging app/code changes into dev (any PR); also auto on PRs into main |
Lint, typecheck, unit tests, build, a11y (ci.yml). Uses the development Environment when the PR targets dev (Cloud), or production when it targets main (self-hosted + Tailscale). |
CI |
/infra-ci |
Before merging into dev when the PR touches migrations, database.types.ts, PowerSync sync rules, and/or Edge Functions |
Against development (Cloud): Supabase db push --dry-run, PowerSync validate, Edge Functions functions list smoke check, and local Docker types check if migration paths changed (supabase-migrations.yml, powersync-sync-config.yml, supabase-edge-functions.yml, supabase-db-types-pr.yml). Irrelevant paths are skipped (still green). |
infra / supabase-dry-run, infra / powersync-validate, infra / edge-functions, infra / database-types |
/preview-web |
When you want a Vercel preview of user web (apps/web) |
Preview deploy (vercel-preview.yml); secrets VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_WEB_PROJECT_ID |
(posts a PR comment with the URL; not a required status) |
/preview-practitioner |
When you want a Vercel preview of practitioner (apps/practitioner) |
Same workflow; secrets VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PRACTITIONER_PROJECT_ID |
(posts a PR comment with the URL; not a required status) |
Notes
- Comment workflows run from the default branch (
dev). Merge workflow YAML changes todevbefore relying on new comment behavior. /infra-cicloud jobs use thedevelopmentGitHub Environment. Becauseissue_commentruns ondev, that environment’s “Selected branches =dev” policy works without allowingrefs/pull/*/merge./ciEnvironments: PR basedev→development(CloudNEXT_PUBLIC_*). PR basemain→production(self-hostedNEXT_PUBLIC_*+ Tailscale). Put those Variables (andSUPABASE_SECRET_KEYfor integration tests) on each Environment — not only as repository-level vars.productiondeployment branches: allowmain(deploys) andrefs/pull/*/mergeso CI on adev→mainPR can read production Environment vars. Without the pull-request pattern, the job fails the environment gate before steps run.- Do not use a looser match like “contains
/ci”:/infra-ciwould incorrectly start app CI. Commands are exact. - Fork PRs cannot use
/infra-ci(no environment secrets).
| Environment | NEXT_PUBLIC_SUPABASE_URL |
Notes |
|---|---|---|
| development | Cloud project URL (https://<ref>.supabase.co) |
Public internet; no Tailscale in the CI job. |
| production | Self-hosted Kong/API URL reachable on the tailnet (MagicDNS OK) | CI joins Tailscale (TS_OAUTH_* on production) before tests run. |
Also set NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY (sb_publishable_…) and SUPABASE_SECRET_KEY (sb_secret_…, for @abstrack/supabase integration tests) on each Environment. Self-hosted publishable/secret keys come from that stack’s API keys, not the Cloud project.
The workflow .github/workflows/supabase-migrations.yml uses the same non-interactive variables as the Supabase managing environments guide. Put these on the development GitHub Environment:
| Secret | Purpose |
|---|---|
SUPABASE_ACCESS_TOKEN |
Personal access token for the CLI (supabase login equivalent). |
SUPABASE_PROJECT_ID |
Project ref string (same as supabase link --project-ref). |
SUPABASE_DB_PASSWORD |
Postgres password for the postgres role (from Connect or Database → Settings; see §4 above). |
development: comment /infra-ci dry-runs against Cloud; merge to dev runs supabase db push. production: merge to main pushes via Tailscale + PROD_DB_URL (see the workflow). You can also Actions → Supabase migrations → Run workflow.
.github/workflows/powersync-sync-config.yml: comment /infra-ci validates against PowerSync Cloud; merge to dev runs deploy sync-config; merge to main scp’s sync-rules.yaml and restarts self-hosted PowerSync. Details: SUPABASE_CLOUD_DEVELOPER.md — PowerSync Sync Streams.
Put these on the development GitHub Environment:
| Secret | Purpose |
|---|---|
POWERSYNC_ADMIN_TOKEN |
PowerSync personal access token (Dashboard → Access tokens). The CLI reads it as PS_ADMIN_TOKEN. |
POWERSYNC_INSTANCE_ID |
Target Cloud instance id (CLI / Dashboard). Mapped to INSTANCE_ID in the workflow. |
POWERSYNC_PROJECT_ID |
PowerSync project id. Mapped to PROJECT_ID. |
POWERSYNC_ORG_ID |
Optional. Set only if your PAT spans multiple orgs (mapped to ORG_ID). |
If those three required secrets are unset, validate completes with a notice and skips the CLI (deploy-dev does not run).
.github/workflows/supabase-edge-functions.yml: comment /infra-ci smoke-checks Cloud (functions list) when supabase/functions/** (or supabase/config.toml) changed; merge to dev runs supabase functions deploy; merge to main rsyncs into self-hosted volumes/functions/ and restarts the functions container. Invite redirect secrets (ABSTRACK_*_INVITE_*) stay on Cloud Dashboard / the VM — CI ships function source only.
development: reuse SUPABASE_ACCESS_TOKEN + SUPABASE_PROJECT_ID (same as migrations).
production: reuse Tailscale + SSH secrets from PowerSync deploy, plus the Supabase docker directory:
| Secret | Purpose |
|---|---|
PROD_SSH_HOST |
Self-hosted hostname on the tailnet (same as PowerSync). |
PROD_SSH_USER |
SSH user (same as PowerSync). |
PROD_SUPABASE_DIR |
Absolute path to the self-hosted stack root (contains docker-compose.yml and volumes/functions/). |
TS_OAUTH_CLIENT_ID |
Tailscale OAuth (shared with migrations / PowerSync). |
TS_OAUTH_SECRET |
Tailscale OAuth secret (shared). |
Types file: after db push on dev / main, supabase-migrations.yml verifies committed packages/supabase/src/lib/database.types.ts against the target DB—it does not commit. If it fails, follow SUPABASE_CLOUD_DEVELOPER.md. supabase-db-types-pr.yml compares types to migration SQL via a CI-only Docker stack (/infra-ci on PRs into dev; automatic on PRs into main).
If you later use separate staging and production projects, duplicate the pattern with different secrets (for example STAGING_PROJECT_ID / PRODUCTION_PROJECT_ID) as in the demo workflow.
From the repo root, align with CI:
pnpm exec nx run-many -t lint test typecheck
pnpm exec nx run-many -t build --exclude=@abstrack/mobileEnv vars are not required for these tasks unless code reads them at import time. If you add or change dependencies under packages/*, run pnpm install and commit the updated pnpm-lock.yaml so pnpm install --frozen-lockfile (CI) keeps working.
Run from the repository root unless noted.
| Goal | Command |
|---|---|
| User web app | pnpm exec nx dev web |
| Practitioner web app | pnpm exec nx dev practitioner |
| Mobile (Expo) | pnpm exec nx start mobile |
Easier:
| Goal | Command |
|---|---|
| User web app | pnpm web |
| Practitioner web app | pnpm practitioner |
| Mobile (Expo) | pnpm mobile |
Default ports depend on Nx/Next/Expo; watch the terminal output. For mobile, the app includes native-only dependencies (@op-engineering/op-sqlite / PowerSync SQLCipher). They are not in the store Expo Go app — opening the dev-server QR code in Expo Go often fails at runtime with errors like “Base module not found” (pod/Gradle hints in the message are a red herring on a physical device). Use one of: (1) a development build installed on the device (apps/mobile/eas.json profile development has developmentClient: true — build with EAS and install that APK/IPA, then open the same Metro URL/QR from that dev client), (2) from the repo root pnpm ios or pnpm android (runs expo run:ios / expo run:android in apps/mobile with a USB device or emulator), or (3) the same against a simulator. Do not rely on Expo Go for this repo’s mobile app.
Nx project graph (Failed to process project graph / @nx/expo/plugin / Invalid string length): From the repo root run pnpm exec nx reset (clears the daemon and cache). The repo root .nxignore keeps apps/mobile/android/**, apps/mobile/ios/**, and common mobile caches out of Nx’s Expo plugin file hashing so local native build trees do not blow up graph processing.
Useful references:
The workspace pins React 19.1.x to match Expo SDK 54 and to stay aligned with react-dom, react-test-renderer, and @types/react / @types/react-dom. If you move to a newer React minor (for example 19.2+), update everything below in one commit so you do not get peer warnings or type/runtime skew.
-
Runtime (exact pins today — bump together)
In the rootpackage.jsondependencies:react,react-dom.
Inapps/web/package.jsonandapps/practitioner/package.json: samereact/react-domversions as the root (or whatever range you standardize on).
Inapps/mobile/package.json:react,react-dom(Expo’s supported versions still apply — prefernpx expo install react react-domfromapps/mobilewhen upgrading the mobile stack). -
Tests
RootdevDependencies:react-test-renderermust match the same React minor asreact(same patch is ideal). -
TypeScript types
RootdevDependencies:@types/reactand@types/react-dom— keep them on the same minor asreact(today the repo uses~19.1.0). -
pnpm overrides
The rootpackage.jsonincludespnpm.overridesfor@types/reactand@types/react-domso transitive packages (e.g. React Native) cannot pull a newer types minor. When you upgrade React typings to 19.2.x, update those override ranges (or remove overrides if you no longer need them). -
Lockfile
Runpnpm install, fix any peer warnings, runpnpm exec nx run-many -t lint test typecheck(and mobile as needed), then commitpackage.jsonfiles andpnpm-lock.yamltogether.
| Area | Notes |
|---|---|
| Nx Console | Editor plugin for tasks and graph: Nx editor setup. |
| Playwright (e2e) | Projects web-e2e and practitioner-e2e; run when you need end-to-end tests (browsers may need a one-time install: pnpm exec playwright install). |
| Docker | Not required for the default Node/Nx workflow unless you add containerized services later. |
| Symptom | Things to try |
|---|---|
pnpm: command not found |
Ensure Corepack prepared pnpm, or install pnpm globally; reopen the terminal. |
| Wrong Node version | Switch to Node 20 with your version manager; confirm with node -v. |
| Next.js cannot see Supabase env | Confirm variables are in apps/<app>/.env.local, not only the repo root. Restart the dev server after changes. |
| Expo cannot see variables | Use EXPO_PUBLIC_* names in apps/mobile/.env; restart Metro / clear cache if needed (pnpm exec nx start mobile with Expo’s cache clear options if documented for your setup). |
supabase link / db push fails in CI |
Confirm Actions secrets match §4 (SUPABASE_ACCESS_TOKEN, SUPABASE_PROJECT_ID, SUPABASE_DB_PASSWORD). The database password is the Postgres password from Connect or Database → Settings, not the Data API publishable key. |
| Fork PR: migration workflow skipped | Expected: GitHub does not expose repository secrets to workflows from forks. Run checks on a branch in the main repo or apply migrations manually after merge. |
| PowerSync workflow fails / skipped | Confirm POWERSYNC_* secrets on the development environment (§ PowerSync sync config). If unset, validate succeeds with a notice and skips CLI. Comment /infra-ci on PRs into dev; merge to dev deploys Cloud sync-config; merge to main deploys self-hosted. See SUPABASE_CLOUD_DEVELOPER.md and PR comment commands. |
| Edge Functions deploy fails / skipped | Confirm SUPABASE_ACCESS_TOKEN + SUPABASE_PROJECT_ID on development; on production set PROD_SUPABASE_DIR (plus existing Tailscale / PROD_SSH_*). Path filter: supabase/functions/**. See Supabase Edge Functions. |
| Mobile: local data but nothing in Supabase | Often PowerSync Cloud was disabled (free tier) or EXPO_PUBLIC_POWERSYNC_URL / Dashboard Client Auth is stale after re-enable. Web may still write via PostgREST. Follow SUPABASE_CLOUD_DEVELOPER.md → PowerSync disabled or re-enabled before changing app code. |
pnpm install fails on Windows |
Run shell as admin once if permission errors; check antivirus locking node_modules; try deleting node_modules and reinstalling. |
| Path errors on Windows | Use forward slashes in copied commands where supported, or use the copy / Copy-Item examples above with backslashes in cmd. |
| Mobile: “Base module not found” (Expo) | You opened the bundle in Expo Go; this project needs a development build or expo run:* install because of OP-SQLite / PowerSync native code. Install the dev client from an EAS development build (see apps/mobile/eas.json), or from the repo root run pnpm android / pnpm ios on a device/emulator. Then connect to Metro from that app, not Expo Go. Clearing Gradle or pod install on the laptop does not fix Expo Go missing modules. See §6 above. |
- Node.js 24 and pnpm 10.29.2 installed
- Repository cloned
-
pnpm install --frozen-lockfilesucceeded -
apps/web/.env.localandapps/practitioner/.env.localcreated from each app’s.env.exampleand filled with project credentials -
apps/mobile/.envcreated fromapps/mobile/.env.exampleand filled with real values - Supabase CLI:
login,link, and for migration PRsdb push+gen types --linked+ Prettier before merge (§4, SUPABASE_CLOUD_DEVELOPER.md) - If using GitHub Actions for migrations: repository secrets set (
SUPABASE_ACCESS_TOKEN,SUPABASE_PROJECT_ID,SUPABASE_DB_PASSWORD) per §4 - If using GitHub Actions for PowerSync:
POWERSYNC_ADMIN_TOKEN,POWERSYNC_INSTANCE_ID,POWERSYNC_PROJECT_ID(andPOWERSYNC_ORG_IDif needed) per PowerSync sync config -
pnpm exec nx run-many -t lint test typecheck(and build excluding mobile if you match CI) passes -
pnpm exec nx dev web(and/or other apps) runs as expected
For product context and milestones, see PRD.md and ROADMAP.md.