ci/woodpecker/push/woodpecker Pipeline was successful
Section d'exécution du dossier (sorties relevées par orch, session dev-65), CLAUDE.md corrigé (volume librodrome-data sur /app/data, m4 de la relecture), HANDOFF : état en ligne et décisions restant à Yvv. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
202 lines
9.8 KiB
Markdown
202 lines
9.8 KiB
Markdown
# Librodrome
|
||
|
||
Site vitrine du projet Le Librodrome — livre + chansons sur l'économie du don.
|
||
|
||
- **Rôle** : site éditorial en production sur `librodrome.org` et hub d'intégration
|
||
de l'écosystème (GrateWizard en popup ; liens libreDecision
|
||
`decision.librodrome.org`, SejeteralO, sweetHome/LaPlanK dans le YAML de `site/`).
|
||
Cap : plateforme éditoriale de bassin de vie (autonomie collective, économie
|
||
monnaie libriste, boîte à outils, événementiel).
|
||
- **Port** : 3000 (dev), 3099 (smoke du build). **Dépôt** : `origin` =
|
||
`gitea@git.open.us.org:EHV/librodrome.git` ; `main` = production.
|
||
|
||
## Protocole de début de session
|
||
|
||
1. `git pull --rebase origin main` (règle canon ; voir § Contenu administrable)
|
||
2. Vérifier que `data/messages.yml` existe — si absent, signaler avant toute opération
|
||
3. Si l'objectif de la session n'est pas précisé, le demander
|
||
|
||
## Stack
|
||
|
||
- Nuxt 4 (Vue 3, TypeScript, Nitro) + Nuxt Content, Pinia, UnoCSS, VueUse, Nuxt Image
|
||
- Layer `@yvv/nuxt-base` (`extends` dans `nuxt.config.ts`, dépendance `git+https` épinglée `#v0.1.0`)
|
||
- Icônes : Lucide + Phosphor (via @iconify-json) ; pnpm épinglé 10.29.3 (`packageManager`)
|
||
- Audience : Umami (`nuxt-umami`)
|
||
- Déploiement : Woodpecker (`.woodpecker.yml`) — push sur `main` →
|
||
`docker compose -f docker/docker-compose.yml -f docker/docker-compose.fabio.yml up --build -d`,
|
||
**sans étape de test**. Secrets CI : `NUXT_ADMIN_PASSWORD`, `NUXT_ADMIN_SECRET`.
|
||
Healthcheck de l'image : `/api/health`.
|
||
|
||
## Structure
|
||
|
||
```
|
||
app/
|
||
pages/
|
||
numerique/ # index + [slug]
|
||
economique/ # index, monnaie-libre, commande, productions-collectives
|
||
modele-eco/ # sommaire + chapitres [slug]
|
||
citoyenne/ # index + [slug]
|
||
en-musique/ # player audio
|
||
admin/ # back-office (pages/, book/, songs, messages, media)
|
||
components/
|
||
book/Actions.vue # boutons partagés (player, PDF, chapitres, commande)
|
||
home/ # BookSection, AxisBlock, AxisGrid, HeroSection, HomeMessages
|
||
admin/, player/, song/, ui/
|
||
composables/ # useAudioPlayer, useBookData, useGrateWizard, usePageContent
|
||
stores/palette.ts # 4 palettes saisonnières (été par défaut, persisté localStorage)
|
||
assets/css/main.css # UnoCSS + overrides light mode
|
||
site/
|
||
pages/ # Contenu YAML administrable par section (sous-dossiers)
|
||
site.yml # Config globale (nav, footer, GrateWizard)
|
||
bookplayer.config.yml # Config player/chapitres
|
||
data/
|
||
messages.yml # Runtime — prod : volume nommé librodrome-data sur /app/data — JAMAIS dans git
|
||
server/
|
||
api/content/ # GET pages YAML (chemins imbriqués)
|
||
api/admin/content/ # PUT pages YAML + liste
|
||
api/messages/ # GET (publiés) + POST (nouveau message)
|
||
api/admin/messages/ # GET tous + PUT (type, reply, published) + DELETE
|
||
utils/content.ts # readDataYaml/writeDataYaml (data/) + readYaml/writeYaml (site/)
|
||
middleware/redirects.ts # 301 : /gestation, /modele-eco, /decision, /lire
|
||
docker/
|
||
Dockerfile, docker-compose.yml, docker-compose.dev.yml
|
||
```
|
||
|
||
## Données runtime (CRITIQUE)
|
||
|
||
- `data/messages.yml` : en prod, volume nommé `librodrome-data` (`external: true`) monté
|
||
sur `/app/data` (l'image tourne dans `/app`, le serveur lit `process.cwd()/data`).
|
||
Créé et semé à la main sur sonic par l'acte `actes/2026-10-26-persistance-messages.md` ;
|
||
un déploiement sans ce volume échoue au lieu de repartir de la graine.
|
||
- Persisté entre les rebuilds depuis le 26/10/2026 — **jamais écrasé par les commits ni
|
||
par le déploiement** (avant, il ne l'a jamais été : voir le dossier d'acte)
|
||
- Structure message : `{ id, author, email, text, type, published, createdAt, reply: { text, publishedAt } | null }`
|
||
- Types : `reaction` (ancien, affiché "Réaction", plus proposé dans les formulaires) | `question` | `suggestion` | `retour`
|
||
- En dev local : `<racine>/data/messages.yml`
|
||
- Graine : `site/messages.yml` (versionné) copié par `server/plugins/seed-messages.ts`
|
||
si `data/messages.yml` est absent
|
||
- ⚠ Hors volume, rien ne persiste : éditions /admin de `site/` et `content/`, médias
|
||
envoyés par /admin (`/app/public`, jamais servis). Décisions d'Yvv en attente (HANDOFF).
|
||
- **Avant toute migration de chemin ou écriture sur data/ : demander confirmation**
|
||
|
||
## Intégration GrateWizard
|
||
|
||
- URL dev : `app/app.config.ts` → `localhost:3001`
|
||
- URL prod : `https://gratewizard.axiom-team.fr`
|
||
|
||
## Contenu administrable
|
||
|
||
- YAML dans `site/pages/` par section ; API supporte les chemins imbriqués
|
||
- Admin : `/admin/pages` liste, `/admin/pages/{path}` édite en YAML
|
||
- Git sync (`server/utils/gitSync.ts`, actif si `ADMIN_GIT_SYNC=true`) : commit
|
||
`[admin] …` puis push depuis le conteneur → `git pull --rebase` obligatoire en début de session
|
||
- ⚠ Constat du 26/10/2026 : la CI ne fixe pas `ADMIN_GIT_SYNC` (défaut `false` dans le
|
||
compose), l'image de prod n'embarque pas `.git`, et l'historique ne contient aucun
|
||
commit `[admin]`. Les éditions /admin en prod ne reviennent donc pas dans git
|
||
(voir HANDOFF).
|
||
|
||
## Commandes
|
||
|
||
```bash
|
||
pnpm dev # Dev server :3000
|
||
pnpm build # Build production
|
||
PORT=3099 node .output/server/index.mjs # Test build prod (toujours avant commit)
|
||
```
|
||
|
||
## Vérifier
|
||
|
||
Niveaux V0 à V4 : gabarit `harness/skills/boucles/_gabarit.md`. Seuls V0 et V3 existent :
|
||
aucun test automatisé, ni lint ni typecheck (aucun script, pas de `vue-tsc`).
|
||
|
||
**V0 — build + smoke** (vert le 26/10/2026, build ≈ 1 min)
|
||
```bash
|
||
date; pnpm build # exit 0
|
||
NUXT_ADMIN_PASSWORD=jetable NUXT_ADMIN_SECRET=jetable PORT=3099 node .output/server/index.mjs &
|
||
sleep 3; for p in / /api/health /api/admin/messages; do
|
||
curl -s -o /dev/null -w "$p %{http_code}\n" http://127.0.0.1:3099$p; done # 200, 200, 401
|
||
kill $(lsof -ti :3099)
|
||
```
|
||
Lancé depuis la racine, le serveur lit et écrit le vrai `data/` : aucun POST pendant le
|
||
smoke. Le build ne lit pas `.env` : sans `NUXT_ADMIN_SECRET`, l'API admin répond 503.
|
||
|
||
**V1 — invariants (à construire)** : vitest sur les API messages (POST/GET/DELETE),
|
||
admin content (PUT YAML → fichier écrit, format préservé), auth (mauvais mot de passe →
|
||
401). `data/` et `site/` se résolvent depuis `process.cwd()` : lancer depuis un dossier jetable.
|
||
|
||
**V2 — parcours (à construire)** : Playwright, chromium bundlé (HANDOFF, Pièges), mobile +
|
||
desktop, ambiances claire et sombre : accueil sans erreur console, les 3 axes, lecteur PDF,
|
||
player, formulaire message (data jetable), login admin. Captures dans `/tmp/librodrome-shots/`.
|
||
|
||
**V3 — données**
|
||
```bash
|
||
sha256sum data/messages.yml # avant et après le run : identiques
|
||
git status --short # rien sous data/ ni .env
|
||
git log --oneline origin/main..HEAD # ce que le prochain push mettrait en ligne
|
||
```
|
||
|
||
**V4** : rapport avec sorties réelles et captures ; tout niveau sauté est annoncé.
|
||
|
||
**Trous** : la CI déploie sans gate (un commit cassé part en prod) ; V1 et V2 inexistants.
|
||
Prochaines briques : gate build + tests dans `.woodpecker.yml` avant le `up`, puis V1 et V2
|
||
exposés en `pnpm verify`.
|
||
|
||
**Gates humains (anneau 1)** : push de `main` (= mise en ligne) ; suppressions admin
|
||
(`server/api/admin/messages/[id].delete.ts`, `media/[...path].delete.ts`,
|
||
`chapters/[slug].delete.ts`) ; toute écriture sous `site/` ou `content/book/` (contenu de
|
||
prod) ; `data/messages.yml` ; lien de commande du livre (Bookelis,
|
||
`site/pages/economique/commande.yml`, aucun paiement sur le site) ; auth admin
|
||
(`server/middleware/admin-auth.ts`) et secrets `NUXT_ADMIN_*`.
|
||
|
||
## Conventions
|
||
|
||
- CSS via UnoCSS + variables CSS palettes ; composants Vue SFC `<script setup lang="ts">`
|
||
- Shadoks SVG inline thématiques sur chaque page (hidden mobile, opacity 0.18–0.28)
|
||
- Hexagramme 益 (#42 Yi, Augmentation) dans `layouts/default.vue`
|
||
- Signature § (logo calligraphique SVG gradient) dans `TheHeader.vue` — ne pas modifier sans demander
|
||
|
||
## SEO & Recherche IA — Checklist permanente
|
||
|
||
### À chaque nouvelle page Vue
|
||
|
||
1. **Appeler `useSeoPage()`** — jamais `useHead({ title })` seul
|
||
```ts
|
||
useSeoPage({
|
||
title: 'Titre page — Le Librodrome', // 50–60 chars
|
||
description: 'Description...', // 120–155 chars
|
||
image: '/images/og-specifique.jpg', // optionnel, sinon og-default.png (logo §)
|
||
type: 'website' | 'article' | 'book', // défaut: website
|
||
})
|
||
```
|
||
2. **og:image** : utiliser la couverture `/images/Couv-Economie-du-don.jpg` pour les pages livre/musique ; `og-default.png` (logo §) pour les pages site générales
|
||
3. **type: 'article'** pour les chapitres du modele-eco ; **type: 'book'** pour les index livre
|
||
|
||
### À chaque nouveau fichier YAML dans `site/pages/`
|
||
|
||
Ajouter le bloc `seo:` en tête :
|
||
```yaml
|
||
seo:
|
||
title: "Titre SEO — 50-60 chars max"
|
||
description: "Description 120-155 chars, avec mots-clés naturels"
|
||
# image: /images/og-specifique.jpg # optionnel
|
||
# keywords: [monnaie libre, TRM, économie du don]
|
||
```
|
||
|
||
### Règles description SEO
|
||
|
||
- Commence par un verbe d'action ou une affirmation forte
|
||
- Contient les mots-clés : économie du don, monnaie libre, TRM, June, bassin de vie, autonomie
|
||
- 120–155 caractères (ni trop court, ni tronqué)
|
||
- Différente du titre et du H1
|
||
|
||
### JSON-LD automatique
|
||
|
||
- JSON-LD `Organization` + `Book` : injecté globalement dans `app.vue` — ne pas redupliquer
|
||
- Chapitres → og:type `article` via `useSeoPage({ type: 'article' })`
|
||
- Sitemap : géré par `@nuxtjs/sitemap` — ajouter manuellement les nouvelles routes statiques dans `nuxt.config.ts > sitemap.urls`
|
||
|
||
### Analytics — Nouveaux composants
|
||
|
||
- Tout bouton CTA externe : `trackCta(label, url)` depuis `useTracking()`
|
||
- Tout `<a target="_blank">` : capturé automatiquement par `useScrollTracking()` dans `default.vue`
|
||
- Tout nouveau lecteur média : appeler `trackAudioPlay` / `trackPdfOpen` depuis `useTracking()`
|