.planning/ documentava in dettaglio cio che era vecchio e per niente cio
che e in produzione: le fasi 11-22 (v2.1 e v2.2, chiuse a giugno) erano
ancora in phases/ mentre v1.0 e v2.0 stavano gia in milestones/, e il
lavoro degli ultimi due mesi - gate OTP e ciclo di vita dei retainer, cioe
quello che gira su hub.iamcavalli.net - non aveva nessuna cartella.
- phases/{11,12,14} -> milestones/v2.1-phases/, phases/{18..22} ->
milestones/v2.2-phases/. Ora phases/ contiene solo la milestone in
corso, che e quello che state.cjs conta per il progresso
- v2.1-ROADMAP.md ricostruito: era l'unica milestone senza archivio,
interrotta dal reset del 19/06 e mai chiusa formalmente
- v2.3-ROADMAP.md + v2.3-REQUIREMENTS.md: v2.3 e stata eseguita fuori dal
ciclo GSD, non esistono PLAN/SUMMARY per fase. L'archivio E la doc
- REQUIREMENTS.md riscritto per v2.4 con il backlog reale
- phases/13 e phases/26: SUMMARY ricostruiti da commit, migration e
STATUS.md. 26 e il primo numero libero
- research/: cancellate 4 varianti dello stesso PITFALLS e FEATURES/
SUMMARY, superati da PROJECT.md. Diverse anti-feature erano ormai
contraddette dai fatti (il Kanban e stato costruito in Phase 19,
l'email in v2.3, il time tracking esiste)
- cancellati UI-RULES.md e DESIGN-SYSTEM.md (CLAUDE.md li dichiara
superseded: impongono l'inverso della regola attuale) e HANDOFF.md,
fermo al 13/06
- SECURITY-*.md -> security/: audit chiuso, ma i report restano la doc di
cosa e stato ruotato e perche
- PROJECT.md/MILESTONES.md/ROADMAP.md allineati: milestone corrente v2.4,
sessione OTP 90gg non 30, migrazioni fino alla 0016
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.0 KiB
Phase 26 — Anteprima admin del portale + toggle password sul login
Milestone: v2.4 Post-vendita · Stato: ✅ in produzione (deploy Coolify 2026-08-08)
Commit: 09a5b1f (toggle password) · 187550f (anteprima admin)
Ricostruito a posteriori il 2026-08-08. Lavoro non pianificato in roadmap, nato da due attriti d'uso reali. 26 è il primo numero di fase libero.
1. Toggle mostra/nascondi password — 09a5b1f
Il campo password del login admin non offriva modo di rileggere quanto digitato: un accesso fallito era indistinguibile da un errore di battitura.
Toggle inline, non un nuovo primitivo in ui/: type="password" compare una
sola volta in tutto il codebase, un'astrazione avrebbe avuto un solo consumatore.
type="button" perché dentro un <form> il default è submit; tabIndex={-1} per
tenere il Tab sulla sequenza campo → Accedi. Classi a token semantici; gli hex
literal preesistenti della pagina restano da migrare col resto del debito design.
Causa a monte (non era un bug): ADMIN_PASSWORD è stata ruotata il 2026-07-28
solo su Coolify, e .env.local è rimasto alla precedente. Vale anche per
NEXTAUTH_SECRET. .env.local non è allineato a produzione e non va trattato come
fonte di verità per le credenziali.
2. Anteprima admin in sola lettura del portale — 187550f
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
(ClientRow) un'icona apre ora /client/<slug>?preview=1 in una scheda nuova.
Come funziona. getClientGate() accetta { previewRequested } e salta il gate
solo se il query param c'è e getServerSession(authOptions) è valida. Senza il
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. Il flag viaggia via
PreviewProvider / usePreview() e non per prop drilling: ApproveButton sta
quattro livelli sotto la dashboard.
Perché sola lettura. 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). Un
click distratto approverebbe un deliverable in modo irreversibile. La protezione è
a livello UI, non API: un admin può ancora chiamare le route a mano. È voluto —
l'obiettivo è impedire l'incidente, non difendersi da sé stessi.
⚠️ Deviazione consapevole dal vincolo LOCKED #4
CLAUDE.md fissa: /client/[token]/* → token middleware, /admin/* → sessione
Auth.js. Ora una route client legge anche la sessione Auth.js. Non indebolisce
nulla — per i clienti il gate OTP è identico — ma la sezione LOCKED richiede
approvazione esplicita prima di essere modificata. Annotato in CLAUDE.md il
2026-08-08 con l'ok dell'utente.
Verifica
Verificato col build di produzione su :3100 contro il DB reale (sole letture):
?preview=1 senza sessione admin → gate OTP; con cookie di sessione contraffatto →
gate OTP; ?preview=0, ?preview=abc, ?preview= → gate OTP; nessun query param
con sessione admin → gate OTP (nessuna regressione); sessione admin valida +
?preview=1 → portale con banner e composer disattivato, sia sul cliente a progetto
singolo sia su quello a due progetti.
Non verificato dal vivo: il ramo ApproveButton — in produzione la tabella
deliverables è vuota (0 righe), quindi quel pulsante oggi non si renderizza
mai. Wiring controllato solo a livello di codice. Il click dell'occhiolino sul login
è verificato solo nel markup renderizzato (type="button", tabindex="-1",
aria-label).
Nota di metodo: Playwright non funziona contro npm run dev — la CSP blocca
eval e i client component non si idratano. Va usato il build di produzione.