Files
librodrome/CLAUDE.md
T

11 KiB
Raw Blame History

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

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)

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

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

  • Consignes d'Yvv, datées et mot pour mot : docs/consignes-yvv.md. Les relire avant de toucher au design ; y inscrire toute nouvelle consigne le jour où elle est donnée.
  • CSS via UnoCSS + variables CSS palettes ; composants Vue SFC <script setup lang="ts">
  • Oiseaux du kit du studio DA (depuis le 03/11/2026, à la place des shadoks) : UiOiseau, app/utils/oiseaux.ts (copiés tels quels du kit), posés par UiOiseauPose près du contenu de leur métier, jamais en rangée. Règles du kit : currentColor, au moins 72 px, opacité 0,18–0,28 près du texte et 0,10–0,14 derrière une carte, un oiseau jamais deux fois sur une page, un seul sous 768 px, pas de miroir pour calligraphe et typographe. Consignes : docs/consignes-yvv.md § Les oiseaux.
  • Hexagramme 益 (#42 Yi, Augmentation) dans layouts/default.vue
  • Logotype validé du studio DA (UiLogotype) dans TheHeader.vue, et UiSectionSign du kit — ne pas modifier sans demander. Source du kit : /home/yvv/Documents/PROD/IMAGES/_atelier/2026-10-27-librodrome-logo/livrables/kit-site/.

SEO & Recherche IA — Checklist permanente

À chaque nouvelle page Vue

  1. Appeler useSeoPage() — jamais useHead({ title }) seul
    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 :

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()