Guardando il DB: Caruso Speaker / B ha offer_value_override = 7500 a fronte di un calcolo di 20.250. La 0020 non fa backfill, quindi quel numero è stato messo a mano dal tab Offerte — updateOfferValueOverride è provata sul campo, non più solo buildata. Resta scoperta Rossi Inc / B: venduta a 7.000, il portale mostra 20.250. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
12 KiB
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, 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)
- AUD-01: Schema additivo per audit, finding, ottimizzazioni, rubrica, esiti, run e visite — migration
0017_audits.sql, in prod 2026-08-18 - AUD-02: La rubrica del motore (264 voci falsificabili) vive in
checklist_items, non nel documento — in prod 2026-08-18 - 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 - 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
- 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 —
notaincrux.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 inerrore "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 diproxy.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 (StatusBadgeper gli impatti). Decisione del 2026-08-18, sostituisce la deroga tipografica prevista dal piano. Due conseguenze: i font sono già self-hostati danext/font/google, quindi la CSPfont-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.
- HUB-01: Via il tab Commenti dal progetto —
/admin/conversazionili aggrega già tutti con l'etichetta dell'entità. Perde solo la risposta sulla singola entità, che era già confluita sul thread generale. - HUB-02: Via il timer dalla lista progetti — si avvia dove c'è il contesto
- HUB-03: Riepilogo soldi + avanzamento in testa al progetto, senza query nuove
- HUB-04: Timer per fase e task — migration
0018,ON DELETE SET NULLperché le ore sopravvivono al task - HUB-05: Inbox in cima alla dashboard, con da-quanto-aspetta e contesto del messaggio
- HUB-06: Analytics per linea di prodotto (Entry/Signature/Retainer) dalla tassonomia, con l'incassato non attribuibile mostrato a parte
- HUB-07: Timeline delle consegne con semaforo ritardo/anticipo; scadenza dedotta da offerta + durata,
projects.due_datecome override - 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_SECRETsu 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)
- 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. - 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, migration0020. 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.
updateOfferValueOverrideè provata (Caruso Speaker ha un override a 7.500 messo dal pannello); restano da cliccarereorderTasks,updatePaymentField,clearPaymentOverridee il drag, 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/serviceSchemadead 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 sunode: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/<id> →
"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).