# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Interazione - L'utente scrive in italiano. **Rispondi in italiano.** - Codice, commenti, nomi di variabili, commit message, log e documentazione tecnica: **in inglese.** - Nei commit **non aggiungere `Co-Authored-By: Claude`** né altri trailer di attribuzione. ## Stato Struttura, content type, pagine blog, Docker e Caddy sono in piedi. Restano da fare: sitemap e `robots.txt` dinamici, ricerca, e i test (nessun framework ancora configurato). ## Architettura ```text Browser → Caddy ─┬─ /admin e i path dei plugin Strapi → Strapi 5 → PostgreSQL └─ tutto il resto → Nuxt 4 (SSR) → REST Strapi ``` Caddy instrada per **path**, non per sottodominio: `PUBLIC_DOMAIN` serve sia il sito che il pannello Strapi. Ogni plugin Strapi monta la propria API admin sul proprio path di primo livello, non tutto sotto `/admin` (es. `/content-manager`, `/upload`, `/i18n`...): l'elenco completo dei prefissi da instradare a Strapi vive nel `Caddyfile`. Se aggiungi un plugin Strapi, aggiungi il suo prefisso lì. Niente di questo tocca `/api`, riservato agli endpoint Nitro del frontend. Un solo dominio, un solo certificato TLS. - Strapi è la **sola** fonte di verità editoriale. Niente altro backend (no Express/Nest/Fastify): se serve logica server, sta in Nitro (`frontend/server/`) o in un controller Strapi. - I visitatori pubblici non si autenticano mai. Solo editor/admin usano l'auth Strapi. - **Il browser dei visitatori pubblici non parla mai con Strapi.** Le pagine chiamano gli endpoint Nitro in `frontend/server/api/`, che sono l'unico posto dove si costruiscono query Strapi. Così `NUXT_STRAPI_URL` resta l'indirizzo interno Docker, niente CORS e niente token nel client. Se aggiungi una vista, aggiungi l'endpoint lì e tipizza il ritorno in `shared/types/blog.ts`. Fanno eccezione, per costruzione: l'admin panel (`/admin`, uso editor/admin autenticato) e le immagini cover, che il browser carica direttamente da `PUBLIC_STRAPI_URL` (`/uploads/...`, sola lettura, nessun'autenticazione richiesta né concessa). - Il Markdown dell'articolo è convertito in HTML **nell'endpoint**, non nel componente: il contenuto è già nell'HTML SSR e `marked` resta fuori dal bundle client. - L'**interfaccia** è solo in italiano, stringhe statiche nei componenti (niente `@nuxtjs/i18n`, niente file di traduzione): `` è fisso in `nuxt.config.ts`. La traduzione per i visitatori stranieri è delegata all'estensione Google Translate del browser, non è gestita dall'app. I **contenuti** restano monolingua — la localizzazione di Strapi è disattivata. - `cms/src/index.ts` (`bootstrap`) dà al ruolo Public solo `find`/`findOne` su Article e Category, e disattiva la registrazione pubblica: non esistono utenti front-end, solo amministratori. Qualsiasi permesso in più va motivato. - Postgres non è esposto pubblicamente. Media su volume persistente, mai binari nel DB. ## Comandi Package manager: quello del lockfile. Se non esiste ancora → `npm`. ```bash # frontend/ npm run dev # dev server npm run build # build produzione npm run typecheck # nuxi typecheck — obbligatorio prima di dichiarare fatto npm run lint # cms/ npm run develop # Strapi con content-type builder attivo npm run build # admin panel npm run start # produzione # stack completo (con Caddy e domini reali) docker compose up -d --build docker compose logs -f cms # stack locale senza domini né TLS: porte su localhost, niente Caddy docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build database cms frontend ``` `docker-compose.dev.yml` va passato **sempre esplicitamente**: non è un `override.yml` proprio per non finire per sbaglio in produzione esponendo le porte. Nessun test framework è ancora configurato. Se ne aggiungi uno, documenta qui il comando per lanciare **un singolo test**. ## Modelli di contenuto | Type | Campi | |---|---| | Article | title, slug (UID da title), content (Markdown), cover, category (rel) | | Category | name, slug | Deliberatamente minimale: **niente Author** (l'unico autore è l'admin), niente tag, nessun campo SEO separato. La meta description è ricavata dall'inizio del body (`summarise` in `frontend/server/utils/strapi.ts`), la data è il `publishedAt` di Draft & Publish, l'immagine social è la cover. Non reintrodurre questi campi senza che servano davvero. Draft & Publish attivo su Article. Le URL pubbliche usano lo **slug**, mai l'id numerico. ## Frontend Rotte: `/`, `/blog`, `/blog/[slug]`, `/category/[slug]`. - SSR o prerender per tutto ciò che è indicizzabile. Mai pagine blog client-only senza motivo scritto. - `