Files
portfolio-generator-app/CLAUDE.md
Valentin Le Moign ef1f14335d Textes projet éditables suivant Grav + écriture atomique du store
- 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
2026-07-06 13:26:06 +02:00

4.7 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 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é :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.

Conventions de rendu (templates)

  • Fond background : useThemebackgroundColorOf(bg, fullColor) / backgroundStyle(bg, fullColor). Le mode full dérive sa couleur du filet au rendu (fullColor) — ne rien stocker en dur ; le BackgroundEditor (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 : stripTargetParam retire le ?target=_blank que 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 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).