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

88 lines
4.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 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`** : `useTheme``backgroundColorOf(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).