Documentation · Vue d'ensemble

Un CMS qui refuse
de produire du mauvais code

Le site est écrit en JSON et converti en HTML par un programme qui connaît les règles du référencement, de l'accessibilité et des moteurs de rendu. Ce qui serait fautif ne peut pas être écrit — et ce qui passe malgré tout est arrêté sur les octets produits.

Composants
25
le vocabulaire complet
Jetons de design
29
couleurs, tailles, espacements
Pages produites
108
4 langues, en 157 ms
Défauts au contrôle
0
contre 75 sur l'ancien site
§1

Un site décrit, pas codé

Le site de Kaina Expérience n'existe nulle part sous forme de HTML écrit à la main. Il existe sous forme de fichiers JSON — des textes, des jetons de design, une arborescence de blocs — et un programme de 7 884 lignes qui les convertit en pages.

La conséquence tient en une phrase : le rédacteur ne peut pas écrire une faute qu'il ne peut pas exprimer. Il n'écrit pas <h3>, il place une section dans une autre. Il n'écrit pas #6e6e6e, il nomme une couleur. Le niveau de titre et la valeur exacte sont calculés.

Les sources JSON à gauche, le convertisseur au centre, les pages produites à droite. Aucun HTML n'entre dans le système.
Les sources JSON à gauche, le convertisseur au centre, les pages produites à droite. Aucun HTML n'entre dans le système.

Ce document est engendré depuis le dépôt. Les chiffres qui suivent — 25 composants, 29 jetons, 1 776 textes — sont lus dans les fichiers, jamais recopiés.

Ce mécanisme est né d'un constat : trois chiffres écrits à la main ici avaient déjà divergé du code — 4 140 lignes annoncées pour 7 884 réelles, 1 568 textes pour 1 776, deux langues pour 4. Un chiffre recopié est un chiffre qui finira par mentir.

§2

Rendre la faute inexprimable

La différence entre détecter une faute et la rendre impossible est tout le sujet. Un CMS qui affiche « attention, contraste faible » laisse publier. Ici, la compilation s'arrête.

Trois exemples réels, tous trouvés sur l'ancien site.

Le clamp() qui rétrécit au zoom

L'ancien site contenait 35 clamp() écrits à la main sans composante rem. Mesuré au zoom 200 % : le H1 passait de 152 px à 79 px — il rétrécissait. Échec WCAG 1.4.4, technique F94. Invisible à l'œil, impossible à repérer en relisant le code.

Dans le langage à jetons, le rédacteur n'écrit pas de clamp() : il écrit titre_xl. Et un jeton de taille qui ne contiendrait pas de rem arrête la compilation.

for famille in FAMILLES_TEXTE:
    for nom, obj in self.brut.get(famille, {}).items():
        v = str(obj["valeur"])
        if "clamp(" in v or "vw" in v:
            if "rem" not in v and "em" not in v:
                fautes.append(
                    f"tailles.{nom} = {v}\n"
                    "    Aucune composante rem : au zoom 200 % "
                    "ce texte ne grossira pas.")
Le jeton qui n'existait pas

Un gabarit contenait color:var(--gris). Le vrai nom est --c-gris. CSS ignore silencieusement une variable inconnue : la règle était morte depuis toujours, sans erreur nulle part, sans effet visible.

Ici, un nom de jeton inconnu ne produit pas une règle morte : il produit un arrêt, avec la liste des noms valides.

Les deux h1 sur une même page

Cette faute-là, je l'ai commise en construisant ce CMS. Le composant appel était marqué « sectionnant » : il recevait donc un niveau de titre, et sur une page sans autre section titrée il obtenait le niveau 1. Deux <h1>.

Le contrôle de hiérarchie ne l'a pas vue, parce qu'il examinait l'arbre et ne comptait que les types qu'il connaissait. Correction en deux temps : le type est sorti des sectionnants, et un second contrôle a été ajouté sur les octets produits. C'est le cas qui justifie la double vérification — un contrôle sur le produit fini n'a pas d'angle mort de ce genre.

§3

Quatre choses qui changent séparément

Les textes, l'architecture, l'apparence et les images changent à des rythmes différents, pour des raisons différentes, et par des personnes différentes. Elles sont donc dans quatre fichiers différents.

C'est cette séparation qui rend la traduction possible : txt_de.json est une copie de txt_fr.json dont on change les valeurs. La structure est partagée, donc elle ne peut pas diverger d'une langue à l'autre.

FichierContenuQui le modifieEffet d'une modification
site/textes/les motsrédacteur, traducteurune ligne de HTML
site/structure/l'architectureconcepteur, agent IAla page concernée
site/design/l'apparencegraphistetout le site, 2 lignes de CSS
site/medias/les imagesphotographetoutes les pages qui l'utilisent

Mesuré. Changer un texte dans txt_fr.json modifie exactement une ligne du HTML produit. La mise en page n'est pas concernée — c'est la garantie que la séparation est réelle et pas seulement annoncée.

§4

De l'intention aux octets

Dix étapes, dont sept peuvent refuser de continuer. Aucune n'est un avertissement.

Les sources traversent une suite de contrôles. Ce qui ne passe pas n'est pas signalé : c'est rejeté, et rien n'est publié.
Les sources traversent une suite de contrôles. Ce qui ne passe pas n'est pas signalé : c'est rejeté, et rien n'est publié.
site/*.json
   │
   ├── 1  chargement des sources ......... clés manquantes ?
   ├── 2  vérification des jetons ........ taille sans rem ?
   ├── 3  calcul des contrastes .......... sous 4,5:1 ?
   ├── 4  construction de l'arbre ........ composant inconnu ?
   ├── 5  calcul des niveaux de titre .... profondeur > 6 ?
   ├── 6  rendu des composants ........... propriété non déclarée ?
   ├── 7  assemblage du <head> ........... charset après 1024 o ?
   ├── 8  contrôle du CSS produit ........ règle qui casse un moteur ?
   ├── 9  contrôle du HTML produit ....... deux h1 ? saut de niveau ?
   └── 10 annexes ........................ sitemap, robots, 404
        │
     dist/*.html
§5

Ce qui a été mesuré

Un contrôle qui ne trouve jamais rien ne prouve rien. Le même contrôle a donc été exécuté sur l'ancien site : il y relève 75 problèmes. Sur le nouveau : zéro.

C'est la seule manière de savoir qu'un contrôle n'est pas complaisant.

MesureAncien siteNouveau
Gabarits HTML à maintenir13 fichiers, 274 Ko0
HTML produit (13 pages)816 Ko315 Ko
Attributs style= en ligne1360
Texte masqué en -9999px140
Éléments <article> sémantiques131190
Noms accessibles (aria-label)65195
Défauts relevés au contrôle750
Fidélité du contenu100 % (1 534 fragments)

Les 75 problèmes de l'ancien site se répartissent en 28 fautes de compatibilité navigateur, 27 d'accessibilité, 14 d'ordre du <head> et 6 d'images prioritaires concurrentes. Aucun n'était visible à l'œil sur la page rendue.