diff --git a/docs/superpowers/specs/2026-07-05-contenuti-taggati-pannello-editor-design.md b/docs/superpowers/specs/2026-07-05-contenuti-taggati-pannello-editor-design.md new file mode 100644 index 0000000..b2078bc --- /dev/null +++ b/docs/superpowers/specs/2026-07-05-contenuti-taggati-pannello-editor-design.md @@ -0,0 +1,146 @@ +# Sistema di contenuti taggati e pannello editor + +Data: 2026-07-05 +Stato: approvato dall'operatore (design), spec in revisione + +## Obiettivo + +Ogni testo e ogni immagine delle pagine vetrina del sito riceve un tag univoco e stabile. I contenuti vengono estratti dal codice e salvati in SQLite, così da poter essere modificati senza intervenire sui sorgenti. Un pannello di amministrazione (`/admin/content`) permette a un utente con ruolo dedicato di modificare testi, stili (tramite preset controllati) e immagini; una modalità "annotate" sovrappone al sito i badge con i nomi dei tag, rendendo immediato individuare l'elemento da modificare. + +Il sistema serve due scopi complementari: nel breve periodo dà all'operatore un vocabolario preciso per dettare modifiche ("cambia `home.hero.title` in…"); nel medio periodo consente al cliente di aggiornare i contenuti in autonomia, senza rischio di rompere il layout. + +## Decisioni prese in fase di design + +| Decisione | Scelta | +|---|---| +| Scopo | Tagging + pannello editor nello stesso progetto | +| Ambito modifiche | Testi, immagini, stili tramite preset chiusi (no CSS libero) | +| Utenti | Ruolo `admin` (tutto) e ruolo `editor` (solo contenuti) | +| Pubblicazione | Pagine vetrina passano a SSR con cache in memoria invalidata al salvataggio | +| UX | Ibrido: pannello `/admin/content` + overlay annotate sul sito con link ai form | + +## Architettura + +### 1. Schema dei tag + +Formato gerarchico `pagina.sezione.elemento`, tutto minuscolo, separatore punto, senza spazi: + +- `home.hero.title`, `home.hero.subtitle`, `home.hero.cta` +- `about.team.donata.bio`, `about.intro.body` +- `programs.pilates-flow.price`, `training.fit-remote.description` +- `global.footer.tagline`, `global.header.cta` (elementi condivisi tra pagine) + +I tag sono stabili: una volta assegnati non cambiano nome, perché sono chiave primaria nel DB e riferimento nelle conversazioni. I dati oggi strutturati in `src/data/*.ts` (team, pricing, agenda, method, programs, trainings, partners, site) vengono scomposti in tag granulari con lo stesso schema. + +### 2. Storage (SQLite, DB esistente) + +Nuova tabella nella `SCHEMA` di `src/lib/db.ts`: + +```sql +CREATE TABLE IF NOT EXISTS content_blocks ( + tag TEXT PRIMARY KEY, + type TEXT NOT NULL CHECK (type IN ('text','html','image')), + value TEXT NOT NULL DEFAULT '', + styles TEXT NOT NULL DEFAULT '{}', -- JSON, solo chiavi preset + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_by TEXT +); +``` + +- `type = 'text'`: testo semplice, escapato in render. +- `type = 'html'`: frammenti con markup minimo (grassetti, a-capo) sanificati con la pipeline già usata dal blog (`src/lib/sanitize.ts`). Usato solo dove il testo attuale contiene markup. +- `type = 'image'`: `value` contiene il percorso dell'override caricato (es. `content/home-hero-1.jpg`, relativo a `uploads/`) oppure stringa vuota se vale l'asset originale. + +### 3. Seed e fonte di verità + +`src/data/content-seed.ts` contiene l'inventario completo dei tag con i valori estratti dai testi attuali: `{ tag, type, value, styleOptions }`. Il campo `styleOptions` dichiara quali preset ha senso esporre per quel tag (vedi §6); non finisce nel DB, guida solo il pannello. + +All'avvio (in `createDb`) un passo di sync inserisce nel DB i tag presenti nel seed ma assenti nella tabella (`INSERT OR IGNORE`). Regole: + +- In produzione il DB è la fonte di verità: le modifiche del cliente non vengono mai sovrascritte dal seed. +- I tag nuovi arrivano con il codice (aggiunta al seed) e compaiono nel DB al deploy successivo. +- I tag rimossi dal seed restano orfani nel DB e vengono ignorati; nessuna cancellazione automatica. +- Il seed nel repo funge anche da backup versionato dei testi originali. + +### 4. Rendering + +- Le pagine vetrina (`index`, `about`, `contact`, `training`, `programs/index`, `programs/[slug]`, `404`) passano da `prerender = true` a `prerender = false` (SSR, già attivo per blog e admin con adapter node standalone). +- Nuovo modulo `src/lib/content.ts`: + - `getContent(tag)` / `getAllContent()`: lettura con cache in memoria (`Map` popolata al primo accesso con una sola query). + - `invalidateContentCache()`: svuota la cache; chiamata dall'API di salvataggio. Modifica visibile alla richiesta successiva. + - Fallback a cascata: valore nel DB → valore nel seed → stringa vuota con log di warning. Il sito non si rompe mai per un tag mancante. +- Nuovi componenti Astro: + - ``: renderizza il contenuto nel tag HTML indicato (`as`, default `span`), applica le classi preset derivate da `styles`, emette `data-tag` per l'overlay annotate. Per `type='text'` il valore è escapato; per `type='html'` è già sanificato al salvataggio. + - ``: se esiste override in DB serve il file da `/uploads/content/…` (route `src/pages/uploads/[...path].ts` già esistente), altrimenti l'asset originale ottimizzato dalla pipeline Astro. Le prop di layout (dimensioni, alt di default, loading) restano nel codice. +- La sitemap non cambia (le route restano le stesse); verificare che l'integrazione sitemap continui a emetterle con `output: 'server'` e prerender disattivato, altrimenti passare a una sitemap statica generata a mano. + +### 5. Ruoli utente + +- Migrazione additiva: `ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'admin'` (eseguita in `createDb` se la colonna manca; gli utenti esistenti restano admin). +- `getSessionUser` ritorna anche `role`; `context.locals.user` lo espone. +- Middleware: le route `/admin/content` e `/api/admin/content*` sono accessibili ad `admin` ed `editor`; tutte le altre route `/admin*` e `/api/admin*` restano riservate ad `admin` (l'editor che le richiede riceve redirect a `/admin/content` o 403 sulle API). +- Creazione utente editor: script CLI in `scripts/` (pattern già usato per l'admin), credenziali consegnate al cliente fuori banda. + +### 6. Stili preset + +Il DB memorizza solo chiavi simboliche; la mappa chiave → classe CSS vive nel codice (`src/lib/style-presets.ts` + classi in `global.css`). Nessun CSS arbitrario può entrare dal pannello. + +| Gruppo | Chiavi | Note | +|---|---|---| +| `size` | `s` / `m` / `l` | scala relativa alla dimensione base dell'elemento | +| `color` | palette del sito (brand-dark, tan, taupe, bianco, …) | lista chiusa derivata dalle variabili CSS esistenti | +| `weight` | `normal` / `bold` | | +| `style` | `normal` / `italic` | | +| `align` | `left` / `center` / `right` | solo su elementi a blocco | + +Ogni tag dichiara nel seed quali gruppi espone (`styleOptions`): un titolo hero espone size e color, una voce di prezzo magari solo color. Valori non in whitelist vengono rifiutati dall'API al salvataggio. + +### 7. Pannello `/admin/content` + +Pagina SSR protetta, stessa shell di `src/layouts/Admin.astro`: + +- Tag raggruppati per pagina (Home, About, Programs, Training, Contact, Globali), gruppi collassabili, campo di ricerca per nome tag o contenuto. +- Per ogni tag: form con campo testo (input o textarea in base alla lunghezza), controlli preset solo per i gruppi dichiarati, upload immagine per i tag `image` (validazione mime/dimensione riusando la logica dell'upload blog) con anteprima e pulsante "ripristina originale". +- Salvataggio per singolo tag (pulsante per riga) via `fetch` alle API; feedback inline di esito. +- Ancore `id` per tag: `/admin/content#home.hero.title` scrolla ed evidenzia il form corrispondente. +- Pulsante "Vedi sul sito con tag" che apre la pagina pubblica in modalità annotate. + +API: + +- `GET /api/admin/content` — elenco completo (per il pannello). +- `PUT /api/admin/content/[tag]` — aggiorna `value` e/o `styles`; valida tipo, preset in whitelist, sanifica se `type='html'`; aggiorna `updated_at`/`updated_by`; invalida la cache. +- `POST /api/admin/content/[tag]/image` — upload override immagine in `uploads/content/`; `DELETE` sullo stesso path rimuove l'override e ripristina l'asset originale. + +### 8. Overlay annotate + +- Attivazione: query string `?annotate=1` su qualunque pagina vetrina, solo con sessione valida (admin o editor). Senza sessione il parametro è ignorato: nessuna traccia dei tag per i visitatori. +- `Base.astro` in modalità annotate inietta un piccolo CSS+JS vanilla (coerente con lo stile del progetto: niente librerie): ogni elemento con `data-tag` riceve un bordo tratteggiato e un badge con il nome del tag; il click sul badge apre `/admin/content#` in nuova scheda. +- Le animazioni reveal restano attive; il badge è posizionato in modo da non alterare il layout (position absolute su wrapper relativo o outline, da verificare in implementazione). + +### 9. Gestione errori + +- Tag mancante nel DB e nel seed: stringa vuota + `console.warn` lato server. La pagina renderizza comunque. +- `styles` JSON corrotto nel DB: si ignora e si usa `{}`. +- Upload immagine: stessi limiti e messaggi del blog; file orfani in `uploads/content/` tollerati (nessuna garbage collection automatica). +- API: 401 senza sessione, 403 per ruolo insufficiente, 400 con messaggio esplicito per tag inesistente, tipo errato o preset fuori whitelist. + +### 10. Test + +- Unit (`vitest`, pattern esistente in `tests/`): content lib (fallback a cascata, invalidazione cache, sync del seed), validazione preset, migrazione colonna `role`. +- API: autorizzazione per ruolo (editor può contenuti, non può post; anonimo 401), salvataggio e rilettura, rifiuto preset non in whitelist. +- Smoke: le pagine vetrina renderizzano in SSR con contenuti dal DB; overlay annotate presente con sessione e assente senza. + +## Fasi di lavoro previste (indicative per il piano) + +1. Infrastruttura: tabella, migrazione ruoli, content lib con cache, componenti ``/``, preset. +2. Estrazione e tagging: inventario completo testi/immagini pagina per pagina, creazione seed, sostituzione nei template e nei componenti (lavoro meccanico ma esteso; è la fase più lunga). +3. Pannello + API + overlay annotate + ruolo editor. +4. Test, verifica end-to-end locale, deploy e verifica in produzione. + +## Fuori ambito + +- CSS libero per elemento (escluso per scelta). +- Versionamento/storico delle modifiche ai contenuti (eventuale evoluzione futura). +- Modifica della struttura delle pagine (aggiunta/rimozione sezioni) dal pannello. +- Gestione multilingua. +- Blog: resta gestito dal suo admin TipTap esistente, fuori dal sistema tag.