La cartella aveva dentro solo rules/ e i settings: nessun posto dove mettere una skill, un hook o un piano. Ora ha lo scheletro completo e un .claude/CLAUDE.md che spiega cosa va dove — non duplica CLAUDE.md di progetto, che resta quello che comanda. Due skill locali (le altre restano globali in ~/.claude/skills/): - /preventivo — la catena agent.ts → schema.ts → assemble.ts → ProposalDeck e i tre modi di romperla, di cui uno solo fa rumore. Nessun prompt di generazione qui dentro: quello vive in agent.ts ed e' l'unico. Porta check-profilo.sh. - /audit — guida scripts/audit-fonti.ts, nuovo, che mette in moto le cinque fonti di src/lib/audit/sources/, in prod dal 2026-08-19 ma mai chiamate da nessuno. Provate su giojello.com: 5 su 5, 42,7 s, PageSpeed mobile 58 / desktop 93. Due hook, provati a mano (6 casi il primo, 5 il secondo): - guardia-migration.sh BLOCCA l'SQL distruttivo sulle entita' protette — il vincolo Data Safety (LOCKED) fatto rispettare dalla macchina invece che dalla memoria. - guardia-token.sh AVVISA sulle classi Tailwind grezze. Non blocca: con ~450 occorrenze di debito, bloccare lo renderebbe un ostacolo da disattivare. I tre piani di v2.5 entrano nel repo: stavano solo in ~/.claude/plans/ e STATE.md avvertiva che senza quelli la milestone non era ricostruibile. Passati al setaccio per credenziali prima di committarli. Corretta in rules/memory-discipline.md la chiave della memoria persistente: e' …-Vault-IAMCAVALLI-hub, non quella del workspace. Sedici file stavano nella prima, la regola indicava la seconda. Impeccable resta abilitato solo a livello globale: fuori da settings.json locale. Nessun tocco al prodotto. Build e lint verdi, lint identico al baseline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
31 KiB
Audit — documento di restituzione
Context
iamcavalli vende un servizio di analisi sito in tre livelli (La Radiografia / Il Prima-Dopo / La Rotta). Il deliverable è un documento di restituzione presentato in una call da 40 minuti. Oggi si fa a mano fuori dall'hub.
Il servizio si chiama audit → rotta /audit/[slug]. Il nome del livello acquistato compare solo in copertina.
acquisto Whop ──┐
├─► audit creato ─► intake dati ─► agent: analisi profonda
creazione manuale ┘ │
▼
call (fissata a mano) ◄── consegna ◄── revisione + redesign (manuale)
Il redesign lo prepari tu mentre gli agent lavorano. Deve essere possibile creare un cliente e far partire l'audit in manuale, senza acquisto.
Volume: ~50 audit/anno. Overhead non-analitico sotto i 10 minuti per audit. Regola guida: tu scrivi solo analisi e redesign, tutto il resto si popola.
Documento canonico
La Spec V1 (blocchi, copy fisso, campi) è il documento canonico. L'Excel Ecommerce Growthlist è rubrica interna del motore di analisi, non struttura del documento — vedi §4.
Verdetto di fattibilità
Fattibile. Tre pezzi da costruire da zero: motore di analisi, PDF, hosting immagini. Il resto ha precedenti diretti in casa.
Conflitti risolti a favore del progetto
| Spec V1 | Qui | Perché |
|---|---|---|
| Supabase | Neon + Drizzle | Lo stack è quello |
uuid |
text + nanoid() |
Convenzione di tutte le tabelle in schema.ts |
| PDF serverless headless | Print CSS | Deploy Docker su Coolify, non serverless. Stesso risultato, zero infra |
/r/[slug]?k=[token], slug = nome cliente |
/audit/[slug], slug nanoid nel path |
Sicurezza (sotto) |
Sullo slug. teckell-2026 è indovinabile: la segretezza si sposterebbe tutta sul token in query string, e i parametri di query finiscono nei log d'accesso e nei referrer molto più facilmente di un path. Il Referrer-Policy: strict-origin-when-cross-origin già presente mitiga in parte, ma la convenzione collaudata qui è nanoid non indovinabile nel path, niente query (/preventivo/[slug], /quote/[token]). Un audit nomina un'azienda reale e ne elenca le debolezze: è il contenuto più sensibile che il portale pubblicherà.
Due correzioni ad altre premesse:
- I
gsd-audit-*non c'entrano. Verificato:gsd-audit-milestoneverifica una milestone GSD,gsd-audit-uatraccoglie i test in sospeso,gsd-audit-fixsistema i finding nel tuo codebase. Auditano il progetto, non il sito del cliente. Motore custom; il precedente èsrc/lib/proposal/agent.ts. - Il deck dei preventivi non è riusabile come layout.
ProposalDeck.tsx:151monta solo la slide corrente e:133impostabody.overflow = "hidden": stamparlo produce una pagina sola. Il documento è a scorrimento. Si riusa schema e macchina a stati, non il guscio visivo.
Decisioni prese
| Punto | Decisione |
|---|---|
| Motore | In-app, background, polling dall'admin (§5) |
| "La direzione" (blocco 8) | Manuale, foglio bianco, con il materiale grezzo a fianco |
| Print CSS — stesso DOM, vincolo "una sola fonte" per costruzione | |
| Immagini | Volume persistente. Modifica il vincolo LOCKED #5 (§9) |
| Redesign | Immagini caricate e link Figma, con ruoli distinti (§6) |
| Miglioramento nel tempo | Template versionato: tocca gli audit futuri, mai i consegnati (§3) |
| Copertina | Titolo = nome del livello acquistato |
| Scadenza | Nessuna. Depubblicazione manuale reversibile |
| Tracking | Prima apertura, ultima apertura, conteggio |
1 · Spike sul motore — si parte da qui
È la parte più incerta e quella su cui si regge tutto il resto. Se la qualità dell'analisi non regge, meglio scoprirlo prima di costruirci sopra schema, editor e documento.
Non tocca il database, non tocca l'hub. Uno script isolato, scripts/spike-audit.ts, sul sito di un cliente attuale, che stampa l'output grezzo.
Deve dimostrare che:
- Il fetch delle pagine chiave e l'estrazione del testo reggono su un sito reale
- La verifica delle voci di checklist è affidabile e ripetibile (§4)
- I problemi sono concreti, non generici — "il messaggio non è chiaro" non vale niente, "l'headline non nomina il destinatario" sì
- La conseguenza per il business è specifica
Da riusare subito: la protezione da prompt injection di src/lib/proposal/agent.ts:32-35 (blocco SICUREZZA che dichiara il contenuto come dati, non istruzioni) e :49 (neutralizzazione dei tag di chiusura). Con HTML scrapato da un sito esterno serve più che con le trascrizioni: è contenuto di terzi e può contenere istruzioni ostili.
Il resto si esegue dopo esito positivo dello spike.
2 · Il documento
Un solo documento ben progettato; i tre livelli sono configurazioni di quello.
| # | Blocco | Radiografia | Prima/Dopo | Rotta |
|---|---|---|---|---|
| 1 | Copertina | ● | ● | ● |
| 2 | Sintesi | ● | ● | ● |
| 2b | Cosa funziona già | ● | ● | ● |
| 3 | Stato di fatto | ● | ● | ● |
| 4 | I problemi, per impatto | ● | ● | ● |
| 5 | Analisi per area | ● | ● | ● |
| 6 | Il redesign | — | ● | ● |
| 6b | Cosa il redesign non risolve | — | ● | ● |
| 7 | Le ottimizzazioni | — | — | ● |
| 8 | La direzione | ● | ● | ● |
| 9 | Come si prosegue | ● | ● | ● |
I blocchi non pertinenti non esistono nel DOM, non sono nascosti via CSS.
Il copy fisso di ogni blocco è quello della Spec V1, riportato integralmente in template/v1.ts (§3). Nel blocco 9 le fasi già completate si derivano dal livello acquistato, e il credito riconosciuto è importo_pagato.
I due blocchi 2b e 6b sono aggiunte del prototipo Giojello, non della Spec V1, e vanno tenute:
- Cosa funziona già costruisce credibilità prima di criticare, e dichiara cosa non va toccato
- Cosa il redesign non risolve è onesto e commercialmente più efficace della vendita: apre al progetto completo senza promettere
Campi: punti_forza (lista) e redesign_limiti (testo).
Il prototipo Giojello è il riferimento del template v1
Il file HTML prodotto per Giojello non è il documento: è la fonte di template/v1.ts — copy, tipografia, gerarchia. Va spacchettato in componenti alimentati dal record. Quattro difetti da NON portarsi dietro:
- Contenuto cablato nel markup (699€, 8,0s, 500€, le date). Viola il vincolo "un record → una pagina → un PDF": tutto viene dal DB
- Google Fonts via
<link>— la CSP del progetto èfont-src 'self' data:estyle-src 'self' 'unsafe-inline': verrebbe bloccato e il documento cadrebbe sui font di sistema. Font self-hostati (Instrument Serif / Inter / IBM Plex Mono, se si conferma quel trio in luogo di Plus Jakarta Sans — è una deroga consapevole a DESIGN-SYSTEM.md, giustificabile perché è un documento pubblico, non la UI admin) - Print CSS di sei righe. Nasconde la legenda interna ma non i colori che la legenda spiega: i bordi e i testi rossi/blu dei blocchi di lavorazione finiscono stampati senza più nulla che li spieghi. I marcatori di lavorazione non devono esistere nel documento consegnato
- Colore come unico portatore di informazione nelle metriche —
3,4 srosso e1,0 sverde diventano identici in scala di grigi. Serve un secondo canale (glifo, peso, etichetta)
⚠️ Tassonomia degli impatti. Il prototipo usa cinque valori ("medio-alto", "basso-medio"…), lo schema ne prevede tre. Con cinque l'ordinamento automatico su tre non funziona. Decisione: si resta a tre valori, e la sfumatura sta nell'ordine dentro il gruppo (sort_order).
Regola di collocazione: finding vs analisi
Il prototipo v2 ha aggiunto una sezione discorsiva "Interfaccia" con otto paragrafi che contengono almeno quattro veri finding — contrasto insufficiente sulla CTA della hero, riflesso che mangia il 40% dell'immagine prodotto, titoli troncati, e soprattutto il carrello (spedizione assicurata 39,90€ contro 6,13€ con la non assicurata preselezionata). Quest'ultimo è da primi cinque ed era in coda a una sezione discorsiva.
Regola per il motore e per l'editor: se una cosa ha un impatto e una conseguenza, è un finding — va nel blocco 4, prende un numero e viene ordinata. Il blocco 5 resta discorsivo e resta a tre aree (struttura / messaggio / conversione). Nessuna quarta area.
Motivo: il documento promette "non un elenco di quaranta punti", e una seconda lista non ordinata dopo quella ordinata annulla la promessa. La forza del documento è la selezione.
Disciplina sui numeri — vincolo del motore
Confronto con un audit parallelo dello stesso sito prodotto da un altro modello: conteneva "~65% abbandono stimato" e "+60% velocità mobile immediata". Nessuno dei due è misurato: sono congetture presentate come rilevazioni.
Il prototipo Claude è disciplinato su questo — dice "una parte importante del traffico" proprio perché non può quantificarla. Quella disciplina va imposta nel prompt di sistema del motore: un numero compare nel documento solo se proviene da una rilevazione (PageSpeed, conteggio DOM, peso pagina, dati del sito). Mai stime di conversione o di guadagno percentuale. In un deliverable premium basta un cliente che verifichi per bruciare tutta la credibilità.
Voce di verifica: nessun numero nel documento consegnato è privo di fonte in audit_runs.raw.
Contributi da assorbire dall'audit parallelo
- La misura/taglia nella griglia prodotti — su pezzi unici rigenerati è criterio di scelta primario e non compare da nessuna parte. Si aggancia al finding sull'unicità e apre a un servizio ("messa a misura disponibile"). Va nella checklist profilo
ecommercecome voce - Conteggio dei nodi DOM — quantifica il problema di peso meglio dei MB. Da aggiungere alle rilevazioni di
fetch.ts - Stime in giornate — alimentano la colonna Impegno del blocco 7
Estensione opzionale — allegato tecnico (NON in fase 1)
Il blocco 9 promette "il documento resta tuo e puoi darlo a chiunque lavorerà sul sito", ma consegna prosa da imprenditore a uno sviluppatore.
Un allegato tecnico generato dallo stesso record — stessi finding, registro da sviluppatore, con selettori, file coinvolti e stime — renderebbe quella promessa molto più preziosa. È una seconda vista sugli stessi dati, quindi non viola il vincolo della fonte unica e non richiede contenuto aggiuntivo. Da valutare dopo il primo audit consegnato.
3 · Versionamento del template
Requisito: migliorare il documento deve toccare gli audit successivi, mai quelli già consegnati.
Precedente in casa: proposals.content congela profilo e prezzi alla generazione, così modificarli dopo non retro-cambia un preventivo pubblicato.
Copy fisso e configurazione dei blocchi vivono in moduli TS versionati, non nel database:
src/lib/audit/template/
v1.ts copy fisso di ogni blocco + ordine + mappa livello→blocchi
index.ts registry versione→modulo + LATEST_TEMPLATE_VERSION
audits.template_versionimpostata alla versione corrente alla creazione- Un audit consegnato resta congelato sulla sua versione, per sempre
- Migliorare il documento = aggiungere
v2.tse alzareLATEST_TEMPLATE_VERSION. Nessuna migration, nessun backfill, storia completa in git - Le bozze si portano all'ultima versione con un bottone esplicito, mai in automatico
4 · La checklist come rubrica del motore
L'Excel ha 264 voci su 7 step di funnel, con scoring ICE e peso di importanza per sezione. Serve al motore, non al documento.
Perché è preziosa: ogni voce è un'asserzione binaria e verificabile ("Il checkout consente l'acquisto come ospite"). Verificare 264 affermazioni falsificabili è molto più affidabile che chiedere a un modello "analizza questo sito": trasforma l'analisi da generativa a verificativa, controllabile voce per voce. E lo scoring ICE risolve gratis l'ordinamento del blocco 4 e le priorità del blocco 7.
Tre vincoli emersi dalla lettura del file:
- È al 73% ecommerce. Generale (50) e Homepage (21) valgono per qualsiasi sito; Categoria, Scheda Prodotto, Carrello, Checkout, Ringraziamento — 193 voci su 264 — presuppongono un carrello. Servono due profili:
ecommerceeservizi. Il secondo va scritto, non esiste nel file. - Misura la conformità, non l'adeguatezza. Un sito può fare 250/264 e continuare a descrivere un'azienda che non esiste più. Nessuna voce chiede se il posizionamento dichiarato corrisponde a quello che vendi oggi, o se le prove sono della fascia di cliente giusta. La checklist è il pavimento (guasti meccanici, li trova l'agent); lo scarto strategico è il soffitto e resta analisi tua. Se il documento diventa il rendering della checklist, torna a sembrare un audit automatico gratuito.
- Molte voci sono tattiche da ecommerce a volume — scarsità, urgenza, countdown, popup di social proof. Su un brand premium danneggiano: abbassano il segnale di prezzo mentre tu vendi il contrario. Ogni voce porta quindi un campo
registro(volume/premium/neutro) e l'audit di un brand premium non propone mai i trigger da discount.
⚠️ Note tecniche sul file: il foglio Algoritmo ha errori #REF!, e la colonna Facilità è 1.0 su tutte le righe — non compilata, quindi lo Score attuale è di fatto solo Impatto × Confidenza. Da sistemare prima di seminarne i default.
Il foglio Best Ecommerce List (88 siti per settore) alimenta confronti concreti: "il tuo checkout chiede 11 campi, i riferimenti del settore ne chiedono 6".
Nel documento la checklist non compare come elenco. Il blocco 3 mostra al massimo il grado di conformità per step di funnel (una barra per step), accanto alle metriche di performance. I problemi del blocco 4 sono una selezione curata — voci non conformi ad alto impatto più lo scarto strategico — non il dump delle non conformità.
5 · Migration 0017_audits.sql
Additiva e idempotente. Template di stile: src/db/migrations/0016_retainer_lifecycle.sql (header in italiano che spiega il perché, ADD COLUMN IF NOT EXISTS, CHECK ... NOT VALID dentro DO $$ ... pg_constraint, CREATE INDEX IF NOT EXISTS).
Convenzioni da src/db/schema.ts: id text con $defaultFn(() => nanoid()), nessun pgEnum (text + CHECK in SQL + tupla as const in TS + Zod nell'action), timestamp withTimezone: true, updated_at bumpato a mano nell'action.
audits — colonne scalari, non jsonb: non c'è snapshot da congelare (ci pensa template_version) e l'editor mappa 1:1.
- Identità:
id,slugunique nanoid,lead_id→leadsSET NULL,client_id→clientsSET NULL (nullable entrambi, comeproposals) - Config:
livelloCHECK('radiografia','prima_dopo','rotta'),template_version,profiloCHECK('ecommerce','servizi'),cliente_nome,cliente_referente,sito_url,importo_pagato numeric(10,2),data_consegna date - Origine:
originCHECK('manuale','whop')default'manuale',external_ref— predispone Whop senza costruirlo - Stato:
stateCHECK('draft','published')default'draft'(etichettati Bozza / Consegnata nella UI),published_at - Tracking:
first_viewed_at,last_viewed_at,view_count integer default 0 - Rilevazioni (blocco 3):
perf_mobile,perf_desktop,lcp numeric(6,2),cls numeric(5,3),inp,pagine_indicizzate,screenshot_desktop_url,screenshot_mobile_url,measured_at - Contenuto (blocchi 2/2b/5/8):
sintesi,punti_forza jsonb(lista, blocco 2b),analisi_struttura,analisi_messaggio,analisi_conversione,direzione - Redesign (blocchi 6/6b):
redesign_sezione,redesign_prima_url,redesign_dopo_url,redesign_razionale,redesign_limiti,redesign_figma_url - Intake:
intake jsonb— i dati che il cliente condivide, forma ancora da definire
Tutti i campi di contenuto sono nullable. È ciò che rende possibile "si salva sempre, anche a metà": la validazione di completezza scatta solo alla consegna.
audit_findings (blocco 4) — audit_id CASCADE, titolo, impatto CHECK ('alto','medio','basso'), area CHECK ('struttura','messaggio','conversione','performance'), descrizione, conseguenza, screenshot_url, sort_order integer default 0, origin CHECK ('agent','manuale')
audit_optimizations (blocco 7) — audit_id CASCADE, intervento, priorita CHECK ('alta','media','bassa'), motivazione, impegno, sort_order integer default 0
checklist_items (rubrica, versionata come il template) — profilo, step, focus, testo, impatto_default, confidenza_default, registro CHECK ('volume','premium','neutro'), sort_order
audit_checklist_results — audit_id CASCADE, item_id, esito CHECK ('conforme','non_conforme','non_rilevante'), note, evidenza, origin CHECK ('agent','manuale')
audit_runs (§6) — audit_id CASCADE, status CHECK ('queued','running','done','error'), step, started_at, finished_at, heartbeat_at, error, raw jsonb (output grezzo: materiale per il blocco 8)
Indici: unique su slug; index su client_id, lead_id, (audit_id, sort_order) per le figlie, (audit_id, started_at desc) per le run, (audit_id, item_id) unique per i risultati.
Ordinamento automatico (blocchi 4 e 7): la query ordina per rango di impatto/priorità con sort_order come spareggio dentro il gruppo. Tu non ordini niente.
⚠️ Checkpoint bloccante. Migration applicata in produzione prima di pushare il codice schema-dipendente (regola CLAUDE.md "Ordering"), via SSH/docker-exec. Aggiornare
src/db/schema.tsa mano in parallelo —drizzle-kit generateè rotto.
6 · Motore di analisi
src/lib/audit/ modellato su src/lib/proposal/ ma multi-step:
src/lib/audit/
pipeline.ts funzione pura: (auditId) => risultato. Non conosce chi la chiama
agent.ts chiamate Anthropic, una per step di funnel
schema.ts validazione Zod dell'output
fetch.ts recupero pagine + estrazione testo + PageSpeed
Passi: recupero pagine chiave → metriche PageSpeed → verifica delle voci di checklist del profilo → sintesi dei problemi con impatto+area+descrizione+conseguenza.
Tutto l'output è bozza: finisce in audit_checklist_results e in audit_findings con origin='agent'. Tu rivedi e correggi prima di consegnare. Si automatizza il meccanico, il giudizio resta tuo.
Il blocco 8 "La direzione" resta manuale, foglio bianco. L'editor mostra a lato il materiale grezzo — problemi ad alto impatto e temi ricorrenti, da audit_runs.raw. Nessun testo proposto, nessun template. È il blocco che giustifica il prezzo: se diventa formula, il cliente lo sente.
Esecuzione: in-app, background, con polling.
- "Avvia analisi" → riga
audit_runsconstatus='queued', lancia la pipeline - L'admin fa polling e mostra
stepcorrente e avanzamento - Progresso scritto a ogni passo: un'interruzione non perde il lavoro fatto
⚠️ Rischio da mettere in conto. Con
output: "standalone"su singolo container Coolify, un redeploy uccide un job in corso e lascia una riga bloccata surunning. Mitigazione:heartbeat_ataggiornato a ogni passo, le run senza heartbeat da N minuti vanno inerror, e "Rilancia" riparte dall'ultimo passo completato. Non è un sistema a code: è deliberatamente il minimo che regge 50 audit/anno.
pipeline.ts è una funzione pura riusabile: oggi la chiama il bottone, domani il webhook Whop. Nessuna riscrittura.
7 · Storage immagini e redesign
Servono: 2 screenshot home, N screenshot dei problemi, 2 immagini redesign. Con URL esterni incollati a mano il budget dei 10 minuti non regge, e un URL morto uccide il documento del cliente.
Volume persistente, non servizio esterno. Nessuna dipendenza npm nuova, nessun account terzo, nessuna credenziale.
- Volume Coolify montato su
/app/uploads - Upload via server action che riceve
FiledaFormData. Niente upload diretto dal browser: la CSP haconnect-src 'self', un POST verso un host esterno sarebbe bloccato — passare dal nostro origin è l'unica strada e va bene così - Validazione: whitelist MIME
image/png|jpeg|webp, max 5MB, nome filenanoid()(mai l'originale) - Lettura via
src/app/api/uploads/[...path]/route.tscon guardia sul path traversal eCache-Controllungo - CSP invariata: serviamo dal nostro origin, coperto da
img-src 'self'
⚠️ Checkpoint bloccante. Volume creato in Coolify prima del deploy, altrimenti gli upload si perdono a ogni redeploy.
Nuovo componente ImageUploadField — in tutto src/ non c'è un solo <input type="file">.
Il redesign usa entrambi i formati, con ruoli distinti:
- Immagini caricate — rappresentazione canonica, quelle che si vedono nello slider e le uniche che finiscono nel PDF
- Link Figma — opzionale, "apri il redesign interattivo", nuova scheda
Perché non embeddare Figma: la CSP ha default-src 'self' senza frame-src, quindi l'iframe sarebbe bloccato; e un iframe in stampa non produce nulla, rompendo il vincolo "una sola fonte" proprio sul blocco che vale di più.
8 · Pagina pubblica e PDF
/audit/[slug], modellata su src/app/preventivo/[slug]/page.tsx: export const revalidate = 0, fetch by slug, notFound() se assente.
Differenze deliberate dal preventivo:
- Documento a scorrimento. Nessun
h-screen, nessunoverflow-hidden, nessuna manipolazione dibody.style - Rate limit: aggiungere
/audital matcher disrc/proxy.tsriusandosrc/lib/rate-limit.ts. Il preventivo non ce l'ha — non ereditare quell'omissione - noindex:
X-Robots-Tag: noindex, nofollowinnext.config.tspiùmetadata.robots - Anteprima admin: con
state === 'draft'mostrare comunque il documento segetServerSessionrestituisce una sessione admin, altrimenti il placeholder. Non tocca LOCKED #4, che riguarda/client/* - Tracking: alla prima render non-admin aggiornare
first_viewed_at/last_viewed_at/view_count
Nuovi componenti in src/components/public/audit/, uno per blocco, server components salvo slider e bottone stampa. Non riusare le 20 sezioni di public/proposal/sections/: sono saldate a ProposalContent con copy hard-coded.
Da riusare: RichText.tsx per ogni testo libero (obbligatorio su output del modello — mai dangerouslySetInnerHTML), e le convenzioni visive esistenti (eyebrow text-xs font-mono tracking-widest uppercase, headline text-5xl font-light, card border border-border rounded-xl p-8).
⚠️ Il deck preventivi è light-only (bg-white hard-coded in ProposalDeck.tsx:139,141,156, PricingSection.tsx:46) e viola la regola dei token in due punti (TimelineSection.tsx:5-9, ClosingSection.tsx:20,34). Non replicare quei difetti: solo token semantici, dual light/dark.
Leggibilità in B/N: il livello di impatto non può dipendere dal colore. Etichetta + glifo — ●●● alto / ●●○ medio / ●○○ basso.
PDF via print CSS
Stessa pagina, stesso DOM, stesso record: il vincolo "una sola fonte" è soddisfatto per costruzione.
In src/app/globals.css — oggi zero regole print in 218 righe — un blocco @media print:
- Palette chiara forzata,
print-color-adjust: exactdove serve break-inside: avoidsu ogni card problema e riga della tabella;break-before: pagesui blocchi maggiori- Nascosti: bottone stampa, nav, controllo dello slider
- Slider prima/dopo: a schermo interattivo, in stampa due immagini impilate con etichetta. Tecnica: entrambe le
<img>sempre nel DOM, clippate in overlay via CSS a schermo; in@media printsi toglie il clip e si impila. Nessun ramo JS, nessuna divergenza possibile - Bottone "Scarica PDF" sticky in alto a destra = client island con
window.print()
9 · Admin
Mutazioni come Server Actions colocate in actions.ts (niente REST per l'admin).
src/app/admin/audit/page.tsx— lista. RiusarePageHeader,SearchInput, badge di statosrc/app/admin/audit/nuovo/— creazione manuale: cliente o lead, livello, profilo, URL, importo → redirect all'editorsrc/app/admin/audit/[id]/edit/page.tsx— RSC sottile + client componentsrc/components/admin/audit/AuditEditor.tsxsrc/app/admin/audit/actions.ts- Voce in
AdminSidebar.tsx
Pattern editor: saveOfferEditor + OfferEditorClient (src/app/admin/offers/actions.ts:177-274, src/components/admin/offers/OfferEditorClient.tsx) — unico precedente di salvataggio a payload intero con array di figli:
- Zod annidato con
z.array(findingSchema)/z.array(optimizationSchema),idopzionale sui figli - Nell'action: update degli scalari, loop sui figli con upsert per presenza di
id, delete delle righe sparite,sort_order: indexassegnato server-side - Client: array in
useState, update immutabili via.map,startTransition+ try/catch →setSaveError - ⚠️ Il re-sync degli id dopo il primo salvataggio (
OfferEditorClient.tsx:93-98): senza, i figli appena creati vengono re-inseriti invece che aggiornati al salvataggio successivo. È il bug più facile da introdurre qui
Aggiungi / rimuovi / riordina righe: non esiste nel codebase — sort_order oggi è write-once. @dnd-kit/sortable è già in package.json e usato in KanbanBoard.tsx: riusarlo, con l'indice dell'array che diventa sort_order al salvataggio.
Campi condizionali: redesign e ottimizzazioni non si renderizzano se il livello non li prevede.
Bozza: niente autosave (non esiste nel codebase, complessità sproporzionata). "Salva bozza" esplicito + avviso all'uscita con modifiche pendenti. Tutti i campi nullable. La validazione di completezza scatta solo su "Consegna", sui soli blocchi pertinenti.
Consegna / Ritira: transizioni guardate WHERE id = ? AND state = ? come publishProposal (src/app/admin/preventivi/actions.ts:117-127). Entrambe reversibili.
Automatismi (budget 10 minuti): credito = importo_pagato, fasi già completate derivate dal livello, date da published_at/measured_at, ordinamento dei blocchi 4 e 7, analisi draftata dagli agent.
10 · Ingresso: manuale ora, Whop dopo
Fase 1 = solo creazione manuale, che resta comunque un requisito permanente.
Predisposto senza costruirlo: origin + external_ref sulla tabella, e pipeline.ts chiamabile da un webhook. L'intake del cliente è ancora da definire — la colonna intake jsonb accoglie qualunque forma prenderà, senza migration aggiuntive.
11 · Modifica al vincolo LOCKED #5 — richiede approvazione
CLAUDE.md oggi: 5. No file hosting v1 — documenti come URL esterni
Nuova versione proposta:
5. No file hosting per i documenti — restano URL esterni.
Deroga (Phase 27, 2026-08-16): le immagini dell'audit (screenshot e
redesign) sono caricate su volume persistente e servite da
/api/uploads/[...path], con whitelist MIME e limite di dimensione.
Non estendere l'upload ad altre entità senza modificare questo vincolo.
12 · GSD e memoria di progetto
Non c'è niente da costruire: state file, roadmap e checkpoint automatico esistono già. .planning/STATE.md è il digest che comanda i comandi /gsd-*, .planning/ROADMAP.md traccia la posizione, execute-plan.md aggiorna STATE.md dopo ogni piano, e .claude/rules/memory-discipline.md è la regola scritta.
Due difetti reali:
.planning/config.jsonnon ha la chiavehooks. Verificato: entrambi gli hook globali (gsd-session-state.sh,gsd-phase-boundary.sh) escono a vuoto senza"hooks": { "community": true }. Aggiungerla riattiva l'iniezione di STATE.md all'avvio sessione e il promemoria a ogni scrittura in.planning/. Una riga.gsd-sdkegsd-toolsnon sono nel PATH (verificato:not found). Le mutazioni automatiche di STATE.md inexecute-plan.mdnon hanno backend e ricadono su edit manuali — è il motivo per cui le fasi 13 e 26 hanno SUMMARY ricostruiti a posteriori.
L'hook Stop in .claude/settings.json è solo advisory (echo) e guarda solo src e .planning: non nota modifiche a STATUS.md o CLAUDE.md.
Inquadramento: nuovo milestone v2.5 — Audit via /gsd-new-milestone. Numerazione progressiva e mai riusata (l'ultima è 26 ⇒ si parte da 27), come impone .planning/PROJECT.md.
Da aggiornare a fine lavoro: .planning/STATE.md, .planning/ROADMAP.md, STATUS.md, CLAUDE.md.
13 · File toccati
Nuovi — scripts/spike-audit.ts · src/db/migrations/0017_audits.sql · src/lib/audit/{pipeline,agent,schema,fetch}.ts · src/lib/audit/template/{v1,index}.ts · src/lib/audit-queries.ts · src/lib/audit-view.ts · src/app/audit/[slug]/page.tsx · src/components/public/audit/* · src/app/admin/audit/** · src/components/admin/audit/AuditEditor.tsx · src/components/ui/ImageUploadField.tsx · src/app/api/uploads/[...path]/route.ts
Modificati — src/db/schema.ts · src/proxy.ts · next.config.ts · src/app/globals.css · src/components/admin/AdminSidebar.tsx · CLAUDE.md · .planning/config.json · Dockerfile + volume Coolify
14 · Verifica
Non esiste test suite (nessun vitest/jest/playwright, nessuno script test). npm run build è la verifica di riferimento — fa il typecheck. npm run lint è eslint nudo.
Fase 1 (spike) — gira su un sito reale, i problemi sono concreti e verificabili, la valutazione delle voci di checklist è ripetibile. È un giudizio tuo, non un test automatico.
Dopo:
npm run build— typecheck e compilazione- Migration applicata in prod prima del push; verificare che le sei tabelle esistano
- Un audit di prova per ciascuno dei tre livelli, su entrambi i profili: i blocchi condizionali compaiono e spariscono correttamente
- Salvare a metà, ricaricare, nulla si perde. Salvare due volte di fila: i figli si aggiornano e non si duplicano (test del re-sync degli id)
- Riordinare i problemi, salvare, ricaricare: l'ordine regge
- Avviare un'analisi e riavviare il container a metà: la run va in
errore "Rilancia" riparte senza perdere i passi completati - Upload di un'immagine → redeploy → l'immagine è ancora lì (prova del volume)
- Slug consegnato in incognito: si apre. Bozza in incognito: placeholder. Bozza da admin loggato: si vede
X-Robots-Tag: noindex, nofollowpresente nella risposta- Stampa: Cmd+P sul documento consegnato → interruzioni di pagina corrette, slider impilato in due immagini, nessun marcatore di lavorazione (bordi/testi colorati) sopravvissuto, e in scala di grigi sia i livelli di impatto sia i valori delle metriche restano distinguibili 10b. Rete disattivata dopo il primo caricamento: i font restano quelli giusti (prova che sono self-hostati e che nessuna risorsa esterna è rimasta)
- Il tracking apertura si incrementa da visitatore e non dalla preview admin
- Consegnato un audit su
v1, alzareLATEST_TEMPLATE_VERSIONav2: il consegnato continua a renderev1, uno nuovo nascev2 - Un audit su brand premium non propone mai voci con
registro='volume'
⚠️ Playwright non funziona contro npm run dev (la CSP blocca eval, i client component non si idratano). Per E2E usare il build di produzione.