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>
3.4 KiB
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. Modedev= lecture directe du FS Grav monté en:ro; modeprod= cache alimenté par rsync/SSH.grav-reader.ts: parse les pages Grav (frontmatter YAML,media_order, taxonomies) enProject(voirtypes/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é
:rocô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 viapdftoppm.- Webfonts en
font-display: block= texte absent du PDF. Le pipeline réinvalide les styles avantpage.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).