Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
16 KiB
ClientHub (IAMCAVALLI) — Status
Ultimo aggiornamento: 2026-08-20
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.5 "Audit" in pausa a Phase 27 a metà: in produzione c'è solo lo schema dell'audit, nessuna pagina. La pausa è una scelta del 2026-08-19 — prima le modifiche all'hub chieste il 2026-08-18, poi il motore.
| Milestone | Fasi | Stato |
|---|---|---|
| Modifiche hub | — | 🔨 in corso — dashboard e progetti in produzione, pipeline a metà |
| v2.5 Audit | 27–30 | ⏸️ in pausa — Phase 27 ~50% |
| v2.4 Post-vendita | 13, 26 | ✅ chiusa 2026-08-08, entrambe in produzione |
| 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 |
In corso
Modifiche hub (richieste 2026-08-18)
Tre aree: dashboard, progetti, pipeline. Piano in
~/.claude/plans/sei-arrivato-qua-search-recursive-kettle.md, fuori dal repo.
In produzione dal 2026-08-19 (commit 4b135ce → 19ed377):
- Progetti. Via il tab Commenti (duplicava
/admin/conversazioni, che è un sovrainsieme stretto) e il timer dalla lista. Riepilogo soldi + avanzamento in testa al progetto. Timer per fase e task — migration0018,ON DELETE SET NULLperché cancellare un task non deve cancellare le ore lavorate. - Dashboard. Inbox promossa in cima a piena larghezza, con da-quanto-aspetta e su
cosa è stato scritto. Analytics per linea di prodotto (Entry/Signature/Retainer,
letti dalla tassonomia). Timeline delle consegne con semaforo ritardo/anticipo,
scadenza dedotta da offerta + durata con override manuale
projects.due_date. - Pipeline.
POST /api/webhooks/lead, un endpoint solo per form del sito e bridge; regge payload piatto,fieldsannidati di Elementor e urlencoded, e non duplica chi compila due volte. Provato contro il DB di produzione via tunnel, poi ripulito.
In produzione dal 2026-08-20 (3fcb10d, 5547e55) — due modifiche uscite dall'uso
reale del pannello, nessuna migration (verificato: tasks.status è text senza CHECK):
- Rinomina di un valore di tassonomia da
/admin/impostazioni. Prima si poteva solo aggiungere o eliminare: per cambiare nome a una fase bisognava cancellarla — il che la strappava via da ogni servizio — e ricrearla a mano.renamePoolValuec'era già e propagava ovunque tranne in un punto:importOfferIntoProjectcopiaservices.fasedentrophases.titlee poi ritrova la fase confrontando i titoli, perchéphases.offer_phase_idesiste in schema ma non viene mai popolata. Un rename fermo al catalogo lasciava le fasi dei progetti col vecchio nome e al re-import ne nasceva una duplicata. Ora propaga anche lì, con lo stesso matchtrim+lowercasedel merge, ed è l'unico rename che chiede conferma — dicendo quante fasi e quanti progetti tocca. - Stato task "In revisione", fra "In corso" e "Fatto", visibile anche al cliente. Il
lavoro vero non era il nuovo stato ma i tre letterali ricopiati a mano in otto file:
ora tutto deriva da
TASK_STATUSESinsrc/lib/task-status.ts.
Cosa manca, e perché:
- [BLOCCANTE] TidyCal. Non ha webhook — è scritto nella loro FAQ, la strada
suggerita è Zapier/Make. Ha una REST API con Personal Access Token su tutti i piani,
ma path, filtri per data e paginazione stanno dietro il login e non sono
indicizzati. Serve che l'utente apra
tidycal.com/integrations→ API Keys e passi token o documentazione: dedurre la forma dell'API violerebbe la regola che il progetto si è già dato dopo il caso PageSpeed. - Alleggerire l'hub. Senza perimetro. Da affrontare guardando cosa è davvero poco usato, ora che dashboard e progetti sono in produzione.
- Whop → audit. Dipende dal motore: oggi sarebbe un innesco che non innesca nulla.
⚠️ Il tab Commenti permetteva di rispondere sulla singola entità;
replyToConversation salva sempre sul thread generale. Non è una regressione introdotta
ora — era già una scelta di prodotto — ma da oggi è l'unica via.
v2.5 — Audit (Phases 27–30), in pausa
Il servizio di analisi sito (tre livelli: Radiografia / Prima-Dopo / Rotta) diventa
un documento privato su /audit/[slug], generato da un motore multi-agente e rifinito a
mano prima della consegna. I tre livelli sono configurazioni di un unico documento:
i blocchi non pertinenti non esistono nel DOM.
Fatto finora (Phase 27, ~50%):
- [prod 2026-08-18] Schema. Migration
0017_audits.sql, 7 tabelle additive, piùchecklist_itemsseminata con 264 voci. Nessuna UI le legge ancora. - [prod 2026-08-19, inerte] Le fonti del motore.
src/lib/audit/sources/— PageSpeed, CrUX, Wayback, RDAP, robots/sitemap/JSON-LD, header. Provate sul campo su giojello.com: giro completo in 73 s, tutte e cinque hanno risposto. Deployate, ma nessuna route le chiama.
Manca: agent + sintetizzatore + pipeline, storage immagini, editor admin, pagina
pubblica. Dettaglio in .planning/ROADMAP.md e
.planning/REQUIREMENTS.md.
⚠️ I due piani della milestone stanno in ~/.claude/plans/, fuori dal repo
(…woolly-puddle.md per il documento, …radiant-valley.md per il motore). Senza quei
file la milestone non è ricostruibile.
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. - Una lista di valori validi dimentica in silenzio, una negazione no.
recomputePhaseStatusdecideva "fase iniziata" constatus === "in_progress" || status === "done". Aggiungendo "In revisione", una fase con tutti i task in revisione non rientrava né inallDonené inanyActivee retrocedeva a "upcoming": si leggeva "non iniziata" quando era quasi finita. Stesso schema inClientKanban, dove i task erano ripartiti da un oggetto a tre chiavi fisse invece che dalle colonne: un task fuori da quelle spariva da ogni colonna e da ogni contatore, e il cliente ne vedeva meno di quanti ce n'erano, senza errore. La causa a monte di entrambi era lo stessoasal confine del portale, che TypeScript non verifica. Quando un insieme di stati può crescere: derivare le colonne dalla costante, scrivere il predicato come negazione, e normalizzare al confine invece di castare. - Playwright non funziona contro
npm run dev: la CSP bloccaevale i client component non si idratano. Serve il build di produzione. - Le API di Google cambiano forma sotto i piedi, e in silenzio. Scrivendo
src/lib/audit/sources/due estrazioni dedotte dalla documentazione hanno restituito valori vuoti senza errore:largest-contentful-paint-elementnon esiste più (ora èlcp-breakdown-insight, consubpart/duratione senza percentuali) econfigSettings.screenEmulationnon esiste affatto nelle risposte pubbliche. Trovate solo perché le fonti sono state fatte girare su un sito vero prima di costruirci sopra. Ogni estrazione da un'API di terzi va vista funzionare, non dedotta dai docs. - Laboratorio e campo misurano cose diverse, e la differenza è il risultato. Su
giojello.com Lighthouse dà
server-response-time7 ms e CrUX dà TTFB p75 3.553 ms con l'1% di visite nel verde: il server risponde in fretta al datacenter Google e lento a tutti gli altri. Due numeri con lo stesso nome verrebbero fusi in uno — da quirisposta_server_msinvece dittfb_ms. - I punteggi PageSpeed ballano fra un giro e l'altro: performance mobile 52 e poi 64 sullo stesso sito a 30 minuti di distanza. Nei documenti consegnati un punteggio va sempre con la sua data, mai presentato come una costante del sito.
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/lib/audit/sources/ | Le 5 fonti del motore audit (nessun LLM) — fetch espone anche gli helper di rete condivisi |
| src/db/migrations/ | 0011 (email/phone), 0012 (offer_type), 0015 (OTP), 0016 (ciclo di vita), 0017 (audit) |
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/ |