6.1 KiB
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 useor install matching.nvmrc) - pnpm 11+ (
corepack enable pnpmornpm 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.
# 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 http://localhost:3000 — 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):
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.protectedProcedurereturns 401.AUTH_DEV_AUTOLOGIN=true— requests with no session silently resolve to the seededadmin@demo.localuser, skipping the login UI.
⚠️ Never set
AUTH_DEV_AUTOLOGIN=truein production. The fallback is a back door intended only for local dev and CI. The chokepoint isapps/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-queryagainst/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.