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.
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.
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é.
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.
@clé. C'est ce qui rend une même structure compilable dans les trois langues.txt_XX.json qui active réellement une langue.cle, jamais des libellés. Conséquence : un menu allemand ne peut pas avoir un ordre différent du français.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.
Le détail de chaque fichier
Ce que chacun porte, et l'effet d'une modification.
Le détail de chaque fichier
| Fichier | Contient | Taille réelle |
|---|---|---|
| textes/txt_fr.json | 1 568 textes, structure plate, la clé est le chemin dans le document | ~180 Ko |
| textes/txt_de.json | les mêmes 1 568 clés — une clé absente empêche la compilation de cette langue seule | ~180 Ko |
| structure/*.json | 13 fichiers, 283 blocs au total, zéro mot — uniquement des références @clé | ~95 Ko |
| design/design.json | 29 jetons en 5 familles : couleurs, typographie, tailles, espacements, effets | 3,2 Ko |
| medias/mediatheque.json | 37 images : jeton, texte alternatif, dimensions, variantes AVIF/WebP, pages qui l'utilisent | ~42 Ko |
| langues/catalogue.json | 27 langues : endonyme, drapeau, code HTML, sens de lecture | 3,8 Ko |
| donnees/site.json | domaine, navigation, pied de page, coordonnées | 4,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.
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.
| Module | Responsabilité | Peut refuser ? |
|---|---|---|
| sources.py | charge les JSON, vérifie les traductions | oui |
| jetons.py | le vocabulaire fermé des valeurs | oui |
| document.py | l'arbre et le calcul des niveaux de titre | oui |
| vocabulaire.py | les 25 composants — le langage lui-même | oui |
| images.py | la balise <picture> responsive | oui |
| contraste.py | calcule les rapports WCAG réels | oui |
| navigateurs.py | les règles par moteur de rendu | oui |
| convertisseur.py | JSON → HTML, ordre du <head> | oui |
| budgets.py | poids des pages, nœuds DOM | alerte |
| annexes.py | sitemap, robots.txt, 404 | non |
| archives.py | historique et restauration | non |
| compiler.py | orchestre les 10 étapes | oui |
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).
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]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)