docs: STATUS.md unico documento narrativo, STATE.md a digest
STATUS.md e .planning/STATE.md raccontavano la stessa storia in due posti
con date diverse, e STATE.md si contraddiceva: frontmatter fermo a
"milestone v2.3 / executing" quando v2.3 e shipped, l'anteprima admin data
per "NON committata" mentre e il commit 187550f deployato l'8 agosto,
Session Continuity ferma al 29/07 e la tabella Performance Metrics spezzata
a meta.
Il template GSD dice esplicitamente che STATE.md deve stare sotto le 100
righe ("a DIGEST, not an archive"): ne aveva 177, quasi tutte narrativa.
- STATUS.md assorbe la narrativa e diventa l'unico posto dove si racconta
il progetto. Nuova sezione "Lezioni operative" per le trappole in cui si
ricasca: il gate OTP non va nel layout App Router (il payload RSC
trapela), ricreare il dominio Resend rigenera la chiave DKIM, .env.local
non e allineato a produzione dal 28/07, Playwright non funziona contro
npm run dev
- STATE.md sceso a 98 righe, con i campi che state.cjs legge davvero.
Frontmatter corretto a v2.4, blocchi gia risolti (DKIM, env Coolify)
rimossi, nulla risulta piu "non committato"
- il debito design era sottostimato: non 11 pagine ma ~40 file e ~450
occorrenze. Esclusi perche legittimi AdminSidebar (eccezione brand),
mailer.ts (HTML email) e i colori di stato di StatusBadge
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,44 +1,132 @@
|
||||
# ClientHub (IAMCAVALLI) — Status
|
||||
|
||||
_Ultimo aggiornamento: 2026-08-01_
|
||||
_Ultimo aggiornamento: 2026-08-08_
|
||||
|
||||
Questo è **l'unico documento narrativo** del progetto: a che punto siamo, cosa manca,
|
||||
cosa abbiamo imparato. `.planning/STATE.md` è il digest che leggono i comandi
|
||||
`/gsd-*` — non raddoppia questo file, ci rimanda.
|
||||
|
||||
## Stato attuale
|
||||
|
||||
In prod su Coolify (Gitea → deploy automatico su push a `main`). Build verde, `npm audit` pulito.
|
||||
In produzione su `hub.iamcavalli.net` (Coolify/Hetzner, deploy automatico su push a
|
||||
`main` via Gitea). Build verde, `npm audit` pulito.
|
||||
|
||||
Milestone **v2.3 "Email & Accesso" chiusa e in produzione** dal 2026-07-29: il portale cliente non è più apribile col solo link. In corso **v2.4 Phase 13** — ciclo di vita dei servizi ricorrenti.
|
||||
Milestone **v2.4 "Post-vendita"** — Phase 13 e Phase 26 consegnate e in produzione.
|
||||
Nessun lavoro in sospeso non committato.
|
||||
|
||||
---
|
||||
| Milestone | Fasi | Stato |
|
||||
|---|---|---|
|
||||
| v2.4 Post-vendita | 13, 26 | 🔨 in corso — consegnato tutto ciò che era pianificato |
|
||||
| v2.3 Email & Accesso | 23–25 | ✅ shipped 2026-07-29, verificata E2E |
|
||||
| v2.2 Sales Loop | 18–22 | ✅ shipped 2026-06-20 |
|
||||
| v2.1 Offer Studio + CRM | 11, 12, 14 | ✅ chiusa per reset 2026-06-19 |
|
||||
| v1.0 + v2.0 | 1–10 | ✅ shipped giugno 2026 |
|
||||
|
||||
## Fatto (recente, cumulativo)
|
||||
## Fatto
|
||||
|
||||
- **[v2.4, in corso] Ciclo di vita dei servizi ricorrenti**: `project_offers.status` (attivo/sospeso/cessato) + `end_date` (migr. 0016, in prod). Prima un retainer non poteva finire e il forecast lo sommava a ogni mese in eterno. Comandi Sospendi/Riattiva/Cessa nella tab Offerte del progetto; il cliente vede stato, "attivo dal" e canone mensile.
|
||||
- **[v2.3] Gate OTP sul portale cliente**: whitelist email per cliente (`client_emails`), codice a 6 cifre via Resend, sessione firmata 90 giorni, revoca in blocco dall'admin (`clients.sessions_valid_from`). Migr. 0015. Verificato E2E in produzione.
|
||||
- **[2026-07] Audit di sicurezza chiuso**: 4 vulnerabilità risolte e deployate, slug clienti ruotati a 12 char CSPRNG, `INTERNAL_SECRET` e `ADMIN_PASSWORD` configurati su Coolify. Report in `.planning/SECURITY-*.md`.
|
||||
- **Tassonomie centralizzate** in Impostazioni (modello Notion, pool persistenti `src/lib/taxonomy.ts`).
|
||||
- **Lead → Cliente**: `clients.email/phone` + `leads.archived` (migr. 0011). `convertLeadToClient` riusa `createClientCore`, porta i transcript, archivia il lead mantenendo "won".
|
||||
- **Offerta → Fasi/Task**: `importOfferIntoProject` crea fasi raggruppando i servizi del tier per `services.fase`.
|
||||
- **Offerte (modello + UI)**: `offer_macros.offer_type` ('una_tantum'|'retainer') + toggle "Modalità" nell'editor (migr. 0012). Tab Offerte a 2 step (Offerta → Tier con prezzo).
|
||||
### v2.4 — Post-vendita
|
||||
|
||||
- **[Phase 13, prod 2026-08-01] Ciclo di vita dei servizi ricorrenti.**
|
||||
`project_offers.status` (attivo/sospeso/cessato) + `end_date` (migr. 0016). Prima
|
||||
un retainer non poteva finire e il forecast lo sommava a ogni mese in eterno.
|
||||
Comandi Sospendi/Riattiva/Cessa nella tab Offerte; il cliente vede stato, "attivo
|
||||
dal" e canone mensile. Dettaglio: [`.planning/phases/13-…/13-SUMMARY.md`](.planning/phases/13-ciclo-vita-servizi-ricorrenti/13-SUMMARY.md)
|
||||
- **[Phase 26, prod 2026-08-08] Anteprima admin del portale + toggle password.**
|
||||
`?preview=1` più una sessione Auth.js valida apre il portale di un cliente in
|
||||
**sola lettura**, senza passare dal gate OTP. Deviazione consapevole dal vincolo
|
||||
LOCKED #4, annotata in `CLAUDE.md`. Dettaglio: [`.planning/phases/26-…/26-SUMMARY.md`](.planning/phases/26-anteprima-admin-e-login/26-SUMMARY.md)
|
||||
|
||||
### v2.3 — Email & Accesso
|
||||
|
||||
- **Gate OTP sul portale cliente**: whitelist email per cliente (`client_emails`),
|
||||
codice a 6 cifre via Resend, sessione firmata 90 giorni, revoca in blocco
|
||||
dall'admin (`clients.sessions_valid_from`). Migr. 0015. Verificato E2E in
|
||||
produzione. Archivio: [`v2.3-ROADMAP.md`](.planning/milestones/v2.3-ROADMAP.md)
|
||||
|
||||
### Prima di v2.3
|
||||
|
||||
- **[2026-07] Audit di sicurezza chiuso**: 4 vulnerabilità risolte e deployate, slug
|
||||
clienti ruotati a 12 char CSPRNG, `INTERNAL_SECRET` e `ADMIN_PASSWORD` configurati
|
||||
su Coolify. Report in [`.planning/security/`](.planning/security/).
|
||||
- **Design system "Quiet Luxury"**: dashboard, liste (Clienti/Offerte/Catalogo/
|
||||
Preventivi/Progetti), Conversazioni, Impostazioni, Pipeline+Kanban, dettaglio Lead
|
||||
e portale cliente base sono a token semantici e dual-theme.
|
||||
- **Tassonomie centralizzate** in Impostazioni (modello Notion, pool persistenti
|
||||
`src/lib/taxonomy.ts`).
|
||||
- **Lead → Cliente**: `clients.email/phone` + `leads.archived` (migr. 0011).
|
||||
`convertLeadToClient` riusa `createClientCore`, porta i transcript, archivia il
|
||||
lead mantenendo "won".
|
||||
- **Offerta → Fasi/Task**: `importOfferIntoProject` crea fasi raggruppando i servizi
|
||||
del tier per `services.fase`.
|
||||
- **Offerte (modello + UI)**: `offer_macros.offer_type` ('una_tantum'|'retainer') +
|
||||
toggle "Modalità" nell'editor (migr. 0012). Tab Offerte a 2 step.
|
||||
|
||||
## Da fare
|
||||
|
||||
- [ ] **Whitelist portale**: dei 4 clienti solo alcuni hanno email autorizzate. Chi ha la whitelist vuota non entra nel proprio portale — si popola da `/admin/clients/<id>` → "Accessi al portale".
|
||||
- [ ] **Fasi/Task dall'offerta** funzionano solo se i servizi hanno il campo **Fase** valorizzato nel Catalogo (altrimenti finiscono in "Generale").
|
||||
- [ ] **Whitelist portale**: dei 4 clienti solo uno ha email autorizzate. Chi ha la
|
||||
whitelist vuota **non entra nel proprio portale** — si popola da
|
||||
`/admin/clients/<id>` → "Accessi al portale", poi va reinviato il link.
|
||||
- [ ] **Fasi/Task dall'offerta** funzionano solo se i servizi hanno il campo **Fase**
|
||||
valorizzato nel Catalogo (altrimenti finiscono in "Generale").
|
||||
- [ ] **Debito design (DEBT-01)**: **~40 file, ~450 occorrenze** di palette Tailwind
|
||||
raw e hex literal invece 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` (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), i colori di stato di `StatusBadge` (sanzionati dal design system,
|
||||
hanno già le varianti `dark:`).
|
||||
- [ ] Micro legacy "Mantenimento" senza tier: valutare se rimuoverlo/normalizzarlo.
|
||||
- [ ] **Debito design**: 11 pagine ancora a palette raw invece che a token semantici. Le più pesanti: `/admin/projects/[id]` (~140 occorrenze fra i suoi tab), `/admin/offers/[id]/edit`, e tutto `/quote/[token]` (~40, ed è rivolto al cliente). Anche `ui/dialog.tsx`, che propaga il look vecchio a ogni modale.
|
||||
- [ ] **Backlog v2.5**: canoni mensili tracciabili (agosto pagato / settembre no) — serve una tabella nuova, `payments` è protetta e la sua riscalatura è pensata per i piani una tantum. Più PROP-03 (Stripe sul deck), PROP-04 (auto-provisioning al "Vinto"), SEND-01/02 (invio preventivo via email — il mailer è già pronto).
|
||||
- [ ] **Backlog**: canoni mensili tracciabili (RET-06 — serve una tabella nuova),
|
||||
PROP-03 (Stripe sul deck), PROP-04 (auto-provisioning al "Vinto"), SEND-01/02
|
||||
(invio preventivo via email — il mailer è già pronto). Elenco completo in
|
||||
[`.planning/REQUIREMENTS.md`](.planning/REQUIREMENTS.md).
|
||||
|
||||
## Lezioni operative
|
||||
|
||||
Cose imparate a caro prezzo. Non sono documentazione di feature: sono trappole in
|
||||
cui si ricasca.
|
||||
|
||||
- **Il gate OTP non va nel layout.** Prima implementazione: gate in
|
||||
`client/[token]/layout.tsx` che rendeva `<OtpGate/>` al posto di `{children}`.
|
||||
**Non protegge nulla.** Nell'App Router il segmento `page` è renderizzato in
|
||||
parallelo al layout: la dashboard spariva a schermo ma fasi, task e pagamenti
|
||||
restavano leggibili nel payload RSC dell'HTML (46.907 byte → 17.594 dopo il fix).
|
||||
Il gate sta in cima alla `page`, prima di ogni query, via `getClientGate()`.
|
||||
**Ogni nuova route sotto `/client/[token]/` deve fare lo stesso.**
|
||||
- **Ricreare il dominio su Resend rigenera la chiave DKIM.** Se il dominio torna
|
||||
`failed`, non fidarsi di valori DKIM annotati in passato: rileggerli da
|
||||
`GET /domains` e confrontarli con
|
||||
`dig +short TXT resend._domainkey.iamcavalli.net @8.8.8.8`.
|
||||
- **`.env.local` non è allineato a produzione.** `ADMIN_PASSWORD` e
|
||||
`NEXTAUTH_SECRET` sono stati ruotati il 2026-07-28 **solo su Coolify**. Per
|
||||
costruire firme o hash validi in prod vanno letti da Coolify, non da `.env.local`.
|
||||
- **L'API Coolify rifiuta `is_build_time`** con 422 sul POST a
|
||||
`/api/v1/applications/<uuid>/envs`: mandare solo `key`, `value`, `is_preview`.
|
||||
- **Playwright non funziona contro `npm run dev`**: la CSP blocca `eval` e i client
|
||||
component non si idratano. Serve il build di produzione.
|
||||
|
||||
## Note tecniche
|
||||
|
||||
- **Il DB di `.env.local` È la produzione** (178.104.27.55). Non esiste un database di sviluppo separato: qualunque cosa si esegua in locale scrive su dati reali. Verificare i conteggi delle tabelle protette prima e dopo ogni prova.
|
||||
- **Migrazioni**: SQL scritto a mano in `src/db/migrations/` (`drizzle-kit generate` è rotto — vanno tenuti in sync `schema.ts` e l'SQL). Si applicano **da locale via SSH + docker exec**, senza tunnel:
|
||||
`cat src/db/migrations/NNNN.sql | ssh root@178.104.27.55 "docker exec -i xwkk0040w0kk0gsgcgog8owk psql -U clienthub -d clienthub -v ON_ERROR_STOP=1 --single-transaction"`
|
||||
Il tunnel `ssh -f -N -L 54321:localhost:54321` serve solo per puntare il tooling locale (es. `npx tsx`) al DB di prod, riscrivendo l'host di `DATABASE_URL` a `127.0.0.1:54321`.
|
||||
- **Ordine di deploy con schema**: applicare la migrazione a prod **prima** del push (il deploy fa girare subito il codice nuovo).
|
||||
- **`NEXTAUTH_SECRET` locale ≠ produzione**: per costruire firme o hash validi in prod va letto da Coolify, non da `.env.local`.
|
||||
- **Coolify API**: credenziali in `~/.coolify.env` (formato `export VAR=…`, va sorgentato). App uuid `xsksow44g4kcoo8wocsgkscc`. Il POST su `/api/v1/applications/<uuid>/envs` rifiuta il campo `is_build_time` con 422.
|
||||
- **`overrides` in package.json** forzano `postcss >= 8.5.18` e `sharp >= 0.35.0`: le versioni che Next si porta dietro hanno CVE high e non c'è fix upstream. Se un aggiornamento di Next rompe qualcosa, è il primo posto dove guardare.
|
||||
- **Il DB di `.env.local` È la produzione** (178.104.27.55). Non esiste un database
|
||||
di sviluppo separato: qualunque cosa si esegua in locale scrive su dati reali.
|
||||
Verificare i conteggi delle tabelle protette prima e dopo ogni prova.
|
||||
- **Migrazioni**: SQL scritto a mano in `src/db/migrations/` (`drizzle-kit generate`
|
||||
è rotto — vanno tenuti in sync `schema.ts` e l'SQL). Si applicano **da locale via
|
||||
SSH + docker exec**, senza tunnel — procedura completa in `CLAUDE.md`. Il tunnel
|
||||
`ssh -f -N -L 54321:localhost:54321` serve solo per puntare il tooling locale
|
||||
(es. `npx tsx`) al DB di prod.
|
||||
- **Ordine di deploy con schema**: applicare la migrazione a prod **prima** del push
|
||||
(il deploy fa girare subito il codice nuovo).
|
||||
- **Coolify API**: credenziali in `~/.coolify.env` (formato `export VAR=…`, va
|
||||
sorgentato). App uuid `xsksow44g4kcoo8wocsgkscc`.
|
||||
- **`overrides` in package.json** forzano `postcss >= 8.5.18` e `sharp >= 0.35.0`:
|
||||
le versioni che Next si porta dietro hanno CVE high e non c'è fix upstream. Se un
|
||||
aggiornamento di Next rompe qualcosa, è il primo posto dove guardare.
|
||||
- `offer_micros` non ha `created_at` (no "tier più vecchio" affidabile).
|
||||
- **Debito tecnico non bloccante**: tabelle legacy `service_catalog` /
|
||||
`offer_services` / `offer_micro_services` come deadweight; `createService` /
|
||||
`serviceSchema` dead code in `src/app/admin/catalog/actions.ts`.
|
||||
|
||||
## File chiave
|
||||
|
||||
@@ -54,3 +142,16 @@ Milestone **v2.3 "Email & Accesso" chiusa e in produzione** dal 2026-07-29: il p
|
||||
| src/components/admin/tabs/OffersTab.tsx | Tab Offerte + comandi ciclo di vita |
|
||||
| src/lib/admin-queries.ts / client-view.ts | I due layer separati: admin vs proiezioni client-safe |
|
||||
| src/db/migrations/ | 0011 (email/phone), 0012 (offer_type), 0015 (OTP), 0016 (ciclo di vita) |
|
||||
|
||||
## Dove sta il resto
|
||||
|
||||
| Cosa | Dove |
|
||||
|---|---|
|
||||
| Regole per Claude, procedure deploy/DB, vincoli LOCKED | `CLAUDE.md` |
|
||||
| Digest di stato per i comandi `/gsd-*` | `.planning/STATE.md` |
|
||||
| Requisiti e backlog della milestone corrente | `.planning/REQUIREMENTS.md` |
|
||||
| Roadmap e storico milestone | `.planning/ROADMAP.md`, `.planning/MILESTONES.md` |
|
||||
| Archivi delle milestone chiuse | `.planning/milestones/` |
|
||||
| Fasi della milestone in corso | `.planning/phases/` |
|
||||
| Report dell'audit di sicurezza (chiuso) | `.planning/security/` |
|
||||
| Design system e mock per pagina | `design-reference/` |
|
||||
|
||||
Reference in New Issue
Block a user