# Requirements: ClientHub v2.5 Audit **Definiti:** 2026-08-16 (piano approvato) · **rivisti:** 2026-08-18 (motore) **Core Value della milestone:** L'imprenditore paga un'analisi del suo sito e riceve un documento che gli dice, con numeri misurati, cosa non funziona e cosa costa — non un elenco di quaranta punti generato da un tool gratuito. Milestone precedente: [v2.4 Post-vendita](milestones/v2.4-REQUIREMENTS.md), chiusa 2026-08-08. Piani di riferimento (fuori dal repo, in `~/.claude/plans/`): `dovremmo-fare-una-cosa-woolly-puddle.md` (documento, editor, template) + `vorrei-solo-farti-capire-radiant-valley.md` (motore — sostituisce §6/§7 del primo). ## Il prodotto Tre livelli venduti, che sono **configurazioni di un unico documento**, non tre documenti: | Livello | Blocchi inclusi | |---|---| | **Radiografia** | 1, 2, 2b, 3, 4, 5, 8, 9 | | **Prima/Dopo** | + 6 (il redesign), 6b (cosa il redesign non risolve) | | **Rotta** | + 7 (le ottimizzazioni, con priorità e impegno in giornate) | I blocchi non pertinenti **non esistono nel DOM**, non sono nascosti via CSS. ## Requisiti ### Motore (Phase 27) - [x] **AUD-01**: Schema additivo per audit, finding, ottimizzazioni, rubrica, esiti, run e visite — *migration `0017_audits.sql`, in prod 2026-08-18* - [x] **AUD-02**: La rubrica del motore (264 voci falsificabili) vive in `checklist_items`, non nel documento — *in prod 2026-08-18* - [x] **AUD-03**: Le fonti raccolgono **rilevazioni, non stime**: PageSpeed (153 audit sul DOM renderizzato), CrUX (utenti reali), Wayback, RDAP, robots/sitemap/JSON-LD, header — *`src/lib/audit/sources/`, provato sul campo 2026-08-18, non ancora pushato* - [x] **AUD-04**: Ogni fonte fallisce in modo **non fatale** e dice *perché*: "non ha risposto" e "ha risposto che non ci sono dati" sono informazioni diverse - [x] **AUD-05**: Quando CrUX non ha dati di campo il documento lo **dice** ("i visitatori non sono abbastanza numerosi perché Google raccolga dati"), non lascia un buco — *`nota` in `crux.ts`; il caso "zero dati" resta da vedere su un sito vero* - [ ] **AUD-06**: Ogni output di modello è validato con Zod, `safeParse`, fallimento duro — nessun loop di riparazione (precedente: `src/lib/proposal/schema.ts`) - [ ] **AUD-07**: Quattro sub-agent in parallelo (checklist, visivo, storico, tecnico) più un sintetizzatore che **incrocia** le loro osservazioni in un solo finding con più evidenze indipendenti - [ ] **AUD-08**: Massimo **10 finding**, ordinati per impatto su tre soli valori (`alto|medio|basso`); la sfumatura sta nell'ordine dentro il gruppo - [ ] **AUD-09**: Disciplina sui numeri imposta nel prompt di sistema — un numero entra nel documento solo se misurato, e ogni numero consegnato è rintracciabile in `audit_runs.raw` - [ ] **AUD-10**: Fan-out con tetto di concorrenza e retry con backoff sulle 429/529 di Anthropic - [ ] **AUD-11**: Heartbeat a ogni passo su `audit_runs`; una run senza battito va in `error` e "Rilancia" riparte dall'ultimo passo completato *(un redeploy Coolify uccide un job in corso)* ### Storage immagini (Phase 28) - [ ] **AUD-12**: Volume persistente Coolify su `/app/uploads`, lettura da `/api/uploads/[...path]` con guardia sul path traversal, whitelist MIME e limite di dimensione - [ ] **AUD-13**: Due immagini caricate a mano per audit (hero **prima** e **dopo** del redesign, JPG ≤ 512 KB); due scritte dalla pipeline (screenshot mobile e desktop da PageSpeed) ### Editor admin (Phase 29) - [ ] **AUD-14**: Creazione **manuale** di un audit (livello, profilo, URL, cliente/lead). L'ingresso Whop è predisposto nello schema (`origin`, `external_ref`) ma **non costruito** - [ ] **AUD-15**: Editor a payload intero (modello: `admin/offers/actions.ts`) che **salva sempre, anche a metà** — tutti i campi di contenuto sono nullable, la validazione di completezza scatta solo alla consegna - [ ] **AUD-16**: Riordino di finding e ottimizzazioni con `@dnd-kit/sortable`, con re-sync degli id dei figli - [ ] **AUD-17**: Il **blocco 8 (La direzione) resta manuale, foglio bianco** — è il blocco che giustifica il prezzo; se diventa formula il cliente lo sente - [ ] **AUD-18**: Il registro delle visite è visibile nell'editor, in ordine cronologico ### Documento pubblico (Phase 30) - [ ] **AUD-19**: `/audit/[slug]` — pagina privata, `X-Robots-Tag: noindex, nofollow`, rate limit sul matcher di `proxy.ts` - [ ] **AUD-20**: Il template è **congelato alla creazione** (`template_version`): migliorare il documento tocca gli audit successivi, mai quelli già consegnati - [ ] **AUD-21**: PDF via **print CSS**, non libreria: interruzioni di pagina corrette, slider impilato in due immagini, **nessun marcatore di lavorazione sopravvissuto** - [ ] **AUD-22**: In **scala di grigi** impatti e metriche restano distinguibili — il colore non può essere l'unico portatore di informazione - [ ] **AUD-23**: Il documento usa **il design system dell'area admin** ("Quiet Luxury", `design-reference/DESIGN-SYSTEM.md`): token semantici, Plus Jakarta Sans per il testo, **Geist Mono per metriche, punteggi e date**, e i primitivi già esistenti (`StatusBadge` per gli impatti). *Decisione del 2026-08-18, sostituisce la deroga tipografica prevista dal piano.* Due conseguenze: i font sono già self-hostati da `next/font/google`, quindi la CSP `font-src 'self'` è soddisfatta senza lavoro; e il documento **non aggiunge debito a DEBT-01** perché nasce già a token. - [ ] **AUD-24**: Tracciamento delle aperture (`view`) e delle stampe (`print`) via isola client + Server Action; **un admin loggato non viene contato** (altrimenti i numeri li inquiniamo noi rileggendo le bozze) - [ ] **AUD-25**: L'IP non si salva in chiaro — SHA-256 di `ip + NEXTAUTH_SECRET`, come il digest del gate admin ## Vincoli che questa milestone tocca - **LOCKED #5 (no file hosting)** — emendato limitatamente agli asset di audit, deroga già annotata in `CLAUDE.md`. Non estendere ad altre entità. - **Nessun renderer headless, da nessuna parte.** Il VPS non regge Chromium (RAM), e non serve: gli audit Lighthouse arrivano già fatti sul DOM renderizzato. ## Modifiche hub (richieste 2026-08-18, in corso) Fuori dalla milestone v2.5, che è in pausa. Piano in `~/.claude/plans/sei-arrivato-qua-search-recursive-kettle.md`. - [x] **HUB-01**: Via il tab Commenti dal progetto — `/admin/conversazioni` li aggrega già tutti con l'etichetta dell'entità. *Perde solo la risposta sulla singola entità, che era già confluita sul thread generale.* - [x] **HUB-02**: Via il timer dalla lista progetti — si avvia dove c'è il contesto - [x] **HUB-03**: Riepilogo soldi + avanzamento in testa al progetto, senza query nuove - [x] **HUB-04**: Timer per fase e task — migration `0018`, `ON DELETE SET NULL` perché le ore sopravvivono al task - [x] **HUB-05**: Inbox in cima alla dashboard, con da-quanto-aspetta e contesto del messaggio - [x] **HUB-06**: Analytics per linea di prodotto (Entry/Signature/Retainer) dalla tassonomia, con l'incassato non attribuibile mostrato a parte - [x] **HUB-07**: Timeline delle consegne con semaforo ritardo/anticipo; scadenza dedotta da offerta + durata, `projects.due_date` come override - [x] **HUB-08**: `POST /api/webhooks/lead` — un endpoint per form del sito e bridge, con dedup sull'email - [ ] **HUB-09**: Confermare la forma del payload Elementor con un invio **vero** — oggi è gestita in modo difensivo - [ ] **HUB-10**: `LEAD_WEBHOOK_SECRET` su Coolify — finché manca, la route risponde 403 a tutti - [ ] **HUB-11**: TidyCal. **[BLOCCANTE]** Niente webhook (loro FAQ): serve polling della REST API. Path e filtri stanno dietro il login → servono token o documentazione dall'utente - [ ] **HUB-12**: Alleggerire l'hub. Senza perimetro: si definisce guardando cosa è poco usato - [ ] **HUB-13**: Whop → progetto + audit automatico. Dipende dal motore v2.5 (AUD-06→11) - [x] **HUB-14**: Rifiniture dall'uso reale del pannello — rinomina di un valore di tassonomia con propagazione alle fasi dei progetti, stato task "In revisione", tab pagamenti con ordine stabile e importi a mano, riordino task per trascinamento. *In prod 2026-08-20, migration `0019`.* - [x] **HUB-15**: Portale cliente — barra di avanzamento compatta e a tutta larghezza, card offerta senza accordion, "Valore dell'offerta" con override admin (`project_offers.offer_value_override`). *In prod 2026-08-21, migration `0020`. Serviva perché la somma dei prezzi di catalogo mostrava €20.250 su offerte vendute a 7.000 e 5.500.* - [ ] **HUB-16**: Guardare a mano HUB-14 e HUB-15 in produzione — le scritture nuove (`reorderTasks`, `updatePaymentField`, `clearPaymentOverride`, `updateOfferValueOverride`) e il drag non sono mai state **cliccate**, e dal 2026-08-21 la verifica locale contro i dati veri non è più possibile ## Backlog (ereditato, nessuno in corso) - [ ] **SEND-01 / SEND-02** — Invio del link `/preventivo/[slug]` via email. Il mailer è già in produzione dalla v2.3: manca il pulsante e l'action. *Rinviati il 2026-07-28.* - [ ] **PROP-03** — Stripe Payment Link sul deck pubblico del preventivo. - [ ] **PROP-04** — Auto-provisioning cliente / progetto / fasi al passaggio del lead a "Vinto". - [ ] **RET-06** — Canoni mensili tracciabili. **Serve una tabella nuova**: `payments` è protetta dai vincoli di Data Safety. - [ ] **OFFER-14** — Sezioni analitiche stile Notion sull'offerta. - [ ] **ARCH-01** — Split del modulo "compartimento stagno" in un deploy separato. *Solo se cresce.* - [ ] **DEBT-01** — Debito design: ~40 file, ~450 occorrenze di palette raw/hex al posto dei token. Cluster in `/admin/projects/[id]` (~182), `/admin/offers/[id]/edit` (~79), `/admin/clients/[id]` (~59), `/quote/[token]` (~48, ed è rivolto al cliente), `ChatPanel` (37), `ui/dialog.tsx`. *Misurato il 2026-08-08.* - [ ] **DEBT-02** — Tabelle legacy `service_catalog` / `offer_services` / `offer_micro_services`; `createService` / `serviceSchema` dead code. ## Rinviati esplicitamente da v2.5 - **Allegato tecnico** — seconda vista sugli stessi finding con registro da sviluppatore (selettori, file, stime). Renderebbe vera la promessa del blocco 9 *"il documento resta tuo e puoi darlo a chiunque lavorerà sul sito"*. Da valutare **dopo il primo audit consegnato**. - **Affettare lo screenshot a pagina intera** — richiede `sharp`, da verificare su `node:20-alpine`. Non serve in fase 1: `final-screenshot` (250×498) è leggibile. - **Ingresso via webhook Whop** — schema predisposto, costruzione in fase 2. ## Aperto, non un requisito **`.env.local` da riallineare a Coolify.** `ADMIN_PASSWORD`, `NEXTAUTH_SECRET` **e la password del DB** sono stale, e l'host che il file dichiara (`178.104.27.55:5432`) è chiuso dal firewall — il DB vero è su `127.0.0.1:54321` dietro tunnel. Finché resta così **una modifica al portale si verifica solo in produzione**: build, migration e query di controllo, poi occhio umano sul sito. Recuperare la password viva dal container è bloccato dal classifier dei permessi e non va aggirato: la sblocca l'utente copiando le variabili da Coolify. Le migration non ne soffrono (`docker exec` non usa quelle credenziali). **Whitelist del portale vuota per 3 clienti su 4.** La migration 0015 ha seedato solo `mario@test.it`. Protocollo Estetico, Caruso Speaker e Teckell hanno whitelist vuota e finché lo è **il loro portale non è accessibile**. Si popola da `/admin/clients/` → "Accessi al portale", poi va reinviato il link. ## Fuori scope - Tabella utenti / multi-admin: l'auth resta una singola credenziale da env. - File hosting per i documenti del portale cliente: restano URL esterni (LOCKED #5, non emendato per quelli).