Files
clienthub/.planning/phases/26-anteprima-admin-e-login/26-SUMMARY.md
T
simone f7eb7eec23 docs(planning): archivia v2.1/v2.2/v2.3 e documenta v2.4
.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>
2026-08-08 22:38:36 +02:00

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.