f7eb7eec23
.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>
72 lines
4.0 KiB
Markdown
72 lines
4.0 KiB
Markdown
# 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.
|