- Champ Présentation prérempli avec le texte Grav, suivi modifié/synchro (textOverride undefined = suit Grav, sinon figé ; bouton Réinitialiser) - Fix génération PDF : savePortfolio écrit en atomique (temp+rename) pour éviter la corruption de lecture par l'autosave concurrent du rendu print - Toast d'échec de génération explicite (message serveur) - Refonte architecture visuelle, fond unifié white/tint/full, dispositions couverture bandeau/colonne, instantané public complet, index 3 lignes
4.7 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 d'un projet, disposition band = bandeau haut ou
split = colonne texte + image pleine hauteur à droite), 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.
Chaque page porte un fond unifié background à trois modes :
white | tint (aplat beige fixe #efece3) | full (la page — ou le bandeau /
la colonne d'une couverture projet — prend la couleur du filet bandColor, défaut
encre FL_INK). Il n'y a plus de couleur automatique par catégorie
(bandColorOf/stripColorOf retombent sur FL_INK).
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.
Conventions de rendu (templates)
- Fond
background:useTheme→backgroundColorOf(bg, fullColor)/backgroundStyle(bg, fullColor). Le modefulldérive sa couleur du filet au rendu (fullColor) — ne rien stocker en dur ; leBackgroundEditor(Blanc / Teinté / Plein) est le même sur tous les types de page. - Syne toujours en Regular (poids 400), y compris titres et gras de prose ; seule exception : le mot « Figures Libres » de la couverture (Bold).
- Liens Grav :
stripTargetParamretire le?target=_blankque Grav accole à chaque URL (lien projet + liens dans les corps de texte), à l'affichage. - Filet :
--fl-strip(5 mm, couverture/sommaire/page libre) ou--fl-strip-light(2 mm, filet fin des grilles et des couvertures projet non « plein »).
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).