The build sequence for turning the signed-off storyboard into a working platform — starting with the foundation you can build now, before the client discovery meeting, and slotting the domain logic in afterward. Anchored to the existing FastAPI / Docker / Dokploy setup already in this repo.
Stack · Next.js + FastAPI + PostgresAuth · SupabaseDeploy · Docker + DokployLocal · no third-party data
01
Approach: build the spine, defer the domain
Most of the platform doesn't depend on the discovery meeting. Build that foundation immediately; only the job-specific fields and permissions wait for the client conversation.
Build now (zero rework risk): design tokens, app shell, auth, RBAC, the four UI states, local dev loop, CI. None of this changes based on how MOS runs their day.
Wait for discovery: exact job fields, real status transitions, document types, payroll rules, equipment attributes — anything where guessing wastes effort.
Anchor: the FastAPI app, uv.lock, Dockerfile, compose, and nixpacks config already run. We grow that skeleton — no greenfield restart.
Proof point: one thin end-to-end Jobs slice validates the whole pipeline before the domain is fully known.
02
Milestone timeline (Week 1)
Five days to a running full-stack loop. Each step names what it reuses before what it adds.
DAY 1
Monorepo + frontend skeleton
Reuses: existing FastAPI app/, Docker, Dokploy. Move API to apps/api, scaffold apps/web (Next.js App Router + TS + Tailwind), init shadcn/ui. Keep docs/ serving so the client preview link never breaks.
DAY 2
Tokenize the approved design
Reuses: docs/design-system.html tokens (the flat navy system). Port every CSS variable, the four status colors, radii, and the flat/no-shadow rule into tailwind.config.ts + globals.css. Done once → every screen inherits it.
DAY 3
App shell + primitives
Reuses: the storyboard layouts. Build the responsive sidebar / mobile bottom-nav, top bar, and reusable StatusPill, role badge, and a DataState wrapper (loading / empty / error / populated).
DAY 4
Backend structure + auth foundation
Reuses: running FastAPI + uv + Alembic scaffolding. Add core/ (settings, security), db/ session, models/, a require_permission() RBAC dependency, Supabase JWT verification, and the first real migration (users, roles).
DAY 5
Glue + first vertical slice
Reuses: everything above. Local docker-compose (web + api + db), generate the typed client from /openapi.json, then a thin Jobs slice (list + detail) end-to-end.
MILESTONE
Sprint 0 complete
A user logs in, sees a role-appropriate shell, and the full stack is observable, typed, and deployable to Dokploy. Domain build begins the day after discovery.
03
Discovery-independent vs deferred
Build now
Won't change after the client meeting
Design tokens → Tailwind theme
App shell (sidebar, top bar, nav, command palette)
One thin path through every layer proves the stack before the domain is fully known. Minimal fields now (job number, client, address, status, assigned crew); widen after discovery.
DB model + migration — Job SQLAlchemy model, Alembic revision.
API (RBAC-guarded) — FastAPI CRUD router behind require_permission("jobs:*").
Typed client — regenerate TS client from /openapi.json.
Jobs list — data table in the app shell, status pills, the four states.
Job detail — the central-entity screen from the storyboard, minimal fields.
# Day 1 — scaffold (run from repo root)
mkdir -p apps && git mv app apps/api
npx create-next-app@latest apps/web --typescript --tailwind --app --eslint
cd apps/web && npx shadcn@latest init
# Day 4 — backend deps via uv (single dependency manager)
cd apps/api && uv add sqlalchemy "psycopg[binary]" alembic pyjwt
06
SOLID principles compliance
How the foundation keeps the codebase changeable as MOS's real workflows land on top of it.
Single Responsibility
Routers handle HTTP, services hold business logic, models own persistence, the web layer renders. Payroll rules live in one service, not scattered across endpoints.
Open / Closed
New entities (equipment, timecards) add routers + models without touching auth, the RBAC layer, or the app shell. The typed client regenerates rather than being hand-edited.
Liskov Substitution
Storage is an interface (local now, S3/Supabase later) — callers depend on the contract, so swapping the backend changes nothing upstream. Same for the auth verifier.
Interface Segregation
The generated client exposes only the endpoints each screen uses; permission checks are small, composable FastAPI dependencies, not one god-guard.
Dependency Inversion
High-level handlers depend on injected abstractions (DB session, current-user, permission, storage) via FastAPI Depends — concretes are wired at the edge, making everything testable.
07
Decisions to lock
Hard-to-reverse once data or callers depend on them — settle these in the plan, ideally confirmed in discovery.
Q1Job number scheme — sequential, per-year, or per-client? Becomes a public identifier callers depend on. Default: capture MOS's existing scheme in discovery; until then, year-sequence JOB-YYYY-NNNN.
Q2Tax-form data — store W-2/1099 (SSN/EIN) or link out to the payroll provider? Changes the security posture. Default: link out; store only if discovery requires.
Q3Monorepo tooling — plain folders + scripts, or pnpm/Turborepo? Default: plain apps/ + packages/ now; add Turborepo only if build times demand it.
Q4Storyboard hosting — keep at /, or move client preview to its own Dokploy app once apps/web takes the root? Default: move preview to a second tiny Dokploy app so the link is stable.