infra(04-00): route /c/ → /client/, Dockerfile, Gitea deploy

- Rename src/app/c/[token] → src/app/client/[token]
- Update proxy.ts, ClientRow, admin client detail with /client/ path
- Add output: "standalone" to next.config.ts for Docker build
- Add Dockerfile (multi-stage, node:20-alpine) and .dockerignore
- Push schema to Coolify Postgres via SSH tunnel (drizzle-kit push ✓)
- Update CLAUDE.md constraint 4 to reflect /client/ route
- Add Phase 4 planning artifacts (04-00, 04-RESEARCH, 04-PATTERNS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 16:12:05 +02:00
parent 49ef45da83
commit 5bf5dfce71
21 changed files with 3164 additions and 63 deletions
+191
View File
@@ -0,0 +1,191 @@
# Architecture — ClientHub Freelancer Client Portal
**Project:** ClientHub (welcomeclient.iamcavalli.net)
**Researched:** 2026-05-09
**Confidence:** HIGH
---
## Component Boundaries
Single Next.js application on Vercel. No separate backend. All server logic lives in Route Handlers (`/api/**`). One Postgres database (Neon serverless) accessed via Drizzle ORM. Admin auth via env-var secret + cookie. Client access via UUID token in URL — no auth library needed for clients.
| Component | Responsibility | Communicates With |
|-----------|---------------|-------------------|
| Client Portal `/c/[token]` | Read-only view: status, phases, tasks, deliverables, payments, documents | API Routes (GET only) |
| Admin Dashboard `/admin` | List all clients with status summary | API Routes (full CRUD) |
| Admin Client Workspace `/admin/clients/[id]` | Edit phases, tasks, deliverables, payments, documents | API Routes (full CRUD) |
| Service Catalog Manager `/admin/catalog` | CRUD on service items + unit prices | API Routes (catalog entity) |
| Quote Builder `/admin/clients/[id]/quote` | Compose quote from catalog items, write `accepted_total` to client row | Catalog + API Routes |
| Comments System | Client posts on task/deliverable; admin replies | API Route POST |
| Approval Flow | Client PATCHes a deliverable to `approved` | API Route, validates token ownership |
| API Routes `/api/**` | Validate token or admin session; query/mutate DB; return JSON | Postgres only |
| Database | Single source of truth | API Routes only — never queried from browser |
---
## Data Flow
**Client reading their dashboard:**
```
Browser → GET /c/[token]
→ Next.js server component
→ DB: clients WHERE token = [token] → 404 if missing
→ JOIN: project + phases + tasks + deliverables + payments + documents
→ Omit: quote_items, service prices
→ Render read-only portal
```
**Client posting a comment:**
```
Browser → POST /api/comments { token, entity_type, entity_id, body }
→ Validate token → write comment { author: 'client' }
→ 201 → re-fetch thread
```
**Client approving a deliverable:**
```
Browser → PATCH /api/deliverables/[id]/approve { token }
→ Validate token owns deliverable → set status='approved', approved_at=now()
→ Return updated deliverable
```
**Admin editing:**
```
Browser (admin) → PATCH /api/admin/tasks/[id] + admin cookie
→ Validate session → update row → return updated task
```
**Quote building:**
```
Admin UI selects services → computes line items
→ POST /api/admin/clients/[id]/quote { line_items[], accepted_total }
→ Write quote_items rows + write clients.accepted_total (denormalized)
→ Client portal reads clients.accepted_total — never touches quote_items
```
---
## Data Model
```
clients
id UUID PK
name TEXT
brand_name TEXT
brief TEXT
token UUID UNIQUE ← the secret link key (separate from PK!)
accepted_total NUMERIC ← denormalized; only price client ever sees
created_at TIMESTAMPTZ
phases
id UUID PK
client_id UUID → clients.id
title TEXT
sort_order INT
status TEXT (upcoming | active | done)
tasks
id UUID PK
phase_id UUID → phases.id
title TEXT
description TEXT
status TEXT (todo | in_progress | done)
sort_order INT
deliverables
id UUID PK
task_id UUID → tasks.id
title TEXT
url TEXT ← external link (Google Drive, PDF, etc.)
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 → 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 → clients.id
label TEXT
url 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 → clients.id
service_id UUID → service_catalog.id
quantity NUMERIC
unit_price NUMERIC ← snapshot at time of quote
subtotal NUMERIC
-- NEVER exposed via client API
```
**Key design decisions:**
- `clients.token` is the only secret. Rotation = single UPDATE. No session store needed.
- `clients.accepted_total` is deliberately denormalized so client API never touches `quote_items`.
- Approval `approved_at` stored as immutable audit trail — disputes resolved by timestamp.
- `comments` use `entity_type + entity_id` polymorphic pair — correct at this scale.
- `payments` always two rows per client (created when quote is finalized).
---
## Suggested Build Order
```
1. DB schema + migrations
└─ everything depends on this
2. API: token lookup + project read (GET only)
└─ unblocks client portal
3. Client portal UI /c/[token]
└─ the core deliverable; clients need this first
4. Admin auth middleware (env-var secret, cookie check)
└─ gate before admin routes go live
5. Admin: client list + client workspace CRUD
└─ phases, tasks, status, documents, payments
6. Comments system + deliverable approval
└─ depends on both client portal and admin workspace
7. Service catalog CRUD ← can run parallel with step 5
└─ independent of client-facing features
8. Quote builder
└─ depends on catalog + client entity
9. Claude onboarding flow (v2)
└─ depends on all CRUD APIs being complete
```
---
## Roadmap Implications
- Phase 1: DB schema + token API + client portal (all three coupled)
- Phase 2: Admin auth + CRUD management + comments + approvals
- Phase 3: Service catalog + quote builder
- Phase 4 (v2): Claude onboarding flow
+107
View File
@@ -0,0 +1,107 @@
# FEATURES.md — ClientHub Freelancer Client Portal
**Domain:** Freelancer client portal — solo personal branding consultant
**Project:** ClientHub (welcomeclient.iamcavalli.net)
**Researched:** 2026-05-09
**Confidence:** HIGH
---
## Context
Two asymmetric roles. Admin (the freelancer) has full CRUD. Client (read + lightweight interaction) accesses via secret URL — no login, no account — and can view, comment, and approve. The product competes indirectly with Notion client portals, HoneyBook, Dubsado, and bespoke agency portals. The differentiator is zero-friction secret link access and personal brand positioning.
---
## Table Stakes
Features clients expect when opening any project portal. Missing these causes confusion, distrust, or support overhead.
| Feature | Why Expected | Complexity | Notes |
|---------|--------------|------------|-------|
| Project overview at a glance | Client needs to know "where we are" without reading walls of text | Low | Name, brand, brief, current phase |
| Phase + task status visibility | Primary client question is "what's done, what's next" | Low | Phases with nested tasks; status per task (todo / in progress / done) |
| Deliverable approval | Client must formally sign off on outputs | Medium | Per-deliverable approve action; state persists; admin sees approval timestamp |
| Inline commenting on tasks/deliverables | Feedback and questions without email | Medium | Flat comments sufficient for v1; threading is nice-to-have |
| Document / file links | Deliverables, briefs, contracts surface in the portal | Low | Links to Google Drive, PDF, external URL; no file hosting needed |
| Payment status visibility | Client needs to know what they owe | Low | Deposit 50% + balance 50%; three states each: pending / invoiced / paid |
| Total quoted amount (not itemized) | Client expects to see the agreed number | Low | Single total; line items are admin-only |
| Mobile-readable layout | Clients open links on phones | Low | Responsive web; no native app |
| Persistent secret link | Link must not expire or rotate without notice | Low | UUIDs in DB, never regenerated unless admin resets explicitly |
| Trustworthy, branded appearance | First impression determines confidence in the consultant | Low | Logo, brand colors, professional typography — not a generic SaaS look |
---
## Differentiators
Not expected, but meaningfully improve experience or workflow.
| Feature | Value Proposition | Complexity | Notes |
|---------|-------------------|------------|-------|
| Decision log / history | Running record of agreed decisions — eliminates "we never agreed on that" disputes | Low | Append-only note stream visible to client; admin writes entries |
| Phase progress indicator | Visual progress bar gives a sense of momentum | Low | Derived from task completion %; no extra data model needed |
| "Last updated" timestamp on dashboard | Shows the portal is live and maintained | Low | Trivially derived from DB updated_at |
| Admin overview: all clients at a glance | Freelancer scans all active projects and overdue payments in one view | Medium | List with status badges; payment alert if overdue |
| Payment status badge with clear labels | Color-coded states (red = unpaid, yellow = invoiced, green = paid) | Low | Client sees their own; admin sees all |
| Shareable link reset | Admin can invalidate and regenerate a client's link if it leaks | Low | DB field update + redirect; rarely used but reassuring |
| Service catalog | Admin builds quotes from a curated menu of services; reusable across clients | Medium | Lookup table; admin-only; used by Claude in v2 |
| Claude-assisted onboarding (v2) | Generates phases + quote draft from a brief — massively speeds up admin work | High | Explicitly v2 in PROJECT.md |
---
## Anti-Features
Deliberately NOT building these.
| Anti-Feature | Why Avoid | What to Do Instead |
|--------------|-----------|-------------------|
| Client login / account creation | Adds friction with no benefit for a small client list | Secret UUID link |
| In-app invoicing / PDF generation | Accounting is out of scope | Show payment status only |
| File upload / storage | Massive complexity | Link to Google Drive or Dropbox |
| Email / SMS notifications | Transactional email infrastructure is heavy | Manual communication fine for small client list |
| Multi-admin / team roles | Freelancer works alone | Single admin |
| Client-editable project structure | Clients editing phases corrupts admin's source of truth | Comment and approve only |
| Itemized pricing visible to client | Erodes commercial confidentiality | Single total; detail is admin-only |
| Kanban / drag-and-drop board | Phases are sequential, not a fluid backlog | Ordered phase list |
| Time tracking | Out of scope for project-based billing | Not relevant |
| Multi-language / i18n | Single consultant, single-market | Hardcode interface language |
---
## Feature Dependencies
```
Secret link (UUID) → Client dashboard
Client dashboard → Phase/task display
Phase/task display → Deliverable approval
Phase/task display → Inline commenting
Admin client management → Secret link generation
Admin client management → Payment tracking
Service catalog → Quote building (admin picks from catalog)
Quote building → Payment tracking (total = basis for deposit/balance)
Service catalog → Claude onboarding v2
```
**Key insight:** Admin must create data before the client dashboard shows anything meaningful. Admin-first, then client.
---
## MVP Build Order
1. Admin: create/edit client record with secret link generation
2. Admin: create/edit phases and tasks per client
3. Admin: set payment amounts and statuses
4. Client dashboard: read-only view (overview, phases, tasks, payment status, documents)
5. Client: deliverable approval
6. Client: inline comments
7. Admin: all-clients overview
8. Admin: service catalog
9. v2: Claude-assisted onboarding
---
## Open Questions
- What happens when a client accidentally shares their secret link? Is link reset sufficient, or should there be an access log?
- Does the decision log need to be visible to clients from day one, or deferred?
- Should approval actions be reversible (un-approve)?
+177
View File
@@ -0,0 +1,177 @@
# Pitfalls — ClientHub Freelancer Client Portal
**Domain:** Freelancer client portal with secret-link access, solo developer, Vercel deploy
**Researched:** 2026-05-09
**Confidence:** HIGH
---
## Critical Pitfalls
### 1. Secret Links That Are Guessable or Enumerable
**What goes wrong:** If client tokens are generated from names, sequential IDs, or short strings, they can be enumerated. `/client/mario-rossi` or `/client/3` are not secrets.
**Prevention:**
- Generate tokens as cryptographically random UUIDs (v4) or nanoid (21 chars / ~126 bits of entropy)
- Never derive from name, company, or sequential ID
- Never log or display full tokens in admin analytics
**Warning signs:** Client slug contains the client's name. Token under 20 characters. Link can be reconstructed from info the client already knows.
**Phase mapping:** Phase 1 — data model + routing. Cannot be retrofitted after links are distributed.
---
### 2. Client API Exposes Admin Data (Hidden in UI Only)
**What goes wrong:** Developers fetch all client data and conditionally render fields. A technical client opens DevTools and sees the full quote breakdown — the exact thing the product prevents.
**Prevention:**
- Define two distinct data shapes: `ClientView` and `AdminView`
- Client API routes (`/api/c/[token]/`) return `ClientView` only — enforced server-side, not in the UI
- `accepted_total` goes on the `clients` row. Client API never queries `quote_items`
**Warning signs:** Client API response includes `lineItems` filtered in the frontend. "Client sees only total" enforced with `display: none`.
**Phase mapping:** Data model and API shape — Phase 1.
---
### 3. Data Loss from Vercel Filesystem / In-Memory Storage
**What goes wrong:** Vercel serverless functions are stateless. File writes to local filesystem and in-memory state are lost between invocations. SQLite on disk silently vanishes after cold start. Bug only manifests in production.
**Prevention:**
- External persistent DB from day one: Neon (Postgres free tier)
- Never write to `fs` for persistent data on Vercel
- Validate persistence in the first production deploy
**Warning signs:** Project stores data in a `data/` JSON directory. SQLite without a remote adapter. Data changes in one request not visible in the next.
**Phase mapping:** Day-one infrastructure decision — Phase 1, before any real data is entered.
---
### 4. Over-Engineering Before the First Client Uses It
**What goes wrong:** Building the Claude onboarding flow and quote generator before a single client has opened their dashboard. Real clients still managed via email while the portal is "almost ready."
**Prevention:**
- Hard success criterion for Phase 1: one client link is shareable and works
- Phase 1 ships read-only client dashboard only
- Collect direct feedback from one real client before adding features
**Warning signs:** >2 weeks pass without a shareable client link. Claude integration started before admin edit UI exists.
**Phase mapping:** Enforced by roadmap phase ordering.
---
### 5. Token Is the Primary Key (Unrotatable)
**What goes wrong:** A client forwards their link. The link appears in a screenshot. There is no mechanism to rotate without losing data or breaking bookmarks. Worse if token = primary key: rotation requires a migration.
**Prevention:**
- Data model: stable internal UUID as PK; secret token is a separate, independently updatable field
- Build "Regenerate link" in admin area during Phase 2 — it's a single field UPDATE
- Overwriting the token field is sufficient to invalidate the old link
**Warning signs:** Token is used as PK of the client record. No admin affordance to regenerate a link.
**Phase mapping:** Data model separation — Phase 1. Admin rotation UI — Phase 2.
---
### 6. Client Approval Has No Immutable Record
**What goes wrong:** Client clicks "Approve." Later: "I never approved that." If approval is a boolean with no timestamp, there is no evidence. Weakens your position in commercial disputes.
**Prevention:**
- Store `approved_at` timestamp alongside the approval boolean — from day one
- Display approval timestamp in admin view
- Approvals are immutable for clients — no "undo" button
**Warning signs:** Approval is a boolean column with no timestamp. Client-visible "undo approval" button exists.
**Phase mapping:** Schema — Phase 1. Display in admin — Phase 2.
---
## Moderate Pitfalls
### 7. Payment Status Without a Valid State Machine
Payment has three states (da_saldare / inviata / saldato) for two payments. If transitions are not enforced, the dashboard shows contradictory states.
**Prevention:** Enforce valid transitions: `da_saldare → inviata → saldato`. Admin UI offers only valid next states.
**Phase mapping:** Admin UI — Phase 2.
---
### 8. Google Drive Links That Rot
Document links break when sharing settings change or the Drive account changes. Client sees a broken link to their own deliverable.
**Prevention:** Store a display name alongside the URL so broken links are visible in admin. Build a simple "update link" affordance in Phase 2.
---
### 9. Admin Area With No Real Access Control
`/admin` is a secret route with no authentication. A client who guesses the URL accesses all client data.
**Prevention:** Add Next.js middleware check against `ADMIN_PASSWORD` env variable before Phase 2 ships. Never rely on security through obscurity for a route that contains all client data.
**Phase mapping:** Phase 2, before admin area contains real data.
---
### 10. Comments Scope Creeping Into Threading
"Clients can leave comments" becomes complex when replies, read/unread state, and notifications are added. Can double Phase 1 scope.
**Prevention:** Phase 1 ships comments as a flat append-only list per task. No threading, no replies, no email notifications.
---
## Minor Pitfalls
### 11. DNS Configuration as a Last-Minute Task
`welcomeclient.iamcavalli.net` requires a CNAME to Vercel's DNS. Propagation takes minutes to hours. Doing this the day of a client demo misses the deadline.
**Prevention:** Configure DNS in Phase 1 before the UI is complete. Verify propagation independently.
---
### 12. Mobile Responsiveness as an Afterthought
Clients open the link on their phone from a shared message. A broken mobile layout is the first impression.
**Prevention:** Use Tailwind mobile-first defaults from the start. Test on a real phone before any link is sent.
---
### 13. No Empty States
A new client record with no tasks shows a blank page. The client assumes something is broken.
**Prevention:** Design minimal empty states for no-tasks, no-documents, no-comments.
---
## Phase-Specific Warnings Summary
| Phase Topic | Likely Pitfall | Mitigation |
|---|---|---|
| Client token generation | Guessable slug from name | Crypto-random UUID/nanoid, never name-derived |
| Client-facing API | Admin data in JSON response | `ClientView` type enforced server-side |
| Storage choice | Vercel filesystem not persistent | External DB (Neon) before first data write |
| Admin area access | No real auth | Middleware check before Phase 2 ships |
| Approval recording | Boolean-only, no audit trail | Store `approved_at` from day one |
| Token in data model | Token = PK, unrotatable | Separate stable ID from rotatable token field |
| Phase ordering | Claude flow before dashboard | Enforce: client view → admin edit → Claude |
| Comments | Threading scope creep | Flat list in Phase 1 |
| DNS | Last-minute failure | Configure and verify in Phase 1 |
+132
View File
@@ -0,0 +1,132 @@
# Technology Stack — ClientHub Freelancer Client Portal
**Project:** ClientHub (welcomeclient.iamcavalli.net)
**Researched:** 2026-05-09
**Confidence:** HIGH
---
## Recommended Stack
### Core Framework
| Technology | Version | Purpose | Why |
|------------|---------|---------|-----|
| Next.js | 15.x (latest stable) | Full-stack app framework | App Router + Server Actions replace a separate API layer. Vercel-native: no adapter needed. First-class TypeScript. |
| React | 19.x | UI rendering | Bundled with Next.js. Server Components eliminate client-side waterfalls for the read-heavy client portal. |
| TypeScript | 5.x | Type safety | Drizzle + Zod give end-to-end type inference from DB schema to form validation. |
**Why NOT Remix / SvelteKit / Astro:** They work on Vercel but add unfamiliarity overhead with no gain at this scale.
---
### Database
| Technology | Purpose | Why |
|------------|---------|-----|
| Neon (serverless Postgres) | Primary database | Free plan: 0.5 GB storage + 100 CU-hours/month — sufficient for 520 clients. Scales to zero between uses. Native Vercel integration that auto-injects DATABASE_URL per preview branch. |
| Drizzle ORM | DB access + migrations | Lightest-weight TS ORM. Ships `drizzle-orm/neon-http` serverless driver — no persistent TCP connections, works in Vercel Node and Edge runtimes for free. Schema-as-code with `drizzle-kit` handles migrations. |
**Why NOT Prisma:** Needs PgBouncer or Prisma Accelerate ($) for serverless connection pooling. Drizzle's `neon-http` handles this natively at zero cost.
**Why NOT Supabase:** Adds RLS, Realtime, and Auth overhead you don't need and will have to maintain.
---
### Authentication
| Technology | Purpose | Why |
|------------|---------|-----|
| Auth.js (next-auth) v4 | Admin session management | Credentials provider with a single admin account. Session stored as signed JWT cookie. No user table in DB. |
| Next.js Middleware (custom) | Client secret-link validation | Each client has a `secretToken` (nanoid, 21 chars) stored in DB. Middleware reads `[token]` path segment, validates against Neon, returns 404 on miss. Runs at the edge before any page renders. |
| nanoid | Token generation | Cryptographically secure, URL-safe, 21-char default (~126 bits of entropy). Generated once at client creation. |
**Auth flow summary:**
- `/admin/*` → Auth.js session required (single admin account)
- `/c/[token]/*` → Middleware validates token against Neon, 404 on miss
- Client pages: zero auth library overhead
---
### UI
| Technology | Purpose | Why |
|------------|---------|-----|
| Tailwind CSS v4 | Utility-first styling | CSS-first configuration, zero runtime overhead. |
| shadcn/ui | Component library | Components copied into codebase (no runtime dep). Built on Radix UI (accessible). Provides: Badge, Progress, Card, Dialog, Table, Textarea, Select. |
| lucide-react | Icons | Tree-shaken, SVG-based, consistent. |
---
### Forms and Validation
| Technology | Purpose | Why |
|------------|---------|-----|
| Zod | Schema validation | Server-side in Server Actions + client-side with RHF resolver. Single source of truth for data shapes. |
| React Hook Form | Admin form state | Complex admin forms (client onboarding, task editing, quote builder). Client-facing pages use native `<form>` + Server Actions. |
---
### File Handling (v1)
None — document links stored as text fields in Postgres. Eliminates S3, CDN, and upload infrastructure from the initial build entirely.
**If direct uploads needed in v2:** UploadThing integrates directly with Next.js App Router, free tier (2 GB storage).
---
### Infrastructure
| Technology | Purpose | Why |
|------------|---------|-----|
| Vercel Hobby plan | Deploy + CDN + serverless | Native Next.js. Custom subdomain (`welcomeclient.iamcavalli.net`) via DNS CNAME. No Docker, VPS, or CI/CD to manage. |
| Neon Vercel Integration | DB branch per preview | Creates a fresh Neon branch per Git branch automatically. Safe schema migration testing. |
---
## Installation Sequence
```bash
# 1. Bootstrap Next.js
npx create-next-app@latest clienthub --typescript --tailwind --app --src-dir
# 2. Database
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit
# 3. Auth
npm install next-auth
# 4. Token generation
npm install nanoid
# 5. Validation + Forms
npm install zod @hookform/resolvers react-hook-form
# 6. shadcn/ui
npx shadcn@latest init
npx shadcn@latest add badge button card dialog dropdown-menu input label progress select separator table textarea
```
---
## Key Architectural Decisions
1. **Secret-link without Auth.js:** Next.js Middleware validates `[token]` at the edge. Fast, zero client-side JS, 404 on invalid token.
2. **Server Actions for all mutations:** Task updates, comments, payment status — no REST API layer to maintain.
3. **Privacy model is a DB query filter:** Admin sees `quote_items`; clients see only `clients.accepted_total`. Not a UI filter — a DB design.
4. **Two auth systems, zero overlap:** Admin JWT cookie on `/admin/*`. Client token middleware on `/c/*`.
---
## Confidence Levels
| Area | Confidence | Notes |
|------|------------|-------|
| Next.js App Router | HIGH | Stable since Oct 2024 |
| Neon free tier | HIGH | 0.5 GB storage, 100 CU-hours/month |
| Drizzle + neon-http | HIGH | Free serverless driver, no connection pooling needed |
| Auth.js Credentials (admin) | HIGH | Mature, well-documented |
| nanoid secret tokens | HIGH | Cryptographically secure default |
| Tailwind v4 + Next.js | HIGH | Stable, PostCSS plugin verified |
| Vercel Hobby plan | HIGH | Custom subdomain supported |
+126
View File
@@ -0,0 +1,126 @@
# Project Research Summary
**Project:** ClientHub — welcomeclient.iamcavalli.net
**Domain:** Freelancer client portal (secret-link access, solo consultant)
**Researched:** 2026-05-09
**Confidence:** HIGH
---
## Executive Summary
ClientHub è un portale web a due ruoli per un consulente di personal branding. I clienti accedono via UUID segreto casuale — nessun account, nessun login, zero attrito. L'admin gestisce tutto: crea clienti, fasi, task, deliverable, pagamenti e preventivi. Il consensus della ricerca è chiaro: costruisci prima la dashboard cliente, poi l'admin CRUD, poi catalogo servizi e preventivi, poi il flusso Claude AI (v2).
Stack confermato: **Next.js 15 + Neon (Postgres) + Drizzle ORM + Auth.js + nanoid + Tailwind v4 + shadcn/ui**. Ogni scelta è ottimizzata per un developer solo su Vercel: nessun backend da mantenere, nessun costo di connection pooling, nessuna infrastruttura di upload file, nessuna libreria di auth per i clienti. Il meccanismo "secret link" è un Next.js Middleware edge check — veloce, zero client JS, 404 se il token non esiste.
I rischi dominanti sono architetturali, non tecnici. Se il token è la primary key diventa non-rotazionabile. Se la client API restituisce `quote_items` (anche nascosti nell'UI), un cliente con DevTools vede i prezzi dei singoli servizi. Se il progetto parte dal flusso Claude prima che un cliente possa aprire la sua dashboard, il portale non esce. Tutti e tre prevenibili con le decisioni corrette sul data model dal giorno uno.
---
## Key Findings
### Stack Raccomandato
| Tecnologia | Ruolo | Perché |
|------------|-------|--------|
| Next.js 15 (App Router) | Framework full-stack | Server Actions sostituiscono un'API REST separata; nativo Vercel |
| Neon (serverless Postgres) | Database principale | Free tier (0.5 GB, 100 CU-h/mese) sufficiente per 520 clienti; scala a zero |
| Drizzle ORM + neon-http | Accesso DB + migrazioni | Nessun costo di connection pooling; schema-as-code; inferenza TypeScript end-to-end |
| Auth.js v4 (Credentials) | Sessione admin | Account singolo, cookie JWT firmato, nessuna tabella utenti in DB |
| nanoid | Generazione token | 21 char, ~126 bit di entropia, URL-safe, crittograficamente sicuro |
| Tailwind v4 + shadcn/ui | UI | Componenti copiati nel codebase, accessibilità Radix UI, zero runtime dep |
| Zod + React Hook Form | Validazione e form | Schema unico; RHF solo per form admin complessi |
File upload deliberatamente esclusi dalla v1. I link ai documenti sono campi testo che puntano a Google Drive.
### Features v1
**Table stakes (obbligatori):**
- Panoramica progetto (nome, brand, brief, fase corrente)
- Visibilità fasi e task con stato (todo / in corso / fatto)
- Approvazione deliverable con timestamp immutabile
- Commenti inline su task e deliverable (lista piatta, no threading)
- Link a documenti esterni (solo URL, no file hosting)
- Stato pagamenti: acconto 50% + saldo 50% (da saldare / inviata / saldato)
- Totale preventivo accettato visibile al cliente (cifra unica, mai dettaglio)
- Layout mobile-ready
- Link segreto persistente e non-scadente
**Differenziatori (low-effort, includibili in v1):**
- Log decisioni / storico (nota append-only)
- Indicatore di avanzamento fase (derivato da % task completati)
- Timestamp "ultimo aggiornamento" sulla dashboard
- Vista admin: tutti i clienti con badge stato pagamenti
- Reset link segreto (single UPDATE, solo admin)
**Anti-features (mai costruire):** login cliente, PDF fatture in-app, multi-admin, struttura progetto modificabile dal cliente, prezzi singoli visibili al cliente, kanban board.
### Architettura
Singola applicazione Next.js su Vercel, un database Neon Postgres. Nessun backend separato.
**Due path di accesso isolati:**
- `/c/[token]/*` → Middleware valida il token contro Neon, 404 se mancante
- `/admin/*` → Auth.js session check, singolo account admin
**Decisioni chiave del data model:**
- `clients.token` è un campo separato e rotazionabile — **non** la primary key
- `clients.accepted_total` denormalizzato: la client API non tocca mai `quote_items`
- `deliverables.approved_at` come audit trail immutabile dal giorno uno
- `payments` sempre due righe per cliente (acconto + saldo), create alla finalizzazione del preventivo
- `ClientView` e `AdminView` sono tipi distinti lato server — privacy enforce a livello di query, non di UI
### Pitfall Critici
1. **Token = primary key (non rotazionabile)** — Usa UUID stabile come PK e campo `token` separato e aggiornabile. Deve essere nella schema della Fase 1; non si può correggere dopo che i link sono stati distribuiti.
2. **Client API espone `quote_items` (nascosti solo nell'UI)** — Definisci `ClientView` come tipo server-side che non interroga mai `quote_items`. Un cliente tecnico con DevTools non deve mai vedere i prezzi singoli.
3. **Over-engineering prima che un cliente usi il portale** — Criterio di successo duro per la Fase 1: un link cliente reale è condivisibile e funziona. Non iniziare il flusso Claude prima che l'admin possa creare un cliente e il cliente possa aprire la sua dashboard.
4. **Nessun record di approvazione immutabile** — Salva `approved_at` (timestamp, non solo boolean) dallo schema iniziale.
5. **Area admin senza vera autenticazione** — Il check Middleware su `ADMIN_PASSWORD` env var deve essere in place prima che la Fase 2 vada in produzione.
---
## Implicazioni per la Roadmap
### Struttura suggerita: 4 fasi
**Fase 1 — Foundation: DB schema, token API, dashboard cliente**
Consegna: un link cliente reale condivisibile che mostra il progetto su mobile e desktop.
Copre: panoramica, fasi/task, pagamenti, documenti, link segreto, DNS.
**Fase 2 — Admin CRUD + auth + commenti + approvazioni**
Consegna: admin crea/modifica clienti, fasi, task, deliverable, pagamenti. Cliente commenta e approva. Admin può rigenerare il link.
Copre: auth Middleware, CRUD completo, flow approvazione con timestamp, commenti lista piatta.
**Fase 3 — Catalogo servizi + preventivi**
Consegna: admin costruisce catalogo riutilizzabile e compone preventivi da esso. `accepted_total` scritto sulla riga cliente.
Nessuna dipendenza client-facing oltre `accepted_total` (già in schema dalla Fase 1).
**Fase 4 (v2) — Flusso Claude AI per onboarding**
Dipende da CRUD stabile + catalogo completo. Claude legge il brief e suggerisce fasi + preventivo.
*Richiede ricerca dedicata durante la pianificazione.*
### Flag di ricerca
- Fasi 13: pattern standard, nessuna ricerca aggiuntiva necessaria
- Fase 4: richiede ricerca su Claude API structured output, streaming vs batch, prompt engineering per generazione fasi
---
## Confidence Assessment
| Area | Confidence | Note |
|------|------------|------|
| Stack | HIGH | Tutte le tecnologie stabili e in produzione |
| Features | HIGH | Feature set opinionated e ben delimitato |
| Architettura | HIGH | Data model completo, pattern two-path auth provato |
| Pitfall | HIGH | Tutti mappabili a decisioni concrete della Fase 1 |
**Domande aperte (da risolvere durante la pianificazione delle fasi):**
- Access log per i link (utile per rilevare accessi non autorizzati)?
- Approvazioni reversibili (admin-only revoke)?
- Log decisioni visibile al cliente dalla v1 o solo admin?
- DNS: configurare e verificare la propagazione nella Fase 1
---
*Ricerca completata: 2026-05-09 | Pronto per la roadmap: sì*