App Nuxt 3 + Nuxt UI qui lit les projets du CMS Grav et compose des portfolios A3 paysage exportés en PDF (gabarit book_v3) : composeur WYSIWYG, templates cover/toc/projet/grille/page libre, pipeline Playwright + Ghostscript, gel du contenu à la génération, partage public opt-in. Dépôt autonome : Dockerfile (base Playwright + Ghostscript + ffmpeg + rsync) inclus. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
3.4 KiB
Markdown
68 lines
3.4 KiB
Markdown
# CLAUDE.md — générateur de portfolios
|
||
|
||
App Nuxt (Nuxt 3 + Nuxt UI v3) qui lit les projets du CMS Grav de Figures Libres
|
||
et compose des portfolios **A3 paysage** exportés en PDF, fidèles au gabarit
|
||
`book_v3`. Ce dépôt est **autonome et déployable seul** (son `Dockerfile` est ici).
|
||
|
||
## Tout tourne dans Docker
|
||
|
||
Node, build Nuxt, Playwright/Chromium, Ghostscript, ffmpeg, rsync — **rien sur
|
||
l'hôte**. En dev, l'app est lancée via le dépôt d'orchestration `portfolio-stack`
|
||
(`docker compose up -d`), qui monte ce dossier en volume sur le port 8091.
|
||
|
||
```bash
|
||
# depuis le dépôt stack (ce dossier cloné en ./generator) :
|
||
docker compose run --rm --no-deps generator npm install # dépendances
|
||
docker compose exec generator npm run build # build de prod (.output)
|
||
docker logs -f figureslibres-portfolio-generator # logs du dev server
|
||
```
|
||
|
||
Mot de passe dev : `figureslibres` (`PORTFOLIO_PASSWORD`).
|
||
|
||
## Architecture
|
||
|
||
- `server/lib/` — le cœur :
|
||
- `content-source.ts` : abstrait la source de contenu. Mode `dev` = lecture
|
||
directe du FS Grav monté en `:ro` ; mode `prod` = cache alimenté par rsync/SSH.
|
||
- `grav-reader.ts` : parse les pages Grav (frontmatter YAML, `media_order`,
|
||
taxonomies) en `Project` (voir `types/index.ts`).
|
||
- `pdf-pipeline.ts` : rend `/print/<id>` via Playwright/Chromium en A3 paysage
|
||
(1190,55 × 841,89 pt), images à 2× (sharp), post-traitement Ghostscript, métadonnées.
|
||
- `portfolio-store.ts` : CRUD des portfolios (JSON sur disque), gel/dégel du contenu.
|
||
- `media.ts`, `session.ts`.
|
||
- `server/api/` — endpoints (auth, projects, portfolios, media, sync, exports, public).
|
||
- `components/templates/` — pages du gabarit (cover, toc, projet, grille, page libre).
|
||
- `components/preview/` — `PageRenderer` (dispatch) + `PageViewport` (aperçu à l'échelle).
|
||
- `components/composer/` — UI de composition (curation médias, fonds, pickers).
|
||
- `pages/print/[id].vue` — **rendu print servant à la génération PDF** (ne pas casser).
|
||
- `types/index.ts` — modèle de données complet et commenté (source de vérité du schéma).
|
||
|
||
## Modèle de données (résumé)
|
||
|
||
Un `Portfolio` = `cover` (CoverConfig) + `pages[]`. Types de page : `cover`,
|
||
`toc`, `project` (couverture bandeau + image), `project-grid` (grille d'images
|
||
entières), `free`. Le contenu Grav (`Project`, `MediaAsset`) est **en lecture
|
||
seule**. À la génération, le contenu utilisé est **figé** dans `data/frozen/<id>/`
|
||
(`FrozenSnapshot`) : régénérer redonne le même PDF ; « Réactualiser depuis Grav »
|
||
resynchronise volontairement.
|
||
|
||
## Contraintes fermes
|
||
|
||
- Le contenu Grav n'est jamais modifié par l'app (monté `:ro` côté stack).
|
||
- Les images ne sont **jamais tronquées** dans les grilles (calées entières).
|
||
- Les vidéos ne sont pas sélectionnables dans les grilles.
|
||
- Format PDF : A3 paysage aux dimensions exactes du gabarit.
|
||
|
||
## Pièges Chromium print (avant de toucher aux templates)
|
||
|
||
- **`columns-*` (CSS multicol) = page PDF blanche** dans Chromium print. Ne pas
|
||
utiliser de colonnes CSS dans les templates ; vérifier chaque template via `pdftoppm`.
|
||
- **Webfonts en `font-display: block` = texte absent du PDF**. Le pipeline
|
||
réinvalide les styles avant `page.pdf()` — ne pas retirer cette étape.
|
||
|
||
## Données
|
||
|
||
Tout est sur disque dans `data/` (sauvegarde = copie du dossier), git-ignoré :
|
||
`portfolios/*.json`, `exports/*.pdf`, `frozen/<id>/`, `cache/` (régénérable),
|
||
`content-cache/` (rsync prod).
|