# FieldOps Modular industrial SaaS monorepo. **This is the scaffold phase** — no business features yet. The goal of this branch is to prove the end-to-end wiring: client → tRPC → Prisma → Postgres, with multi-tenancy enforced from day zero. ## What's here ``` apps/ operator-pwa/ Next 15 — operator console (port 3000) admin-web/ Next 15 — backoffice placeholder (port 3001) packages/ db/ Prisma 6 schema + tenant-scoping extension api/ tRPC v11 routers ui/ Shared shadcn-style components domain/ Pure domain logic (empty in this phase) config/ tsconfig / eslint / tailwind presets e2e/ Playwright smoke test ``` ## Prerequisites - **Node.js 22+** (use `nvm use` or install matching `.nvmrc`) - **pnpm 11+** (`corepack enable pnpm` or `npm i -g pnpm`) - **Docker Desktop** with the engine running (provides Postgres) - **Git** ## From-zero setup These steps assume a fresh clone with no `node_modules`, no Docker volumes, no `.env`. Run them in order from the repo root. ```sh # 1. environment cp .env.example .env # Open .env and set AUTH_DEV_AUTOLOGIN="true" for local development. # (Default is "false" on purpose — see "Auth" below.) # 2. install pnpm install # 3. start Postgres docker compose up -d # 4. apply the schema pnpm db:migrate # 5. seed the demo tenant pnpm db:seed # 6. start the operator app pnpm --filter @repo/operator-pwa dev ``` Open — you should see a **Connected** card with `Tenant: Demo Factory`. ### Run the E2E In a separate shell (with the dev server NOT running, or with `reuseExistingServer` left on — Playwright handles either): ```sh pnpm --filter @repo/e2e install-browsers # one-time, downloads Chromium pnpm test:e2e ``` The Playwright config force-sets `AUTH_DEV_AUTOLOGIN=true` for the dev server it launches, so the test does not depend on the developer's `.env`. ## Auth This scaffold has **no real authentication**. Auth.js v5 is wired up with a single Credentials provider that accepts any email present in the seeded `User` table — no password check. The `AUTH_DEV_AUTOLOGIN` env flag controls a server-side fallback: - **`AUTH_DEV_AUTOLOGIN=false`** (default in `.env.example`) — requests with no Auth.js session are unauthenticated. `protectedProcedure` returns 401. - **`AUTH_DEV_AUTOLOGIN=true`** — requests with no session silently resolve to the seeded `admin@demo.local` user, skipping the login UI. > ⚠️ **Never set `AUTH_DEV_AUTOLOGIN=true` in production.** The fallback is a > back door intended only for local dev and CI. The chokepoint is > `apps/operator-pwa/lib/auth.ts → resolveUser()`. Replace it with real > authentication before any non-dev deployment. ## Multi-tenancy Every Prisma model except `Tenant` has a `tenantId` column. Tenant scoping is enforced at runtime by an extension in `packages/db/src/tenant-extension.ts` — read the header comment in that file for the full list of operations it covers and the operations it does **not** (`$queryRaw`, interactive `$transaction` with external clients, etc.). Call sites get the scoped client from the tRPC context: `ctx.db.*`. The unscoped root client (`ctx.prisma`) is available for the rare cross-tenant case but should be a red flag at PR review. ## tRPC The operator app uses both tRPC paths: - **RSC caller** — `apps/operator-pwa/lib/trpc/server.ts` — direct in-process invocation from Server Components. Default for reads. - **Client hooks** — `apps/operator-pwa/lib/trpc/client.ts` — `@trpc/react-query` against `/api/trpc`. Default for mutations and client-rendered reads. The home page exercises both: the `Connected` card is rendered server-side; the `Client-side ping` card uses `useQuery`. Both must succeed for the hybrid wiring to be considered green. ## Common commands | Command | What it does | | --- | --- | | `pnpm install` | Install all workspace deps | | `docker compose up -d` | Start Postgres | | `docker compose down` | Stop Postgres (data persists) | | `docker compose down -v` | Stop Postgres **and drop the volume** | | `pnpm dev` | Run all dev tasks via Turbo | | `pnpm --filter @repo/operator-pwa dev` | Operator app only | | `pnpm --filter @repo/admin-web dev` | Admin app only (port 3001) | | `pnpm db:migrate` | Apply pending Prisma migrations | | `pnpm db:reset` | Drop, recreate, migrate, seed | | `pnpm db:seed` | Re-seed the demo tenant (idempotent) | | `pnpm db:studio` | Open Prisma Studio | | `pnpm typecheck` | Typecheck every package | | `pnpm test:e2e` | Run Playwright | | `pnpm format` | Prettier write | ## Versions of note | Package | Version | Why | | --- | --- | --- | | Node | 22 LTS | Required by Next 15 | | pnpm | 11 | Workspace + `allowBuilds` security feature | | Next.js | 15 | App Router, React 19 | | React | 19 | | | Prisma | 6 | Kept on 6.x — Prisma 7 mandates the new `prisma-client` ESM generator, which is a separate migration | | Auth.js (next-auth) | 5.0 beta | v5 split-config pattern (edge-safe middleware + Node providers) | | tRPC | 11 | Stable on the v11 series | | Tailwind | 3 | Conservative choice; v4 swap is a separate spike | | shadcn/ui | components inlined | Not the CLI scaffold — components live in `packages/ui/src/components` | ## Troubleshooting **`ERR_PNPM_IGNORED_BUILDS` after adding a dep** — pnpm 11 blocks postinstall scripts unless approved. Edit `pnpm-workspace.yaml` `allowBuilds:` and set the offending package to `true` (or `false` if you don't want its postinstall to run). **Page loads but says `UNAUTHORIZED`** — your `.env` has `AUTH_DEV_AUTOLOGIN=false` (the default). Flip it to `true` for local dev, or sign in through Auth.js. **Page errors with `Tenant not found`** — the seeded tenant was wiped. Run `pnpm db:seed` again. **Migration fails with `Environment variable not found: DATABASE_URL`** — make sure `.env` exists at the repo root and Docker Postgres is running. **Playwright complains the port is busy** — kill any leftover dev servers: on Windows `Get-Process node | Stop-Process -Force`; macOS/Linux `pkill -f 'next dev'`. ## License Internal. All rights reserved.