Files
clienthub/.planning/phases/01-foundation-client-dashboard/SKELETON.md
T
Simone Cavalli 81c667838f docs(01-foundation-client-dashboard): complete phase 1 planning with 5-plan structure
Create comprehensive phase plans for Foundation & Client Dashboard:
- 01-01-PLAN.md: Walking Skeleton (Next.js 15 bootstrap + DB connection)
- 01-02-PLAN.md: Database schema (11 tables, Drizzle ORM, drizzle-kit push)
- 01-03-PLAN.md: Middleware token validation + ClientView type + data fetching
- 01-04-PLAN.md: Client dashboard UI (header, timeline, progress, payments, docs, notes)
- 01-05-PLAN.md: Seed script + DNS CNAME configuration

Also create SKELETON.md documenting locked architectural decisions for all future phases:
- Next.js 15 + Drizzle + postgres-js driver (Coolify Postgres)
- Token as separate rotatable field (not PK)
- ClientView enforcement (no quote_items exposed to client API)
- Approved_at immutable audit trail
- Two independent auth systems (client token + admin session)
- Vercel deployment with custom domain

Update ROADMAP.md to mark Phase 1 as planned (5 plans created) and ready for execution.

All plans follow MVP vertical-slice structure with 2-3 tasks per plan.
Walking Skeleton proves the entire stack works end-to-end.
Requirements mapping: DASH-01 through DASH-04, DASH-07 through DASH-10 covered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 11:27:19 +02:00

11 KiB

ClientHub — Walking Skeleton (Phase 1)

Project: ClientHub — Freelancer Client Portal
Phase: 01 — Foundation & Client Dashboard
Date: 2026-05-13
Status: Blueprint (decisions below are LOCKED for all subsequent phases)


Project Architecture — Locked Decisions

This Walking Skeleton establishes the architectural foundation for all future phases. These decisions are immutable without explicit user approval.

Core Stack

Layer Technology Why Locked?
Framework Next.js 15 (App Router, TypeScript, src/) Server Components + Edge Middleware for performance; Vercel-native YES
Database Postgres on Coolify (Hetzner), via postgres-js driver Self-hosted (no Neon/Supabase cost); persistent via external DB YES
ORM Drizzle ORM with postgres-js Zero-cost serverless driver; schema-as-code migrations YES
UI Tailwind CSS v4 + shadcn/ui components Utility-first, copied components, mobile-first YES
Auth (Admin) Auth.js v4 Credentials provider (Phase 2) Single admin account, JWT cookie YES
Auth (Client) Custom Next.js Middleware + token validation No session store needed; token in URL YES
Token Generation nanoid (21 chars) Cryptographically secure, URL-safe, non-enumerable YES
Deployment Vercel (Hobby plan) + custom subdomain Native Next.js; auto-SSL; single deploy command YES

Data Model — Locked Entities

All tables below must exist and maintain these field definitions. Modifications require explicit approval.

clients
  id              UUID PK (stable, never changes)
  name            TEXT
  brand_name      TEXT
  brief           TEXT
  token           UUID UNIQUE  ← SEPARATE from PK, rotatable
  accepted_total  NUMERIC      ← denormalized, only price client sees
  created_at      TIMESTAMPTZ

phases
  id              UUID PK
  client_id       UUID FK → clients.id
  title           TEXT
  sort_order      INT
  status          TEXT  (upcoming | active | done)

tasks
  id              UUID PK
  phase_id        UUID FK → phases.id
  title           TEXT
  description     TEXT
  status          TEXT  (todo | in_progress | done)
  sort_order      INT

deliverables
  id              UUID PK
  task_id         UUID FK → tasks.id
  title           TEXT
  url             TEXT
  status          TEXT  (pending | submitted | approved)
  approved_at     TIMESTAMPTZ  ← immutable audit trail

comments
  id              UUID PK
  entity_type     TEXT  (task | deliverable)
  entity_id       UUID
  author          TEXT  (client | admin)
  body            TEXT
  created_at      TIMESTAMPTZ

