Files
portfolio-generator-app/CLAUDE.md
Valentin Le Moign 59dfbe4155 Générateur de portfolios PDF Figures Libres (app Nuxt autonome)
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>
2026-07-05 18:37:09 +02:00

3.4 KiB
Raw Blame History

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.

# 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].vuerendu 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).