Le CMS · 1

L'arborescence
et ses règles

Chaque dossier porte une responsabilité unique et n'empiète pas sur les autres. C'est ce qui permet de régénérer entièrement le site à tout moment sans rien perdre — et donc de traiter le HTML produit comme un produit jetable.

Fichiers source
20
JSON décrivant le site
Modules du noyau
15
4 140 lignes de Python
Pages produites
27
HTML + sitemap + 404
Une archive
543 Ko
contre 84 Mo d'images
§1

L'arborescence complète

Quatre dossiers portent chacun une responsabilité unique, et aucun ne déborde sur les autres : site/ ne contient aucun programme, noyau/ ne contient aucun contenu, dist/ n'est jamais édité à la main.

Cette règle n'est pas une convention de rangement. C'est ce qui permet de régénérer entièrement dist/ à tout moment sans rien perdre — et donc de traiter le HTML comme un produit jetable.

Les sources alimentent le convertisseur, qui produit les pages. La flèche ne va que dans un sens.
Les sources alimentent le convertisseur, qui produit les pages. La flèche ne va que dans un sens.
CMS3/
│
├── site/                      LES SOURCES — tout ce qui décrit le site
│   ├── textes/
│   │   ├── txt_fr.json        1 568 textes, à plat, une clé par texte
│   │   └── txt_de.json        les mêmes clés, en allemand
│   ├── structure/
│   │   ├── accueil.json       l'architecture du document, sans un seul mot
│   │   └── …                  13 pages, une par fichier
│   ├── design/
│   │   └── design.json        couleurs, tailles, espacements — 29 jetons
│   ├── medias/
│   │   └── mediatheque.json   37 images : jeton, alt, variantes, usages
│   ├── langues/
│   │   └── catalogue.json     27 langues : endonyme, drapeau, sens
│   └── donnees/
│       ├── site.json          domaine, navigation, pied de page
│       └── annexes.json       contenu de la page 404
│
├── noyau/                     LE CONVERTISSEUR — aucun contenu ici
│   ├── sources.py             chargement et vérification des sources
│   ├── jetons.py              le vocabulaire fermé des valeurs
│   ├── document.py            l'arbre, et le calcul des niveaux de titre
│   ├── composants.py          l'infrastructure des composants
│   ├── vocabulaire.py         LES 25 COMPOSANTS — c'est le langage
│   ├── images.py              la balise responsive
│   ├── convertisseur.py       JSON → HTML, et l'ordre du <head>
│   ├── contraste.py           calcul WCAG — pas déclaration
│   ├── navigateurs.py         les règles par moteur, exécutables
│   ├── budgets.py             poids des pages, nombre de nœuds DOM
│   ├── annexes.py             sitemap, robots.txt, 404, JavaScript
│   ├── archives.py            historique du site
│   ├── compiler.py            l'orchestration des 10 étapes
│   ├── reset.css              le socle
│   └── style.css              les composants
│
├── outils/
│   ├── valider.py             vérifier un brouillon SANS compiler
│   ├── generer_docs.py        engendre docs/ DEPUIS le code
│   └── construire_mediatheque.py
│
├── docs/                      POUR UN AGENT IA
│   ├── LANGAGE.md             la spécification, à lire
│   ├── langage.json           la même, exécutable — un agent valide contre
│   └── EXTENSION.md           protocole d'ajout d'un jeton ou composant
│
├── tests/
│   ├── controle.py            9 familles de règles sur le HTML produit
│   └── audit.py               comparaison mesurée avec l'ancien site
│
├── documentation/             CE SITE — explicatif, hors production
│
├── archives/                  une version par fichier, 10 conservées
├── dist/                      LE SITE PRODUIT — jamais modifié à la main
└── verifier.sh                valide → compile → contrôle → audite

La règle du sens unique. Rien ne remonte jamais de dist/ vers site/. Si une page produite est fausse, on corrige la source ou le convertisseur — jamais le HTML. C'est ce qui garantit qu'une recompilation ne réintroduit pas un défaut corrigé.

§2

site/ — chaque fichier, un par un

25 fichiers, 756 Ko. Ensemble, ils contiennent la totalité de ce qui distingue ce site d'un autre : ses mots, son architecture, son apparence, ses images.

Le tableau ci-dessous est lu dans le dépôt à chaque construction — tailles et compteurs compris. Une arborescence recopiée à la main finit toujours par décrire un dépôt qui n'existe plus.

