Mike O'Dell Surveys · Implementation Plan

From approved design to running code

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 + Postgres Auth · Supabase Deploy · Docker + Dokploy Local · 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)
  • Auth end-to-end (Supabase login → JWT → FastAPI verify)
  • RBAC enforcement layer + UI gates
  • Four UI states pattern + component kit
  • Local dev loop (compose: web + api + db)
  • Typed client generation from OpenAPI
  • CI (lint, type-check, test)

Defer to after discovery

Depends on real MOS workflows
  • Job fields beyond the minimal core
  • Status transitions (what triggers "Waiting")
  • Document types that matter (CAD/permit/plat)
  • Field-report form structure
  • Timecard / payroll rules (OT, per-diem, mileage)
  • Equipment / license attributes + alerts
  • Per-screen RBAC matrix specifics
  • Integrations (QuickBooks/Xero, Trimble/Carlson)
04

Architecture & data flow

The shape the Week-1 work assembles. Typed contract top to bottom; auth offloaded to Supabase; files via signed URLs.


apps/web — Next.js (App Router)
React · Tailwind · shadcn/ui · TanStack Query
▼   HTTPS / JSON  ·  typed client generated from OpenAPI   ▼
apps/api — FastAPI (Python, uv)
SQLAlchemy 2.0 · Pydantic v2 · Alembic · RBAC dependency
PostgreSQL
data
Supabase Auth
JWT verify
Storage / S3
signed URLs
05

The Jobs vertical slice

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.


  1. DB model + migrationJob SQLAlchemy model, Alembic revision.
  2. API (RBAC-guarded)FastAPI CRUD router behind require_permission("jobs:*").
  3. Typed clientregenerate TS client from /openapi.json.
  4. Jobs listdata table in the app shell, status pills, the four states.
  5. Job detailthe 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.