docs(planning): archivia v2.1/v2.2/v2.3 e documenta v2.4
.planning/ documentava in dettaglio cio che era vecchio e per niente cio
che e in produzione: le fasi 11-22 (v2.1 e v2.2, chiuse a giugno) erano
ancora in phases/ mentre v1.0 e v2.0 stavano gia in milestones/, e il
lavoro degli ultimi due mesi - gate OTP e ciclo di vita dei retainer, cioe
quello che gira su hub.iamcavalli.net - non aveva nessuna cartella.
- phases/{11,12,14} -> milestones/v2.1-phases/, phases/{18..22} ->
milestones/v2.2-phases/. Ora phases/ contiene solo la milestone in
corso, che e quello che state.cjs conta per il progresso
- v2.1-ROADMAP.md ricostruito: era l'unica milestone senza archivio,
interrotta dal reset del 19/06 e mai chiusa formalmente
- v2.3-ROADMAP.md + v2.3-REQUIREMENTS.md: v2.3 e stata eseguita fuori dal
ciclo GSD, non esistono PLAN/SUMMARY per fase. L'archivio E la doc
- REQUIREMENTS.md riscritto per v2.4 con il backlog reale
- phases/13 e phases/26: SUMMARY ricostruiti da commit, migration e
STATUS.md. 26 e il primo numero libero
- research/: cancellate 4 varianti dello stesso PITFALLS e FEATURES/
SUMMARY, superati da PROJECT.md. Diverse anti-feature erano ormai
contraddette dai fatti (il Kanban e stato costruito in Phase 19,
l'email in v2.3, il time tracking esiste)
- cancellati UI-RULES.md e DESIGN-SYSTEM.md (CLAUDE.md li dichiara
superseded: impongono l'inverso della regola attuale) e HANDOFF.md,
fermo al 13/06
- SECURITY-*.md -> security/: audit chiuso, ma i report restano la doc di
cosa e stato ruotato e perche
- PROJECT.md/MILESTONES.md/ROADMAP.md allineati: milestone corrente v2.4,
sessione OTP 90gg non 30, migrazioni fino alla 0016
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,58 +0,0 @@
|
|||||||
# Design System — Offer Studio UI direction (v2.1)
|
|
||||||
|
|
||||||
**Definito:** 2026-06-13 (via skill `ui-ux-pro-max`)
|
|
||||||
**Scope:** Applies to Phase 11 (Catalog DB-view), 12 (Offer composition/DnD), 13 (Servizi Attivi), 14 (CRM Attio-style) — any "database view" table in `/admin/*`.
|
|
||||||
|
|
||||||
## Direzione
|
|
||||||
|
|
||||||
ClickUp / Pipedrive: dense ma leggibile, flat, zero decorazione. **Pattern:** Minimalism & Swiss Style + Flat Design — grid-based, alto contrasto, hover/transition rapidi (150-250ms), nessuna ombra/gradiente pesante.
|
|
||||||
|
|
||||||
## Brand tokens — INVARIATI (da `src/app/globals.css`)
|
|
||||||
|
|
||||||
Non introdurre una nuova palette: ClickUp/Pipedrive è una direzione di LAYOUT/interazione, non di colore. Il brand iamcavalli resta:
|
|
||||||
|
|
||||||
| Token | Valore | Uso |
|
|
||||||
|---|---|---|
|
|
||||||
| `--color-primary` | `#1A463C` (verde scuro) | azioni primarie, focus ring, link attivi |
|
|
||||||
| `--color-accent` | `#DEF168` (lime) | highlight/badge di stato attivo, CTA secondarie |
|
|
||||||
| `--color-background` | `#ffffff` | sfondo pagina/tabella |
|
|
||||||
| `--color-muted` / `--color-bg-subtle` | `#f9f9f9` | righe alternate, header tabella, quick-add row |
|
|
||||||
| `--color-border` | `#e5e7eb` | bordi cella sottili (1px), MAI ombre pesanti |
|
|
||||||
| `--color-foreground` | `#1a1a1a` | testo primario |
|
|
||||||
| `--color-muted-foreground` | `#71717a` | placeholder, metadati, celle vuote |
|
|
||||||
| Font | Geist Sans (già configurato) | nessun cambio — coerente con "Minimal Swiss" |
|
|
||||||
|
|
||||||
## Pattern tabella database-view (Phase 11-13)
|
|
||||||
|
|
||||||
- **Riga**: altezza compatta (~40px), padding orizzontale `px-3`, bordo inferiore `border-border` 1px — NO bordi verticali tra celle (look ClickUp, non Excel)
|
|
||||||
- **Inline edit**: click su cella → diventa `<input>`/`<select>` borderless con `ring-1 ring-primary` on focus → Enter salva, Esc annulla, blur salva. Nessun modal, nessun reload.
|
|
||||||
- **Tag multi-select**: `Badge` (già in `components/ui/badge.tsx`) con colori derivati da una palette fissa a rotazione (6-8 colori pastello su sfondo, testo scuro per contrasto AA) + pulsante "+" inline per creare un nuovo tag senza uscire dalla riga
|
|
||||||
- **Quick-add row**: ultima riga della tabella, sempre visibile, placeholder "+ Aggiungi servizio" — stile identico alle righe dati ma `text-muted-foreground`, diventa riga normale dopo il primo salvataggio
|
|
||||||
- **Filtri/ricerca**: barra sopra la tabella, input singolo con icona search (Lucide), filtro client-side istantaneo su nome/tag — NO bottone "Cerca", NO reload
|
|
||||||
- **Header tabella**: sticky, `bg-muted`, font-weight 600, NO maiuscolo decorativo eccessivo (small-caps ok, ALL-CAPS pesante no)
|
|
||||||
- **Hover riga**: `bg-muted/50`, transizione `transition-colors duration-150`, cursore pointer solo su celle editabili
|
|
||||||
|
|
||||||
## Componenti shadcn da riusare/estendere
|
|
||||||
|
|
||||||
Già presenti: `table`, `badge`, `dialog`, `select`, `input`, `button`, `form`. Per Phase 11 servirà probabilmente:
|
|
||||||
- Un componente `EditableCell` (input/select inline, non in shadcn — da costruire ad-hoc su `input.tsx`)
|
|
||||||
- Un `TagMultiSelect` (combobox + badge, da costruire su `select.tsx`/`badge.tsx` — shadcn `command`/`popover` non ancora installati, valutare in planning)
|
|
||||||
|
|
||||||
## Anti-pattern da evitare
|
|
||||||
|
|
||||||
- Ombre pesanti, glassmorphism, gradienti decorativi
|
|
||||||
- Icone emoji (usare SVG Lucide, coerente col resto dell'app)
|
|
||||||
- Tabelle senza filtro/ricerca
|
|
||||||
- Azioni riga-per-riga quando serve bulk (Phase 12+: valutare checkbox + action bar per operazioni multiple)
|
|
||||||
- Hover che causa layout shift (no scale transform su righe tabella)
|
|
||||||
|
|
||||||
## Checklist pre-delivery (per ogni componente nuovo)
|
|
||||||
|
|
||||||
- [ ] Contrasto testo ≥ 4.5:1 (light mode — testo muted minimo `#475569`/`text-muted-foreground` attuale è `#71717a`, verificare su `bg-muted`)
|
|
||||||
- [ ] `cursor-pointer` su celle/righe editabili e cliccabili
|
|
||||||
- [ ] Focus ring visibile (`ring-1 ring-primary` o `--color-ring`) su input inline e bottoni
|
|
||||||
- [ ] Transizioni 150-250ms, `transform`/`opacity` non `width`/`height`
|
|
||||||
- [ ] Responsive: tabella in `overflow-x-auto` wrapper sotto 1024px, niente layout rotto
|
|
||||||
|
|
||||||
---
|
|
||||||
*Riferimento per CONTEXT.md (Phase 11) e per eventuale `/gsd-ui-phase` su fasi 11-14.*
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# Handoff
|
|
||||||
|
|
||||||
Living document — update at the end of each session so the next one can resume without re-deriving context. Overwrite stale sections; keep it short and actionable.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2026-06-13 — Milestone v2.1 "Offer Studio + Proposal AI" pianificata — pronta per esecuzione
|
|
||||||
|
|
||||||
### Cosa è stato fatto
|
|
||||||
|
|
||||||
Eseguito ciclo completo `/gsd-new-milestone "Offer Studio + Proposal AI"` (research saltata su scelta utente):
|
|
||||||
|
|
||||||
- **PROJECT.md**: nuovo milestone v2.1 con goal, 4 target feature, sezione "Validated" aggiornata con v2.0 (Phase 7-10), "Active" riscritta in 4 categorie prioritizzate, nuove Key Decisions (compartimenti stagni confermato, ordine Offer Studio→Proposal AI, tab Preventivo→Servizi Attivi zero-perdita verificata)
|
|
||||||
- **v2.0 archiviata** (copie, non spostamenti): `REQUIREMENTS.md`/`ROADMAP.md`/phases 07-10 → `.planning/milestones/v2.0-*`
|
|
||||||
- **REQUIREMENTS.md** riscritto: 23 requisiti v1 in 5 categorie (Offer Studio, Workspace Servizi Attivi, CRM Attio, Dashboard [bloccata], Proposal AI) + deferred v2 (OFFER-14, AUTH-OTP-01, ARCH-01) + out of scope
|
|
||||||
- **ROADMAP.md** creato: 7 nuove fasi (11-17), copertura 100% (23/23 requisiti mappati), tutte approvate dall'utente
|
|
||||||
- **STATE.md**: switch a v2.1, focus = Phase 11
|
|
||||||
|
|
||||||
### Roadmap v2.1 (Phase 11-17)
|
|
||||||
|
|
||||||
| Fase | Titolo | Requisiti | Note |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| 11 | Catalog Database-View UX & Legacy Consolidation | OFFER-07,08,09,10,13 | unifica `service_catalog`/`offer_services` → `services` PRIMA della nuova UX |
|
|
||||||
| 12 | Offer Composition Drag&Drop & CSV Import | OFFER-11,12 | `@dnd-kit`, totale live durante drag, import CSV one-shot |
|
|
||||||
| 13 | Workspace — Servizi Attivi | PROJ-06..10 | rimuove tab Preventivo (zero perdita, `accepted_total` resta via Payments) e Forecast; nuova tab Servizi Attivi (one-shot/ricorrenti + tracking incassi mensili) |
|
|
||||||
| 14 | CRM Attio-style & Fix | CRM-08..12 | inline edit lead + tag, fix FollowUpWidget IT / LeadForm types / SendQuoteModal |
|
|
||||||
| 15 | Dashboard Revenue Stats | DASH-11 | **BLOCCATA** — attesa mockup utente, isolata/skippabile, non blocca 16/17 |
|
|
||||||
| 16 | Proposal AI — Data Foundations & Auto-Provisioning | PROP-03,04 | campo Stripe Payment Link + auto-provisioning su accettazione (ex-Phase 11) |
|
|
||||||
| 17 | Proposal AI — Builder, Pagina Pubblica & Email | PROP-01,02,05 | AI builder + redesign `/quote/[token]` + invio email Resend (ex-Phase 12) |
|
|
||||||
|
|
||||||
### Nota trasparenza — deviazione dal workflow
|
|
||||||
|
|
||||||
Il workflow `/gsd-new-milestone` prevede uno step "phases clear" che farebbe `rm -rf` di `.planning/phases/01-10/` senza backup. **Non l'ho eseguito**: è distruttivo, senza archiviazione automatica, e CLAUDE.md richiede conferma prima di operazioni distruttive/di investigare prima di rimuovere lavoro storico. Le fasi 07-10 sono state invece COPIATE (non spostate) in `.planning/milestones/v2.0-phases/`; le directory originali `01-10` restano in `.planning/phases/`. Nessuna perdita — solo directory duplicate, pulizia facoltativa in futuro.
|
|
||||||
|
|
||||||
### Prossima sessione
|
|
||||||
|
|
||||||
1. **Pianificare Phase 11** (Catalog Database-View UX & Legacy Consolidation): `/gsd-plan-phase 11` (oppure `/gsd-discuss-phase 11` prima per decisioni aperte: schema tag, formato CSV import, strategia consolidamento `service_catalog`/`offer_services`)
|
|
||||||
2. Se arriva il **mockup dashboard** dall'utente: Phase 15 (DASH-11) può essere sbloccata, usare `/gsd-ui-phase` come contratto UI
|
|
||||||
3. Migration Phase 11 (consolidamento catalogo) e Phase 13/16 (nuovi campi recurring/payment link) vanno applicate a prod via SSH+docker exec PRIMA del push del codice dipendente (regola storica, vedi sotto)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2026-06-12 — Direzione "Offer Studio" + "Proposal AI" → ora pianificata (vedi sopra)
|
|
||||||
|
|
||||||
- **BUG fixato e deployato**: `/admin/leads/[id]` 500 per `params` non awaited (Next.js 16) → fix commit `ea20685`, confermato live in prod (container `857af5c1...`).
|
|
||||||
- Decisioni strutturali (Preventivo→Servizi Attivi, Forecast→Dashboard, CRM Attio-style, compartimenti stagni) sono ora formalizzate in PROJECT.md/REQUIREMENTS.md/ROADMAP.md — vedi sezione 2026-06-13 sopra.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2026-06-11 (sera) — Phase 10 redo COMPLETATO, root cause risolta (storico)
|
|
||||||
|
|
||||||
- **Root cause del crash post-deploy Phase 10**: il DB prod non aveva NESSUNA migration dopo la 0000 (mancavano `services`, `leads`, `offer_phases`, `quotes`…). Catalogo e quote già rotti prima di Phase 10; il deploy Phase 10 ha aggiunto il crash dashboard (FollowUpWidget→leads). NON era un problema di piattaforma (l'app è Gitea→Coolify, non Vercel).
|
|
||||||
- **Fix**: migrations 0001+0003+0004+0005 applicate atomicamente al DB prod via `ssh root@178.104.27.55` → `docker exec -i xwkk0040w0kk0gsgcgog8owk psql` (porta 54321 firewallata dall'esterno, si passa da SSH). Dati protetti verificati intatti (4 clients / 5 projects / 13 payments / 6 phases).
|
|
||||||
- **Redo Phase 10 deployato**: commit `5aa6614` (deps+UI primitives) e `008a434` (modulo CRM completo). Utente conferma pagine visibili in prod.
|
|
||||||
- **REGOLA**: le migration qui sono manuali — applicare al DB prod PRIMA di pushare codice che usa il nuovo schema. Pattern: `cat migration.sql | ssh root@178.104.27.55 "docker exec -i xwkk0040w0kk0gsgcgog8owk sh -c 'psql -U \$POSTGRES_USER -d clienthub -v ON_ERROR_STOP=1 --single-transaction'"` (verificare prima che sia additive-only).
|
|
||||||
- Branch `phase10-wip` (= `8e2752a`) cancellabile quando il redo è considerato definitivo. Dangling ancora recuperabile: `5d75752` (sidebar App shortcuts).
|
|
||||||
- Script riusabile: `scripts/push-phase10-migration.ts` (solo dal server o con tunnel).
|
|
||||||
@@ -1,5 +1,40 @@
|
|||||||
# Milestones
|
# Milestones
|
||||||
|
|
||||||
|
## v2.4 Post-vendita (Phases 13 + 26, in corso)
|
||||||
|
|
||||||
|
**Consegnato:** 2 fasi, entrambe in produzione e verificate.
|
||||||
|
|
||||||
|
**Key accomplishments:**
|
||||||
|
|
||||||
|
- Ciclo di vita dei servizi ricorrenti (Phase 13, prod 2026-08-01): `project_offers.status` (attivo/sospeso/cessato) + `end_date` via migr. 0016; il forecast a 12 mesi smette di sommare un retainer fermo; comandi Sospendi/Riattiva/Cessa nella tab Offerte; il cliente vede stato, "attivo dal / fino al" e canone mensile (RET-01..05)
|
||||||
|
- Anteprima admin del portale + toggle password sul login (Phase 26, prod 2026-08-08): `?preview=1` con sessione Auth.js valida apre il portale di un cliente in sola lettura, senza passare dal gate OTP (PREV-01/02, AUTH-09)
|
||||||
|
|
||||||
|
**Deviazione registrata:** vincolo LOCKED #4 — una route `/client/*` ora legge anche la sessione Auth.js (Phase 26).
|
||||||
|
|
||||||
|
**Aperto:** RET-06 (canoni mensili tracciabili), più il backlog ereditato. Vedi `REQUIREMENTS.md`.
|
||||||
|
|
||||||
|
Fasi: [13-ciclo-vita-servizi-ricorrenti](phases/13-ciclo-vita-servizi-ricorrenti/13-SUMMARY.md) · [26-anteprima-admin-e-login](phases/26-anteprima-admin-e-login/26-SUMMARY.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v2.3 Email & Accesso (Phases 23–25, shipped 2026-07-29)
|
||||||
|
|
||||||
|
**Phases completed:** 3 fasi (23–25) · eseguite fuori dal ciclo GSD (nessun PLAN/SUMMARY per fase)
|
||||||
|
|
||||||
|
**Key accomplishments:**
|
||||||
|
|
||||||
|
- Resend Setup (Phase 23): `resend@6.18.1`, `src/lib/mailer.ts` con Result tipizzato, template OTP in italiano, env configurate su Coolify
|
||||||
|
- Schema + Whitelist Admin (Phase 24): migr. 0015 additiva pura applicata a prod — `client_emails`, `otp_codes`, `clients.sessions_valid_from`; sezione "Accessi al portale" in `/admin/clients/[id]` (OTP-01, OTP-08)
|
||||||
|
- OTP Gate + Sessione (Phase 25): codice 6 cifre CSPRNG hashato, TTL 15 min, monouso, max 5 tentativi; cookie HMAC per-cliente, 90 giorni; rate limiting e no-enumeration (OTP-02..07)
|
||||||
|
|
||||||
|
**Verificata end-to-end in produzione** su `hub.iamcavalli.net` il 2026-07-29: senza cookie il gate non lascia trapelare **nessun dato di progetto** nell'HTML.
|
||||||
|
|
||||||
|
**Known deferred items at close:** SEND-01/SEND-02 (invio preventivo via email) — rinviati, il mailer resta comunque in prod.
|
||||||
|
|
||||||
|
Archive: [`milestones/v2.3-ROADMAP.md`](milestones/v2.3-ROADMAP.md) · [`milestones/v2.3-REQUIREMENTS.md`](milestones/v2.3-REQUIREMENTS.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v2.2 Sales Loop (Phases 18–22, shipped 2026-06-20)
|
## v2.2 Sales Loop (Phases 18–22, shipped 2026-06-20)
|
||||||
|
|
||||||
**Phases completed:** 5 phases (18–22) · 9 plans · 27 commits · 87 files · +7.349/-842 righe
|
**Phases completed:** 5 phases (18–22) · 9 plans · 27 commits · 87 files · +7.349/-842 righe
|
||||||
@@ -28,6 +63,10 @@ Archive: `.planning/milestones/v2.2-ROADMAP.md` · `.planning/milestones/v2.2-RE
|
|||||||
- Offer Editor Tier A/B/C (Phase 12): editor offerte con matrice checkbox servizi×tier, totale live, prezzo pubblico manuale, tag 4-dimensioni, promessa di trasformazione; 55 servizi reali caricati (OFFER-11, OFFER-15..18)
|
- Offer Editor Tier A/B/C (Phase 12): editor offerte con matrice checkbox servizi×tier, totale live, prezzo pubblico manuale, tag 4-dimensioni, promessa di trasformazione; 55 servizi reali caricati (OFFER-11, OFFER-15..18)
|
||||||
- CRM Attio-style (Phase 14): `/admin/leads` ridisegnata con inline edit + tag multi-select; FollowUpWidget in italiano; LeadForm tipizzato; SendQuoteModal senza rami irraggiungibili (CRM-08..12)
|
- CRM Attio-style (Phase 14): `/admin/leads` ridisegnata con inline edit + tag multi-select; FollowUpWidget in italiano; LeadForm tipizzato; SendQuoteModal senza rami irraggiungibili (CRM-08..12)
|
||||||
|
|
||||||
|
Phase 13 è poi tornata in vita come milestone v2.4, consegnata il 2026-08-01.
|
||||||
|
|
||||||
|
Archive: [`milestones/v2.1-ROADMAP.md`](milestones/v2.1-ROADMAP.md) (ricostruito il 2026-08-08) · fasi in [`milestones/v2.1-phases/`](milestones/v2.1-phases/)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## v2.0 Business Operations Suite (Phases 7–10, completato 2026-06-13)
|
## v2.0 Business Operations Suite (Phases 7–10, completato 2026-06-13)
|
||||||
|
|||||||
+29
-15
@@ -8,15 +8,16 @@ Suite operativa per un consulente di personal branding, live su hub.iamcavalli.n
|
|||||||
|
|
||||||
Il cliente apre il link e vede esattamente a che punto è il suo progetto, cosa deve ancora succedere e cosa ha già approvato — senza dover scrivere email per chiedere aggiornamenti.
|
Il cliente apre il link e vede esattamente a che punto è il suo progetto, cosa deve ancora succedere e cosa ha già approvato — senza dover scrivere email per chiedere aggiornamenti.
|
||||||
|
|
||||||
## Current Milestone: v2.3 Email & Accesso
|
## Current Milestone: v2.4 Post-vendita
|
||||||
|
|
||||||
**Goal:** Aggiungere uno strato email all'app — OTP gate per il portale cliente e invio link preventivo dall'admin — con un'unica integrazione Resend condivisa.
|
**Goal:** Chiudere il ciclo di vita di ciò che è già venduto — un retainer deve poter finire, e l'admin deve poter vedere il portale con gli occhi del cliente.
|
||||||
|
|
||||||
**Target features:**
|
**Consegnato (in produzione):**
|
||||||
- AUTH-OTP-01 — OTP gate portale cliente (whitelist email + sessione 30gg + admin UI)
|
|
||||||
- PUB-03 — Invio link `/preventivo/[slug]` via email Resend dall'admin
|
|
||||||
|
|
||||||
**Backlog v2.4+:** PROP-03 (Stripe Payment Link), PROP-04 (auto-provisioning al "Vinto"), Phase 13 (servizi ricorrenti)
|
- Phase 13 — Ciclo di vita dei servizi ricorrenti (RET-01..05), prod 2026-08-01
|
||||||
|
- Phase 26 — Anteprima admin del portale + toggle password sul login (PREV-01/02, AUTH-09), prod 2026-08-08
|
||||||
|
|
||||||
|
**Backlog:** RET-06 (canoni mensili tracciabili), SEND-01/02 (invio preventivo via email), PROP-03 (Stripe Payment Link), PROP-04 (auto-provisioning al "Vinto"), DEBT-01 (debito design). Elenco completo in `REQUIREMENTS.md`.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -57,10 +58,19 @@ Validated in v2.2 Sales Loop (shipped 2026-06-20):
|
|||||||
- ✓ Agente AI: Claude Opus 4.8, Zod schema 20+ sezioni, snapshot JSONB proposals, form admin — Phase 21 (AI-01, AI-02)
|
- ✓ Agente AI: Claude Opus 4.8, Zod schema 20+ sezioni, snapshot JSONB proposals, form admin — Phase 21 (AI-01, AI-02)
|
||||||
- ✓ Deck pubblico `/preventivo/[slug]`: 20+ slide 100vh, keyboard nav; accept/reject `accepted_at` immutabile — Phase 22 (PUB-01, PUB-02)
|
- ✓ Deck pubblico `/preventivo/[slug]`: 20+ slide 100vh, keyboard nav; accept/reject `accepted_at` immutabile — Phase 22 (PUB-01, PUB-02)
|
||||||
|
|
||||||
### Active — v2.3
|
Validated in v2.3 Email & Accesso (shipped 2026-07-29):
|
||||||
|
|
||||||
- [ ] AUTH-OTP-01 — Accesso cliente via OTP email: whitelist `client_emails`, gate `/client/[token]/*`, sessione 30gg, admin whitelist UI
|
- ✓ Gate OTP sul portale cliente: whitelist `client_emails`, codice 6 cifre via Resend, sessione firmata **90 giorni** (non 30: modificata il 2026-07-28), revoca in blocco dall'admin — Phase 23/24/25 (OTP-01..08). Migr. 0015.
|
||||||
- [ ] PUB-03 — Invia link `/preventivo/[slug]` via email Resend dall'admin
|
- ✗ PUB-03 / SEND-01/02 (invio preventivo via email) **non consegnato**: rinviato al backlog il 2026-07-28. Il preventivo si manda a mano; l'infrastruttura Resend è comunque in prod.
|
||||||
|
|
||||||
|
Validated in v2.4 Post-vendita (in produzione):
|
||||||
|
|
||||||
|
- ✓ Ciclo di vita dei servizi ricorrenti: `project_offers.status` + `end_date`, forecast che si ferma, comandi Sospendi/Riattiva/Cessa, stato visibile al cliente — Phase 13 (RET-01..05). Migr. 0016.
|
||||||
|
- ✓ Anteprima admin in sola lettura del portale cliente + toggle password sul login — Phase 26 (PREV-01/02, AUTH-09).
|
||||||
|
|
||||||
|
### Active
|
||||||
|
|
||||||
|
Nessun requisito in lavorazione. Il prossimo va scelto dal backlog in `REQUIREMENTS.md`.
|
||||||
|
|
||||||
### Out of Scope
|
### Out of Scope
|
||||||
|
|
||||||
@@ -79,8 +89,8 @@ Validated in v2.2 Sales Loop (shipped 2026-06-20):
|
|||||||
- Tutto sotto la stessa app: `/admin/*` (sessione Auth.js) + `/client/[token]/*` (token) + `/preventivo/[slug]` (pubblico)
|
- Tutto sotto la stessa app: `/admin/*` (sessione Auth.js) + `/client/[token]/*` (token) + `/preventivo/[slug]` (pubblico)
|
||||||
- La sidebar admin include: Dashboard, Leads (con toggle Lista/Kanban), Offerte, Catalogo, Preventivi (con CTA globale "Genera preventivo")
|
- La sidebar admin include: Dashboard, Leads (con toggle Lista/Kanban), Offerte, Catalogo, Preventivi (con CTA globale "Genera preventivo")
|
||||||
- Stack v2.2: `@anthropic-ai/sdk@0.105.0` (Claude Opus 4.8), `@dnd-kit` (Kanban), `nanoid` (slug proposals)
|
- Stack v2.2: `@anthropic-ai/sdk@0.105.0` (Claude Opus 4.8), `@dnd-kit` (Kanban), `nanoid` (slug proposals)
|
||||||
- DB live: 10 migrazioni applicate a prod (0000–0010); `proposals` table con `content jsonb` snapshot; `client_transcripts` per lead
|
- DB live: migrazioni applicate a prod fino alla **0016**; `proposals` con `content jsonb` snapshot; `client_transcripts` per lead; `client_emails`/`otp_codes` per il gate OTP
|
||||||
- Migrations sono manuali: SSH tunnel → `node` script PRIMA di pushare codice schema-dipendente; `drizzle-kit generate` rotto da Phase 8
|
- Migrations sono manuali: SQL a mano applicato via **SSH + docker exec** PRIMA di pushare il codice schema-dipendente (procedura in `CLAUDE.md`); `drizzle-kit generate` rotto da Phase 8
|
||||||
- `ANTHROPIC_API_KEY` in Coolify — aggiunta 2026-06-20 via PHP artisan; costo ~$0.44/preventivo (Opus 4.8)
|
- `ANTHROPIC_API_KEY` in Coolify — aggiunta 2026-06-20 via PHP artisan; costo ~$0.44/preventivo (Opus 4.8)
|
||||||
- Il flusso commerciale reale: call con lead → transcript incollato → genera preventivo AI → deck pubblica → cliente sceglie tier → vinto/perso
|
- Il flusso commerciale reale: call con lead → transcript incollato → genera preventivo AI → deck pubblica → cliente sceglie tier → vinto/perso
|
||||||
|
|
||||||
@@ -90,7 +100,7 @@ Validated in v2.2 Sales Loop (shipped 2026-06-20):
|
|||||||
- **Architettura (LOCKED)**: `clients.token` separato e rotatable; `quote_items` mai esposti via client API; `deliverables.approved_at` immutabile; no file hosting
|
- **Architettura (LOCKED)**: `clients.token` separato e rotatable; `quote_items` mai esposti via client API; `deliverables.approved_at` immutabile; no file hosting
|
||||||
- **Compartimenti stagni**: un'unica app Next.js, moduli isolati (route group + service layer propri) su Postgres condiviso; migrations solo additive; niente deploy separati per ora (modello OMC adattato)
|
- **Compartimenti stagni**: un'unica app Next.js, moduli isolati (route group + service layer propri) su Postgres condiviso; migrations solo additive; niente deploy separati per ora (modello OMC adattato)
|
||||||
- **NO database esterno / Excel come fonte dati**: Postgres resta l'unica fonte di verità — il problema è la UX, non il dato
|
- **NO database esterno / Excel come fonte dati**: Postgres resta l'unica fonte di verità — il problema è la UX, non il dato
|
||||||
- **Numerazione fasi**: v2.0 ha chiuso a Phase 10; v2.1 parte da Phase 11
|
- **Numerazione fasi**: progressiva e mai riusata — v1.0 1–6, v2.0 7–10, v2.1 11–17 (13/15/16/17 mai eseguite), v2.2 18–22, v2.3 23–25, v2.4 13 (ripresa dal congelamento) + 26
|
||||||
|
|
||||||
## Key Decisions
|
## Key Decisions
|
||||||
|
|
||||||
@@ -103,14 +113,18 @@ Validated in v2.2 Sales Loop (shipped 2026-06-20):
|
|||||||
| Catalogo servizi unificato (una tabella `services`) | Due cataloghi paralleli (service_catalog + offer_services) duplicano manutenzione prezzi | ✓ Good — tabella `services` live da Phase 7, consolidamento legacy in v2.1 |
|
| Catalogo servizi unificato (una tabella `services`) | Due cataloghi paralleli (service_catalog + offer_services) duplicano manutenzione prezzi | ✓ Good — tabella `services` live da Phase 7, consolidamento legacy in v2.1 |
|
||||||
| Tier offerte indipendenti (A/B/C separati, stesso tag) | Più semplice di un meccanismo di ereditarietà; ogni tier configurato a sé | ✓ Good — usato in deck slide Pricing/StagesRecap/Comparison |
|
| Tier offerte indipendenti (A/B/C separati, stesso tag) | Più semplice di un meccanismo di ereditarietà; ogni tier configurato a sé | ✓ Good — usato in deck slide Pricing/StagesRecap/Comparison |
|
||||||
| Prezzi pacchetti per-preventivo, non da catalogo | Permette di alzare i prezzi nel tempo senza toccare il catalogo | ✓ Good — `public_price` per tier, snapshot in `proposals.content` |
|
| Prezzi pacchetti per-preventivo, non da catalogo | Permette di alzare i prezzi nel tempo senza toccare il catalogo | ✓ Good — `public_price` per tier, snapshot in `proposals.content` |
|
||||||
| Al "Vinto" le fasi dell'offerta sono COPIATE nel progetto | Il progetto resta modificabile senza toccare il template offerta | — Pending (PROP-04, backlog v2.3) |
|
| Al "Vinto" le fasi dell'offerta sono COPIATE nel progetto | Il progetto resta modificabile senza toccare il template offerta | — Pending (PROP-04, backlog) |
|
||||||
| NO DB esterno/Excel, Postgres unica fonte | Lezione 2026-06-11: due fonti disallineate hanno causato il crash Phase 10 | ✓ Good |
|
| NO DB esterno/Excel, Postgres unica fonte | Lezione 2026-06-11: due fonti disallineate hanno causato il crash Phase 10 | ✓ Good |
|
||||||
| Catalogo/Offerte UX = database view custom (non Notion-clone) | Notion troppo complesso per v1; serve velocità, non sezioni analitiche | ✓ Good — confermato in v2.1 |
|
| Catalogo/Offerte UX = database view custom (non Notion-clone) | Notion troppo complesso per v1; serve velocità, non sezioni analitiche | ✓ Good — confermato in v2.1 |
|
||||||
| Tab "Preventivo" rimossa, "Offerte" → "Servizi attivi" | Preventivo Builder è l'unico flusso; `accepted_total` già coperto da Payments | ✓ Confermato — zero perdita funzionale verificata (2026-06-13) |
|
| Tab "Preventivo" rimossa, "Offerte" → "Servizi attivi" | Preventivo Builder è l'unico flusso; `accepted_total` già coperto da Payments | ✓ Confermato — zero perdita funzionale verificata (2026-06-13) |
|
||||||
| Ordine: Offer Studio (UX dato) prima, Proposal AI (AI) dopo | L'AI è l'ultimo miglio, serve un dato pulito e veloce da gestire prima | ✓ Good — strategia validata: catalogo+offerte puliti → AI in v2.2 |
|
| Ordine: Offer Studio (UX dato) prima, Proposal AI (AI) dopo | L'AI è l'ultimo miglio, serve un dato pulito e veloce da gestire prima | ✓ Good — strategia validata: catalogo+offerte puliti → AI in v2.2 |
|
||||||
| Output AI = JSON strutturato Zod → template fisso | Coerenza visiva garantita; zero rischio HTML rotto dall'AI | ✓ Good — 20+ sezioni Zod validate, deck sempre coerente |
|
| Output AI = JSON strutturato Zod → template fisso | Coerenza visiva garantita; zero rischio HTML rotto dall'AI | ✓ Good — 20+ sezioni Zod validate, deck sempre coerente |
|
||||||
| `proposals.content` = JSONB snapshot immutabile | Prezzi e profilo consulente "bloccati" al momento della generazione | ✓ Good — invariante di audit, coerente con `accepted_at` |
|
| `proposals.content` = JSONB snapshot immutabile | Prezzi e profilo consulente "bloccati" al momento della generazione | ✓ Good — invariante di audit, coerente con `accepted_at` |
|
||||||
| Email Resend (PUB-03) deferred | Scope minimo funziona; link condiviso manualmente per ora | — Pending (v2.3 candidato #1) |
|
| Email Resend (PUB-03) deferred | Scope minimo funziona; link condiviso manualmente per ora | — Pending — rinviata di nuovo il 2026-07-28, il mailer però è in prod |
|
||||||
|
| Il gate OTP sta in cima alla `page`, mai nel layout | Nell'App Router il `page` è renderizzato in parallelo al layout: gattare nel layout lascia i dati nel payload RSC | ✓ Good — verificato: 46.907 → 17.594 byte di HTML |
|
||||||
|
| Sessione OTP a 90 giorni invece di 30 | Rientro più fluido per il cliente, compensato dalla revoca in blocco lato admin (OTP-08) | ✓ Good — in prod dal 2026-07-29 |
|
||||||
|
| Storico di vendita ≠ forecast | `getOffersSoldBreakdown` non filtra per stato: escludere le offerte cessate riscriverebbe il fatturato passato | ✓ Good — Phase 13 |
|
||||||
|
| Anteprima admin del portale in sola lettura | Le API client autenticano sul token nel body, non sulla sessione: un click distratto approverebbe un deliverable, e `approved_at` è immutabile (LOCKED #3) | ✓ Good — protezione a livello UI, deviazione da LOCKED #4 accettata (Phase 26) |
|
||||||
|
|
||||||
## Evolution
|
## Evolution
|
||||||
|
|
||||||
@@ -130,4 +144,4 @@ This document evolves at phase transitions and milestone boundaries.
|
|||||||
4. Update Context with current state
|
4. Update Context with current state
|
||||||
|
|
||||||
---
|
---
|
||||||
*Last updated: 2026-06-21 — v2.3 milestone started: Email & Accesso (AUTH-OTP-01 + PUB-03)*
|
*Last updated: 2026-08-08 — v2.3 archiviata, v2.4 Post-vendita corrente (Phase 13 + 26 in produzione)*
|
||||||
|
|||||||
+32
-54
@@ -1,69 +1,47 @@
|
|||||||
# Requirements: ClientHub v2.3 Email & Accesso
|
# Requirements: ClientHub v2.4 Post-vendita
|
||||||
|
|
||||||
**Defined:** 2026-06-21
|
**Definiti:** 2026-08-08 (ricostruiti a posteriori — v2.4 è partita senza requisiti scritti)
|
||||||
**Core Value:** Il cliente apre il link e vede esattamente a che punto è il suo progetto, cosa deve ancora succedere e cosa ha già approvato — senza dover scrivere email per chiedere aggiornamenti.
|
**Core Value:** Il cliente apre il link e vede esattamente a che punto è il suo progetto, cosa deve ancora succedere e cosa ha già approvato — senza dover scrivere email per chiedere aggiornamenti.
|
||||||
|
|
||||||
## v2.3 Requirements
|
Milestone precedente: [v2.3 Email & Accesso](milestones/v2.3-ROADMAP.md), shipped 2026-07-29.
|
||||||
|
|
||||||
### Email OTP Gate (AUTH-OTP-01)
|
## Consegnati
|
||||||
|
|
||||||
Portale cliente blindato da email OTP. Nuovo `client_emails` table (whitelist) + `otp_codes` table (codice, email, expires_at, consumed). Resend come provider email. Sessione **90 giorni** con cookie dopo verifica, revocabile dall'admin.
|
### Ciclo di vita dei servizi ricorrenti (Phase 13) — ✅ in produzione 2026-08-01
|
||||||
|
|
||||||
- [x] **OTP-01**: Admin può aggiungere e rimuovere email dalla whitelist di ogni cliente nell'admin UI
|
- [x] **RET-01**: Un'offerta ricorrente assegnata a un progetto ha uno stato (attivo / sospeso / cessato) e una data di fine opzionale
|
||||||
- [x] **OTP-02**: Cliente senza sessione OTP vede una schermata "inserisci email" invece della dashboard
|
- [x] **RET-02**: L'admin può sospendere, riattivare e cessare un retainer dalla tab Offerte del progetto
|
||||||
- [x] **OTP-03**: Sistema invia OTP via Resend solo se l'email inserita è nella whitelist di quel cliente
|
- [x] **RET-03**: Il forecast a 12 mesi smette di sommare un retainer sospeso, cessato o oltre la sua `end_date`
|
||||||
- [x] **OTP-04**: Cliente inserisce il codice OTP ricevuto e ottiene sessione autenticata (cookie **90 giorni**)
|
- [x] **RET-04**: Lo storico del venduto (`getOffersSoldBreakdown`) **non** filtra per stato — escludere le cessate riscriverebbe il passato
|
||||||
- [x] **OTP-05**: Codici OTP scadono dopo 15 minuti dall'invio
|
- [x] **RET-05**: Il cliente vede stato, "attivo dal / fino al" e "canone mensile"; le offerte cessate non gli arrivano
|
||||||
- [x] **OTP-06**: Endpoint OTP è rate-limited per prevenire brute force
|
|
||||||
- [x] **OTP-07**: Messaggi di errore OTP non rivelano se l'email è in whitelist o no (no enumeration)
|
|
||||||
- [x] **OTP-08**: Admin può revocare in blocco tutte le sessioni attive di un cliente
|
|
||||||
|
|
||||||
> **[2026-07-28] Modifiche alla spec del 2026-06-21**, decise in sessione:
|
### Anteprima admin e login (Phase 26) — ✅ in produzione 2026-08-08
|
||||||
> - Sessione **90 giorni** invece di 30 (rientro più fluido), compensata da OTP-08.
|
|
||||||
> - **SEND-01/SEND-02 spostati al backlog v2.4**: il preventivo si invia a mano, l'automazione non serve ora. Phase 23 si è ridotta alla sola infrastruttura Resend, che l'OTP usa comunque.
|
|
||||||
> - **Il gate NON sta nel layout** ma in cima a ogni page sotto `/client/[token]/`. Nell'App Router il segmento `page` viene renderizzato in parallelo al layout: gattare nel layout nascondeva la dashboard a schermo ma lasciava fasi, task e pagamenti nel payload RSC dell'HTML (verificato: 46.907 byte con i dati → 17.594 dopo il fix). Helper: `src/lib/client-gate.ts`.
|
|
||||||
|
|
||||||
## v2.4+ Backlog
|
- [x] **PREV-01**: L'admin può aprire il portale di un cliente in sola lettura senza passare dal gate OTP (`?preview=1` + sessione Auth.js valida)
|
||||||
|
- [x] **PREV-02**: In anteprima approvazione e composer messaggi sono disattivati a livello di UI
|
||||||
|
- [x] **AUTH-09**: Il campo password del login admin ha un toggle mostra/nascondi
|
||||||
|
|
||||||
### Conversione Commerciale
|
## Backlog v2.4+ (non pianificati)
|
||||||
|
|
||||||
- **PROP-03**: Stripe Payment Link su deck pubblico `/preventivo/[slug]`
|
Ereditati dalle chiusure di milestone precedenti, nessuno in corso:
|
||||||
- **PROP-04**: Auto-provisioning cliente/progetto/fasi al "Vinto" nel CRM
|
|
||||||
- **SEND-01/SEND-02**: invio del link `/preventivo/[slug]` via email dall'admin UI — *rinviato da v2.3 il 2026-07-28, l'invio si fa a mano. L'infrastruttura Resend (`src/lib/mailer.ts`) è già pronta, manca solo l'azione e il pulsante.*
|
|
||||||
|
|
||||||
### Post-Vendita
|
- [ ] **SEND-01 / SEND-02** — Invio del link `/preventivo/[slug]` via email dall'admin. Il mailer (`src/lib/mailer.ts`) è già pronto e in produzione dalla v2.3: manca solo il pulsante e l'action. *Rinviati il 2026-07-28.*
|
||||||
|
- [ ] **PROP-03** — Stripe Payment Link sul deck pubblico del preventivo. *Rinviato al kickoff v2.3.*
|
||||||
|
- [ ] **PROP-04** — Auto-provisioning di cliente / progetto / fasi al passaggio del lead a "Vinto". *Rinviato al kickoff v2.3.*
|
||||||
|
- [ ] **RET-06** — Canoni mensili tracciabili (agosto pagato / settembre no). **Serve una tabella nuova**: `payments` è protetta dai vincoli di Data Safety e la sua riscalatura è pensata per i piani una tantum. *Fuori scope di Phase 13.*
|
||||||
|
- [ ] **OFFER-14** — Sezioni analitiche stile Notion sull'offerta. *Rinviato al kickoff v2.1.*
|
||||||
|
- [ ] **ARCH-01** — Split del modulo "compartimento stagno" in un deploy separato. *Solo se il modulo cresce.*
|
||||||
|
- [ ] **DEBT-01** — Debito design: **~40 file, ~450 occorrenze** di palette Tailwind raw e hex literal al posto dei token semantici. I cluster: `/admin/projects/[id]` e i suoi tab (~182), `/admin/offers/[id]/edit` (~79), `/admin/clients/[id]` (~59), tutto `/quote/[token]` (~48, ed è rivolto al cliente), `ChatPanel` del portale (37), più `ui/dialog.tsx` che propaga il look vecchio a ogni modale. Esclusi perché legittimi: `AdminSidebar` (eccezione brand documentata), `src/lib/mailer.ts` (HTML email, niente CSS var), i colori di stato di `StatusBadge` (sanzionati dal design system, hanno già le varianti `dark:`). *Misurato il 2026-08-08 — la stima precedente di "11 pagine" era sottostimata.*
|
||||||
|
- [ ] **DEBT-02** — Tabelle legacy `service_catalog` / `offer_services` / `offer_micro_services` come deadweight; `createService` / `serviceSchema` dead code in `src/app/admin/catalog/actions.ts`.
|
||||||
|
|
||||||
- **Phase 13**: Gestione servizi attivi/ricorrenti post-vendita nel portale cliente (congelata da v2.1)
|
## Aperto, non un requisito
|
||||||
|
|
||||||
## Out of Scope
|
**Whitelist del portale vuota per 3 clienti su 4.** La migration 0015 ha seedato solo
|
||||||
|
`mario@test.it` (cliente di test). Protocollo Estetico, Caruso Speaker e Teckell hanno
|
||||||
|
whitelist vuota e finché lo è **il loro portale non è accessibile**. Si popola da
|
||||||
|
`/admin/clients/<id>` → "Accessi al portale", poi va reinviato il link.
|
||||||
|
|
||||||
| Feature | Reason |
|
## Fuori scope
|
||||||
|---------|--------|
|
|
||||||
| Self-registration cliente | Solo whitelist admin-gestita — nessun accesso senza approvazione esplicita |
|
|
||||||
| Magic link senza OTP | OTP è più sicuro e già deciso come design; magic link = scope creep |
|
|
||||||
| Email marketing / newsletter | Non pertinente al portale |
|
|
||||||
| Multi-admin | Ancora single admin per ora |
|
|
||||||
|
|
||||||
## Traceability
|
- File hosting (vincolo LOCKED #5: i documenti restano URL esterni).
|
||||||
|
- Tabella utenti / multi-admin: l'auth resta una singola credenziale da env.
|
||||||
| Requirement | Phase | Status |
|
|
||||||
|-------------|-------|--------|
|
|
||||||
| OTP-01 | Phase 24 | ✅ Done (2026-07-28) |
|
|
||||||
| OTP-02 | Phase 25 | ✅ Done (2026-07-28) |
|
|
||||||
| OTP-03 | Phase 25 | ✅ Done (2026-07-28) |
|
|
||||||
| OTP-04 | Phase 25 | ✅ Done (2026-07-28) |
|
|
||||||
| OTP-05 | Phase 25 | ✅ Done (2026-07-28) |
|
|
||||||
| OTP-06 | Phase 25 | ✅ Done (2026-07-28) |
|
|
||||||
| OTP-07 | Phase 25 | ✅ Done (2026-07-28) |
|
|
||||||
| OTP-08 | Phase 24 | ✅ Done (2026-07-28) |
|
|
||||||
| SEND-01 | — | ⏭️ Rinviato a v2.4 |
|
|
||||||
| SEND-02 | — | ⏭️ Rinviato a v2.4 |
|
|
||||||
|
|
||||||
**Coverage:**
|
|
||||||
- v2.3 requirements: 8 in scope (OTP-01…08) + 2 rinviati
|
|
||||||
- Implementati: 8/8 ✓ — verificati con 9 test E2E in locale contro il DB di produzione
|
|
||||||
- **Non ancora in produzione**: il codice è scritto e testato ma NON pushato. Vedi i blocchi in `STATE.md`.
|
|
||||||
|
|
||||||
---
|
|
||||||
*Requirements defined: 2026-06-21*
|
|
||||||
*Last updated: 2026-07-28 — sessione 90gg, OTP-08 aggiunto, SEND-01/02 rinviati, OTP-01…08 implementati*
|
|
||||||
|
|||||||
+24
-96
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
- ✅ **v1.0 Client Portal & Offer System** — Phases 1–6 (shipped 2026-06-10) — [archive](milestones/v1.0-ROADMAP.md)
|
- ✅ **v1.0 Client Portal & Offer System** — Phases 1–6 (shipped 2026-06-10) — [archive](milestones/v1.0-ROADMAP.md)
|
||||||
- ✅ **v2.0 Business Operations Suite** — Phases 7–10 (shipped 2026-06-13) — [archive](milestones/v2.0-ROADMAP.md)
|
- ✅ **v2.0 Business Operations Suite** — Phases 7–10 (shipped 2026-06-13) — [archive](milestones/v2.0-ROADMAP.md)
|
||||||
- ✅ **v2.1 Offer Studio + CRM** — Phases 11–14 parziale (chiuso 2026-06-19, reset → v2.2)
|
- ✅ **v2.1 Offer Studio + CRM** — Phases 11, 12, 14 (chiusa per reset 2026-06-19) — [archive](milestones/v2.1-ROADMAP.md)
|
||||||
- ✅ **v2.2 Sales Loop** — Phases 18–22 (shipped 2026-06-20) — [archive](milestones/v2.2-ROADMAP.md)
|
- ✅ **v2.2 Sales Loop** — Phases 18–22 (shipped 2026-06-20) — [archive](milestones/v2.2-ROADMAP.md)
|
||||||
- ✅ **v2.3 Email & Accesso** — Phases 23–25 (shipped 2026-07-29)
|
- ✅ **v2.3 Email & Accesso** — Phases 23–25 (shipped 2026-07-29) — [archive](milestones/v2.3-ROADMAP.md)
|
||||||
- 🔨 **v2.4 Post-vendita** — Phase 13 (in corso)
|
- 🔨 **v2.4 Post-vendita** — Phases 13 + 26 (entrambe in produzione)
|
||||||
|
|
||||||
## Phases
|
## Phases
|
||||||
|
|
||||||
@@ -15,10 +15,10 @@
|
|||||||
<summary>✅ v1.0 + v2.0 + v2.1 (Phases 1–17) — SHIPPED / CHIUSE</summary>
|
<summary>✅ v1.0 + v2.0 + v2.1 (Phases 1–17) — SHIPPED / CHIUSE</summary>
|
||||||
|
|
||||||
Vedi archivi:
|
Vedi archivi:
|
||||||
|
|
||||||
- `milestones/v1.0-ROADMAP.md` — Phases 1–6
|
- `milestones/v1.0-ROADMAP.md` — Phases 1–6
|
||||||
- `milestones/v2.0-ROADMAP.md` — Phases 7–10
|
- `milestones/v2.0-ROADMAP.md` — Phases 7–10
|
||||||
- Phases 11, 12, 14 — Offer Studio + CRM Attio (shipped in prod)
|
- `milestones/v2.1-ROADMAP.md` — Phases 11, 12, 14 shipped; 15/16/17 abbandonate o ri-scopate; **Phase 13 ripresa in v2.4**
|
||||||
- Phases 13, 15, 16, 17 — congelate/abbandonate/ri-scopate in v2.2
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -35,57 +35,27 @@ Archivio completo: [milestones/v2.2-ROADMAP.md](milestones/v2.2-ROADMAP.md)
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### 🔨 v2.3 — Email & Accesso (Phases 23–25)
|
<details>
|
||||||
|
<summary>✅ v2.3 Email & Accesso (Phases 23–25) — SHIPPED 2026-07-29</summary>
|
||||||
|
|
||||||
- [x] **Phase 23: Resend Setup** — SDK Resend + `src/lib/mailer.ts` + template OTP *(l'invio preventivo è stato rinviato a v2.4 il 2026-07-28)*
|
- [x] Phase 23: Resend Setup — SDK + `src/lib/mailer.ts` + template OTP *(SEND-01/02 rinviati al backlog il 2026-07-28)*
|
||||||
- [x] **Phase 24: Schema + Whitelist Admin** — Tabelle `client_emails` e `otp_codes`, admin UI gestione whitelist + revoca sessioni
|
- [x] Phase 24: Schema + Whitelist Admin — `client_emails`, `otp_codes`, UI whitelist + revoca sessioni (migr. 0015)
|
||||||
- [x] **Phase 25: OTP Gate + Sessione** — Gate OTP completo, sessione **90gg**, rate limiting, no enumeration
|
- [x] Phase 25: OTP Gate + Sessione — gate completo, sessione **90gg**, rate limiting, no enumeration
|
||||||
|
|
||||||
✅ **Shipped 2026-07-29** (commit `27da969`), verificata end-to-end su `hub.iamcavalli.net`. SEND-01/02 rinviati a backlog.
|
Shipped col commit `27da969`, verificata end-to-end su `hub.iamcavalli.net`.
|
||||||
|
Archivio completo: [milestones/v2.3-ROADMAP.md](milestones/v2.3-ROADMAP.md)
|
||||||
|
|
||||||
### 🔨 v2.4 — Post-vendita (Phase 13)
|
</details>
|
||||||
|
|
||||||
- [ ] **Phase 13: Ciclo di vita dei servizi ricorrenti** — `project_offers.status` + `end_date` (migr. 0016), forecast che si ferma davvero, comandi Sospendi/Riattiva/Cessa nella tab Offerte, stato dell'abbonamento visibile al cliente.
|
### 🔨 v2.4 — Post-vendita (Phases 13 + 26)
|
||||||
|
|
||||||
Fuori scope di questo giro, rimandato: tracciamento dei canoni mese per mese (serve una tabella nuova — `payments` è protetta e la sua riscalatura è pensata per i piani una tantum).
|
- [x] **Phase 13: Ciclo di vita dei servizi ricorrenti** — `project_offers.status` + `end_date` (migr. 0016), forecast che si ferma davvero, comandi Sospendi/Riattiva/Cessa nella tab Offerte, stato dell'abbonamento visibile al cliente. ✅ **prod 2026-08-01** (`5177a37`) — [13-SUMMARY.md](phases/13-ciclo-vita-servizi-ricorrenti/13-SUMMARY.md)
|
||||||
|
- [x] **Phase 26: Anteprima admin del portale + toggle password** — `?preview=1` con sessione Auth.js valida apre il portale di un cliente in sola lettura, senza gate OTP. ✅ **prod 2026-08-08** (`09a5b1f`, `187550f`) — [26-SUMMARY.md](phases/26-anteprima-admin-e-login/26-SUMMARY.md)
|
||||||
|
|
||||||
## Phase Details
|
**Nessuna fase aperta.** Il prossimo lavoro va scelto dal backlog in
|
||||||
|
[REQUIREMENTS.md](REQUIREMENTS.md) — i candidati principali sono RET-06 (canoni
|
||||||
### Phase 23: Resend Setup + Invio Preventivo
|
mensili tracciabili, richiede una tabella nuova perché `payments` è protetta) e
|
||||||
**Goal**: Admin può inviare il link `/preventivo/[slug]` via email con un click dall'admin UI
|
DEBT-01 (debito design, ~40 file).
|
||||||
**Depends on**: Nothing — primo uso di Resend, nessuna dipendenza DB
|
|
||||||
**Requirements**: SEND-01, SEND-02
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
1. Admin fa click su "Invia preventivo" nel dettaglio lead/preventivo e l'email parte senza uscire dall'app
|
|
||||||
2. Il destinatario riceve un'email in italiano con nome cliente e link cliccabile al deck pubblico
|
|
||||||
3. L'invio usa Resend con le variabili d'ambiente configurate su Coolify (`RESEND_API_KEY`, `RESEND_FROM`)
|
|
||||||
4. In caso di errore Resend, l'admin vede un messaggio di errore chiaro nell'UI (non un crash silenzioso)
|
|
||||||
**Plans**: TBD
|
|
||||||
**UI hint**: yes
|
|
||||||
|
|
||||||
### Phase 24: Schema + Whitelist Admin
|
|
||||||
**Goal**: Admin può gestire la whitelist email di ogni cliente, con le tabelle DB pronte per l'OTP gate
|
|
||||||
**Depends on**: Phase 23 (Resend SDK già installato e variabili d'ambiente configurate)
|
|
||||||
**Requirements**: OTP-01
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
1. Admin può aggiungere una o più email alla whitelist di un cliente dalla pagina dettaglio cliente
|
|
||||||
2. Admin può rimuovere un'email dalla whitelist di un cliente
|
|
||||||
3. Le tabelle `client_emails` e `otp_codes` esistono in produzione (migration additive applicata via SSH prima del codice)
|
|
||||||
4. La migration non tocca nessuna delle tabelle protette (`clients`, `projects`, `payments`, `phases`)
|
|
||||||
**Plans**: TBD
|
|
||||||
**UI hint**: yes
|
|
||||||
|
|
||||||
### Phase 25: OTP Gate + Sessione
|
|
||||||
**Goal**: Il portale `/client/[token]/*` richiede verifica OTP email prima di mostrare la dashboard
|
|
||||||
**Depends on**: Phase 24 (tabelle `client_emails` e `otp_codes` in prod)
|
|
||||||
**Requirements**: OTP-02, OTP-03, OTP-04, OTP-05, OTP-06, OTP-07
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
1. Cliente senza sessione OTP valida vede una schermata "inserisci la tua email" al posto della dashboard
|
|
||||||
2. Inserita un'email in whitelist, il cliente riceve il codice OTP via Resend; inserendo il codice corretto ottiene accesso con cookie valido 30 giorni
|
|
||||||
3. Un'email non in whitelist non riceve OTP — il messaggio d'errore mostrato è identico a quello per email valide (no enumeration)
|
|
||||||
4. Un codice OTP non utilizzato entro 15 minuti viene rifiutato; il cliente deve richiederne uno nuovo
|
|
||||||
5. Tentativi ripetuti sugli endpoint OTP vengono bloccati dal rate limiter (no brute force)
|
|
||||||
**Plans**: TBD
|
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
|
|
||||||
@@ -95,10 +65,9 @@ Fuori scope di questo giro, rimandato: tracciamento dei canoni mese per mese (se
|
|||||||
| 7–10. Unified Catalog → CRM Pipeline | v2.0 | 12/12 | ✅ Done | 2026-06-13 |
|
| 7–10. Unified Catalog → CRM Pipeline | v2.0 | 12/12 | ✅ Done | 2026-06-13 |
|
||||||
| 11. Catalog Database-View UX | v2.1 | 4/4 | ✅ Done | 2026-06-13 |
|
| 11. Catalog Database-View UX | v2.1 | 4/4 | ✅ Done | 2026-06-13 |
|
||||||
| 12. Offer Editor Tier A/B/C | v2.1 | 5/5 | ✅ Done | 2026-06-18 |
|
| 12. Offer Editor Tier A/B/C | v2.1 | 5/5 | ✅ Done | 2026-06-18 |
|
||||||
| 13. Workspace Servizi Attivi | v2.1 | — | ❌ Congelata | — |
|
|
||||||
| 14. CRM Attio-style & Fix | v2.1 | 3/3 | ✅ Done | 2026-06-14 |
|
| 14. CRM Attio-style & Fix | v2.1 | 3/3 | ✅ Done | 2026-06-14 |
|
||||||
| 15. Dashboard Revenue Stats | v2.1 | — | ❌ Abbandonata | — |
|
| 15. Dashboard Revenue Stats | v2.1 | — | ❌ Abbandonata | — |
|
||||||
| 16–17. Proposal AI originale | v2.1 | — | ❌ Ri-scopata in v2.2 | — |
|
| 16–17. Proposal AI originale | v2.1 | — | ♻️ Ri-scopata in v2.2 | — |
|
||||||
| 18. Cleanup & Consolidamento | v2.2 | 3/3 | ✅ Done | 2026-06-19 |
|
| 18. Cleanup & Consolidamento | v2.2 | 3/3 | ✅ Done | 2026-06-19 |
|
||||||
| 19. Pipeline CRM Kanban | v2.2 | 1/1 | ✅ Done | 2026-06-19 |
|
| 19. Pipeline CRM Kanban | v2.2 | 1/1 | ✅ Done | 2026-06-19 |
|
||||||
| 20. Knowledge Base Cliente | v2.2 | 3/3 | ✅ Done | 2026-06-20 |
|
| 20. Knowledge Base Cliente | v2.2 | 3/3 | ✅ Done | 2026-06-20 |
|
||||||
@@ -107,50 +76,9 @@ Fuori scope di questo giro, rimandato: tracciamento dei canoni mese per mese (se
|
|||||||
| 23. Resend Setup | v2.3 | 1/1 | ✅ Done | 2026-07-28 |
|
| 23. Resend Setup | v2.3 | 1/1 | ✅ Done | 2026-07-28 |
|
||||||
| 24. Schema + Whitelist Admin | v2.3 | 1/1 | ✅ Done | 2026-07-28 |
|
| 24. Schema + Whitelist Admin | v2.3 | 1/1 | ✅ Done | 2026-07-28 |
|
||||||
| 25. OTP Gate + Sessione | v2.3 | 1/1 | ✅ Done | 2026-07-29 |
|
| 25. OTP Gate + Sessione | v2.3 | 1/1 | ✅ Done | 2026-07-29 |
|
||||||
| 13. Ciclo di vita servizi ricorrenti | v2.4 | 1/1 | 🔨 In corso | — |
|
| 13. Ciclo di vita servizi ricorrenti | v2.4 | 1/1 | ✅ Done | 2026-08-01 |
|
||||||
|
| 26. Anteprima admin + login | v2.4 | 1/1 | ✅ Done | 2026-08-08 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
*Roadmap aggiornata: 2026-08-08 — v2.3 archiviata, v2.4 documentata a posteriori*
|
||||||
|
|
||||||
## Requirement Coverage (v2.3)
|
|
||||||
|
|
||||||
| Requirement | Phase |
|
|
||||||
|-------------|-------|
|
|
||||||
| SEND-01 | Phase 23 |
|
|
||||||
| SEND-02 | Phase 23 |
|
|
||||||
| OTP-01 | Phase 24 |
|
|
||||||
| OTP-02 | Phase 25 |
|
|
||||||
| OTP-03 | Phase 25 |
|
|
||||||
| OTP-04 | Phase 25 |
|
|
||||||
| OTP-05 | Phase 25 |
|
|
||||||
| OTP-06 | Phase 25 |
|
|
||||||
| OTP-07 | Phase 25 |
|
|
||||||
|
|
||||||
**Mapped: 9/9. No orphans.**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Dependency Chain (v2.3)
|
|
||||||
|
|
||||||
```
|
|
||||||
Phase 23 (Resend config + SDK)
|
|
||||||
└── Phase 24 (schema DB additive: client_emails + otp_codes)
|
|
||||||
└── Phase 25 (OTP gate + sessione cookie 30gg)
|
|
||||||
```
|
|
||||||
|
|
||||||
Phase 23 first: Resend SDK e variabili d'ambiente sono infrastruttura condivisa con Phase 25 (email OTP).
|
|
||||||
Phase 24 before Phase 25: le tabelle `client_emails` e `otp_codes` devono essere in prod (via SSH migration) prima del gate.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Notes (v2.3)
|
|
||||||
|
|
||||||
**Migration constraint:** `client_emails` e `otp_codes` sono nuove tabelle — migration additiva pura. SQL a mano (drizzle-kit generate rotto da Phase 8). Applicare via SSH tunnel PRIMA di pushare il codice dipendente.
|
|
||||||
|
|
||||||
**OTP middleware layer:** Il token middleware esistente (proxy.ts → `/api/internal/validate-token`) rimane invariato. Il gate OTP è uno strato aggiuntivo dopo la validazione del token, non un suo rimpiazzo.
|
|
||||||
|
|
||||||
**Resend shared infra:** La stessa istanza Resend client e le stesse variabili d'ambiente (`RESEND_API_KEY`, `RESEND_FROM`) servono sia Phase 23 (email preventivo) sia Phase 25 (email OTP). Configurare una volta in Phase 23, riusare in Phase 25.
|
|
||||||
|
|
||||||
**Security invariants (Phase 25, come consegnata):** rate limiting su entrambi gli endpoint OTP; risposta identica per email in whitelist e non; OTP 6 cifre, scade 15 minuti, monouso (`consumed_at` al primo uso), max 5 tentativi; cookie HttpOnly + Secure + SameSite=Lax, **MaxAge 90 giorni** (non 30: modificato il 2026-07-28, compensato dalla revoca admin), per-cliente. Il gate sta in cima alla `page`, non nel layout — vedi la lezione in `STATE.md`.
|
|
||||||
|
|
||||||
---
|
|
||||||
*Roadmap created: 2026-06-21 — v2.3 Email & Accesso*
|
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
# UI Rules — ClientHub admin
|
|
||||||
|
|
||||||
Derived from the live codebase (`src/app/globals.css`, design system usage across admin pages).
|
|
||||||
These are the rules to follow for any new or modified admin page.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Brand palette
|
|
||||||
|
|
||||||
All colours must be written as **hex literals** — no Tailwind semantic tokens (`text-foreground`, `bg-muted`, etc.) in admin UI. Semantic tokens are defined in `globals.css` but mixing hex and tokens creates inconsistency.
|
|
||||||
|
|
||||||
| Role | Hex | Usage example |
|
|
||||||
|-------------------|-------------|-----------------------------------------|
|
|
||||||
| Primary | `#1A463C` | Primary buttons, active states, accents |
|
|
||||||
| Primary hover | `#163a31` | Hover on primary buttons |
|
|
||||||
| Accent | `#DEF168` | Brand highlights (use sparingly) |
|
|
||||||
| Foreground | `#1a1a1a` | Body text, headings |
|
|
||||||
| Muted text | `#71717a` | Secondary text, labels, meta |
|
|
||||||
| Border | `#e5e7eb` | Card borders, table dividers, inputs |
|
|
||||||
| Background muted | `#f9f9f9` | Table header rows, card hover bg |
|
|
||||||
| White | `#ffffff` | Card / panel backgrounds |
|
|
||||||
| Destructive | `#dc2626` | Delete/archive actions, error text |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Typographic scale
|
|
||||||
|
|
||||||
| Level | Classes |
|
|
||||||
|--------------------|----------------------------------------------|
|
|
||||||
| Page title (h1) | `text-2xl font-bold text-[#1a1a1a]` |
|
|
||||||
| Section heading | `text-base font-semibold text-[#1a1a1a]` |
|
|
||||||
| Subsection (h3) | `text-sm font-bold text-[#71717a] uppercase tracking-wider` |
|
|
||||||
| Body | `text-sm text-[#1a1a1a]` |
|
|
||||||
| Secondary/label | `text-xs text-[#71717a]` |
|
|
||||||
| Numeric values | always add `tabular-nums` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Layout
|
|
||||||
|
|
||||||
- **Page wrapper:** `<div className="space-y-6">` — uniform vertical rhythm, full-width.
|
|
||||||
- **Page header:** always use `<PageHeader>` from `src/components/admin/PageHeader.tsx` — never hand-roll the title + action row.
|
|
||||||
- **No width constraints on list pages:** do not add `max-w-*` or `mx-auto` to the page root. Width is controlled by the sidebar layout (`src/app/admin/layout.tsx`).
|
|
||||||
- **Detail/form pages** (e.g. OfferEditorClient) may keep their own `max-w-4xl mx-auto` — this rule applies to list/overview pages only.
|
|
||||||
- **Cards:** `rounded-lg border border-[#e5e7eb] bg-white p-4`; hover: `hover:shadow-[0_4px_12px_rgba(0,0,0,0.08)]`.
|
|
||||||
- **Tables:** `bg-white rounded-xl border border-[#e5e7eb] overflow-hidden`; thead `bg-[#f9f9f9] border-b border-[#e5e7eb]`; row divider `border-b border-[#e5e7eb]`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Interaction
|
|
||||||
|
|
||||||
- Clickable elements: `cursor-pointer`
|
|
||||||
- Colour transitions: `transition-colors duration-150`
|
|
||||||
- Focus ring: `focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1A463C]/30`
|
|
||||||
- Minimum touch target on buttons: 44 × 44 px (use `py-2 px-4` minimum or `h-10`)
|
|
||||||
- Primary CTA: `bg-[#1A463C] text-white hover:bg-[#163a31] transition-colors`
|
|
||||||
- Secondary/ghost CTA: `border border-[#e5e7eb] text-[#1a1a1a] hover:bg-[#f9f9f9] transition-colors`
|
|
||||||
- Destructive action: `text-[#dc2626] hover:bg-red-50 transition-colors`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Anti-patterns (do not do these)
|
|
||||||
|
|
||||||
- **No emoji as UI icons** — use Lucide React icons instead.
|
|
||||||
- **No mixed semantic tokens and hex** — pick hex throughout any given page/component.
|
|
||||||
- **No per-page `max-w-*` on list pages** — layout width is the sidebar shell's responsibility.
|
|
||||||
- **No hand-rolled page header divs** — always use `<PageHeader>` to keep title size/weight/colour uniform.
|
|
||||||
- **No inline `style={{}}` for colours that have a Tailwind class** — reserve `style` for dynamic values only (e.g. progress bar width percentages).
|
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Archivio milestone v2.1 — Offer Studio + CRM
|
||||||
|
|
||||||
|
**Fasi previste:** 11–17 · **Consegnate:** 11, 12, 14 · **Aperta:** 2026-06-13 · **Chiusa per reset:** 2026-06-19
|
||||||
|
|
||||||
|
> **Ricostruito a posteriori il 2026-08-08.** v2.1 è l'unica milestone rimasta senza
|
||||||
|
> archivio: è stata interrotta da un reset di scope e nessuno l'ha chiusa
|
||||||
|
> formalmente, così le sue fasi sono rimaste in `.planning/phases/` per due mesi.
|
||||||
|
> Questo file è ricostruito da `MILESTONES.md`, dalla tabella Progress di
|
||||||
|
> `ROADMAP.md` e dalle cartelle di fase archiviate in [v2.1-phases/](v2.1-phases/).
|
||||||
|
> Non esiste un `v2.1-REQUIREMENTS.md`: i 23 requisiti originali sono stati
|
||||||
|
> sovrascritti quando `REQUIREMENTS.md` è stato riscritto per v2.3.
|
||||||
|
|
||||||
|
## Obiettivo originale
|
||||||
|
|
||||||
|
Offer Studio (fasi 11–15) prima di Proposal AI (fasi 16–17): prima una UX dati
|
||||||
|
pulita e veloce, poi il builder AI costruito sopra.
|
||||||
|
|
||||||
|
## Esito per fase
|
||||||
|
|
||||||
|
| Fase | Titolo | Plans | Esito |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 11 | Catalog Database-View UX + consolidamento legacy | 4/4 | ✅ 2026-06-13 |
|
||||||
|
| 12 | Offer Editor Tier A/B/C | 5/5 | ✅ 2026-06-18 |
|
||||||
|
| 13 | Workspace — Servizi Attivi | — | ❄️ Congelata → ripresa in **v2.4** |
|
||||||
|
| 14 | CRM Attio-style & Fix | 3/3 | ✅ 2026-06-14 |
|
||||||
|
| 15 | Dashboard Revenue Stats | — | ❌ Abbandonata (bloccata su un mockup mai fornito) |
|
||||||
|
| 16–17 | Proposal AI (impianto originale) | — | ♻️ Ri-scopate in v2.2 (fasi 21–22) |
|
||||||
|
|
||||||
|
Documentazione di dettaglio (PLAN, SUMMARY, RESEARCH, VERIFICATION) in
|
||||||
|
[v2.1-phases/](v2.1-phases/).
|
||||||
|
|
||||||
|
## Cosa è stato consegnato
|
||||||
|
|
||||||
|
- **Phase 11 — Catalog Database-View UX** (OFFER-07..10, OFFER-13): il catalogo
|
||||||
|
`services` come tabella a edit inline, tag multi-select, quick-add, ricerca
|
||||||
|
istantanea; consolidamento delle tabelle legacy.
|
||||||
|
- **Phase 12 — Offer Editor Tier A/B/C** (OFFER-11, OFFER-15..18): editor offerte
|
||||||
|
con matrice checkbox servizi × tier, totale live, prezzo pubblico manuale, tag su
|
||||||
|
4 dimensioni, promessa di trasformazione. 55 servizi reali caricati.
|
||||||
|
- **Phase 14 — CRM Attio-style** (CRM-08..12): `/admin/leads` ridisegnata con edit
|
||||||
|
inline e tag multi-select; FollowUpWidget in italiano; LeadForm tipizzato;
|
||||||
|
SendQuoteModal ripulita dai rami irraggiungibili.
|
||||||
|
|
||||||
|
## Il reset del 2026-06-19
|
||||||
|
|
||||||
|
A metà milestone il piano è stato riscritto: la milestone **v2.2 "Sales Loop"**
|
||||||
|
sostituisce le fasi residue. Decisioni bloccate in quel momento:
|
||||||
|
|
||||||
|
1. L'URL del preventivo è `/preventivo/[slug]`, pubblico.
|
||||||
|
2. Si tagliano Forecast, quote builder manuale e Phase 15; `/admin/analytics` viene
|
||||||
|
fusa nella Dashboard.
|
||||||
|
3. Il portale post-vendita resta core e non si tocca (Phase 13 congelata).
|
||||||
|
4. L'agente AI è "io scelgo l'offerta, l'AI personalizza" leggendo i transcript;
|
||||||
|
provider Claude.
|
||||||
|
|
||||||
|
Phase 13 è poi tornata in vita come **milestone v2.4**, consegnata il 2026-08-01.
|
||||||
|
|
||||||
|
## Debito lasciato aperto
|
||||||
|
|
||||||
|
- Tabelle legacy `service_catalog` / `offer_services` / `offer_micro_services`
|
||||||
|
rimaste come deadweight.
|
||||||
|
- `createService` / `serviceSchema` dead code in `src/app/admin/catalog/actions.ts`
|
||||||
|
(Phase 11 Plan 04, annotato in `deferred-items.md`).
|
||||||
|
- `offer_micros` senza `created_at` — nessun "tier più vecchio" affidabile.
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Requirements: ClientHub v2.3 Email & Accesso
|
||||||
|
|
||||||
|
**Defined:** 2026-06-21
|
||||||
|
**Core Value:** Il cliente apre il link e vede esattamente a che punto è il suo progetto, cosa deve ancora succedere e cosa ha già approvato — senza dover scrivere email per chiedere aggiornamenti.
|
||||||
|
|
||||||
|
## v2.3 Requirements
|
||||||
|
|
||||||
|
### Email OTP Gate (AUTH-OTP-01)
|
||||||
|
|
||||||
|
Portale cliente blindato da email OTP. Nuovo `client_emails` table (whitelist) + `otp_codes` table (codice, email, expires_at, consumed). Resend come provider email. Sessione **90 giorni** con cookie dopo verifica, revocabile dall'admin.
|
||||||
|
|
||||||
|
- [x] **OTP-01**: Admin può aggiungere e rimuovere email dalla whitelist di ogni cliente nell'admin UI
|
||||||
|
- [x] **OTP-02**: Cliente senza sessione OTP vede una schermata "inserisci email" invece della dashboard
|
||||||
|
- [x] **OTP-03**: Sistema invia OTP via Resend solo se l'email inserita è nella whitelist di quel cliente
|
||||||
|
- [x] **OTP-04**: Cliente inserisce il codice OTP ricevuto e ottiene sessione autenticata (cookie **90 giorni**)
|
||||||
|
- [x] **OTP-05**: Codici OTP scadono dopo 15 minuti dall'invio
|
||||||
|
- [x] **OTP-06**: Endpoint OTP è rate-limited per prevenire brute force
|
||||||
|
- [x] **OTP-07**: Messaggi di errore OTP non rivelano se l'email è in whitelist o no (no enumeration)
|
||||||
|
- [x] **OTP-08**: Admin può revocare in blocco tutte le sessioni attive di un cliente
|
||||||
|
|
||||||
|
> **[2026-07-28] Modifiche alla spec del 2026-06-21**, decise in sessione:
|
||||||
|
> - Sessione **90 giorni** invece di 30 (rientro più fluido), compensata da OTP-08.
|
||||||
|
> - **SEND-01/SEND-02 spostati al backlog v2.4**: il preventivo si invia a mano, l'automazione non serve ora. Phase 23 si è ridotta alla sola infrastruttura Resend, che l'OTP usa comunque.
|
||||||
|
> - **Il gate NON sta nel layout** ma in cima a ogni page sotto `/client/[token]/`. Nell'App Router il segmento `page` viene renderizzato in parallelo al layout: gattare nel layout nascondeva la dashboard a schermo ma lasciava fasi, task e pagamenti nel payload RSC dell'HTML (verificato: 46.907 byte con i dati → 17.594 dopo il fix). Helper: `src/lib/client-gate.ts`.
|
||||||
|
|
||||||
|
## v2.4+ Backlog
|
||||||
|
|
||||||
|
### Conversione Commerciale
|
||||||
|
|
||||||
|
- **PROP-03**: Stripe Payment Link su deck pubblico `/preventivo/[slug]`
|
||||||
|
- **PROP-04**: Auto-provisioning cliente/progetto/fasi al "Vinto" nel CRM
|
||||||
|
- **SEND-01/SEND-02**: invio del link `/preventivo/[slug]` via email dall'admin UI — *rinviato da v2.3 il 2026-07-28, l'invio si fa a mano. L'infrastruttura Resend (`src/lib/mailer.ts`) è già pronta, manca solo l'azione e il pulsante.*
|
||||||
|
|
||||||
|
### Post-Vendita
|
||||||
|
|
||||||
|
- **Phase 13**: Gestione servizi attivi/ricorrenti post-vendita nel portale cliente (congelata da v2.1)
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
| Feature | Reason |
|
||||||
|
|---------|--------|
|
||||||
|
| Self-registration cliente | Solo whitelist admin-gestita — nessun accesso senza approvazione esplicita |
|
||||||
|
| Magic link senza OTP | OTP è più sicuro e già deciso come design; magic link = scope creep |
|
||||||
|
| Email marketing / newsletter | Non pertinente al portale |
|
||||||
|
| Multi-admin | Ancora single admin per ora |
|
||||||
|
|
||||||
|
## Traceability
|
||||||
|
|
||||||
|
| Requirement | Phase | Status |
|
||||||
|
|-------------|-------|--------|
|
||||||
|
| OTP-01 | Phase 24 | ✅ Done (2026-07-28) |
|
||||||
|
| OTP-02 | Phase 25 | ✅ Done (2026-07-28) |
|
||||||
|
| OTP-03 | Phase 25 | ✅ Done (2026-07-28) |
|
||||||
|
| OTP-04 | Phase 25 | ✅ Done (2026-07-28) |
|
||||||
|
| OTP-05 | Phase 25 | ✅ Done (2026-07-28) |
|
||||||
|
| OTP-06 | Phase 25 | ✅ Done (2026-07-28) |
|
||||||
|
| OTP-07 | Phase 25 | ✅ Done (2026-07-28) |
|
||||||
|
| OTP-08 | Phase 24 | ✅ Done (2026-07-28) |
|
||||||
|
| SEND-01 | — | ⏭️ Rinviato a v2.4 |
|
||||||
|
| SEND-02 | — | ⏭️ Rinviato a v2.4 |
|
||||||
|
|
||||||
|
**Coverage:**
|
||||||
|
- v2.3 requirements: 8 in scope (OTP-01…08) + 2 rinviati
|
||||||
|
- Implementati: 8/8 ✓ — verificati con 9 test E2E in locale contro il DB di produzione
|
||||||
|
- **Non ancora in produzione**: il codice è scritto e testato ma NON pushato. Vedi i blocchi in `STATE.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Requirements defined: 2026-06-21*
|
||||||
|
*Last updated: 2026-07-28 — sessione 90gg, OTP-08 aggiunto, SEND-01/02 rinviati, OTP-01…08 implementati*
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Archivio milestone v2.3 — Email & Accesso
|
||||||
|
|
||||||
|
**Fasi:** 23–25 · **Aperta:** 2026-06-21 · **Shipped:** 2026-07-29 (commit `27da969`)
|
||||||
|
**Requisiti:** [v2.3-REQUIREMENTS.md](v2.3-REQUIREMENTS.md)
|
||||||
|
|
||||||
|
> **Nota di archivio.** v2.3 è stata eseguita **fuori dal ciclo GSD**: non sono mai
|
||||||
|
> esistite cartelle `phases/23`, `24`, `25` con PLAN/SUMMARY. Questo file *è*
|
||||||
|
> la documentazione della milestone — non cercare altrove.
|
||||||
|
|
||||||
|
## Obiettivo
|
||||||
|
|
||||||
|
Aggiungere uno strato email all'app: gate OTP per il portale cliente e invio del
|
||||||
|
link preventivo dall'admin, con un'unica integrazione Resend condivisa.
|
||||||
|
|
||||||
|
Il portale non doveva più essere apribile col solo link: chiunque avesse l'URL
|
||||||
|
vedeva il progetto del cliente.
|
||||||
|
|
||||||
|
## Fasi
|
||||||
|
|
||||||
|
### Phase 23 — Resend Setup ✅ 2026-07-28
|
||||||
|
|
||||||
|
**Goal:** infrastruttura email condivisa.
|
||||||
|
**Requisiti:** SEND-01, SEND-02 (poi ridotti — vedi sotto).
|
||||||
|
|
||||||
|
Consegnato: `resend@6.18.1`, `src/lib/mailer.ts` (Result tipizzato, mai un catch
|
||||||
|
silenzioso), template OTP in italiano. `RESEND_API_KEY` e `RESEND_FROM` configurate
|
||||||
|
su Coolify (production **e** preview).
|
||||||
|
|
||||||
|
**Riduzione di scope del 2026-07-28:** SEND-01/SEND-02 (invio del preventivo via
|
||||||
|
email dall'admin) spostati al backlog. Il preventivo si manda a mano; l'automazione
|
||||||
|
non serviva subito. Phase 23 si è ridotta alla sola infrastruttura Resend, che il
|
||||||
|
gate OTP usa comunque.
|
||||||
|
|
||||||
|
### Phase 24 — Schema + Whitelist Admin ✅ 2026-07-28
|
||||||
|
|
||||||
|
**Goal:** l'admin gestisce la whitelist email di ogni cliente; tabelle pronte per il gate.
|
||||||
|
**Requisiti:** OTP-01. **Dipende da:** Phase 23.
|
||||||
|
|
||||||
|
Migration `0015_otp_access.sql`, **additiva pura**, applicata a prod via SSH prima
|
||||||
|
del codice dipendente: `client_emails` (whitelist, unique case-insensitive),
|
||||||
|
`otp_codes` (hash del codice, mai il codice in chiaro), `clients.sessions_valid_from`
|
||||||
|
(revoca in blocco). Conteggi pre/post identici sulle tabelle protette —
|
||||||
|
clients 4 / projects 5 / payments 11 / phases 10.
|
||||||
|
|
||||||
|
UI: sezione "Accessi al portale" in `/admin/clients/[id]` — aggiungi/rimuovi email,
|
||||||
|
"Revoca sessioni attive". Server actions in `clients/[id]/actions.ts`.
|
||||||
|
|
||||||
|
### Phase 25 — OTP Gate + Sessione ✅ 2026-07-29
|
||||||
|
|
||||||
|
**Goal:** `/client/[token]/*` richiede verifica OTP prima di mostrare la dashboard.
|
||||||
|
**Requisiti:** OTP-02..OTP-07. **Dipende da:** Phase 24.
|
||||||
|
|
||||||
|
Consegnato: `src/lib/otp.ts` (codice 6 cifre CSPRNG, hash SHA-256 con
|
||||||
|
`NEXTAUTH_SECRET`+clientId, TTL 15 minuti, monouso, max 5 tentativi),
|
||||||
|
`src/lib/client-session.ts` (cookie HMAC per-cliente `ch_sess_<id>`, httpOnly +
|
||||||
|
secure + SameSite=lax, `path=/client`), `src/lib/client-gate.ts`, le route
|
||||||
|
`/api/client/otp/request|verify`, il componente `OtpGate`.
|
||||||
|
|
||||||
|
**Scostamento dalla spec del 21/06:** sessione **90 giorni** invece di 30 — rientro
|
||||||
|
più fluido, compensato da OTP-08 (revoca in blocco lato admin).
|
||||||
|
|
||||||
|
## Catena di dipendenze
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 23 (Resend SDK + env)
|
||||||
|
└── Phase 24 (schema additivo: client_emails + otp_codes)
|
||||||
|
└── Phase 25 (gate OTP + sessione cookie 90gg)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Copertura requisiti
|
||||||
|
|
||||||
|
| Requisito | Fase | Esito |
|
||||||
|
|---|---|---|
|
||||||
|
| SEND-01, SEND-02 | 23 | ⏭ Rinviati al backlog il 2026-07-28 |
|
||||||
|
| OTP-01 | 24 | ✅ |
|
||||||
|
| OTP-02 … OTP-07 | 25 | ✅ |
|
||||||
|
| OTP-08 (revoca) | 24 | ✅ |
|
||||||
|
|
||||||
|
## Verifica in produzione (2026-07-29, `hub.iamcavalli.net`)
|
||||||
|
|
||||||
|
Gate mostrato senza cookie e **zero dati di progetto nell'HTML** (12.487 byte);
|
||||||
|
email fuori e dentro whitelist danno risposta identica e solo la seconda genera un
|
||||||
|
OTP; codice sbagliato rifiutato, corretto accettato; cookie `ch_sess_<id>` con
|
||||||
|
`Secure` + `HttpOnly` + `SameSite=lax` + `Max-Age=7776000`; rientro col cookie
|
||||||
|
mostra la dashboard; la sessione di un cliente sull'URL di un altro mostra il gate.
|
||||||
|
Nessun errore d'invio nei log del container. Dati di test rimossi, tabelle protette
|
||||||
|
invariate.
|
||||||
|
|
||||||
|
## Lezioni
|
||||||
|
|
||||||
|
Le due lezioni operative di questa milestone (il gate non va nel layout App Router;
|
||||||
|
ricreare il dominio su Resend rigenera la chiave DKIM) sono in `STATUS.md`,
|
||||||
|
sezione "Lezioni operative" — è lì che si vanno a cercare.
|
||||||
|
|
||||||
|
## Strascico alla chiusura
|
||||||
|
|
||||||
|
La whitelist è stata seedata solo con `mario@test.it` (cliente di test). Tre clienti
|
||||||
|
reali su quattro hanno whitelist vuota e finché lo è **il loro portale non è
|
||||||
|
accessibile**. Voce aperta in `STATUS.md`.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Phase 13 — Ciclo di vita dei servizi ricorrenti
|
||||||
|
|
||||||
|
**Milestone:** v2.4 Post-vendita · **Stato:** ✅ in produzione, verificata end-to-end
|
||||||
|
**Consegnata:** 2026-08-01 · **Commit:** `5177a37` · **Migration:** `0016_project_offer_lifecycle.sql`
|
||||||
|
|
||||||
|
> **Ricostruito a posteriori il 2026-08-08** dal commit, dalla migration e da
|
||||||
|
> `STATUS.md`. La fase è stata eseguita fuori dal ciclo GSD: non esiste un PLAN.
|
||||||
|
> Phase 13 nasce congelata in v2.1 (giugno) e riaperta come milestone v2.4.
|
||||||
|
|
||||||
|
## Il problema
|
||||||
|
|
||||||
|
Un retainer, una volta assegnato, non si poteva fermare. `project_offers` aveva solo
|
||||||
|
`start_date`, e il ramo retainer di `src/lib/forecast-queries.ts` sommava il canone a
|
||||||
|
**ogni mese** dell'orizzonte da lì in poi, per sempre. Un cliente che disdiceva
|
||||||
|
continuava a gonfiare il forecast a 12 mesi e a vedersi l'abbonamento attivo nel
|
||||||
|
proprio portale.
|
||||||
|
|
||||||
|
## Cosa è stato fatto
|
||||||
|
|
||||||
|
**Schema** — migration `0016`, additiva pura, applicata a prod **prima** del push:
|
||||||
|
`project_offers.status` (`attivo|sospeso|cessato`, CHECK `NOT VALID` per evitare un
|
||||||
|
lock lungo) e `project_offers.end_date` (nullable, NULL = continuativo). Default
|
||||||
|
`'attivo'` così ogni riga esistente conserva esattamente il comportamento precedente.
|
||||||
|
Nessun DROP, nessun TRUNCATE. Idempotente.
|
||||||
|
|
||||||
|
**Forecast** — i retainer si fermano a `end_date`; sospesi e cessati escono dal
|
||||||
|
calcolo. `getOffersSoldBreakdown` **non** filtra per stato di proposito: è uno
|
||||||
|
storico di vendita, ed escludere le cessate riscriverebbe il passato.
|
||||||
|
`offersAcceptedTotal` esclude le cessate (default del piano pagamenti).
|
||||||
|
|
||||||
|
**Admin** — comandi Sospendi / Riattiva / Cessa più data di fine nella tab Offerte,
|
||||||
|
mostrati solo per i ricorrenti. `setProjectOfferLifecycle` valida con Zod e filtra
|
||||||
|
**anche per `project_id`**, così un id arbitrario non può toccare un altro progetto.
|
||||||
|
|
||||||
|
**Portale cliente** — "Attivo dal", "fino al", badge *In pausa*, "Canone mensile" al
|
||||||
|
posto di "Prezzo finale". Le offerte cessate non arrivano mai al client.
|
||||||
|
|
||||||
|
**Fix collaterale** — un retainer sospeso continuava a intestare i pagamenti "Totale
|
||||||
|
Pagamento Mensile" e a sovrascriverne l'importo.
|
||||||
|
|
||||||
|
## Decisioni
|
||||||
|
|
||||||
|
- **Storico di vendita ≠ forecast.** Due letture diverse degli stessi dati: il
|
||||||
|
breakdown del venduto ignora lo stato, il forecast lo rispetta. Unificarle
|
||||||
|
avrebbe fatto sparire fatturato già incassato dai report.
|
||||||
|
- **Default `'attivo'` invece di NULL.** Rende la migration a comportamento
|
||||||
|
invariato senza una backfill separata.
|
||||||
|
- **Filtro per `project_id` nell'action**, non solo per id dell'offerta: difesa in
|
||||||
|
profondità contro un id manipolato.
|
||||||
|
|
||||||
|
## Fuori scope, rimandato
|
||||||
|
|
||||||
|
Tracciamento dei canoni mese per mese (agosto pagato / settembre no). Serve una
|
||||||
|
tabella nuova: `payments` è protetta dai vincoli di Data Safety e la sua riscalatura
|
||||||
|
è pensata per i piani una tantum. Voce di backlog in `REQUIREMENTS.md`.
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Phase 26 — Anteprima admin del portale + toggle password sul login
|
||||||
|
|
||||||
|
**Milestone:** v2.4 Post-vendita · **Stato:** ✅ in produzione (deploy Coolify 2026-08-08)
|
||||||
|
**Commit:** `09a5b1f` (toggle password) · `187550f` (anteprima admin)
|
||||||
|
|
||||||
|
> **Ricostruito a posteriori il 2026-08-08.** Lavoro non pianificato in roadmap,
|
||||||
|
> nato da due attriti d'uso reali. 26 è il primo numero di fase libero.
|
||||||
|
|
||||||
|
## 1. Toggle mostra/nascondi password — `09a5b1f`
|
||||||
|
|
||||||
|
Il campo password del login admin non offriva modo di rileggere quanto digitato: un
|
||||||
|
accesso fallito era indistinguibile da un errore di battitura.
|
||||||
|
|
||||||
|
Toggle **inline**, non un nuovo primitivo in `ui/`: `type="password"` compare una
|
||||||
|
sola volta in tutto il codebase, un'astrazione avrebbe avuto un solo consumatore.
|
||||||
|
`type="button"` perché dentro un `<form>` il default è submit; `tabIndex={-1}` per
|
||||||
|
tenere il Tab sulla sequenza campo → Accedi. Classi a token semantici; gli hex
|
||||||
|
literal preesistenti della pagina restano da migrare col resto del debito design.
|
||||||
|
|
||||||
|
**Causa a monte (non era un bug):** `ADMIN_PASSWORD` è stata ruotata il 2026-07-28
|
||||||
|
**solo su Coolify**, e `.env.local` è rimasto alla precedente. Vale anche per
|
||||||
|
`NEXTAUTH_SECRET`. `.env.local` non è allineato a produzione e non va trattato come
|
||||||
|
fonte di verità per le credenziali.
|
||||||
|
|
||||||
|
## 2. Anteprima admin in sola lettura del portale — `187550f`
|
||||||
|
|
||||||
|
Quando un cliente segnalava "non trovo una cosa" non c'era modo di guardare il
|
||||||
|
portale con i suoi occhi: il gate OTP lascia entrare solo lui. Dall'elenco clienti
|
||||||
|
(`ClientRow`) un'icona apre ora `/client/<slug>?preview=1` in una scheda nuova.
|
||||||
|
|
||||||
|
**Come funziona.** `getClientGate()` accetta `{ previewRequested }` e salta il gate
|
||||||
|
solo se il query param c'è **e** `getServerSession(authOptions)` è valida. Senza il
|
||||||
|
param anche un admin vede il gate OTP — così il gate resta testabile dal vivo.
|
||||||
|
Ritorna `preview: true` **senza sintetizzare una `ClientSession`**: un admin in
|
||||||
|
anteprima non è un cliente autenticato, e confondere i due stati li renderebbe
|
||||||
|
indistinguibili proprio dove serve distinguerli. Il flag viaggia via
|
||||||
|
`PreviewProvider` / `usePreview()` e non per prop drilling: `ApproveButton` sta
|
||||||
|
quattro livelli sotto la dashboard.
|
||||||
|
|
||||||
|
**Perché sola lettura.** Il portale scrive davvero: `/api/client/approve` e
|
||||||
|
`/api/client/comment` autenticano sul token nel body, non sulla sessione, e
|
||||||
|
`deliverables.approved_at` è immutabile una volta impostato (**LOCKED #3**). Un
|
||||||
|
click distratto approverebbe un deliverable in modo irreversibile. La protezione è
|
||||||
|
a livello **UI, non API**: un admin può ancora chiamare le route a mano. È voluto —
|
||||||
|
l'obiettivo è impedire l'incidente, non difendersi da sé stessi.
|
||||||
|
|
||||||
|
## ⚠️ Deviazione consapevole dal vincolo LOCKED #4
|
||||||
|
|
||||||
|
`CLAUDE.md` fissa: `/client/[token]/*` → token middleware, `/admin/*` → sessione
|
||||||
|
Auth.js. Ora una route client legge **anche** la sessione Auth.js. Non indebolisce
|
||||||
|
nulla — per i clienti il gate OTP è identico — ma la sezione LOCKED richiede
|
||||||
|
approvazione esplicita prima di essere modificata. **Annotato in `CLAUDE.md` il
|
||||||
|
2026-08-08 con l'ok dell'utente.**
|
||||||
|
|
||||||
|
## Verifica
|
||||||
|
|
||||||
|
**Verificato** col build di produzione su `:3100` contro il DB reale (sole letture):
|
||||||
|
`?preview=1` senza sessione admin → gate OTP; con cookie di sessione contraffatto →
|
||||||
|
gate OTP; `?preview=0`, `?preview=abc`, `?preview=` → gate OTP; nessun query param
|
||||||
|
**con** sessione admin → gate OTP (nessuna regressione); sessione admin valida +
|
||||||
|
`?preview=1` → portale con banner e composer disattivato, sia sul cliente a progetto
|
||||||
|
singolo sia su quello a due progetti.
|
||||||
|
|
||||||
|
**Non verificato dal vivo:** il ramo `ApproveButton` — in produzione la tabella
|
||||||
|
`deliverables` è **vuota** (0 righe), quindi quel pulsante oggi non si renderizza
|
||||||
|
mai. Wiring controllato solo a livello di codice. Il click dell'occhiolino sul login
|
||||||
|
è verificato solo nel markup renderizzato (`type="button"`, `tabindex="-1"`,
|
||||||
|
`aria-label`).
|
||||||
|
|
||||||
|
**Nota di metodo:** Playwright non funziona contro `npm run dev` — la CSP blocca
|
||||||
|
`eval` e i client component non si idratano. Va usato il build di produzione.
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
# 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)?
|
|
||||||
@@ -1,412 +0,0 @@
|
|||||||
# Feature Landscape: Business Operations Suite v2.0 (New Features)
|
|
||||||
|
|
||||||
**Domain:** Proposal generation + lightweight CRM for solo personal-branding consultant
|
|
||||||
**Researched:** 2026-06-10
|
|
||||||
**Research Mode:** Ecosystem (proposal software + lightweight CRM patterns)
|
|
||||||
**Confidence:** MEDIUM-HIGH
|
|
||||||
**Scope:** ONLY new v2.0 features (does NOT review v1.0 table stakes already shipped)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
The Business Operations Suite adds three interconnected workflows to ClientHub:
|
|
||||||
|
|
||||||
1. **Proposal Generation & Delivery** — Sales call → multistep proposal page (2-hour delivery) → lead selects tier A/B/C → acceptance triggers automation
|
|
||||||
2. **Lead Pipeline Management** — Minimal CRM for tracking prospects through 5 stages (Contacted → Qualified → Proposal Sent → Negotiating → Won/Lost)
|
|
||||||
3. **Onboarding Automation** — When lead is marked Won, auto-create client + project with phases copied from chosen offer + configurable payment schedule
|
|
||||||
|
|
||||||
**For a solo consultant, this is radically different from team CRMs** (Pipedrive, HubSpot, GoHighLevel). Avoid: territory management, team routing, email automation, role-based access, forecasting rollups, lead scoring algorithms. Instead: **minimal data entry, follow-up reminders based on last-contact date, and seamless handoff from proposal acceptance to project setup.**
|
|
||||||
|
|
||||||
**Core insight:** Solo consultants manage long-term relationships across years, not one-time deals closed in 30 days. Pipeline stages should reflect relationship milestones (Contacted, Qualified, Proposal Sent), not sales rep activity (Attempted, Left Message). Follow-ups are "who did I talk to recently?" not "assign task to rep."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Table Stakes Features (New v2.0)
|
|
||||||
|
|
||||||
Features users expect when adopting a proposal + CRM system. Missing these = product feels incomplete.
|
|
||||||
|
|
||||||
### Proposal Generation & Delivery
|
|
||||||
|
|
||||||
| Feature | Why Expected | Complexity | Notes |
|
|
||||||
|---------|--------------|-----------|-------|
|
|
||||||
| Pick client + 1–3 offers, set per-quote prices | Core value: sales call → proposal in 2 hours. Prices set per quote, not from catalog (allows price increases over time without updating service catalog) | Medium | Depends on existing `offers` & `clients` (Phase 5). Quote prices override offer base prices. |
|
|
||||||
| Generate public multistep HTML/CSS page | Leads need clickable link, not PDF email. Interactive engagement beats static documents (Qwilr/Proposify standard). Reduces abandonment. | Medium | Responsive, mobile-first, branded with consultant name/logo. No login required. |
|
|
||||||
| Multistep flow (intro → pricing tiers → CTA) | Reduces cognitive overload vs. single long page. Step 1: offer overview. Step 2: A/B/C tiers with pricing. Step 3: accept/decline button. | Low | Conditional logic hides irrelevant sections; simple progressive disclosure. |
|
|
||||||
| Public acceptance button (no e-signature) | Lead confirms tier choice WITHOUT entering hub. Triggers: timestamp capture + email record + lead status update. | Low | Simple "Accept this offer" button → records accepted_at + accepted_by_email. E-signature deferred (design ready, v1 constraint). |
|
|
||||||
| Proposal URL shareable & public | Lead receives link in email, opens in browser, no friction. | Low | Generate unique slug (`/proposal/[uuid]`). No auth. Optionally expires after N days or on acceptance. |
|
|
||||||
| Acceptance proof (signer + timestamp) | Documentation for dispute resolution. Downloadable PDF or stored audit record. | Low | Store: accepted_by_name, accepted_by_email, accepted_at, accepted_offer_id. |
|
|
||||||
|
|
||||||
### Lead Pipeline
|
|
||||||
|
|
||||||
| Feature | Why Expected | Complexity | Notes |
|
|
||||||
|---------|--------------|-----------|-------|
|
|
||||||
| Pipeline stages: Contacted → Qualified → Proposal Sent → Negotiating → Won/Lost | Core tracking. Each stage requires buyer action, not just rep activity. Termination: Won or Lost. | Low | **Minimal for solo:** 5 stages max. More = data entry overhead. Stages are immutable enums. |
|
|
||||||
| Move lead between stages (UI) | Basic pipeline UX. See all leads grouped by stage at a glance. | Low | Dropdown per row (simple) or kanban board (polish). Dropdown sufficient for MVP. |
|
|
||||||
| Lead details: name, email, company, phone, last_contact_date, next_action, notes | Minimum context for follow-ups. | Low | Notes = freeform text (call outcomes, concerns, personality notes, next steps). |
|
|
||||||
| Lead created manually OR auto-created from proposal send | Manual: import prospects. Auto-create: when proposal sent to unknown email, create lead record automatically. | Low | Auto-create is convenience; manual is fallback. Both supported. |
|
|
||||||
| Log activities: calls, emails, meetings, notes | Track interactions without leaving dashboard. Auto-updates last_contact_date. | Low | Activity types: [call, email, meeting, note]. Log date, duration, description. Displayed as feed. |
|
|
||||||
|
|
||||||
### Follow-Up Reminders
|
|
||||||
|
|
||||||
| Feature | Why Expected | Complexity | Notes |
|
|
||||||
|---------|--------------|-----------|-------|
|
|
||||||
| Dashboard widget: "Follow up today" sorted by last_contact_date | Solves core pain: "who did I talk to recently that I should check in with?" | Low | Query: WHERE last_contact_date <= TODAY - 7 days AND stage NOT IN (Won, Lost). Red badge. |
|
|
||||||
| Proposal stall detector: if Proposal Sent >3 days, no response | Flags silence = red flag for follow-up. | Low | Simple dashboard alert; no email automation (consultant checks dashboard in morning). |
|
|
||||||
| Auto-update last_contact_date on activity | Keeps reminders accurate. Prevents "I called them but forgot to log it" drift. | Low | Trigger: on call/email/meeting/note creation OR stage change → update lead.last_contact_date = NOW. |
|
|
||||||
|
|
||||||
### Project Auto-Creation (Won → Onboarding)
|
|
||||||
|
|
||||||
| Feature | Why Expected | Complexity | Notes |
|
|
||||||
|---------|--------------|-----------|-------|
|
|
||||||
| On lead.stage = Won: auto-create `client` + `project` with copied phases | Closing automation. Proposal acceptance → instant hub readiness for delivery. | Medium | Requires: chosen offer known, project_offers finalized (Phase 5), 1-4 configurable installments. |
|
|
||||||
| Copy offer phases → project phases (modifiable) | Project inherits offer structure but can be customized per client without mutating offer template. | Medium | Phases are modifiable in hub (Phase 1). Creation source logged: "Copied from Offer X on [date]" for audit. |
|
|
||||||
| Set payment installments per project (not per offer) | Payment plan configured case-by-case: some 50/50, others 4-part. Allows flexibility. | Medium | Templates: 50/50 (acconto/saldo), 3-part, 4-part, custom. UI: choose template during Won → project creation. |
|
|
||||||
| Lead → Client transition: copy email, create token, link to existing client if repeat | Avoid duplicate clients. Allow linking Won lead to existing client (repeat engagement). | Medium | Check email collision. If exists: "Add project to existing client X?" vs. "Create new client". Generate secret token. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Differentiators (New v2.0)
|
|
||||||
|
|
||||||
Features that set ClientHub apart from generic proposal software + CRM. Not expected, but valued for solo consultant use case.
|
|
||||||
|
|
||||||
| Feature | Value Proposition | Complexity | Notes |
|
|
||||||
|---------|-------------------|-----------|-------|
|
|
||||||
| Tier-independent offer structure (Signature A/B/C as separate offers) | Each tier has own URL, phases, pricing. A = professional, B = budget—same catalog, different scope. No cloning, no inheritance logic. | Medium | Requires flexible offer builder (drag services between phases), offer status (draft/active/archived). Built in Phase 5. |
|
|
||||||
| Proposal phases visible in public page (lead sees what they're buying) | Lead sees not just price, but scope: "4-week branding sprint, 3 revision rounds, monthly retainer". Transparency builds confidence. | Low | Proposal page shows phase names, deliverables summary (text), timeline (start + duration). |
|
|
||||||
| Dashboard follow-up list filterable by offer type + stage | "Show all Signature A leads in Negotiating" to spot bottlenecks by tier. | Low | Filter buttons: by offer type, stage, last_contact_date range. Dropdown or toggle buttons. |
|
|
||||||
| Activity feed per lead (call notes, email log, meetings, searchable) | Context without leaving dashboard. "Last contact: 2026-05-28, 11am call—timeline concerns". | Medium | Depends on activity logging system. Store type, date, duration, description. Searchable. |
|
|
||||||
| Proposal pages branded with consultant info (not generic SaaS) | Proposal is part of sales process; branding reinforces personal brand. | Low | Logo, consultant name, brand colors, professional typography inherited from admin settings. |
|
|
||||||
| Email integration hint (design ready, defer to later batch) | Send proposal via dashboard; auto-log email send as activity. | Medium | Deferred per PROJECT.md (v1 constraint). Placeholder: manual copy-paste OK for MVP. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Anti-Features: What NOT to Build (v2.0)
|
|
||||||
|
|
||||||
| Anti-Feature | Why Avoid | What to Do Instead |
|
|
||||||
|--------------|-----------|-------------------|
|
|
||||||
| Multi-user team collaboration, role-based access | Solo consultant is single admin. Team features add auth complexity, sync issues, notification storms. Overhead >> value. | Stay single-admin. Architecture with permission flags but don't build UI/logic. If team joins later, migrate incrementally. |
|
|
||||||
| CRM email automation, drip campaigns, email sequences | Solo consultant sends proposals + checks in manually. Automation-heavy workflows are for outbound prospecting funnels (not this use case). Lead generation not in scope. | Keep simple: dashboard reminder = "check in with X". Consultant sends email manually, logs it as activity. |
|
|
||||||
| Forecasting with rollups, quota tracking, team capacity planning, territory assignment | These are team sales metrics. Solo consultant cares: "How many leads in pipeline?" and "What's revenue if all Negotiating deals close?" | Simple dashboard: shows total value by stage (e.g., "Negotiating: $45k"), breakdown by offer type. No forecast math. |
|
|
||||||
| Lead scoring, MQL → SQL qualification algorithms, scoring rules | Solo consultant qualifies leads manually (call + gut feel). Scoring requires training data and tuning. | Keep manual: stage = Contacted, Qualified, Proposal Sent. "Qualified" is consultant's call. No automation. |
|
|
||||||
| Calendar sync, email sync, call recording, Slack/Teams integration | Adds external dependency surface area. Consultant logs calls + emails manually. | Fallback: freeform "Call notes" text field per activity. No attempt to auto-log from email/Slack. |
|
|
||||||
| Multi-currency support, tax calculation, invoice generation, accounting system integration | Accounting is out of scope (PROJECT.md locked). Payment tracking only. | Keep simple: amount_accepted in EUR, payment schedule in local currency. No conversion logic or invoicing. |
|
|
||||||
| Proposal version control, edit history, detailed audit trail | Overkill for solo consultant. One proposal per client, accept or decline. | Simple: created_at, updated_at timestamps. Accepted version is locked (immutable once accepted_at set). No versioning. |
|
|
||||||
| Client can reverse/un-approve acceptance | Creates ambiguity in closing. | Once accepted_at is set, proposal is final. Consultant can move lead back to Proposal Sent if needed. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Feature Dependencies (v2.0)
|
|
||||||
|
|
||||||
```
|
|
||||||
Proposal Generation:
|
|
||||||
├─ clients (exists, Phase 1)
|
|
||||||
├─ projects (exists, Phase 1)
|
|
||||||
├─ offers (Phase 5: id, name, offer_type [Entry/Signature/Retainer/custom], created_at)
|
|
||||||
├─ services catalog (Phase 3: id, name, price, duration_days)
|
|
||||||
└─ offer_services (Phase 5: junction table, offers ↔ services)
|
|
||||||
|
|
||||||
Lead Pipeline & CRM:
|
|
||||||
├─ leads table (NEW: id, name, email, company, phone, created_at, last_contact_date, stage, offer_id FK, next_action text)
|
|
||||||
├─ activities table (NEW: id, lead_id FK, type enum [call/email/meeting/note], log_date, description, duration_minutes)
|
|
||||||
├─ lead_stage enum (NEW: Contacted, Qualified, Proposal Sent, Negotiating, Won, Lost)
|
|
||||||
|
|
||||||
Project Auto-Creation (Won → Onboarding):
|
|
||||||
├─ leads.stage = Won (from pipeline, not auto-set)
|
|
||||||
├─ projects (exists, must be creatable via API/trigger)
|
|
||||||
├─ phases (exists, must be copyable from offer phases)
|
|
||||||
├─ payments (Phase 5: must support project_id FK, amount, due_date, status)
|
|
||||||
└─ clients.token (exists, Phase 1/4: rotatable secret)
|
|
||||||
|
|
||||||
Proposal Public Pages:
|
|
||||||
└─ proposals table (NEW: id, client_id, lead_email, offers [JSON array], created_at, public_url, accepted_at, accepted_by_name, accepted_by_email, accepted_offer_id)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Critical dependency:** Phase 5's `project_offers` relationship must be finalized. If not, proposal generation cannot proceed. Phase 7 proposal work is blocked by Phase 5 completion.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Feature Categorization by Component
|
|
||||||
|
|
||||||
### Component 1: Proposal Builder & Public Pages
|
|
||||||
|
|
||||||
**Responsibility:**
|
|
||||||
- Admin UI: select client, pick 1–3 offers, confirm per-quote pricing, set lead email, generate shareable link
|
|
||||||
- Public page: multistep form (intro → tiers → CTA), responsive, no login, branded
|
|
||||||
- Acceptance: simple "Accept" button, no e-signature
|
|
||||||
- Database: `proposals` table
|
|
||||||
|
|
||||||
**Features in scope:**
|
|
||||||
- Proposal generation from client + offers + prices
|
|
||||||
- Public multistep page with interactive tier selection
|
|
||||||
- Conditional phase visibility (show/hide sections based on tier)
|
|
||||||
- Acceptance button + timestamp + email capture
|
|
||||||
- Auto-create lead on proposal send (if lead email not in DB)
|
|
||||||
- Proposal expiry (optional, default: never)
|
|
||||||
|
|
||||||
**Database schema:**
|
|
||||||
```
|
|
||||||
proposals:
|
|
||||||
id UUID PK
|
|
||||||
client_id FK → clients
|
|
||||||
lead_email VARCHAR (recipient)
|
|
||||||
offer_ids JSON array (which offers are included)
|
|
||||||
quote_prices JSON {offer_id: price} (per-quote override)
|
|
||||||
created_at timestamp
|
|
||||||
updated_at timestamp
|
|
||||||
public_url slug
|
|
||||||
accepted_at timestamp nullable
|
|
||||||
accepted_by_name VARCHAR nullable
|
|
||||||
accepted_by_email VARCHAR nullable
|
|
||||||
accepted_offer_id FK nullable (which tier was chosen)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Component 2: Lead Pipeline & Dashboard
|
|
||||||
|
|
||||||
**Responsibility:**
|
|
||||||
- Lead table with name, email, company, stage, last_contact_date, notes
|
|
||||||
- Dashboard: table or kanban view of all leads grouped by stage
|
|
||||||
- Activities: log calls, emails, meetings, notes
|
|
||||||
- Follow-up reminder widget: leads not contacted in 7+ days
|
|
||||||
|
|
||||||
**Features in scope:**
|
|
||||||
- Create/edit/delete leads manually or auto-create from proposal
|
|
||||||
- Move lead between stages (dropdown or drag/drop)
|
|
||||||
- Log activities: type [call/email/meeting/note], duration, description
|
|
||||||
- Auto-update last_contact_date on activity
|
|
||||||
- Follow-up reminders: "Follow up today" widget, proposal stall detector
|
|
||||||
- Lead notes: freeform text, searchable
|
|
||||||
- Filter by offer type, stage, date range
|
|
||||||
- Activity feed per lead (searchable)
|
|
||||||
|
|
||||||
**Database schema:**
|
|
||||||
```
|
|
||||||
leads:
|
|
||||||
id UUID PK
|
|
||||||
name VARCHAR
|
|
||||||
email VARCHAR (unique or indexed)
|
|
||||||
company VARCHAR nullable
|
|
||||||
phone VARCHAR nullable
|
|
||||||
stage enum [Contacted, Qualified, Proposal Sent, Negotiating, Won, Lost]
|
|
||||||
offer_id FK → offers nullable (which offer was sent)
|
|
||||||
created_at timestamp
|
|
||||||
last_contact_date date
|
|
||||||
next_action text nullable (next step consultant plans)
|
|
||||||
notes text (accumulated call notes, concerns, personality)
|
|
||||||
|
|
||||||
activities:
|
|
||||||
id UUID PK
|
|
||||||
lead_id FK → leads
|
|
||||||
type enum [call, email, meeting, note]
|
|
||||||
log_date timestamp
|
|
||||||
duration_minutes INT nullable
|
|
||||||
description text
|
|
||||||
created_at timestamp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Component 3: Project Auto-Creation & Onboarding Flow
|
|
||||||
|
|
||||||
**Responsibility:**
|
|
||||||
- On lead.stage = Won, trigger creation: new client (if not exists), new project, copy phases, set payment schedule
|
|
||||||
- UI: lead moves to Won → modal: "Which offer was chosen?" → "Confirm project creation?" → choose payment template → create
|
|
||||||
- Result: lead archived, project appears in /admin/projects, client gets dashboard URL
|
|
||||||
|
|
||||||
**Features in scope:**
|
|
||||||
- Auto-create client from lead details
|
|
||||||
- Auto-create project with copied phases
|
|
||||||
- Copy phases from offer (modifiable in hub)
|
|
||||||
- Payment installments: choose template → creates N payment records
|
|
||||||
- Generate client secret token + dashboard URL
|
|
||||||
- Optional: link to existing client instead of creating new
|
|
||||||
- Notification: "Lead X → Project Y created on [date]"
|
|
||||||
|
|
||||||
**Database schema:**
|
|
||||||
```
|
|
||||||
No new tables needed. Uses existing: clients, projects, phases, payments.
|
|
||||||
When lead → Won:
|
|
||||||
1. Check if lead.email in clients table
|
|
||||||
2. If not: create new client, generate secret token
|
|
||||||
3. If yes: ask to link to existing client or create new
|
|
||||||
4. Copy phases from offer_id to new project
|
|
||||||
5. Create N payment records based on chosen template
|
|
||||||
6. Update lead.stage = Won (already set by UI)
|
|
||||||
7. Record: project creation_source = "Lead X, offer Y"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## MVP Recommendation
|
|
||||||
|
|
||||||
### Phase 7 (MVP): Core Proposal Generation + Basic Pipeline
|
|
||||||
|
|
||||||
**Priority 1: Must-Have (Launch features)**
|
|
||||||
|
|
||||||
1. **Proposal builder UI:** client + offer(s) + per-quote prices → generate public multistep page
|
|
||||||
2. **Public proposal page:** Step 1 = offer overview, Step 2 = A/B/C tiers + pricing, Step 3 = "Accept this Offer" CTA
|
|
||||||
3. **Acceptance flow:** "Accept" button → timestamp + email capture, marks proposal as accepted
|
|
||||||
4. **Lead auto-creation:** when proposal sent to unknown email, auto-create lead record
|
|
||||||
5. **Basic lead pipeline:** Contacted, Qualified, Proposal Sent, Negotiating, Won, Lost stages
|
|
||||||
6. **Lead dashboard:** table view, move stage via dropdown, lead details sidebar
|
|
||||||
7. **Follow-up reminder widget:** leads not contacted in 7+ days, sorted by last_contact_date, dashboard badge
|
|
||||||
8. **Activity logging (basic):** create call/email/meeting records, auto-update last_contact_date
|
|
||||||
|
|
||||||
**Estimated effort:** 5–7 days (proposal builder UI + public page + lead CRUD + follow-up reminders)
|
|
||||||
|
|
||||||
**Launch readiness:** Solo consultant can generate proposal in 2 hours, track lead through pipeline, see follow-up reminders.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 8 (Enrichment): Lead Context & Polish
|
|
||||||
|
|
||||||
**Priority 2: Features**
|
|
||||||
|
|
||||||
1. **Lead notes:** freeform text per lead, searchable
|
|
||||||
2. **Activity feed:** display recent activities per lead (call, email, meeting, note), searchable
|
|
||||||
3. **Dashboard filters:** by offer type, stage, date range (last_contact_date)
|
|
||||||
4. **Proposal stall detector:** flag if Proposal Sent >3 days with no activity
|
|
||||||
5. **Lead detail card:** expand to show full context (notes, activities, next_action) without leaving dashboard
|
|
||||||
|
|
||||||
**Estimated effort:** 3–4 days
|
|
||||||
|
|
||||||
**Value:** Consultant has full context for each lead without switching views.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 9+ (Automation & Onboarding): Project Auto-Creation
|
|
||||||
|
|
||||||
**Priority 3: Features**
|
|
||||||
|
|
||||||
1. **Project auto-creation:** on lead → Won, auto-create client + project with copied phases
|
|
||||||
2. **Copy phases from offer:** phases inherit name, duration, deliverables from offer template; modifiable in hub
|
|
||||||
3. **Payment installments:** choose template (50/50, 3-part, 4-part, custom) → create N payment records
|
|
||||||
4. **Client token generation:** auto-generate secret token, send dashboard URL to client
|
|
||||||
5. **Duplicate client check:** if lead.email exists in clients, ask to link or create new
|
|
||||||
6. **Won lead notification:** dashboard shows "Lead X → Project Y created", activity logged
|
|
||||||
|
|
||||||
**Estimated effort:** 3–4 days
|
|
||||||
|
|
||||||
**Value:** Closing automation. Lead acceptance → instant project setup in hub, ready for delivery.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Complexity Assessment (Effort & Maintenance)
|
|
||||||
|
|
||||||
| Feature | Dev Effort | Maintenance Burden | Deferability | Phase |
|
|
||||||
|---------|-----------|-------------------|----------------|-------|
|
|
||||||
| Proposal generation (builder UI + logic) | 2–3 days | Low (static) | No—core | 7 |
|
|
||||||
| Public multistep page (HTML/CSS template) | 1–2 days | Low (static) | No—core | 7 |
|
|
||||||
| Acceptance flow (button + timestamp) | 1 day | Minimal | No—core | 7 |
|
|
||||||
| Lead pipeline (CRUD + stage transitions) | 1 day | Low | No—core | 7 |
|
|
||||||
| Follow-up reminders (dashboard query) | 4 hours | Minimal | No—core | 7 |
|
|
||||||
| Activity logging (calls, emails, meetings) | 1 day | Low | Yes—Phase 8 | 8 |
|
|
||||||
| Activity feed (search + timeline) | 1 day | Low | Yes—Phase 8 | 8 |
|
|
||||||
| Lead notes + search | 1 day | Low | Yes—Phase 8 | 8 |
|
|
||||||
| Dashboard filters (offer type, stage, date) | 1 day | Low | Yes—Phase 8 | 8 |
|
|
||||||
| Project auto-creation (trigger + cascade) | 2 days | Medium (error handling) | Yes—Phase 9 | 9 |
|
|
||||||
| Payment installments (templates) | 1 day | Low | Yes—Phase 9 | 9 |
|
|
||||||
| Client token generation + link | 4 hours | Low | Yes—Phase 9 | 9 |
|
|
||||||
| Duplicate client check | 4 hours | Low | Yes—Phase 9 | 9 |
|
|
||||||
| Email integration (send proposal, auto-log) | 2–3 days | Medium (provider) | Yes—defer | Later |
|
|
||||||
| Multi-user + team features | 3–5 days | Medium-High (roles) | N/A—anti-feature | Never |
|
|
||||||
|
|
||||||
**Total Phase 7 MVP:** ~5–6 days
|
|
||||||
**Total Phases 7–9:** ~11–14 days (complete suite)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Solo Consultant Design Constraints
|
|
||||||
|
|
||||||
### What's Different from Team CRMs
|
|
||||||
|
|
||||||
| Constraint | Implication | Design Pattern |
|
|
||||||
|-----------|-----------|-----------------|
|
|
||||||
| Single admin, no team routing | No "assign to rep" or "owner" field. All leads = Simone. | All leads belong to admin; no ownership concept. No role-based UI. |
|
|
||||||
| Relationship-heavy, not transaction-heavy | Leads stay in pipeline for months, not 30 days. | Stage = relationship milestone (Contacted, Qualified, Proposal Sent), not rep activity (Attempted, Left Message, Following Up). |
|
|
||||||
| Qualitative follow-up, not automation | "Check in with X, heard timeline concerns" not email sequences. | Notes + activity log, no email automation. Manual send, logged as activity. |
|
|
||||||
| Low deal volume (5–15 in flight) | Pipeline visualization doesn't need advanced analytics. | Simple table or 5-column kanban board. No forecasting rollups. |
|
|
||||||
| Minimal data entry tolerance | Won't log every call if it takes >3 clicks. | Modal: "Log call → name/date/notes → save" in <10 seconds. |
|
|
||||||
| No role-based access | No "manager sees forecast, rep sees pipeline". | Single "admin dashboard" with all data. No permission layers. |
|
|
||||||
| No forecasting pressure | Consultant forecasts deal value by feel, not rollup math. | Dashboard shows "Total Negotiating: $45k". That's it. No probability weighting. |
|
|
||||||
| Calendar/email often offline (field work) | Email sync is nice-to-have, not must-have. | Fallback: manual entry. No real-time sync requirement. |
|
|
||||||
| Accountability is personal, not hierarchical | No "why is this deal stalled?" escalations. | Follow-up reminder = consultant decides action. No automatic escalations. |
|
|
||||||
|
|
||||||
### UX Patterns for Solo Consultant
|
|
||||||
|
|
||||||
1. **Minimize data entry by default:** Yes/No confirmations, checkboxes, dropdowns. Avoid text fields unless essential.
|
|
||||||
2. **Context visible by default:** Open a lead → see last 3 activities, next_action, stage, offer type in one view. No tabs.
|
|
||||||
3. **Dashboard as command center:** Lead reminders + upcoming payments + next meetings all visible. No app switching.
|
|
||||||
4. **Proposal as shared artifact:** Lead sees what they're buying (phases, deliverables, price). Consultant sees acceptance status + lead email.
|
|
||||||
5. **Automation where it reduces clicks:** Accept proposal → auto-create lead. Move to Won → ask once for payment plan, auto-create project. Don't auto-email.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Pipeline Benchmark (Reference)
|
|
||||||
|
|
||||||
From industry research:
|
|
||||||
|
|
||||||
| Metric | Benchmark | Implication for ClientHub |
|
|
||||||
|--------|-----------|---------------------------|
|
|
||||||
| Typical pipeline stages | 5–7 stages | ClientHub uses 5: Contacted, Qualified, Proposal Sent, Negotiating, Won/Lost. Minimal overhead. |
|
|
||||||
| Proposal Sent → Negotiation conversion | 40%+ | Flag stale proposals (>3 days, no activity) in dashboard. |
|
|
||||||
| Negotiation → Won conversion | 50%+ | If low, consultant reviews proposal scope or pricing. |
|
|
||||||
| Lead follow-up frequency | 8+ touches to close | Activity logging tracks touches (calls, emails, meetings). No automation; manual follow-up. |
|
|
||||||
| Typical pipeline review cadence | Weekly or bi-weekly | Consultant checks dashboard daily; reviews pipeline health weekly. |
|
|
||||||
| Average deal cycle for consultants | 30–90 days | ClientHub supports long cycles (no pressure to close fast). |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Confidence & Sources
|
|
||||||
|
|
||||||
| Area | Confidence | Basis | Sources |
|
|
||||||
|------|------------|-------|---------|
|
|
||||||
| Proposal software features (multistep, acceptance, tiering) | HIGH | Verified with Qwilr, PandaDoc, Proposify; 2026 comparison articles | [Qwilr vs PandaDoc](https://www.proposify.com/blog/qwilr-vs-pandadoc), [PandaDoc vs Proposify vs Qwilr](https://saas-tools.medium.com/pandadoc-vs-proposify-vs-qwilr-which-proposal-tool-is-worth-your-budget-in-2026-8e8339d6ab87), [8 best proposal software](https://www.getaccept.com/blog/proposal-software) |
|
|
||||||
| Pipeline stage structure (5–7 stages, Won/Lost termination) | HIGH | Verified with Pipedrive, Capsule CRM, Salesforce; "Proposal Sent → Negotiation → Won" is standard | [Salesforce Pipeline Management](https://www.salesforce.com/sales/pipeline/management/), [CRM Pipeline Stages](https://prospeo.io/s/crm-pipeline-stages), [GoHighLevel Pipeline](https://ecosire.com/blog/ghl-crm-pipeline-management) |
|
|
||||||
| Lead follow-up reminders (last_contact_date, 7+ days flagged) | MEDIUM-HIGH | Sourced from Outreach, HubSpot, Nimble best practices; solo consultant context is reasonable inference | [Sales Pipeline Best Practices](https://www.nimble.com/blog/best-practices-of-sales-pipeline-management/), [Outreach Pipeline Management](https://www.outreach.ai/resources/blog/sales-pipeline-management-best-practices) |
|
|
||||||
| Solo consultant CRM avoidance of team features | MEDIUM | Sourced from "Best CRM for Solopreneurs" guides; solo preference for simplicity consistent across 3+ sources | [Breakcold: CRM for Consultants](https://www.breakcold.com/blog/crm-for-consultants), [Authencio: CRM for Freelancers](https://www.authencio.com/blog/best-crm-for-consultants-freelancers-guide), [Addtocrm: Best CRM Solopreneurs](https://addtocrm.com/tools/best-crm-for-solopreneurs), [Mimiran: Anti-CRM](https://www.mimiran.com/fun-crm-for-solo-consultants-who-hate-selling/) |
|
|
||||||
| Tiered proposal (A/B/C independent offers) | MEDIUM-HIGH | Verified via multiple consulting pricing guides; "3 tiers ideal" is consensus. Independent offers is ClientHub-specific. | [Mercury: Pricing Strategy](https://mercury.com/blog/pricing-strategy-consulting), [Ignition: Tiered Pricing](https://www.ignitionapp.com/blog/tiered-pricing-strategy-for-professional-services-proposal-templates), [Consulting Success: Consulting Rates](https://www.consultingsuccess.com/consulting-rates) |
|
|
||||||
| Multi-step form engagement patterns | HIGH | Verified with Heyflow, Optimonk, HubSpot research; form completion rates improve with progressive disclosure | [Instapage: Multi-Step Forms](https://instapage.com/blog/multi-step-forms), [HubSpot: Multi-Step Forms](https://blog.hubspot.com/marketing/multi-step-forms), [Webstacks: Multi-Step Form Examples](https://www.webstacks.com/blog/multi-step-form) |
|
|
||||||
| Proposal acceptance automation (→ project creation) | MEDIUM | Sourced from Anchor, Estimate Rocket, Monograph workflows. ClientHub implementation is custom but pattern is established. | [Sayanchor: Proposal Acceptance](https://www.sayanchor.com/post/proposal-acceptance-guide), [Monograph: Build, Send, Sign](https://monograph.com/blog/build-send-and-sign-proposals-with-pipeline), [DocuSign: Workflow Automation](https://www.docusign.com/blog/workflow-automation-electronic-signatures) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Open Questions for Later Phases
|
|
||||||
|
|
||||||
1. **Email integration:** Should proposals be sent via dashboard integration, or manual copy-paste? (Deferred to Phase 10+)
|
|
||||||
2. **Proposal expiry:** Should proposals auto-expire after N days, or stay open indefinitely? (MVP: no expiry)
|
|
||||||
3. **Lead de-duplication:** If lead email is already in clients table, how should system handle it? (MVP: manual check; Phase 9+ auto-detect)
|
|
||||||
4. **Activity type richness:** Should activities include location, participant names, sentiment tags? (MVP: basic type + notes; Phase 8 can enhance)
|
|
||||||
5. **Proposal versioning:** Can consultant send multiple proposals to same lead? (MVP: yes, same lead.email + new proposal record)
|
|
||||||
6. **Payment plan customization:** Can consultant define custom installment schedules, or only use templates? (MVP: templates; Phase 9 can add custom)
|
|
||||||
7. **Lead archive vs. delete:** When lead is Won → converted to client/project, should lead record stay in DB for history? (Recommendation: keep for audit trail; mark archived or moved_to_project_id)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Appendix: Frequently Asked Questions
|
|
||||||
|
|
||||||
**Q: Why no e-signature in MVP?**
|
|
||||||
A: Design is ready (CLAUDE.md notes it as deferred). A simple "Accept" button + timestamp is sufficient proof of acceptance for solo consultant use case. E-signature (DocuSign, Stripe Sign) adds external dependency and cost; defer unless client explicitly requests.
|
|
||||||
|
|
||||||
**Q: Should proposals auto-expire?**
|
|
||||||
A: No, not in MVP. Solo consultant may send proposal Monday, follow up Thursday, acceptance Friday. Expiry is team-sales friction (forces re-quote). Keep simple: accepted_at = null means still open.
|
|
||||||
|
|
||||||
**Q: Can a lead be in two stages at once?**
|
|
||||||
A: No. Stage is singular immutable state at any moment. If second proposal sent to same lead: either (a) create new lead record (same email, add suffix "2"), or (b) update same lead.stage to "Proposal Sent" again. Option (b) is simpler; stage can repeat; last_contact_date updates on each proposal send.
|
|
||||||
|
|
||||||
**Q: Should activities auto-log from email/Slack?**
|
|
||||||
A: No, not in MVP. Adds external dependency surface. Fallback: consultant manually logs calls/emails as activities in <10 seconds. Acceptable for solo consultant volume (5–15 leads).
|
|
||||||
|
|
||||||
**Q: What if lead email already exists in clients table?**
|
|
||||||
A: Phase 9 auto-detection: when moving lead to Won, check if lead.email in clients. If yes, ask: "Link to existing client X (repeat engagement)?" or "Create new client". Prevents duplicate records.
|
|
||||||
|
|
||||||
**Q: Can phases be reordered after project creation?**
|
|
||||||
A: Yes. Phases are copied from offer at project creation time, but project phases are modifiable in hub (Phase 1, already built). Offer template is never mutated. Studio-grade immutability.
|
|
||||||
|
|
||||||
**Q: What's the payment flow after Won → project creation?**
|
|
||||||
A: Consultant chooses payment template (50/50, 3-part, 4-part, custom) during Won → project creation modal. System creates N records in `payments` table with due_dates calculated from project start_date. Client sees payment schedule in hub dashboard (Phase 1, already built). No invoicing or collection automation (out of scope, PROJECT.md locked).
|
|
||||||
|
|
||||||
**Q: Can consultant change their mind after accepting a proposal?**
|
|
||||||
A: If consultant moves lead back to Proposal Sent or Negotiating (after accepting and creating project), the original accepted_proposal record remains immutable. Project can be deleted/archived if needed, but proposal acceptance is final. This matches real-world: once accepted, consultant can't un-close a deal.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**End of FEATURES_v2.0.md**
|
|
||||||
+1177
-85
File diff suppressed because it is too large
Load Diff
@@ -1,210 +0,0 @@
|
|||||||
# Pitfalls Checklist: Business Operations Suite v2.0
|
|
||||||
|
|
||||||
Quick reference for preventing the 9 major pitfalls during Phase 7–9 implementation.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Critical Pitfalls (Before Code)
|
|
||||||
|
|
||||||
### 1. Catalog Consolidation Breaks Quotes
|
|
||||||
- [ ] Migration strategy documented (Expand-Contract, not drop)
|
|
||||||
- [ ] Mapping layer between old/new tables designed
|
|
||||||
- [ ] Backfill script tested on production backup
|
|
||||||
- [ ] Referential integrity test query written
|
|
||||||
- [ ] Dual-write period defined (2-3 weeks)
|
|
||||||
- [ ] Rollback plan documented (drop new table, revert code)
|
|
||||||
|
|
||||||
### 2. Offer Template Mutation
|
|
||||||
- [ ] Offer/project hierarchy mapping documented (1 page)
|
|
||||||
- [ ] Copy function uses `db.transaction()` (all-or-nothing)
|
|
||||||
- [ ] Uses `structuredClone()` for deep copy, not `{...spread}`
|
|
||||||
- [ ] Idempotency key added to CRM leads table
|
|
||||||
- [ ] Integration test: copy offer, verify structure consistent across instances
|
|
||||||
- [ ] Test: edit project phase, verify offer template unchanged
|
|
||||||
|
|
||||||
### 3. Public Quote Token Leakage
|
|
||||||
- [ ] Token length ≥ 32 chars (nanoid(32) or nanoid(64))
|
|
||||||
- [ ] Expiration field added (default 7 days)
|
|
||||||
- [ ] Email validation on public page (token + email required)
|
|
||||||
- [ ] Rate limiting implemented (max 3 views/token/minute)
|
|
||||||
- [ ] Code audit: quote_items NOT in public API response
|
|
||||||
- [ ] Test: brute-force 1000 guesses, verify rate limit activates
|
|
||||||
- [ ] Test: enumerate tokens with different emails, verify 401
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Moderate Pitfalls (During Implementation)
|
|
||||||
|
|
||||||
### 4. Drag-and-Drop Race Condition
|
|
||||||
- [ ] `version` field added to offer_phases
|
|
||||||
- [ ] Update mutation checks version (returns 409 if mismatch)
|
|
||||||
- [ ] Server recomputes all sort_orders atomically
|
|
||||||
- [ ] Frontend handles 409 Conflict (refresh UI)
|
|
||||||
- [ ] Test: concurrent drag in 2 tabs, verify final state correct
|
|
||||||
|
|
||||||
### 5. CRM "Win" Double-Click
|
|
||||||
- [ ] Idempotency key added to crm_leads (unique)
|
|
||||||
- [ ] "Win" mutation checks if key already processed
|
|
||||||
- [ ] All sub-steps in single `db.transaction()`
|
|
||||||
- [ ] Button disabled until success
|
|
||||||
- [ ] Idempotency key stored in localStorage
|
|
||||||
- [ ] Test: double-click "Win", verify only 1 client created
|
|
||||||
|
|
||||||
### 6. Offer Copy Missing Tasks/Deliverables
|
|
||||||
- [ ] Mapping documented: offer_micro → project_phase → project_task → deliverable
|
|
||||||
- [ ] Copy function recursive (phases → tasks → deliverables)
|
|
||||||
- [ ] Validation test: assert counts match expected
|
|
||||||
- [ ] Integration test exists (copy, verify structure)
|
|
||||||
- [ ] Query test: no orphaned deliverables (task_id IS NULL)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Minor Pitfalls (Polish)
|
|
||||||
|
|
||||||
### 7. Offer State Not Synced Between Tabs
|
|
||||||
- [ ] `version` field on offer_micros
|
|
||||||
- [ ] Update returns 409 on version mismatch
|
|
||||||
- [ ] UI shows "Offer changed, reload?" dialog
|
|
||||||
|
|
||||||
### 8. CRM Schema Backward Incompatibility
|
|
||||||
- [ ] New columns added as NULLABLE
|
|
||||||
- [ ] Code handles NULL defensively (e.g., `lead.budget ?? 0`)
|
|
||||||
- [ ] Backfill script written (sets defaults for existing rows)
|
|
||||||
- [ ] NOT NULL constraint added only after backfill + validation
|
|
||||||
- [ ] Test: production data copied to test db, code runs without errors
|
|
||||||
|
|
||||||
### 9. CRM Scope Creep
|
|
||||||
- [ ] Must-have features defined for v2.0 (lead pipeline, quote, auto-onboard)
|
|
||||||
- [ ] Should-have features listed for v2.1+ (email, calls, source)
|
|
||||||
- [ ] Won't-have features documented (team, multi-user, integrations)
|
|
||||||
- [ ] Scope document signed off by product
|
|
||||||
- [ ] Success metric defined: "Can go from lead to won project in <30 min"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 7 Checklist (Catalog & Offers)
|
|
||||||
|
|
||||||
**Before code:**
|
|
||||||
- [ ] Catalog consolidation migration plan reviewed
|
|
||||||
- [ ] Offer/project hierarchy documented
|
|
||||||
- [ ] Drag-drop version/locking strategy designed
|
|
||||||
|
|
||||||
**During implementation:**
|
|
||||||
- [ ] Migration backfill tested on production backup
|
|
||||||
- [ ] Drag-drop version field added
|
|
||||||
- [ ] Offer phase copy function atomic (transaction)
|
|
||||||
- [ ] Idempotency key on CRM leads
|
|
||||||
- [ ] Integration tests for copy consistency
|
|
||||||
|
|
||||||
**Before go-live:**
|
|
||||||
- [ ] Dry-run migration on production DB
|
|
||||||
- [ ] Verify referential integrity (no orphaned quote_items)
|
|
||||||
- [ ] Concurrent drag-drop test passes
|
|
||||||
- [ ] Offer copy count validation passes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 8 Checklist (Public Quote Pages)
|
|
||||||
|
|
||||||
**Before code:**
|
|
||||||
- [ ] Token security requirements finalized
|
|
||||||
- [ ] API response schema designed (no quote_items)
|
|
||||||
- [ ] Rate limiting strategy defined
|
|
||||||
|
|
||||||
**During implementation:**
|
|
||||||
- [ ] Token generation (nanoid(32))
|
|
||||||
- [ ] Expiration + email validation
|
|
||||||
- [ ] Rate limiting middleware
|
|
||||||
- [ ] Code audit for quote_items exposure
|
|
||||||
- [ ] Brute-force test (>1000 guesses/sec)
|
|
||||||
|
|
||||||
**Before go-live:**
|
|
||||||
- [ ] Brute-force test activates rate limit
|
|
||||||
- [ ] Enumeration test returns 401 for wrong email
|
|
||||||
- [ ] Expiration test returns 401 after expiration
|
|
||||||
- [ ] Code review: quote_items not in response
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 9 Checklist (CRM Won Automation)
|
|
||||||
|
|
||||||
**Before code:**
|
|
||||||
- [ ] Scope document (Must/Should/Could/Won't) reviewed
|
|
||||||
- [ ] Idempotency strategy designed
|
|
||||||
- [ ] Payment plan algorithm defined
|
|
||||||
- [ ] Offer snapshot structure (JSONB) designed
|
|
||||||
|
|
||||||
**During implementation:**
|
|
||||||
- [ ] Idempotency key on crm_leads
|
|
||||||
- [ ] "Win" mutation atomic (client + project + phases + payments)
|
|
||||||
- [ ] Offer snapshot stored on win
|
|
||||||
- [ ] Button disabled until success
|
|
||||||
- [ ] Payment consistency tests
|
|
||||||
|
|
||||||
**Before go-live:**
|
|
||||||
- [ ] Double-click test: only 1 client created
|
|
||||||
- [ ] Network timeout test: retry returns same project
|
|
||||||
- [ ] Partial failure test: rollback on error
|
|
||||||
- [ ] Payment consistency test: correct number created
|
|
||||||
- [ ] Idempotency test: 10x "Win" with same key = 1 client
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Key Questions Before Each Phase
|
|
||||||
|
|
||||||
**Phase 7:**
|
|
||||||
- ✓ Is catalog consolidation migration safe (Expand-Contract, dual-write)?
|
|
||||||
- ✓ Does offer/project hierarchy mapping make sense (1 micro = 1 phase)?
|
|
||||||
- ✓ Is copy function atomic (all-or-nothing)?
|
|
||||||
- ✓ Do we have integration tests for offer phase structure consistency?
|
|
||||||
|
|
||||||
**Phase 8:**
|
|
||||||
- ✓ Is token length ≥ 32 chars and properly random?
|
|
||||||
- ✓ Do we validate both token AND email on public page?
|
|
||||||
- ✓ Is rate limiting active (max 3 views/token/min)?
|
|
||||||
- ✓ Can we prove quote_items never appears in public response?
|
|
||||||
|
|
||||||
**Phase 9:**
|
|
||||||
- ✓ Is "Win" action atomic (transaction)?
|
|
||||||
- ✓ Does idempotency key prevent duplicates on retry?
|
|
||||||
- ✓ Is button disabled until success?
|
|
||||||
- ✓ Have we defined Must/Should/Won't scope with user?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Validation Matrix
|
|
||||||
|
|
||||||
| Pitfall | Test Case | Expected Outcome | Status |
|
|
||||||
|---------|-----------|------------------|--------|
|
|
||||||
| 1. Catalog consolidation | Backfill migration on prod backup | Zero orphaned quote_items | [ ] |
|
|
||||||
| 2. Offer template mutation | Edit project phase, check offer unchanged | Offer template immutable | [ ] |
|
|
||||||
| 3. Token leakage | Brute-force 1000 guesses/sec | Rate limit activates (429) | [ ] |
|
|
||||||
| 3. Token leakage | Enumerate with wrong email | Returns 401 Unauthorized | [ ] |
|
|
||||||
| 4. Drag-drop race | Concurrent drag in 2 tabs | Final sort_order is coherent | [ ] |
|
|
||||||
| 5. Double-click clients | Click "Win" twice | Only 1 client created | [ ] |
|
|
||||||
| 5. Double-click clients | Network timeout + retry | Same project returned | [ ] |
|
|
||||||
| 6. Missing deliverables | Copy offer to project | All tasks and deliverables copied | [ ] |
|
|
||||||
| 8. Schema backward compat | Copy production data to test DB | No crashes, NULL handled | [ ] |
|
|
||||||
| 9. Payment consistency | "Win" with partial failure | Rollback, no orphaned payments | [ ] |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Red Flags During Development
|
|
||||||
|
|
||||||
🚨 **Stop and review if:**
|
|
||||||
- Offer/project hierarchy mapping is unclear (should be 1 page, explicit)
|
|
||||||
- Drag-drop sort_order updates without version check
|
|
||||||
- "Win" mutation has any non-transactional steps (client created outside txn)
|
|
||||||
- Copy function uses shallow spread (`{...obj}`) instead of `structuredClone()`
|
|
||||||
- Token length < 32 chars
|
|
||||||
- Public API response includes quote_items or per_service_price
|
|
||||||
- CRM leads table has no idempotency_key column
|
|
||||||
- Schema changes add NOT NULL without backfill test
|
|
||||||
- "Win" button not disabled until response received
|
|
||||||
- No rate limiting on public quote page
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Owner:** Development team
|
|
||||||
**Review:** Before each phase kickoff
|
|
||||||
**Update:** As testing results come in
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
# Pitfalls Research Summary: Business Operations Suite (v2.0)
|
|
||||||
|
|
||||||
**Project:** ClientHub v2.0 — Adding Business Operations Suite to Production
|
|
||||||
**Research Date:** 2026-06-10
|
|
||||||
**Confidence:** HIGH (domain-specific research backed by schema review + security analysis)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Adding a complex Business Operations Suite (catalog consolidation + offer builder + public quotes + CRM automation) to a production app with real client data creates integration risks not present when building from scratch. This research documents the most dangerous pitfalls (rewrites/data loss) and operational friction points, with concrete prevention strategies for each.
|
|
||||||
|
|
||||||
**Key insight:** Most pitfalls cluster in four areas of high integration complexity:
|
|
||||||
1. **Schema consolidation** — breaking existing quote_items referential integrity
|
|
||||||
2. **Template-to-instance copy semantics** — offer phases mutating templates
|
|
||||||
3. **Optimistic UI + concurrent edits** — sort order conflicts
|
|
||||||
4. **Multi-step workflow atomicity** — CRM automation creating duplicates or partial state
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Critical Pitfalls (Require Design Changes Before Code)
|
|
||||||
|
|
||||||
### Pitfall 1: Catalog Consolidation Breaks Quotes (Data Loss Risk)
|
|
||||||
|
|
||||||
**Severity:** CRITICAL — Can orphan quote_items, break billing calculations
|
|
||||||
|
|
||||||
**Problem:**
|
|
||||||
Two parallel service tables (`service_catalog` for costs, `offer_services` for marketing pricing) need consolidation. Naive merge breaks `quote_items` referential integrity or loses price history.
|
|
||||||
|
|
||||||
**Prevention Strategy:**
|
|
||||||
- **Expand-Contract migration:** Add new unified `services` table alongside old ones, backfill in phases, only drop old tables after validation
|
|
||||||
- **Immutable snapshot:** Store `quote_snapshot: JSONB` in project_offers (freeze prices at time of win)
|
|
||||||
- **Referential integrity test:** Query for orphaned quote_items before/after migration
|
|
||||||
- **Backward compatibility:** Old quotes read from old tables, new quotes use new table, transition over 2-3 weeks
|
|
||||||
|
|
||||||
**Phase:** 7 (Catalog & Offers) — Plan migration design BEFORE any code changes
|
|
||||||
|
|
||||||
**Validation checklist:**
|
|
||||||
- [ ] Migration mapping layer documented (catalog ↔ services ↔ offer_services)
|
|
||||||
- [ ] Backfill strategy written (small batches, checksum validation)
|
|
||||||
- [ ] Dual-write period defined (how long to run old+new in parallel)
|
|
||||||
- [ ] Referential integrity test query exists
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Pitfall 2: Offer Template Mutation on Copy (Data Corruption Risk)
|
|
||||||
|
|
||||||
**Severity:** CRITICAL — Templates silently mutate when project phases edited
|
|
||||||
|
|
||||||
**Problem:**
|
|
||||||
Copying offer phases to project via shallow copy = shared references. Admin edits project phase, template phase mutates too. Next deal uses corrupted template. Also: partial copies on retry create duplicate phases.
|
|
||||||
|
|
||||||
**Prevention Strategy:**
|
|
||||||
- **Deep copy with atomic transaction:** Use `db.transaction()` for entire copy operation (all-or-nothing); copy at database level, not in JS
|
|
||||||
- **Immutable template flag:** `offer_micros.is_template = true`, prevent UPDATE on templates
|
|
||||||
- **Idempotency key:** "Win" request includes idempotency key; retry returns same project (no duplicates)
|
|
||||||
- **Explicit hierarchy mapping:** Document what offer_micro → project_phase, what offer_service → project_task/deliverable
|
|
||||||
|
|
||||||
**Phase:** 7 (Offers) for design, 9 (CRM) for "Win" automation
|
|
||||||
|
|
||||||
**Validation checklist:**
|
|
||||||
- [ ] Hierarchy mapping documented (offer ↔ project structures)
|
|
||||||
- [ ] Atomic copy function implemented (single transaction, all-or-nothing)
|
|
||||||
- [ ] Idempotency key added to CRM leads
|
|
||||||
- [ ] Integration tests verify copy structure consistency across multiple instances
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Pitfall 3: Public Quote Token Leakage (Security Risk)
|
|
||||||
|
|
||||||
**Severity:** CRITICAL — Token enumeration exposes all pricing
|
|
||||||
|
|
||||||
**Problem:**
|
|
||||||
Public quote page with nanoid 21-char token can be brute-forced or enumerated. Attacker builds pricing database, breaches commercial confidentiality.
|
|
||||||
|
|
||||||
**Prevention Strategy:**
|
|
||||||
- **Longer token:** nanoid(32) instead of 21 (~190 bits vs ~122 bits entropy)
|
|
||||||
- **Access control:** Require both token + email (validate recipient match)
|
|
||||||
- **Expiration:** token_expires_at (default 7 days)
|
|
||||||
- **Rate limiting:** Max 3 views/token/minute (blocks enumeration)
|
|
||||||
- **Never expose quote_items:** Public API response excludes line items, shows TOTAL PRICE ONLY
|
|
||||||
- **Activation state:** Token valid only AFTER admin sends it (not auto-generated on create)
|
|
||||||
|
|
||||||
**Phase:** 8 (Public Quote Pages) — Implement security controls IN PARALLEL with feature
|
|
||||||
|
|
||||||
**Validation checklist:**
|
|
||||||
- [ ] Token length ≥ 32 chars, rate limiting ≥ 3/min
|
|
||||||
- [ ] Email validation on public page (token + email both required)
|
|
||||||
- [ ] Expiration enforced (test expired token returns 401)
|
|
||||||
- [ ] Code audit: quote_items never in public API response
|
|
||||||
- [ ] Brute-force test: verify rate limit activates
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Moderate Pitfalls (Major Refactoring / Data Inconsistency)
|
|
||||||
|
|
||||||
### Pitfall 4: Drag-and-Drop Sort Order Race Condition
|
|
||||||
|
|
||||||
**Severity:** MODERATE — Phases display in wrong order, user frustration
|
|
||||||
|
|
||||||
**Problem:**
|
|
||||||
Concurrent drag-and-drop edits in multiple tabs cause sort_order conflicts. Last write wins, earlier update is lost. No version conflict detection.
|
|
||||||
|
|
||||||
**Prevention Strategy:**
|
|
||||||
- **Optimistic locking:** Add `version` field to offer_phases, update only if version matches (return 409 Conflict if mismatch)
|
|
||||||
- **Server-side recomputation:** Don't trust client's sort_order, recompute all orders atomically based on actual position
|
|
||||||
- **Optimistic UI with reconciliation:** Update frontend instantly, reconcile with server result (409 = refresh)
|
|
||||||
|
|
||||||
**Phase:** 7 (Offer Builder — Drag & Drop)
|
|
||||||
|
|
||||||
**Validation checklist:**
|
|
||||||
- [ ] `version` field added to offer_phases
|
|
||||||
- [ ] Update mutation includes version check
|
|
||||||
- [ ] Server recomputes all sort_orders atomically
|
|
||||||
- [ ] Concurrent edit test exists (open 2 tabs, drag in both)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Pitfall 5: CRM "Win" Double-Click Creates Duplicate Clients
|
|
||||||
|
|
||||||
**Severity:** MODERATE — Duplicate clients, broken billing, audit confusion
|
|
||||||
|
|
||||||
**Problem:**
|
|
||||||
Multi-step "Win" automation (create client → project → phases → payments) has no idempotency. Double-click creates two clients, two projects. Network timeouts are invisible to user.
|
|
||||||
|
|
||||||
**Prevention Strategy:**
|
|
||||||
- **Idempotency key:** Every lead has unique idempotency_key; "Win" request checks if key already processed (if yes, return existing client_id)
|
|
||||||
- **Atomic transaction:** All steps (client, project, phases, payments) in single transaction — all succeed or all rollback
|
|
||||||
- **UI feedback:** Button disabled until success (prevents accidental double-click)
|
|
||||||
- **Browser persistence:** Store idempotency_key in localStorage, preserve across refresh
|
|
||||||
|
|
||||||
**Phase:** 9 (CRM Won Automation)
|
|
||||||
|
|
||||||
**Validation checklist:**
|
|
||||||
- [ ] CRM leads table has idempotency_key column (unique)
|
|
||||||
- [ ] "Win" mutation checks for existing key before creating
|
|
||||||
- [ ] All substeps in single db.transaction()
|
|
||||||
- [ ] Button disabled until response received
|
|
||||||
- [ ] Idempotency key stored in localStorage
|
|
||||||
- [ ] Test: double-click "Win", verify only 1 client created
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Pitfall 6: Offer Phase Copy Misses Tasks/Deliverables
|
|
||||||
|
|
||||||
**Severity:** MODERATE — Client dashboard shows incomplete phases (no tasks to approve)
|
|
||||||
|
|
||||||
**Problem:**
|
|
||||||
Copy offer → project phases but forget to copy tasks or deliverables. Client sees phase with no work items.
|
|
||||||
|
|
||||||
**Prevention Strategy:**
|
|
||||||
- **Explicit mapping:** Document offer_micro → project_phase, offer_service → project_task/deliverable mapping
|
|
||||||
- **Full-tree copy function:** Copy phases, then tasks for each phase, then deliverables for each task (all in one transaction)
|
|
||||||
- **Validation test:** Assert phase_count, task_count, deliverable_count match expected values after copy
|
|
||||||
|
|
||||||
**Phase:** 7 (Offers structure design), 9 (Copy implementation)
|
|
||||||
|
|
||||||
**Validation checklist:**
|
|
||||||
- [ ] Mapping documented (offer vs project hierarchies)
|
|
||||||
- [ ] Copy function handles entire tree (phases → tasks → deliverables)
|
|
||||||
- [ ] Integration test: copy offer, verify counts match expected
|
|
||||||
- [ ] Query test: check no deliverables are orphaned (task_id IS NULL)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Minor Pitfalls (Operational Friction)
|
|
||||||
|
|
||||||
### Pitfall 7: Offer State Not Synced Between Tabs
|
|
||||||
|
|
||||||
**Problem:** Admin edits offer in tab 1, tab 2 doesn't know, last save wins (first admin's work lost)
|
|
||||||
|
|
||||||
**Prevention:** Add `version` field, return 409 on version mismatch, show "Offer changed, reload?"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Pitfall 8: CRM Schema Backward Incompatibility
|
|
||||||
|
|
||||||
**Problem:** Add new fields (budget, source) as NULL, code assumes populated, crashes
|
|
||||||
|
|
||||||
**Prevention:** Expand-Contract pattern — add as NULL, handle NULL defensively in code, backfill, then add NOT NULL constraint
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Pitfall 9: CRM Scope Creep (Feature Bloat)
|
|
||||||
|
|
||||||
**Problem:** "Just add email templates" → 30 hours. "Add calls" → 40 hours. CRM never ships.
|
|
||||||
|
|
||||||
**Prevention:** Explicit Must/Should/Could/Won't scope document. Must-haves only for v2.0: lead pipeline, quote attachment, auto-onboarding. Defer rest.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Key Design Decisions for Phase 7–9
|
|
||||||
|
|
||||||
| Decision | Why | Validation |
|
|
||||||
|----------|-----|-----------|
|
|
||||||
| Expand-Contract migration for catalog | Zero-downtime, safe rollback, no data loss | Dry-run on prod backup before go-live |
|
|
||||||
| Deep copy + atomic transaction for offer phases | Prevents template mutation, partial copies | Integration test, structure consistency query |
|
|
||||||
| Idempotency key on "Win" action | Safe retry, prevents duplicate clients | Double-click test, query for duplicates |
|
|
||||||
| Public quote token security layers (length, expiration, email, rate limit) | Blocks enumeration, limits leakage | Brute-force test, code audit for quote_items |
|
|
||||||
| Offer snapshot (immutable JSONB) | Preserves what was promised vs. what's executing | Store at win time, display in client dashboard |
|
|
||||||
| Version field on offer_phases + optimistic locking | Detects concurrent edit conflicts | Concurrent drag test, 409 Conflict handling |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Roadmap Implications
|
|
||||||
|
|
||||||
**Phase 7 (Catalog & Offers):**
|
|
||||||
- Design catalog consolidation migration before any code
|
|
||||||
- Implement drag-drop with version fields from start
|
|
||||||
- Document offer/project hierarchy mapping explicitly
|
|
||||||
- Write copy function atomically (no partial copies)
|
|
||||||
|
|
||||||
**Phase 8 (Public Quote Pages):**
|
|
||||||
- Implement token security controls in parallel (not after)
|
|
||||||
- Rate limiting + email validation + expiration
|
|
||||||
- Code audit: quote_items never in response
|
|
||||||
- Test brute-force + enumeration
|
|
||||||
|
|
||||||
**Phase 9 (CRM — Won Automation):**
|
|
||||||
- Add idempotency_key to leads table
|
|
||||||
- Implement atomic "Win" transaction
|
|
||||||
- Store offer snapshot at win time
|
|
||||||
- Define Must/Should/Won't scope before design
|
|
||||||
- Integration tests for payment consistency
|
|
||||||
|
|
||||||
**Phase 10+ (Future):**
|
|
||||||
- Defer: email templates, call logging, team features, integrations
|
|
||||||
- Solo consultant doesn't need multi-user or GoHighLevel-scale features
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Strategy for Confidence
|
|
||||||
|
|
||||||
### Phase 7 (Catalog & Offers)
|
|
||||||
1. Dry-run consolidation migration on production database backup
|
|
||||||
2. Verify all quote_items still resolve to services (no orphans)
|
|
||||||
3. Regenerate quotes from old projects, compare prices to originals
|
|
||||||
4. Concurrent drag-drop test: open offer in 2 tabs, drag in both, verify final state
|
|
||||||
5. Copy offer phases to test project, verify phase/task/deliverable counts
|
|
||||||
|
|
||||||
### Phase 8 (Public Quote Pages)
|
|
||||||
1. Brute-force token space: 1000 guesses/sec, verify rate limit activates
|
|
||||||
2. Enumerate tokens: generate 100 quotes, try to access one meant for different email, verify 401
|
|
||||||
3. Expiration test: set token_expires_at to past, verify access fails
|
|
||||||
4. Code audit: search response JSON for price, quote_items, per_service_price (should be zero matches)
|
|
||||||
5. Authorized access test: correct email + token, verify success
|
|
||||||
|
|
||||||
### Phase 9 (CRM — Won Automation)
|
|
||||||
1. Double-click "Win" button, verify only 1 client created
|
|
||||||
2. Network failure during "Win": simulate timeout after client creation, retry, verify same project returned
|
|
||||||
3. Partial failure: mock project creation failure, verify client not created (rollback)
|
|
||||||
4. Payment consistency: verify 2–4 payments created based on payment plan
|
|
||||||
5. Idempotency: call "Win" 10x with same idempotency_key, verify only 1 project, 1 set of payments
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Confidence Assessment
|
|
||||||
|
|
||||||
| Area | Level | Reason |
|
|
||||||
|------|-------|--------|
|
|
||||||
| Catalog consolidation risks | HIGH | Schema review shows existing quote_items dependencies; migration pattern verified via Drizzle docs |
|
|
||||||
| Offer copy semantics | HIGH | JavaScript shallow copy / deep copy distinction well-understood; transaction guarantees verified |
|
|
||||||
| Token security | HIGH | Brute-force math straightforward; nanoid 32 vs 21 entropy difference verified |
|
|
||||||
| CRM idempotency | HIGH | Idempotency pattern researched across multiple sources; double-click problem well-documented |
|
|
||||||
| Scope creep prevention | MEDIUM | CRM feature bloat is common, but solo consultant constraint makes Must/Should/Won't achievable |
|
|
||||||
| Concurrent edit handling | MEDIUM | Optimistic locking pattern standard, but requires careful implementation in Next.js + Drizzle |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps for Phase Planning
|
|
||||||
|
|
||||||
1. **Before Phase 7 design starts:**
|
|
||||||
- Finalize catalog consolidation migration plan (Expand-Contract timeline)
|
|
||||||
- Document offer/project hierarchy mapping (1 page, code-level documentation)
|
|
||||||
- Design offer copy function signature and transaction strategy
|
|
||||||
|
|
||||||
2. **Before Phase 7 code starts:**
|
|
||||||
- Implement drag-drop versioning (version field + optimistic locking)
|
|
||||||
- Write integration tests for offer phase copy
|
|
||||||
|
|
||||||
3. **Before Phase 8 design starts:**
|
|
||||||
- Token security requirements: length (32), expiration (7d), email validation, rate limit (3/min)
|
|
||||||
- API response schema: exclude quote_items, include total_price only
|
|
||||||
|
|
||||||
4. **Before Phase 9 design starts:**
|
|
||||||
- Finalize Must/Should/Won't scope (document with user/consultant)
|
|
||||||
- Design "Win" workflow: idempotency key + atomic transaction
|
|
||||||
- Define payment plan algorithm (2–4 payments based on offer tier)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## References for Implementation
|
|
||||||
|
|
||||||
- **Drizzle migrations:** https://dev.to/whoffagents/zero-downtime-postgres-migrations-with-drizzle-orm-22ga
|
|
||||||
- **Idempotency pattern:** https://codefarm0.medium.com/the-double-click-problem-how-idempotency-saved-our-checkout-system-a704be65d207
|
|
||||||
- **Optimistic updates:** https://www.nirtamir.com/articles/optimistic-updates-state-vs-render/
|
|
||||||
- **Schema evolution:** https://www.dataexpert.io/blog/backward-compatibility-schema-evolution-guide
|
|
||||||
- **Token security:** https://workos.com/blog/oauth-common-attacks-and-how-to-prevent-them/
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Document location:** `/Users/simonecavalli/Vault/IAMCAVALLI/.planning/research/PITFALLS_V2.md` (detailed pitfall reference)
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,126 +0,0 @@
|
|||||||
# 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 5–20 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 1–3: 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ì*
|
|
||||||
Reference in New Issue
Block a user