Fichier
Mesure
Ce qu'il contient
site/
25 fichiers · 756 Ko
Tout ce qui distingue ce site d'un autre. Remplacer ce dossier produit un site entièrement différent, sans toucher une ligne du convertisseur.
textes/
3 langues
Les mots. Structure plate : une clé, une chaîne. Les fichiers ont rigoureusement les mêmes clés, dans le même ordre — c'est ce que le moteur vérifie avant de compiler une langue.
textes/txt_de.json
177 Ko · 1681 textes
traduction
textes/txt_en.json
169 Ko · 1681 textes
traduction
textes/txt_fr.json
175 Ko · 1681 textes
langue maîtresse
structure/
13 pages · 283 blocs
L'architecture des documents. Aucun mot : uniquement des références @clé. C'est ce qui rend une même structure compilable dans les trois langues.
structure/accueil.json
12 Ko · 29 blocs
structure/annuaire-associations-kaina.json
43 Ko · 11 blocs
noindex, absente des menus et du sitemap
structure/kaina.json
7 Ko · 36 blocs
structure/partenaires.json
41 Ko · 10 blocs
64 fiches d'associations
structure/pass-decouverte.json
8 Ko · 21 blocs
structure/pass-experience.json
10 Ko · 24 blocs
structure/pass-prive.json
9 Ko · 24 blocs
structure/temoignage-camille.json
6 Ko · 23 blocs
récit — gabarit partagé par les cinq
structure/temoignage-elodie.json
6 Ko · 23 blocs
récit — gabarit partagé par les cinq
structure/temoignage-nadia.json
6 Ko · 23 blocs
récit — gabarit partagé par les cinq
structure/temoignage-sarah.json
6 Ko · 23 blocs
récit — gabarit partagé par les cinq
structure/temoignage-veronique.json
6 Ko · 23 blocs
récit — gabarit partagé par les cinq
structure/temoignages.json
4 Ko · 13 blocs
design/
29 jetons
design/design.json
3 Ko · couleurs 9 · typographie 7 · tailles 5 · espacements 6 · effets 2
Toute l'apparence. Modifier une valeur ici repeint les 42 pages ; un contraste sous 4,5:1 arrête la compilation.
medias/
37 images
medias/mediatheque.json
45 Ko · 147 variantes
Une entrée par image : jeton, dimensions réelles, variantes AVIF/WebP, pages qui l'utilisent. Les 37 textes alternatifs sont tokenisés, donc traduisibles.
langues/
27 langues
langues/catalogue.json
5 Ko
Endonyme, drapeau, code HTML, sens de lecture, locale. Déclare ce qui est disponible ; c'est la présence d'un txt_XX.json qui active réellement une langue.
navigation/
structure des menus
navigation/navigation.json
2 Ko · 6 entrées · 3 colonnes
Porte des cle, jamais des libellés. Conséquence : un menu allemand ne peut pas avoir un ordre différent du français.
donnees/
réglages
donnees/site.json
450 o · 14 réglages
Domaine, nom, devise, zones, réseaux, polices préchargées. Plus aucun texte depuis l'extraction de la navigation.
donnees/annexes.json
496 o
La page 404 : structure des liens seule, textes tokenisés.
racine/
servis à la racine du site
racine/favicon.svg
280 o
carré 64×64 — le précédent était un rectangle 88×49 que Google ne pouvait pas afficher
racine/favicon.ico
9 Ko
repli 48×48
racine/apple-touch-icon.png
4 Ko
180×180, iOS

Les quatre séparations, visibles d'un coup d'œil. Les mots (textes/), l'architecture (structure/), l'apparence (design/) et les images (medias/) changent à des rythmes différents, pour des raisons différentes, et par des personnes différentes. C'est pourquoi ils occupent quatre dossiers distincts — et qu'aucun ne déborde sur les autres.

§3

Le détail de chaque fichier

Ce que chacun porte, et l'effet d'une modification.

