Savoir ce qui a changé,
et pouvoir revenir
Le journal enregistre chaque modification avec son avant et son après. Les sauvegardes permettent de restaurer l'état exact d'avant. Le moteur compare les deux avant chaque conversion et signale tout écart qu'il ne sait pas expliquer.
Un journal ne répare rien
Deux mécanismes distincts, et c'est la distinction qui compte.
Confondre les deux conduit à croire qu'on peut revenir en arrière alors qu'on ne peut que constater.
| Ce qu'il fait | Ce qu'il ne fait pas | |
|---|---|---|
| logs/logs.json | dit CE QUI a changé, quand et par qui | ne permet pas de revenir en arrière |
| logs/backup/ | permet de RESTAURER l'état exact d'avant | n'explique pas ce qui a changé |
logs/
├── logs.json le journal
├── etat.json empreintes de référence
└── backup/
├── logs_20260824-143207-00.json versions du journal
└── site_20260824-143207-00.json copies COMPLÈTES du site
Une copie complète est écrite AVANT chaque conversion, et avant chaque enregistrement depuis l'interface. Elle contient tous les fichiers source en un seul JSON — textes de toutes les langues, design, structure de chaque page, médiathèque, navigation.
Un seul fichier plutôt qu'une arborescence copiée : une sauvegarde en un fichier se restaure d'un bloc, sans risque d'en oublier une partie.
Au plus 10 sauvegardes, toujours
Sans plafond, le dossier croît indéfiniment. Avec un plafond, le poids du dépôt reste constant quel que soit le nombre de modifications.
Format d'horodatage : AAAAMMJJ-HHMMSS.
avant chaque écriture de logs.json
│
├── copie de l'ancien vers backup/logs_<horodatage>.json
├── si plus de 10 : la plus ancienne est supprimée
└── écriture du nouveau logs.json
Il se trie alphabétiquement dans l'ordre chronologique : trouver la plus ancienne ne demande aucune analyse de date.
Le défaut qui rendait la rotation inopérante
L'horodatage était à la seconde. Deux enregistrements rapprochés produisaient donc le même nom de fichier : la seconde copie écrasait la première.
Mesure avant correction : 14 écritures ne produisaient qu'UNE sauvegarde. Le plafond de dix n'était jamais atteint, et l'historique n'existait pas.
Un suffixe compteur rend l'horodatage unique sans perdre le tri chronologique — -01 se trie après -00 comme la seconde suivante après la précédente. Vérifié : 14 écritures produisent exactement 10 sauvegardes.
13 types d'entrée, chacun traduit
Chaque type porte une clé de texte. Le fil d'actualité s'affiche donc dans la langue de l'administrateur, sans code conditionnel.
Ajouter un type demande une entrée dans le JSON et sa traduction — aucune modification du moteur.
| Type | Niveau | Phrase affichée | Origine |
|---|---|---|---|
| texte_modifie | info | Texte modifié sur « {page} » | administration |
| image_remplacee | info | Image remplacée sur « {page} » | administration |
| image_ajoutee | info | Photo « {apres} » ajoutée à la médiathèque | administration |
| jeton_design_modifie | info | Apparence modifiée — {jeton} | administration |
| langue_ajoutee | info | Nouvelle langue ajoutée : {langue} | moteur |
| langue_retiree | attention | Langue retirée : {langue} | moteur |
| page_ajoutee | info | Nouvelle page : {page} | moteur |
| page_retiree | attention | Page retirée : {page} | moteur |
| bloc_ajoute | info | Bloc ajouté sur « {page} » — {type_bloc} | moteur |
| bloc_retire | info | Bloc retiré de « {page} » — {type_bloc} | moteur |
| structure_modifiee | info | Structure modifiée : {page} | moteur |
| divergence_non_journalisee | alerte | Divergence non expliquée — {nature} | moteur |
| correction_moteur | info | Correction automatique — {quoi} | moteur |
Un type non déclaré arrête le traitement. Sans ce refus, une entrée sans phrase traduite afficherait sa clé brute dans le fil — et le défaut ne se verrait qu'en production.
Cinq textes et une image, un seul clic
Six modifications enregistrées ensemble produisent six entrées portant le même identifiant de groupe.
Le fil peut donc les présenter comme une seule opération — ce qu'elles sont — sans perdre le détail de chacune.
clic sur « Enregistrer »
│
├── 5 textes modifiés → 5 entrées
├── 1 image remplacée → 1 entrée
└── même « groupe » pour les 6
le fil affiche : « 6 modifications enregistrées ensemble »
puis chaque entrée avec son avant/après
L'ordre d'affichage, et pourquoi il est toujours défini
- 1. horodatage décroissant — le plus récent en haut
- 2. à horodatage égal, l'ordre d'apparition dans la page (haut vers bas, puis gauche vers droite)
- 3. à défaut, l'ordre d'insertion dans le fichier
- Un tri toujours défini évite qu'un même ensemble s'affiche différemment à chaque rechargement.
Un tri partiellement défini afficherait le même ensemble dans un ordre différent à chaque rechargement. L'administrateur croirait que le journal change tout seul.
Le défaut de regroupement, et sa cause
L'identifiant de groupe était un horodatage à la seconde. Trois enregistrements distincts effectués dans la même seconde recevaient donc le même identifiant.
Mesure : le fil annonçait « 8 modifications ensemble » là où il y avait eu trois opérations séparées de 6, 1 et 1 modifications.
Corrigé par un suffixe unique. Vérifié : trois groupes distincts.
Le moteur compare le site à ce que dit le journal
Avant chaque conversion : une sauvegarde complète, puis une comparaison.
Une modification faite depuis l'interface laisse deux traces — le fichier change, et une entrée est écrite. Un fichier modifié sans entrée correspondante est signalé.
avant chaque conversion
│
├── 1. sauvegarde complète ← d'abord : même si la suite
│ échoue, l'état est conservé
├── 2. empreinte de chaque famille de sources
├── 3. comparaison à etat.json
└── 4. journalisation des écarts
| Famille | Change normalement par | Écart non expliqué |
|---|---|---|
| textes | l'interface | alerte |
| medias | l'interface | alerte |
| design | l'interface | alerte |
| structure | le dépôt | information |
| navigation | le dépôt | information |
Ce n'est pas un blocage. Un développeur a le droit de modifier un fichier à la main. Mais cela doit être VISIBLE, et la sauvegarde d'avant traitement doit être conservée — elle l'est.
Éprouvé : un texte modifié directement dans txt_fr.json, hors interface, produit ⚠ divergence_non_journalisee à la conversion suivante.
Pourquoi une empreinte, et pourquoi aussi l'inventaire
L'empreinte dit qu'une famille a changé. L'inventaire — liste des clés, des pages, des langues — dit quoi.
Sans lui, le message serait « quelque chose a changé dans les textes », ce qui n'aide personne. Avec lui, le moteur distingue une langue ajoutée d'une valeur modifiée, et le nomme.
sort_keys est indispensable au calcul de l'empreinte : sans lui, deux fichiers identiques dont les clés ont été réordonnées donneraient des empreintes différentes, et le moteur signalerait une divergence à chaque enregistrement.
Le volet Actualité
Une pastille à la flamme, au centre à droite, ouvre le fil.
Le plus récent en haut. L'heure si c'est le jour même, la date sinon.
| Élément | Comportement |
|---|---|
| Horodatage | « Aujourd'hui 14:32 », « Hier 09:14 », puis la date seule |
| En-tête de groupe | affiché seulement si l'opération portait plus d'une modification |
| Différence | ancienne valeur barrée, nouvelle en clair |
| Niveau | un liseré ambre pour « attention », rouge pour « alerte » |
| Nombre d'entrées | les dix dernières, injectées dans la page |
Pourquoi une heure sans date serait ambiguë. « 14:32 » seul, la veille, laisse croire à une modification du jour. Le libellé « Aujourd'hui » / « Hier » est tokenisé, donc traduit — Heute en allemand.
Le fil est lu à CHAQUE affichage, jamais figé dans la page
C'est un défaut qui a été trouvé en usage : après un rafraîchissement forcé, l'activité passée disparaissait.
La cause : les dix dernières entrées étaient écrites dans le HTML au moment de la compilation. La page montrait donc l'état du journal tel qu'il était au dernier build — et une entrée écrite sans recompilation n'apparaissait jamais.
Mesure : 3 entrées au journal, 2 figées dans la page.
Le fil interroge désormais /api/journal à chaque affichage, et à chaque ouverture du volet — le moteur peut avoir journalisé une recompilation pendant que la page était ouverte.
Les entrées figées dans le HTML restent, comme repli immédiat : elles s'affichent sans attendre le réseau, puis sont remplacées par l'état réel. L'administrateur ne voit jamais un volet vide.
Le journal survit à tout
Il vit dans logs/, hors du dossier produit.
Vérifié : après suppression complète de dist/ et recompilation intégrale, les entrées et leurs différences avant/après sont intactes.
C'est la raison pour laquelle il n'est pas dans dist/ : ce dossier est un PRODUIT, entièrement reconstructible. Le journal, lui, est une trace — il ne se reconstruit pas.
Un seul volet ouvert à la fois
Le volet d'actualité et celui d'apparence ont la même largeur, comme demandé. Ouverts ensemble, ils se masqueraient l'un l'autre sans que rien ne l'indique. Ouvrir l'un ferme donc l'autre.
Fermé, un volet est retiré du parcours au clavier : sans cela, la tabulation traverserait des boutons invisibles.
Ce que le bouton « Enregistrer » déclenche
Cinq étapes, dans un ordre qui n'est pas indifférent.
La sauvegarde vient avant l'écriture : si l'écriture échoue à mi-chemin, l'état d'avant est déjà conservé. L'inverse laisserait des fichiers à moitié modifiés sans point de retour.
clic sur « Enregistrer »
│
├── 1. copie complète du site → backup/site_<horodatage>.json
├── 2. rotation : au-delà de 10, la plus ancienne est supprimée
├── 3. une entrée de journal PAR bloc modifié, même groupe
├── 4. écriture des fichiers source (atomique)
├── 5. recompilation du site
└── 6. rechargement — le fil affiche les 10 dernières entrées
L'écriture est atomique. On écrit dans un fichier temporaire, puis on remplace. Sans cela, une interruption au milieu de l'écriture laisserait un JSON tronqué — et le site ne compilerait plus du tout.
Pourquoi recompiler tout de suite
Sans recompilation, le rechargement afficherait l'ancien rendu : l'administrateur verrait son texte revenir à l'ancienne valeur et conclurait que l'enregistrement n'a pas fonctionné.
La réponse de l'API porte le résultat de cette compilation. Si elle échoue, le détail est renvoyé — les fichiers source sont écrits mais le site produit reste l'ancien, ce qui est le comportement sûr.
Le texte alternatif d'une image est écrit dans les TEXTES
Quand on remplace une image et qu'on saisit son texte alternatif, le mot ne va pas dans la médiathèque mais dans le fichier de textes, sous la clé media.<emplacement>.alt.
Sans cela il ne serait pas traduisible : la médiathèque est partagée par toutes les langues.
Le défaut qui produisait onze fausses alertes
La surveillance distinguait une modification légitime d'une modification inexpliquée en comparant des horodatages. Or le moteur recompile dans la seconde qui suit l'enregistrement : les deux horodatages sont alors égaux, et un « strictement postérieur » ne trouve rien.
Mesure : onze fausses alertes sur quatorze enregistrements. Une alerte à chaque enregistrement apprend à les ignorer toutes — c'est ainsi qu'on rate les vraies.
Le repère est désormais l'IDENTIFIANT DE GROUPE, unique et indépendant de toute horloge. Vérifié : zéro fausse alerte sur six enregistrements, et une modification faite à la main hors interface est toujours détectée.