2026-08-25 10:17:53 +02:00
|
|
|
# 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
|
|
|
|
|
|
2026-09-11 11:41:52 +02:00
|
|
|
Struttura, content type, pagine blog, Docker, Caddy e i test (unit/integration/e2e) sono in
|
|
|
|
|
piedi. Restano da fare: sitemap e `robots.txt` dinamici, ricerca.
|
2026-08-25 10:17:53 +02:00
|
|
|
|
|
|
|
|
## Architettura
|
|
|
|
|
|
|
|
|
|
```text
|
2026-09-11 11:25:14 +02:00
|
|
|
Browser → Caddy ─┬─ /_/* e /api/* → PocketBase (admin UI + API)
|
|
|
|
|
└─ tutto il resto → Nuxt 4 (SSR) → REST PocketBase (interno)
|
|
|
|
|
("/admin" fa redirect a /_/, gestito da Nitro)
|
2026-08-25 10:17:53 +02:00
|
|
|
```
|
|
|
|
|
|
2026-09-11 11:25:14 +02:00
|
|
|
Caddy instrada per **path**, non per sottodominio o porta: `PUBLIC_DOMAIN` serve sia il sito
|
|
|
|
|
che il pannello PocketBase. `/_/*` (dashboard) e `/api/*` (REST/file API) vanno **senza prefisso**
|
|
|
|
|
a PocketBase — la sua dashboard referenzia se stessa con quei path assoluti, quindi non si possono
|
|
|
|
|
instradare con uno strip-prefix (es. `/admin/*` riscritto): romperebbe gli asset/le chiamate della
|
|
|
|
|
dashboard. Per questo `/api` è riservato a PocketBase, non a Nitro: gli endpoint del frontend
|
|
|
|
|
vivono sotto `/content/*` (`frontend/server/routes/content/`, non `server/api/`). `/admin` è una
|
|
|
|
|
route Nitro (`frontend/server/routes/admin.get.ts`) che fa redirect a `/_/`, non una regola
|
|
|
|
|
Caddy: così funziona identico anche in sviluppo, dove Caddy non fa parte dello stack. Un solo
|
|
|
|
|
dominio, un solo certificato TLS. Se cambi questa scelta di routing, spiega il trade-off prima
|
|
|
|
|
(vedi [Vincoli](#vincoli)).
|
2026-08-25 19:49:37 +02:00
|
|
|
|
2026-09-11 11:25:14 +02:00
|
|
|
- PocketBase è la **sola** fonte di verità editoriale (CMS + database SQLite in un solo processo).
|
|
|
|
|
Niente altro backend (no Express/Nest/Fastify): se serve logica server, sta in Nitro
|
|
|
|
|
(`frontend/server/`) o in una regola/migration PocketBase.
|
|
|
|
|
- I visitatori pubblici non si autenticano mai. Solo il superuser usa l'auth PocketBase.
|
|
|
|
|
- **Il browser dei visitatori pubblici non parla mai con PocketBase per i contenuti.** Le pagine
|
|
|
|
|
chiamano gli endpoint Nitro in `frontend/server/routes/content/`, che sono l'unico posto dove si
|
|
|
|
|
costruiscono query PocketBase. Così `NUXT_POCKETBASE_URL` resta l'indirizzo interno Docker,
|
|
|
|
|
niente CORS e niente token nel client. Se aggiungi una vista, aggiungi l'endpoint lì (sotto
|
|
|
|
|
`/content/*`, mai `/api/*`) e tipizza il ritorno in `shared/types/blog.ts`. Fanno eccezione, per
|
|
|
|
|
costruzione: l'admin panel (`/_/`, uso superuser autenticato, raggiungibile anche da `/admin`) e
|
|
|
|
|
le immagini cover, che il browser carica direttamente da `PUBLIC_POCKETBASE_URL`
|
|
|
|
|
(`/api/files/...`, sola lettura, nessun'autenticazione richiesta né concessa).
|
2026-08-25 11:42:33 +02:00
|
|
|
- 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.
|
2026-09-11 13:52:13 +02:00
|
|
|
- Il sito è **bilingue** (italiano, sorgente, e inglese), fatto in casa senza `@nuxtjs/i18n` né
|
|
|
|
|
file di traduzione runtime: due lingue, ~20 stringhe statiche, nessun plurale — la libreria
|
|
|
|
|
costerebbe più codice di quanto risolva (il fallback su contenuti non tradotti, la
|
|
|
|
|
localizzazione degli endpoint Nitro e gran parte della SEO restano comunque da scrivere a mano).
|
|
|
|
|
`<html lang>` è dinamico (per pagina, via `useSeo`), non più fisso. Le pagine inglesi vivono sotto
|
|
|
|
|
prefisso `/en/*`, come wrapper sottili attorno agli stessi componenti "view" delle pagine
|
|
|
|
|
italiane — vedi `docs/frontend.md#english-content`. Le stringhe UI statiche vivono **solo** in
|
|
|
|
|
`frontend/shared/utils/i18n.ts`. Gli **articoli** hanno campi paralleli opzionali
|
|
|
|
|
(`titleEn`/`contentEn`/`coverAltEn`, `nameEn` su categorie), tradotti **a mano** dall'editor in
|
|
|
|
|
admin — nessuna traduzione automatica. Vedi `docs/content-model.md#english-content` per lo
|
|
|
|
|
schema e la politica di fallback (404 pulito sul dettaglio non tradotto, filtro nelle liste).
|
2026-09-11 11:25:14 +02:00
|
|
|
- `pocketbase/pb_migrations/*.js` definisce collection e regole: `listRule`/`viewRule` pubblici
|
|
|
|
|
solo su `articles` (solo pubblicati, via `publishedAt`) e `categories`; `createRule`/
|
|
|
|
|
`updateRule`/`deleteRule` sempre `null` (solo superuser). Qualsiasi permesso in più va motivato.
|
|
|
|
|
- SQLite (PocketBase) non è esposto pubblicamente sulla rete — solo tramite l'API PocketBase
|
|
|
|
|
stessa. Media sul volume persistente `pocketbase-data`, mai binari fuori da lì.
|
2026-08-25 10:17:53 +02:00
|
|
|
|
|
|
|
|
## 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
|
|
|
|
|
|
2026-09-11 11:41:52 +02:00
|
|
|
npm run test:unit # Vitest, ambiente Nuxt: funzioni pure + componenti/composable
|
|
|
|
|
npm run test:integration # Vitest, server Nitro reale + PocketBase effimero reale
|
|
|
|
|
npm run test:e2e # Playwright, browser reale contro build + PocketBase effimero reale
|
|
|
|
|
npm run test # i tre, in sequenza
|
|
|
|
|
|
|
|
|
|
# singolo test:
|
|
|
|
|
npx vitest run --config vitest.unit.config.ts tests/unit/queries.utils.test.ts
|
|
|
|
|
npx vitest run --config vitest.integration.config.ts -t "lists only published articles"
|
|
|
|
|
npx playwright test tests/e2e/home.spec.ts
|
|
|
|
|
|
2026-09-11 11:25:14 +02:00
|
|
|
# pocketbase/ (nessun npm script: binario singolo, le migration si applicano da sole all'avvio)
|
|
|
|
|
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build pocketbase frontend
|
2026-08-25 10:17:53 +02:00
|
|
|
|
2026-08-25 11:42:33 +02:00
|
|
|
# stack completo (con Caddy e domini reali)
|
2026-08-25 10:17:53 +02:00
|
|
|
docker compose up -d --build
|
2026-09-11 11:25:14 +02:00
|
|
|
docker compose logs -f pocketbase
|
2026-08-25 11:42:33 +02:00
|
|
|
|
|
|
|
|
# stack locale senza domini né TLS: porte su localhost, niente Caddy
|
2026-09-11 11:25:14 +02:00
|
|
|
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build pocketbase frontend
|
2026-08-25 10:17:53 +02:00
|
|
|
```
|
|
|
|
|
|
2026-08-25 11:42:33 +02:00
|
|
|
`docker-compose.dev.yml` va passato **sempre esplicitamente**: non è un `override.yml` proprio
|
|
|
|
|
per non finire per sbaglio in produzione esponendo le porte.
|
|
|
|
|
|
2026-09-11 11:41:52 +02:00
|
|
|
`tests/` sta nella root del repo (non sotto `frontend/`), perché l'e2e esercita frontend e
|
|
|
|
|
PocketBase insieme — gli strumenti (config Vitest/Playwright, `node_modules`) restano comunque in
|
|
|
|
|
`frontend/`, l'unico progetto npm del repo, e puntano a `../tests/`. Playwright risolve i pacchetti
|
|
|
|
|
dal `node_modules` più vicino al file di test: per questo `npm run test:e2e` crea prima (script
|
|
|
|
|
`pretest:e2e`, idempotente) il symlink `tests/node_modules → ../frontend/node_modules`.
|
|
|
|
|
|
|
|
|
|
I test di integration/e2e avviano un vero binario PocketBase effimero (scaricato una volta in
|
|
|
|
|
`.cache/pocketbase/` nella root, versione allineata a `pocketbase/Dockerfile`) contro le vere
|
|
|
|
|
migration in `pocketbase/pb_migrations/` — niente PocketBase mockato. `npm run test:e2e` richiede
|
|
|
|
|
Chromium installato una tantum: `npx playwright install chromium` (senza `--with-deps`, che
|
|
|
|
|
richiede `sudo`; se mancano librerie di sistema per il browser, installarle a parte).
|
2026-08-25 10:17:53 +02:00
|
|
|
|
|
|
|
|
## Modelli di contenuto
|
|
|
|
|
|
|
|
|
|
| Type | Campi |
|
|
|
|
|
|---|---|
|
2026-09-11 13:52:13 +02:00
|
|
|
| Article | title, slug, content (Markdown), cover, coverAlt, category (rel), publishedAt, authorName, titleEn, contentEn, coverAltEn |
|
|
|
|
|
| Category | name, slug, nameEn |
|
2026-08-25 11:42:33 +02:00
|
|
|
|
2026-09-11 11:25:14 +02:00
|
|
|
Deliberatamente minimale: niente tag, nessun campo SEO separato. La meta description è ricavata
|
|
|
|
|
dall'inizio del body (`summarise` in `frontend/server/utils/pocketbase.ts`), la data è
|
|
|
|
|
`publishedAt`, l'immagine social è la cover. Non reintrodurre campi senza che servano davvero.
|
2026-08-25 10:17:53 +02:00
|
|
|
|
2026-09-11 11:25:14 +02:00
|
|
|
PocketBase non ha Draft & Publish nativo: `publishedAt` vuoto = bozza, valorizzato (e non nel
|
|
|
|
|
futuro) = pubblicato, imposto dalla `listRule`/`viewRule` della collection `articles`, non da
|
|
|
|
|
codice applicativo. Le URL pubbliche usano lo **slug**, mai l'id del record.
|
2026-08-25 10:17:53 +02:00
|
|
|
|
|
|
|
|
## Frontend
|
|
|
|
|
|
2026-09-11 13:52:13 +02:00
|
|
|
Rotte: `/`, `/blog`, `/blog/[slug]`, `/category/[slug]`, ciascuna con equivalente `/en/...`.
|
2026-08-25 10:17:53 +02:00
|
|
|
|
|
|
|
|
- SSR o prerender per tutto ciò che è indicizzabile. Mai pagine blog client-only senza motivo scritto.
|
|
|
|
|
- `<script setup lang="ts">`, Composition API. Convenzioni Nuxt standard (`pages/`, `components/`,
|
|
|
|
|
`composables/`, `layouts/`, `server/`).
|
2026-09-11 11:25:14 +02:00
|
|
|
- Un solo punto di accesso a PocketBase: un composable/util tipizzato. Non sparpagliare `$fetch` nelle pagine.
|
2026-08-25 10:17:53 +02:00
|
|
|
- Tipizza esplicitamente il confine API. Niente `any` — `unknown` + narrowing.
|
2026-09-11 11:25:14 +02:00
|
|
|
- Query PocketBase: richiedi solo i campi e le relazioni che servono (`fields`, `expand` mirati).
|
2026-08-25 10:17:53 +02:00
|
|
|
Gestisci sempre 404, lista vuota, errore API.
|
|
|
|
|
- Ogni articolo indicizzabile: title unico, meta description, canonical, Open Graph, JSON-LD
|
|
|
|
|
`BlogPosting`, gerarchia heading semantica. Il contenuto deve esistere nell'HTML server-rendered.
|
|
|
|
|
- Accessibilità non negoziabile: focus visibile, input etichettati, alt significativi, link
|
|
|
|
|
descrittivi, navigazione da tastiera.
|
|
|
|
|
|
|
|
|
|
Il riferimento Hostinger è solo ispirazione visiva. Non copiare codice o asset.
|
|
|
|
|
|
2026-09-09 15:28:27 +02:00
|
|
|
## Documentazione
|
|
|
|
|
|
|
|
|
|
`docs/` descrive struttura e comportamento del repo (architettura, content model, frontend) a un
|
|
|
|
|
livello più approfondito di questo file. Se implementi nuove funzionalità, endpoint, content type
|
|
|
|
|
o cambi l'architettura (nuovo servizio, nuova rotta Caddy, nuovo modo di scambiare dati tra
|
|
|
|
|
frontend e CMS), **aggiorna il file `docs/*.md` pertinente nello stesso commit**, o creane uno
|
|
|
|
|
nuovo se non esiste una sezione adatta. Non lasciare `docs/` disallineata col codice.
|
|
|
|
|
|
2026-08-25 10:17:53 +02:00
|
|
|
## Priorità
|
|
|
|
|
|
|
|
|
|
Correttezza e integrità dati → sicurezza → semplicità → SEO/a11y → performance.
|
|
|
|
|
|
|
|
|
|
Nessuna dipendenza, astrazione o servizio senza un bisogno concreto e attuale. Preferisci i
|
2026-09-11 11:25:14 +02:00
|
|
|
built-in PocketBase (regole per-collection, migration) al reimplementare funzioni CMS in Nuxt.
|
2026-08-25 10:17:53 +02:00
|
|
|
|
|
|
|
|
## Vincoli
|
|
|
|
|
|
|
|
|
|
- Mai committare `.env`, segreti, token, credenziali, chiavi. Mantieni `.env.example` sanificato.
|
|
|
|
|
- Mai hard-codare domini di produzione, URL privilegiati o credenziali. Vanno in env var.
|
2026-09-11 11:25:14 +02:00
|
|
|
- Non indebolire auth, CORS, TLS o security header per comodità. Least privilege sulle regole
|
|
|
|
|
PocketBase.
|
2026-08-25 10:17:53 +02:00
|
|
|
- Richiedono **approvazione esplicita**: operazioni distruttive, migrazioni irreversibili, modifiche
|
|
|
|
|
a dati o configurazione di produzione, cambi di credenziali.
|
|
|
|
|
- Non riformattare file non correlati, non fare refactor collaterali, non riscrivere la history,
|
|
|
|
|
non force-push.
|
|
|
|
|
- Le decisioni architetturali di questo file non si cambiano in silenzio: spiega il trade-off prima.
|
|
|
|
|
|
|
|
|
|
## Prima di dichiarare completo
|
|
|
|
|
|
|
|
|
|
Lint → test → typecheck → build dell'app toccata, con gli script del `package.json` relativo.
|
2026-09-09 15:28:27 +02:00
|
|
|
Non affermare che un check è passato se non l'hai eseguito. Se hai toccato architettura o
|
|
|
|
|
funzionalità, verifica di aver aggiornato `docs/` (vedi [Documentazione](#documentazione)). Chiudi
|
|
|
|
|
riassumendo cosa è cambiato e quali rischi restano aperti.
|