Le détail de chaque fichier
FichierContientTaille réelle
textes/txt_fr.json1 568 textes, structure plate, la clé est le chemin dans le document~180 Ko
textes/txt_de.jsonles mêmes 1 568 clés — une clé absente empêche la compilation de cette langue seule~180 Ko
structure/*.json13 fichiers, 283 blocs au total, zéro mot — uniquement des références @clé~95 Ko
design/design.json29 jetons en 5 familles : couleurs, typographie, tailles, espacements, effets3,2 Ko
medias/mediatheque.json37 images : jeton, texte alternatif, dimensions, variantes AVIF/WebP, pages qui l'utilisent~42 Ko
langues/catalogue.json27 langues : endonyme, drapeau, code HTML, sens de lecture3,8 Ko
donnees/site.jsondomaine, navigation, pied de page, coordonnées4,1 Ko

Pourquoi les textes sont à plat. Une structure imbriquée obligerait le traducteur à reproduire l'arborescence — et à la maintenir quand elle change. À plat, la clé accueil.concept.etape-1.titre est un chemin lisible, et un simple diff de clés révèle instantanément ce qui manque dans une traduction.

§4

noyau/ — le convertisseur

Quinze fichiers, 4 140 lignes. Aucun ne contient de contenu éditorial : on peut lire l'intégralité du noyau sans jamais apprendre ce que raconte le site.

Chaque module porte une responsabilité, et les plus importants portent une règle qui peut arrêter la compilation.

ModuleResponsabilitéPeut refuser ?
sources.pycharge les JSON, vérifie les traductionsoui
jetons.pyle vocabulaire fermé des valeursoui
document.pyl'arbre et le calcul des niveaux de titreoui
vocabulaire.pyles 25 composants — le langage lui-mêmeoui
images.pyla balise <picture> responsiveoui
contraste.pycalcule les rapports WCAG réelsoui
navigateurs.pyles règles par moteur de renduoui
convertisseur.pyJSON → HTML, ordre du <head>oui
budgets.pypoids des pages, nœuds DOMalerte
annexes.pysitemap, robots.txt, 404non
archives.pyhistorique et restaurationnon
compiler.pyorchestre les 10 étapesoui
§5

outils/ · docs/ · tests/

Trois dossiers qui servent le travail sur le CMS plutôt que sa production : valider avant de compiler, documenter pour un agent, contrôler après coup.

outils/ — valider sans compiler

outils/valider.py vérifie un brouillon de page sans mobiliser la médiathèque, les jetons, les traductions ni le rendu. Il lit docs/langage.json, qui est lui-même engendré depuis le code — il ne peut donc pas se désynchroniser du convertisseur.

C'est l'outil destiné à un agent IA qui écrit une page : retour immédiat, message qui nomme la faute et les valeurs attendues.

$ python3 outils/valider.py brouillon.json

  ✗ brouillon.json
      page › section : propriété « couleur » non reconnue.
          Admises : ancre, chapo, fond, largeur, surtitre, titre…
      page › section : « fond » vaut « bleu », attendu :
          blanc, clair, sombre.
      page › section › bouton : propriété obligatoire « vers »
          absente.

  3 faute(s)
docs/ — la spécification lisible par un agent

Trois fichiers, tous engendrés depuis le code par outils/generer_docs.py. Une documentation écrite à la main diverge du code en quelques semaines ; celle-ci ne le peut pas.

  • LANGAGE.md — la spécification en prose, à lire.
  • langage.json — la même, exécutable. Un agent valide son brouillon contre ce fichier.
  • EXTENSION.md — le protocole obligatoire pour ajouter un jeton ou un composant.
tests/ — le contrôle du produit fini

controle.py applique 9 familles de règles aux octets écrits sur le disque, pas à l'arbre qui les a engendrés. La distinction n'est pas théorique : c'est ce contrôle-là, et lui seul, qui a rattrapé la page à deux <h1>.

audit.py compare le site produit à l'ancien, fragment de texte par fragment de texte. C'est lui qui établit les 100 % de fidélité (1 534 / 1 534).

§6

archives/ — l'historique du site

Chaque archive rassemble toutes les sources dans un seul JSON : textes de chaque langue, structure de chaque page, jetons, médiathèque, réglages. Ce qui n'y est pas : les images, les polices, le HTML produit. C'est ce qui rend l'historique léger.

Ordre de grandeur mesuré : 543 Ko par version, contre 84 Mo pour le dossier d'images. Conserver dix versions coûte donc moins qu'une seule copie du site.

La rotation, et l'épinglage

Dix archives conservées ; la onzième chasse la plus ancienne. Le chiffre est arbitraire et assumé — il faut un plafond, sinon le mécanisme devient un problème au lieu d'être une sécurité.

Une archive peut être épinglée ("_epingle": true) : elle échappe alors à la rotation. C'est ce qui permet de garder « la version validée par la cliente » sans qu'elle disparaisse au bout de dix modifications.

Restaurer sans perdre de travail

L'état courant est archivé avant toute restauration. Revenir en arrière ne doit jamais être une opération qui perd du travail : c'est ce qu'on attend d'une annulation, et son absence est la raison pour laquelle les gens n'osent pas utiliser les historiques.

Les pages et les langues absentes de l'archive sont retirées. Une restauration rend l'état exact, pas un mélange de l'ancien et du nouveau — sinon « revenir en arrière » ne veut plus rien dire.

def empreinte(etat):
    """Empreinte stable, pour détecter l'absence de changement.

    `sort_keys` est indispensable : sans lui, deux états
    identiques dont les clés ont été écrites dans un ordre
    différent produiraient des empreintes différentes, et
    l'on archiverait des versions identiques.
    """
    brut = json.dumps(etat, ensure_ascii=False, sort_keys=True)
    return hashlib.sha256(brut.encode("utf-8")).hexdigest()[:12]
§7

dist/ — le site produit

27 fichiers HTML, jamais modifiés à la main, régénérés intégralement à chaque compilation. Plus le sitemap, le robots.txt, la page 404, la feuille de style et les images.

Ce dossier est jetable. Le supprimer entièrement et relancer ./verifier.sh reproduit exactement les mêmes octets — c'est la propriété qui rend le système fiable.

$ ./verifier.sh

  ── validation ─────────────────────────────
  aucune faute

  ── compilation ────────────────────────────
  jetons      : 29 vérifiés
  contrastes  : 12 paires, minimum 4.56:1 (seuil 4.5)
  style       : 34 812 octets
  ✓ de     13 pages   318 Ko
  ✓ fr     13 pages   315 Ko
  26 pages en 0.043 s

  ── contrôle ───────────────────────────────
  AUCUN PROBLÈME

  ── audit ──────────────────────────────────
  Fidélité du contenu : 100.00 % (1534/1534 fragments)