payments
  id              UUID PK
  client_id       UUID FK → clients.id
  label           TEXT  ("Acconto 50%" | "Saldo 50%")
  amount          NUMERIC
  status          TEXT  (da_saldare | inviata | saldato)
  paid_at         TIMESTAMPTZ

documents
  id              UUID PK
  client_id       UUID FK → clients.id
  label           TEXT
  url             TEXT  ← external links only, no file uploads
  created_at      TIMESTAMPTZ

notes
  id              UUID PK
  client_id       UUID FK → clients.id
  body            TEXT
  created_at      TIMESTAMPTZ

service_catalog
  id              UUID PK
  name            TEXT
  description     TEXT
  unit_price      NUMERIC
  active          BOOLEAN

quote_items
  id              UUID PK
  client_id       UUID FK → clients.id
  service_id      UUID FK → service_catalog.id
  quantity        NUMERIC
  unit_price      NUMERIC
  subtotal        NUMERIC
  ← NEVER exposed via client API

Critical Design Principles — Locked

  1. clients.token is NOT the primary key. Data is keyed by stable UUID id. Token is a separate, rotatable field. Rotation is a single UPDATE statement.

  2. Client API never exposes quote_items. Server-side filtering enforces this; not a UI trick. The accepted_total field is the only price the client API returns.

  3. deliverables.approved_at is immutable. Once set, it cannot be unset. Provides an audit trail for disputes.

  4. Two independent auth systems:

    • /c/[token]/* → Middleware validates token, 404 on miss
    • /admin/* → Auth.js session check (Phase 2)
    • No overlap; no shared session store
  5. No file hosting in v1. Documents are external URLs only (Google Drive, PDFs, Figma links). File uploads → Phase 3+.

  6. No email in v1. Deliverables are dashboard links, not email attachments. Email integration → Phase 2+.

Directory Structure — Locked

IAMCAVALLI/
├── src/
│   ├── app/
│   │   ├── c/[token]/
│   │   │   ├── page.tsx          ← Client dashboard route
│   │   │   └── layout.tsx
│   │   ├── admin/                ← Phase 2 (protected by middleware)
│   │   │   ├── page.tsx          ← Admin dashboard
│   │   │   ├── clients/
│   │   │   │   ├── page.tsx
│   │   │   │   └── [id]/
│   │   │   ├── catalog/
│   │   │   └── ...
│   │   ├── layout.tsx
│   │   └── globals.css
│   ├── components/
│   │   ├── ui/                   ← shadcn/ui components
│   │   ├── client-dashboard.tsx
│   │   ├── phase-timeline.tsx
│   │   ├── payment-status.tsx
│   │   ├── documents-section.tsx
│   │   ├── notes-section.tsx
│   │   └── ...
│   ├── db/
│   │   ├── schema.ts             ← Drizzle schema (source of truth)
│   │   ├── migrations/           ← Generated by drizzle-kit
│   │   └── index.ts              ← db client export
│   ├── lib/
│   │   ├── client-view.ts        ← ClientView type + queries
│   │   ├── auth.ts               ← Phase 2: Auth helpers
│   │   └── ...
│   └── middleware.ts             ← Token validation at edge
├── scripts/
│   ├── seed.ts                   ← Insert first test client
│   └── ...
├── .env.local                    ← DATABASE_URL, secrets
├── drizzle.config.ts
├── next.config.ts
├── tailwind.config.ts
├── tsconfig.json
├── package.json
└── .planning/
    ├── ROADMAP.md
    ├── REQUIREMENTS.md
    ├── STATE.md
    └── phases/
        └── 01-foundation-client-dashboard/
            ├── 01-CONTEXT.md
            ├── 01-DISCUSSION-LOG.md
            ├── 01-01-PLAN.md
            ├── 01-02-PLAN.md
            ├── 01-03-PLAN.md
            ├── 01-04-PLAN.md
            ├── 01-05-PLAN.md
            └── SKELETON.md

Deployment — Locked

  • Host: Vercel (Hobby plan, $0/month for Phase 1 scale)
  • Domain: welcomeclient.iamcavalli.net (CNAME to Vercel DNS)
  • Database: Postgres on Coolify (existing Hetzner server, Simone manages)
  • Environment: DATABASE_URL injected via Vercel Secrets
  • SSL/TLS: Vercel auto-provisioning for custom domain

API Routes Structure (Phase 2+)

Routes created in Phase 2 will follow this pattern:

Client-facing routes (/api/c/[token]/...):

  • No authentication library needed
  • Middleware validates token
  • Routes return ClientView shape only

Admin routes (/api/admin/...):

  • Require Auth.js session
  • Access full AdminView including quote_items
  • CRUD operations on all entities

UI Layer Principles — Locked

  • Light & clean visual style: White backgrounds, strong typography, subtle gray accents
  • Mobile-first design: Tailwind defaults ensure responsive behavior
  • Semantic HTML: Proper heading hierarchy, accessible form controls
  • No client-side state management libraries: Server Components + Server Actions for Phase 1-2
  • Progress visualization: Global bar (top) + per-phase bars (sections) + task status badges
  • Brand consistency: iamcavalli logo in corner, client brand_name prominent

Security Assumptions — Locked

  1. Database credentials are secrets: DATABASE_URL never logged, committed, or exposed
  2. Tokens are non-enumerable: 21-character nanoid cannot be guessed
  3. Client API is isolated: Admin data never leaks to /c/[token]/* routes
  4. Admin password (Phase 2): env var ADMIN_PASSWORD protects /admin/* before Auth.js is added
  5. No PII in logs: Payment amounts and tokens never logged to Vercel logs

What This Skeleton Delivers

After Phase 1 execution:

Functional client portal:

  • One client can open their secret link on any device
  • Dashboard shows project phases, tasks, status, payments, documents, decision log
  • No login required; link is the secret

Production-ready infrastructure:

  • Database is live on Coolify Postgres
  • Custom domain is verified and HTTPS-enabled
  • Application is deployed on Vercel
  • One-command deploy pipeline (git push → Vercel auto-build)

Developer-friendly codebase:

  • TypeScript with strict mode
  • Drizzle ORM manages schema as code
  • Git-tracked migrations (reproducible database state)
  • One seed script to populate test data
  • No manual SQL; no database browser required

Foundation for Phase 2:

  • Data model is stable and comprehensive
  • Admin CRUD can be built without schema changes
  • Auth.js integration point is clear
  • Comments and approvals schema already exists

Phase 1 → Phase 2 Contract

Phase 2 will extend this skeleton by:

  1. Admin authentication: Middleware check + Auth.js session on /admin/* routes
  2. CRUD operations: Forms and API routes to edit clients, phases, tasks, deliverables, payments
  3. Comments & approvals: Client-facing UI for commenting and approving deliverables
  4. Admin workspace: Dashboard to manage all clients with state summary and quick actions
  5. Payment management: Update payment status, send payment reminders

No schema changes required. All Phase 2 features fit into the existing data model.


Validation Checklist (End of Phase 1)

  • Next.js 15 application compiles without TypeScript errors
  • Database schema is live on Coolify Postgres (all 11 tables)
  • Middleware validates tokens at edge
  • Client portal route renders complete dashboard with seeded data
  • Seed script inserts test client and prints shareable link
  • DNS CNAME is live: welcomeclient.iamcavalli.net → Vercel
  • Application is deployed on Vercel (accessible via https://welcomeclient.iamcavalli.net/)
  • Invalid tokens return 404 (no information leakage)
  • Payment amounts are NOT visible on client dashboard (only status)
  • Mobile layout is responsive and readable
  • All DASH-01 through DASH-10 requirements are satisfied (except DASH-05, DASH-06 which are Phase 2)

Future Extensibility Notes

This skeleton is designed for:

  • Phase 2: Admin CRUD + comments + approvals (no schema changes)
  • Phase 3: Service catalog + quote builder (admin-only, client sees only total)
  • Phase 4 (v2): Claude AI onboarding flow (optional; may defer indefinitely)
  • Beyond: Multi-team support, real file uploads, email automation (major schema rework)

The current design is intentionally simple. Future phases should resist scope creep and maintain the "client sees only what they need" principle.


Skeleton locked: 2026-05-13
Next checkpoint: Phase 2 planning (/gsd-plan-phase 2)