STATE.md diceva "Phase 27, nessun bloccante" mentre v2.5 e' ferma per scelta e
in produzione e' andato altro. Ora dice cosa e' vero: v2.5 in pausa, modifiche
hub in corso, blocchi A/B/C1 in produzione, e due bloccanti scritti con cosa
manca e chi li sblocca — le credenziali API di TidyCal e LEAD_WEBHOOK_SECRET
su Coolify, senza la quale la route rifiuta tutti (fallimento chiuso voluto).
Sta di nuovo sotto le 100 righe: ci e' rientrato togliendo cio' che STATUS.md
gia' racconta per esteso, non accorciando i bloccanti.
REQUIREMENTS.md guadagna HUB-01..13, con lo stato reale: otto fatti, cinque no.
Fra quelli aperti c'e' anche la conferma del payload Elementor, che oggi e'
gestito in modo difensivo e non verificato sul campo — distinguere "scritto" da
"visto funzionare" e' il punto della regola.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Il push era rimasto bloccato e la riga delle fonti diceva ancora "non in prod".
Ora e' vero il contrario, ma "in produzione" da solo sarebbe fuorviante: i cinque
moduli sono deployati e nessuna route li chiama. La riga dice entrambe le cose,
perche' confondere *deployato* con *funzionante* e' il modo piu' rapido per
credere di avere una feature che non esiste.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La roadmap era ferma al 2026-08-08 e diceva ancora "nessuna fase aperta" mentre
v2.5 era gia' partita e Phase 27 era a meta'; REQUIREMENTS.md era ancora quello
di v2.4. STATE.md invece era corretto — segno che aggiornare solo quello non
basta. Da qui la divisione dei ruoli, ora esplicita in testa a ogni file:
- STATE.md orientamento breve (99 righe): dove sta cosa, come funziona il
motore, i blocchi vivi. Niente narrativa.
- ROADMAP.md tutte le fasi 1->30, con lo stato di ciascuna
- REQUIREMENTS.md i 25 requisiti di v2.5 (AUD-01..25) e il backlog
- STATUS.md l'unica narrativa lunga: lezioni e note tecniche
v2.4 chiusa e archiviata in milestones/v2.4-REQUIREMENTS.md.
Decisione nuova: il documento di restituzione usa il design system dell'area
admin ("Quiet Luxury"), non una tipografia sua — token semantici, Plus Jakarta
Sans, Geist Mono per metriche e date, StatusBadge per gli impatti. Sostituisce
la deroga tipografica prevista dal piano. I font sono gia' self-hostati da
next/font/google, quindi la CSP font-src 'self' e' soddisfatta senza lavoro, e
il documento non aggiunge debito a DEBT-01 perche' nasce gia' a token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Quando un cliente segnalava "non trovo una cosa" non c'era modo di
guardare il portale con i suoi occhi: il gate OTP lascia entrare solo
lui. Dall'elenco clienti ora un'icona apre /client/<slug>?preview=1.
getClientGate() accetta { previewRequested } e salta il gate solo se il
query param c'è E getServerSession(authOptions) è valida. Senza param
anche un admin vede il gate OTP, così il gate resta testabile dal vivo.
Ritorna preview: true senza sintetizzare una ClientSession: un admin in
anteprima non è un cliente autenticato, e confondere i due stati li
renderebbe indistinguibili proprio dove serve distinguerli.
Sola lettura perché il portale scrive davvero: /api/client/approve e
/api/client/comment autenticano sul token nel body, non sulla sessione,
e deliverables.approved_at è immutabile una volta impostato (LOCKED #3).
La protezione è a livello di UI, non di API — impedisce l'incidente, non
difende da sé stessi. Il flag passa da PreviewProvider e non per prop
drilling: ApproveButton sta quattro livelli sotto la dashboard.
Deviazione consapevole dal vincolo LOCKED #4: una route client ora legge
anche la sessione Auth.js. CLAUDE.md non è aggiornato, la sezione LOCKED
richiede approvazione esplicita.
Verificato col build di produzione contro il DB reale (sole letture):
gate OTP senza sessione admin, con cookie contraffatto e con preview=0/
abc/vuoto; portale con banner e composer disattivato con sessione valida,
sia a progetto singolo sia a due progetti. Il ramo ApproveButton non è
esercitabile dal vivo: in produzione deliverables è vuota.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gate OTP live su hub.iamcavalli.net. Verificato: gate senza cookie con zero
dati di progetto nell'HTML, no-enumeration, codice sbagliato/corretto,
cookie Secure+HttpOnly+SameSite 90 giorni, rientro col cookie, isolamento
fra clienti. Dati di test rimossi, tabelle protette invariate.
Resta da popolare la whitelist dei 3 clienti reali.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Il dominio e stato ricreato su Resend (nuovo id) e i DNS rimessi: DKIM,
SPF TXT e MX tutti verified. Invio da no-reply@iamcavalli.net verso un
indirizzo esterno confermato riuscito.
Annotato che ricreare il dominio su Resend rigenera la chiave DKIM, quindi
i valori DKIM annotati in passato non sono affidabili.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- RESEND_API_KEY e RESEND_FROM create su Coolify (production + preview)
- sendEmail() verificato con il template OTP reale: {ok:true}
- unico blocco residuo: TXT resend._domainkey.iamcavalli.net ha una chiave
vecchia, dominio Resend status=failed, invio dal dominio rifiutato 403
- valore DKIM corretto e sequenza di ripresa annotati in STATE.md
- note API Coolify: envs non accetta is_build_time (422)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Il portale /client/<slug> era protetto dal solo token in URL: chiunque
ricevesse o intercettasse il link entrava, per sempre, senza identificarsi.
Ora l'admin registra le email autorizzate per cliente e il cliente si
identifica con un codice usa-e-getta prima di vedere qualsiasi dato.
- Resend 6.18.1 + src/lib/mailer.ts (Result tipizzato, mai catch silenzioso)
- migration 0015 (gia applicata a prod): client_emails, otp_codes,
clients.sessions_valid_from. Additiva pura, conteggi verificati pre/post
- admin: sezione "Accessi al portale" in /admin/clients/[id] con whitelist
e revoca sessioni in blocco
- gate: codice 6 cifre CSPRNG, hash SHA-256 (mai il codice in chiaro),
TTL 15 min, max 5 tentativi, rate limit su entrambi gli endpoint,
risposta identica per email in whitelist e non (no enumeration)
- sessione: cookie HMAC per-cliente, 90 giorni, httpOnly/secure/lax
Il gate sta in cima alla page, NON nel layout: nell'App Router il segmento
page viene renderizzato in parallelo al layout, quindi gattare nel layout
nascondeva la dashboard a schermo ma lasciava fasi, task e pagamenti nel
payload RSC dell'HTML (46907 byte -> 17594 dopo il fix). Verificato.
Verifica: build OK, 9/9 test E2E in locale contro il DB di produzione.
NON DEPLOYARE prima di: RESEND_API_KEY+RESEND_FROM su Coolify e whitelist
popolata per i 3 clienti reali (oggi vuota) - altrimenti il gate li chiude
fuori dal loro portale. Checklist in .planning/STATE.md.
SEND-01/02 (invio preventivo via email) rinviati a v2.4 su richiesta.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UI-SPEC approved (5/6 PASS, 1 non-blocking flag on focal point).
Locks reuse of Phase 11 primitives (EditableCell, OptionSelect,
OptionMultiSelect) for the leads table, plus scope for the four
CRM bug fixes (i18n, type-safety, dead code).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tags table + legacy consolidation applied via SSH tunnel to Coolify Postgres.
Both validators: ALL CHECKS PASSED. Protected tables (clients/projects/payments/phases) unchanged.
Phase 11 verification: passed (OFFER-07/08/09/10/13 all Complete).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- add 11-03-SUMMARY.md documenting EditableCell/TagMultiSelect delivery
and the react-hooks lint-rule fix (Rule 1 deviation)
- update STATE.md position (3 of 4 -> 4 of 4), progress 86% -> 89%,
performance metrics, and decisions log
- log gsd-sdk unavailability and OFFER-07/08 status timing as deferred
items for Phase 11
- SUMMARY.md for Plan 02 (ServiceWithTags + 4 new server actions)
- STATE.md: advance to Plan 3/4, record metrics and decision
- REQUIREMENTS.md: mark OFFER-07/08/09 backend foundation complete
- Advance STATE.md to plan 2/4, record metrics/decisions/blocker for 11-01
- Fix Status/Progress fields regressed by state advance-plan (status was
reset to "Ready to execute" / 0% despite phase being mid-execution)
- Note .planning/ROADMAP.md is an empty PLACEHOLDER (pre-existing v2.1
planning gap) — roadmap update-plan-progress is a no-op until populated