0aad9caf46
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>
8.9 KiB
8.9 KiB
ClientHub (IAMCAVALLI) — Status
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 produzione su hub.iamcavalli.net (Coolify/Hetzner, deploy automatico su push a
main via Gitea). Build verde, npm audit pulito.
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
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 - [Phase 26, prod 2026-08-08] Anteprima admin del portale + toggle password.
?preview=1più 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 inCLAUDE.md. Dettaglio:.planning/phases/26-…/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
Prima di v2.3
- [2026-07] Audit di sicurezza chiuso: 4 vulnerabilità risolte e deployate, slug
clienti ruotati a 12 char CSPRNG,
INTERNAL_SECRETeADMIN_PASSWORDconfigurati su Coolify. Report in.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).convertLeadToClientriusacreateClientCore, porta i transcript, archivia il lead mantenendo "won". - Offerta → Fasi/Task:
importOfferIntoProjectcrea fasi raggruppando i servizi del tier perservices.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 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.tsxche propaga il look vecchio a ogni modale. Esclusi perché legittimi:AdminSidebar(eccezione brand documentata),src/lib/mailer.ts(HTML email), i colori di stato diStatusBadge(sanzionati dal design system, hanno già le variantidark:). - Micro legacy "Mantenimento" senza tier: valutare se rimuoverlo/normalizzarlo.
- 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.
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.tsxche rendeva<OtpGate/>al posto di{children}. Non protegge nulla. Nell'App Router il segmentopageè 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 allapage, prima di ogni query, viagetClientGate(). 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 daGET /domainse confrontarli condig +short TXT resend._domainkey.iamcavalli.net @8.8.8.8. .env.localnon è allineato a produzione.ADMIN_PASSWORDeNEXTAUTH_SECRETsono 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_timecon 422 sul POST a/api/v1/applications/<uuid>/envs: mandare solokey,value,is_preview. - Playwright non funziona contro
npm run dev: la CSP bloccaevale 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 syncschema.tse l'SQL). Si applicano da locale via SSH + docker exec, senza tunnel — procedura completa inCLAUDE.md. Il tunnelssh -f -N -L 54321:localhost:54321serve 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(formatoexport VAR=…, va sorgentato). App uuidxsksow44g4kcoo8wocsgkscc. overridesin package.json forzanopostcss >= 8.5.18esharp >= 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_microsnon hacreated_at(no "tier più vecchio" affidabile).- Debito tecnico non bloccante: tabelle legacy
service_catalog/offer_services/offer_micro_servicescome deadweight;createService/serviceSchemadead code insrc/app/admin/catalog/actions.ts.
File chiave
| File | Scopo |
|---|---|
| src/proxy.ts | Middleware (Next 16 lo chiama proxy, non middleware) |
| src/lib/client-gate.ts | Gate OTP — va chiamato in cima a ogni page sotto /client/[token]/ |
| src/lib/otp.ts, client-session.ts | Codici OTP e cookie di sessione 90gg |
| src/lib/mailer.ts | Unico punto di invio email (Resend) |
| src/lib/forecast-queries.ts | Forecast 12 mesi — rispetta stato e end_date dei retainer |
| src/lib/taxonomy.ts | Pool tassonomie (Impostazioni) |
| src/app/admin/projects/project-actions.ts | importOfferIntoProject, setProjectOfferLifecycle, piani pagamento |
| 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/ |