- 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
88 lines
4.7 KiB
Markdown
88 lines
4.7 KiB
Markdown
# 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).
|