Changelog

NeoFrag Reborn 1.2.0

Luandre 23 sept. 2026 58 min de lecture 3
Sommaire
Sommaire

Ajouté

  • La mise à jour du cœur se fait en un clic (2026-09-23). À partir de cette version, Administration → Monitoring signale les nouvelles versions de NeoFrag Reborn et les installe : sauvegarde du site, vérification de l'empreinte du paquet, application, mise à jour de la base, et retour à la sauvegarde si quelque chose échoue. C'est la première version publiée par ce chemin ; il a été éprouvé de bout en bout sur un site neuf avant la publication.

  • Les annonces sur Discord (2026-09-23). Un webhook qui pointe vers un salon Discord reçoit désormais un vrai message, avec titre, lien et couleur : nouvelle actualité, nouvel article, nouveau membre, nouveau commentaire, nouveau sujet de forum. Discord refusait jusqu'ici ce que le module envoyait, alors que sa description promettait Discord.

  • « Untel est en direct » (2026-09-23). Quand une chaîne Twitch ou YouTube suivie par le widget « Statut live » passe en direct, les webhooks abonnés à l'événement « Chaîne en direct » l'annoncent, une seule fois par direct, avec la miniature du stream.

  • La démonstration parle aussi anglais dans son contenu (2026-09-23) : ses actualités, articles, pages, catégories et galeries ont une version anglaise, qui montre la fonction multilingue.

  • Tout le produit parle les six langues (2026-09-23). Le site vitrine, l'administration, les modules, les widgets, les thèmes, les réglages, les boutons, les messages et les scripts : près de 800 textes étaient écrits directement en français et le restaient sur un site anglais, allemand, espagnol, italien ou portugais. Ils passent tous par les traductions, dans les six langues. En particulier :

    • L'assistant d'installation se traduit, avec un sélecteur de langue ; il suit d'abord la langue du navigateur. L'installeur en ligne de commande prend --lang=en, ou la langue du terminal.
    • La place de marché montre le nom et la description de chaque addon dans la langue du site.
    • Les pays (profil, adversaires) s'affichent dans la langue du site.
    • Les rôles et les modèles d'e-mails livrés s'affichent traduits ; un nom changé par l'administrateur reste le sien.
    • La modération montre le statut, la raison et le type d'un signalement, et le type d'une sanction, en toutes lettres — elle affichait les codes (pending, forum_message, ban_temp).
    • Un groupe créé dans une langue ne disparaît plus quand le site s'affiche dans une autre — il sortait de la liste, et ses droits avec.
    • Le thème d'administration se déclarait écrit en anglais : sur un site anglais, ses textes français ne se traduisaient jamais. C'est corrigé, comme les scripts des modules, qui cherchaient leurs traductions au mauvais endroit.
    • La confirmation de suppression de compte demande de taper le mot dans la langue du site (« DELETE » en anglais), et non plus « SUPPRIMER » partout.

    Un contrôle, check-textes-en-dur, refuse désormais en CI tout texte d'interface écrit en dur.

  • Les 61 addons de la place de marché ont une vignette (2026-09-22). Chacun — module, widget ou thème — montre une capture d'écran réelle de ce qu'il fait, en 960×600. Pour y arriver, six modules et deux widgets ont été installés sur la démonstration, avec du contenu d'exemple : un dictionnaire, des citations, des recettes, une carte de lieux et une grille de webradio. La démonstration porte aussi enfin l'identité de sa communauté — nom, type, date de création et présentation — qui était restée vide.

  • Le site peut fonctionner sans réseau, si on le demande (2026-09-22). Le manifeste rendait déjà le site installable ; il garde désormais ses images, ses styles et ses scripts dans le navigateur des visiteurs. Éteint par défaut, à allumer dans Administration → Paramètres → Préférences générales.

    Les pages, elles, ne sont jamais gardées. Une page en mémoire survivrait à une mise en ligne : on servirait un site d'avant-hier à qui l'a déjà visité, sans que rien ne le signale. Une modification reste donc visible immédiatement.

    Décocher désinstalle vraiment. C'est le point délicat de cette technologie : un composant de ce type, une fois installé dans un navigateur, y reste même si on retire le fichier du serveur. L'interrupteur ne retire donc rien — il fait servir une version qui se désinstalle d'elle-même à la prochaine visite. C'est pour cette raison que la fonction est éteinte par défaut, et qu'une mise à jour du cœur ne l'allume jamais toute seule.

  • Le site est installable comme une application (2026-09-21). /manifest.webmanifest est produit par le site lui-même — nom, description, couleur de thème, adresse de départ et favicon viennent des réglages — et non déposé en fichier, qui serait faux partout ailleurs. Le service worker, lui, reste à venir : installé dans un navigateur il y demeure même si le fichier disparaît du serveur, ce qui demande un interrupteur de désinscription avant de le livrer.

  • Les dossiers sensibles sont fermés sous Apache aussi (2026-09-21). Les .htaccess du paquet protégeaient moins que la configuration Caddy de la production : sur toute installation Apache, cache/, install/, tools/, tests/ et docs/ étaient joignables — la documentation de travail se lisait en texte. Chaque dossier porte sa garde, la racine refuse aussi .neon et .md, et tools/check-htaccess.php relit la liste à chaque passage, avec la raison de chaque ligne.

  • Un checker peut déclarer que ses refus sont ordinaires (2026-09-21). Le module pages, routeur de repli, écrivait une ligne d'erreur dans le journal de production pour chaque adresse inconnue — chaque robot qui sonde en ajoutait une. Module_Checker::refus_ordinaire() le déclare ; le diagnostic reste rendu à l'écran en mode débogage, et check-journal refuse désormais toute ligne que le produit écrit lui-même dans le journal des erreurs.

  • Deux contrôles tiennent les conventions du dépôt (2026-09-21). tools/check-tools.php vérifie que chaque outil respecte les siennes — en-tête, garde par le socle commun, aucune plomberie recopiée, verdict et codes de sortie, port réservé, catalogue à jour — et tools/check-docs.php celles de la documentation : chiffres d'inventaire justes, renvois et ancres vivants, aucun document orphelin, aucun outil nommé qui n'existe plus, aucune phrase recopiée d'un document vivant à l'autre, documents qui restent lisibles. Tous deux sont en intégration continue.

  • Huit nouveaux addons, tous facultatifs (2026-09-20). Aucun n'est installé d'office : ils s'ajoutent depuis Administration → Addons, et se retirent de même.

    Addon Ce qu'il fait
    Effet saisonnier (widget) Neige, confettis ou feuilles sur tout l'écran, pendant une plage de dates. Les dates s'écrivent MM-JJ sans année — la saison revient toute seule — et peuvent enjamber le Nouvel An (du 12-15 au 01-06). Hors saison, la page ne reçoit rien du tout : ni image, ni script. prefers-reduced-motion est respecté, et l'animation se met en pause quand l'onglet passe à l'arrière-plan.
    Lecteur de flux (widget) Les derniers articles d'un flux RSS ou Atom extérieur. Une page n'attend jamais un site tiers : elle lit un cache, et sert même un contenu périmé plutôt que de faire patienter ; le rafraîchissement a lieu dans la tâche planifiée. Un flux tombé est mis de côté dix minutes au lieu d'être retenté à chaque visite.
    Citations (module) Un recueil classé, avec auteur et source.
    Recettes (module) Ingrédients et étapes saisis une par ligne, durées et nombre de parts, balisage schema.org/Recipe que lisent les moteurs de recherche.
    Dictionnaire (module) Un lexique rangé par lettre. « Éclaireur » se range sous E, « Æther » sous A, « 1v1 » sous #. La lettre est calculée, jamais saisie.
    Carte des lieux (module) Des lieux sur une carte OpenStreetMap, avec adresse, description et lien. La bibliothèque est hébergée par le site : aucun appel à un service tiers pour l'afficher. La page reste utile sans JavaScript — la liste, les adresses et les liens y sont.
    Webradio (module) Un lecteur de flux, ce qui passe à l'antenne, et la grille de la semaine. Une émission peut enjamber minuit : « samedi 22:00 → 02:00 » reste l'émission du samedi.
    Bac à sable (module) Réservé aux membres : on y essaie la mise en forme et on voit le rendu exact du site. Surtout, il affiche ce que le site a retiré — c'est la seule page qui réponde à « pourquoi mon tableau a-t-il disparu ».

    Les trois modules de contenu (citations, recettes, dictionnaire) partagent le moule de la FAQ : catégories, recherche, filtre, tri et brouillons.

  • Choix du contenu à l'installation. L'installateur web propose désormais un profil de site entre les prérequis et la base de données : Complet, Gaming / eSport, Communauté ou Cœur seul. Les modules restent décochables un par un, et ceux qu'un module réclame sont ajoutés automatiquement (le palmarès a besoin des équipes) avec un message. Sans JavaScript, le formulaire fonctionne quand même — le serveur ne retient que ce qui appartient au profil choisi.

    Aucune liste d'addons n'est écrite à la main : les profils se composent à partir des déclarations presets de chaque addon. C'est la différence avec la tentative de juin 2026, abandonnée parce qu'une installation allégée finissait en 500 — des modules du cœur interrogeaient des tables optionnelles sans garde. Ces gardes existent désormais et sont vérifiées à chaque exécution de la CI.

  • Déclarations de découplage sur les 99 addons livrés : chacun déclare dans son __info() s'il appartient au cœur, dans quels profils il entre, et de quels modules il a besoin. L'appartenance au cœur était jusqu'ici un effet de bord de la présence dans le paquet — c'est ainsi qu'emojis, files et webhooks s'y étaient retrouvés sans qu'aucune décision ne soit prise.

  • Cinq garde-fous en intégration continue, dont trois neufs :

    Contrôle Ce qu'il refuse
    check-addon-declarations.php un addon muet, une dépendance inexistante, un addon du cœur qui dépend d'un optionnel, un addon non distribuable publié au catalogue, une déclaration en désaccord avec seed.sql
    check-addon-coupling.php un couplage fatal (table, classe) ni déclaré ni annoté ; un cycle dur. Lu dans le code au tokenizer PHP, pas à l'expression régulière — donc aucun faux positif sur les commentaires
    check-install-profiles.php un profil qui ne démarre pas. Il installe pour de vrai sur une base jetable, sert le site et échoue au moindre 5xx — y compris sur les routes des modules absents, qui doivent rendre un 404 propre
  • Mise à jour du cœur en un clic, depuis Administration → Monitoring. Sauvegarde, téléchargement du paquet, vérification d'empreinte, superposition des fichiers, migrations en attente, recompilation des styles. tools/build-release.php produit les trois fichiers à publier ensemble (neofrag-reborn-update-<v>.zip, version.json, checksum.json) ; la procédure est dans docs/guide/marketplace.md.

  • Trouver un membre depuis la barre de recherche. La recherche du site ne trouvait que du contenu — forum, actualités, pages. Chercher quelqu'un obligeait à ouvrir l'annuaire et à le feuilleter, alors que c'est l'une des choses qu'on cherche le plus souvent sur un site de communauté. Les membres apparaissent désormais dans la recherche globale et dans les suggestions instantanées, par pseudo, prénom ou nom.

    Rien de neuf n'est exposé au passage : ces trois champs sont déjà affichés sur la fiche publique de chaque membre, et la recherche applique exactement le même filtre que l'annuaire — un compte supprimé n'y apparaît pas.

  • Gérer une série d'événements d'un seul geste. Créer un événement récurrent produisait déjà toutes ses occurrences en un clic — les reprendre demandait ensuite de les ouvrir une par une.

    Désormais, une occurrence de série porte un bouton supprimer toute la série, dont la confirmation annonce combien d'occurrences partiront, et le formulaire d'édition propose « appliquer à toutes les occurrences ». Le titre, le type, les descriptions, le lieu, l'image et la publication sont recopiés sur chacune ; les dates ne le sont jamais — ce sont elles qui distinguent une séance de la suivante.

  • Retour arrière d'une mise à jour, et restauration d'une sauvegarde. Le CMS prenait déjà une sauvegarde complète — fichiers et base — juste avant chaque mise à jour du cœur. Il ne savait pas s'en reservir : le filet était tendu, personne ne savait tomber dedans.

    Désormais, si la pose des fichiers ou une migration échoue, le site est automatiquement remis dans l'état où il était ; le message dit à la fois ce qui a échoué et si le retour arrière a réussi. Chaque sauvegarde de la liste porte aussi un bouton Restaurer, pour les cas où le dégât ne vient pas d'une mise à jour — un module tiers, une manipulation regrettée. La confirmation annonce ce qui sera perdu.

    Ce que la restauration ne touche pas, volontairement : la configuration du site (rendre des identifiants de base périmés couperait le site de sa propre base), les journaux (c'est la trace de l'incident qu'on répare) et le cache (vidé pour être reconstruit, plutôt que remis dans l'état d'une autre version).

    Au passage, la sauvegarde embarque maintenant vendor/, que le paquet de mise à jour livre et qu'elle ne prenait pas : sans lui, un retour arrière aurait reposé l'ancien code sur les nouvelles dépendances.

  • Réponses aux commentaires (fonctionnalité qui était désactivée //TODO) : le bouton « Répondre » est réactivé (classe comment-reply attendue par comments.js), le formulaire porte un champ caché comment_id posé au clic, et le back-end rattache la réponse en parent_id — validé en base (le parent doit exister, être de premier niveau, du même contenu, non supprimé), profondeur limitée à 1. L'affichage threadé (comments-child) existait déjà. Vérifié : rendu threadé correct en base.

  • Régions nommées dans les thèmes (idée reprise de HiddenCMS) : les vues rendent une zone par nom sémantique — $this->output->region('content') — plutôt que par index — zone(2). Le thème déclare une correspondance regions (nom → titre de zone) dans son __info() ; le cœur résout nom → titre → index → zone(). Purement additif : zone() reste utilisable, et un thème sans map se comporte normalement. Les 6 thèmes front (vitrine, nebula, forge, granite, blockcraft, extend) migrés vers region(). Lisibilité accrue pour les créateurs de thèmes ; fondation pour les « outlines » à venir.

  • Installeur en ligne de commande (install/cli.php) : alternative scriptable à l'assistant web, pratique pour un déploiement VPS reproductible. Flags --db-*, --admin-*, --site-name, --site-url, --create-db, --demo, --force, --yes, --dry-run, --no-lock + mode interactif (saisie masquée du mot de passe) et --admin-pass-env (mot de passe via variable d'environnement, invisible dans les process). Réutilise exactement la lib Installer et la séquence « tout bundlé » → résultat identique à l'installeur web. Idée reprise de HiddenCMS, portée sur le socle Reborn. Validé end-to-end (install complète : 132 tables, 109 addons, admin, vitrine, wiki).

  • Un lanceur pour toute la batterie : php tools/check-all.php. Les 26 contrôles n'avaient aucun point d'entrée commun — la CI les appelle un par un, et en local chaque séance refaisait une boucle à la main, jamais la même. Le lanceur découvre les contrôles présents (un contrôle neuf entre sans qu'on y touche), joue composer audit en tête, borne chaque contrôle en durée, et énonce à la fin ce qu'il n'a pas joué : les épreuves en navigateur (--navigateur) et les trois contrôles à cible explicite, dont check-restauration qui abîme volontairement le site.

Modifié

  • Toute l'interface parle Bootstrap 5 (2026-09-23). Il restait environ 470 emplois de classes de Bootstrap 3 et 4, que l'on faisait tenir par une feuille de compatibilité : champs de formulaire, pastilles de couleur, boutons de fermeture, boutons pleine largeur, blocs « avatar et texte », groupes de saisie, cases à cocher, grilles de formulaire. Tout le balisage est migré vers ses équivalents Bootstrap 5 ; ce qui est propre au produit porte désormais un nom du produit. Un contrôle refuse qu'un ancien nom revienne, même redéfini. Ce qui se voit :

    • les listes déroulantes ont retrouvé leur flèche — une centaine, dans l'administration et sur le site, ressemblaient à des champs texte ;
    • les pastilles de couleur (statuts, groupes, rôles, sanctions) sont lisibles dans tous les thèmes, en clair comme en sombre : leur texte échouait au contraste ;
    • les groupes « Modérateur » et « Modérateur senior » s'affichent : leur nom était écrit en blanc sur fond blanc ;
    • les boutons de fermeture des fenêtres ont une zone de clic suffisante sur téléphone.
  • Le JavaScript est relu par un linter (2026-09-22). Soixante-dix-huit fichiers écrits sur des années, dont rien ne vérifiait autre chose que la syntaxe. ESLint voit ce que la syntaxe ne dit pas : une variable globale créée par oubli d'un var, un eval déguisé, du code après un return, un innerHTML nourri d'une valeur calculée. Aucun paquet npm n'est déployé : c'est de l'outillage de développement, au même titre que l'analyse statique PHP.

    Les règles sont choisies sur un seul critère — avoir déjà mordu ici. C'est la famille du « $ is not defined » qui avait tué le tri des tables de toute l'administration pendant trois mois. Le désordre existant (des innerHTML à assainir) est gelé en l'état plutôt que réécrit d'un coup : son nombre ne peut plus monter.

  • La baseline PHPStan se régénère par une commande, et la classe Config du cœur est annotée (2026-09-21). Trois erreurs neuves ont fait rouvrir phpstan-baseline.neon : des entrées ajoutées à la main portaient une autre écriture que celle de PHPStan, et près de trois cents blocs ne correspondaient plus à rien — toute une famille « Function NeoFrag not found », morte depuis que le fichier d'amorçage déclare la fonction. La configuration vit dans phpstan-base.neon, phpstan.neon n'y ajoute que la baseline, et composer stan:baseline la régénère entière. Config annote désormais la langue courante, la liste des langues et les réglages du cœur que lisent les modules : ces accès sont analysés au lieu d'être gelés.

  • Les outils de tools/ refondus sur une bibliothèque commune (2026-09-21). Soixante outils écrits chacun à sa manière recopiaient la même plomberie : quatorze lançaient leur serveur PHP, huit ouvraient leur session d'administrateur, six lançaient Chrome, vingt-cinq lisaient config/db.php. Une leçon apprise dans l'un ne se propageait pas aux autres. tools/lib/ porte désormais ce socle — connexion, session, serveur, navigateur, parcours du dépôt, SQL, options, verdicts — et chaque outil tient dans sa logique propre. Cinquante-deux outils au lieu de soixante, sans perte de fonction : check-js-syntax et check-js-jquery forment check-js-sources ; check-docs-counts et check-docs-liens forment check-docs ; check-lang-args rejoint check-langs ; publish-release-note et publish-wiki-changelog forment publish-changelog ; seed-wiki-docs et dump-wiki forment wiki-docs ; smoke-test devient check-smoke, comme tout contrôle ; addons-manifest et table-map, qui sont des données, vivent dans la bibliothèque. Chaque outil a désormais son port réservé (deux en partageaient un), trois codes de sortie qui distinguent « rien à reprocher », « refusé » et « n'a pas pu juger », et un en-tête dont le catalogue de tools/README.md est engendré. bs5-codemod, outil de la migration Bootstrap 4 → 5 de juin, est retiré : check-classes-bs4 couvre tout ce qu'il vérifiait, points de rupture des utilitaires directionnels compris depuis ce jour.

  • La documentation de travail refondue en six documents (2026-09-21), chacun répondant à une question : reste-a-faire.md (le tableau de bord), fiches.md (le détail de chaque reste), decisions.md (ce qui est tranché), lecons.md (les règles de travail, classées par thème, avec le défaut dont chacune est née), journal.md (les trouvailles récentes, court) et veille-hiddencms.md. Le journal de septembre, la feuille de route technique 2026 et le filet de détection d'août sont gelés en archive avec une note de gel ; les fiches terminées aussi. Les règles de tenue — un fait, un endroit ; pas de copie ; les chiffres sous forme d'inventaire ; ce qui n'est plus vrai part en archive — sont écrites dans docs/README.md et tenues par check-docs.

  • declare(strict_types=1) sur tout le périmètre utile (2026-09-21) : 1 365 fichiers contre 78 la veille. Le chantier avançait par lots de quatre à dix fichiers depuis dix mois ; il est terminé. Les 209 fichiers restants sont les gabarits views/**.tpl.php, où la déclaration serait syntaxiquement valide et ne protégerait rien.

    La conversion a été conduite par ordre de risque décroissant — le nombre d'appels de fonction native par fichier — avec la suite de tests jouée entre chaque lot.

    Conséquence de fond, propre à ce produit : PHP en mode strict refuse __toString() pour les fonctions internes. Or lang() rend un objet de traduction différée, résolu au rendu quand la langue est enfin connue, et les dates sont également des objets. La barre d'administration, la page de chaque actualité et plusieurs écrans tombaient donc en erreur fatale — module neutralisé, page amputée, sans un mot. La conversion est désormais posée au point d'échappement (htmlspecialchars), c'est-à-dire là où la valeur doit devenir une chaîne par définition : 410 emplacements dans 84 fichiers.

  • Le journal dit désormais OÙ, pas seulement quoi (2026-09-21). Quand un module lève une exception, son rendu est abandonné et la page s'affiche amputée — en silence. La ligne de journal donnait le message sans jamais nommer un fichier. Elle porte maintenant la première image de pile qui appartient au produit. Retrouver l'origine des erreurs ci-dessus est passé d'une enquête à une lecture.

  • Le compilateur SCSS passe en 2.1 (2026-09-20). Sur PHP 8.5, chaque compilation des styles écrivait dix dépréciations dans le journal ; il n'y en a plus aucune. La 2.x minifie un peu plus (.25rem au lieu de 0.25rem, #ccc au lieu de #CCCCCC), ce qui change quatre feuilles — celles des modules addons, palmarès, commentaires et de la page de maintenance. L'équivalence n'a pas été déduite du texte : les deux jeux de CSS ont été donnés à lire au moteur de Chrome, qui y voit exactement les mêmes règles, propriétés et valeurs calculées.

  • La documentation a été relue en entier contre le code (2026-09-17). Les guides d'installation, de concepts et de développement (créer un module, un widget, un thème, le framework) dataient de juin et décrivaient un CMS d'avant : une installation sans choix de profil, un thème qui chargeait jQuery, des grilles Bootstrap 4. Ils disent désormais ce que fait la 1.1.0 — profils d'installation, déclarations d'addons, régions nommées, front sans jQuery sous politique de sécurité stricte, réglages de widget avec valeurs de repli, vocabulaire de couleurs partagé — et sont republiés dans le wiki du site. Le README, le guide de contribution, la politique de sécurité et la référence technique sont alignés de même.

  • Ce qu'un addon autorise se lit dans sa déclaration, plus dans des listes de noms en dur. Trois d'entre elles décidaient du sort des addons, et les trois étaient fausses : celle des widgets protégeait sept noms qui ne sont pas des widgets ; celle des thèmes protégeait un thème default inexistant en laissant nebula, le seul thème public livré, supprimable dès qu'il était inactif ; celle des modules dupliquait, en désaccord, ce que __info() disait déjà. Vérifié addon par addon contre l'ancienne logique : aucun changement de désactivabilité, aucun changement d'état actif, et 39 addons deviennent non supprimables — ce qui est l'objet du correctif.

  • La Corbeille ne connaît plus les autres modules. Elle tenait en dur la liste des tables, clés primaires et méthodes de restauration de news, articles, gallery, comments et forum : elle ne pouvait donc pas appartenir au cœur sans tirer cinq modules optionnels avec elle. Chaque module déclare maintenant ses types restaurables ; le cœur collecte. Même inversion pour les descripteurs de contenu (réactions, abonnements, révisions).

  • Une seule source de vérité pour les tiers. tools/addons-manifest.php, table écrite à la main et figée en juin, dérive désormais des déclarations. Les deux avaient déjà divergé : emojis y figurait comme cœur alors qu'il se déclare à la carte — il était donc décochable à l'installation mais absent du catalogue, donc impossible à réinstaller. Catalogue : 52 → 53 addons.

  • Installateur repris sur la forme : mise en page à deux colonnes, jetons de style repris de nebula (marine et sarcelle, accent #2dd4bf), dégradé sorti de derrière le texte, logo posé dans une pastille.

  • Chiffres publics honnêtes. La page d'accueil annonçait 54 modules · 38 widgets · 7 thèmes, c'est-à-dire le décompte brut du dépôt. Trois de ces thèmes ne sont pas des thèmes qu'on choisit : admin est le back-office, vitrine n'est pas distribué, nebula est le thème que chacun reçoit par défaut. Elle annonce désormais 27 · 22 · 4, en accord avec le catalogue.

  • Documentation réorganisée et remise à jour (interne, aucun changement de comportement) : docs/ sépare désormais trois étages — référence (l'état du code), guide (public), travail (internal/). Le dossier internal/ ne garde que 4 documents vivants (README, TODO, modernization, detection-net) contre 13 ; les analyses datées et les plans de chantiers terminés sont gelés dans internal/archive/, avec un index et une bannière sur chaque fichier. TODO.md est redevenu un reste-à-faire (journal de 190 lignes extrait vers archive/journal-2026.md, 82 tâches faites retirées, décisions actées regroupées pour ne plus être rouvertes). Faits corrigés contre le code : version 1.1.0 (plusieurs titres annonçaient encore 1.0.0), « front 100 % jQuery » alors que jQuery est retiré depuis juin, chiffres strict_types (64/1436) et état des chunks de modernisation. Deux documents fusionnés dans la référence (carrefours inter-modules → architecture.md §9, procédure de mise à jour d'un site → deploy-ftp.md). L'archive elle-même est consolidée : 10 fichiers → 4 (les 3 analyses HiddenCMS fusionnées en une, classées par date ; les 3 plans de chantiers 2026 en une ; le journal absorbé par historique.md). Enfin la racine du dépôt ne garde que README.md et CHANGELOG.md : CONTRIBUTING, SECURITY et CODE_OF_CONDUCT sont passés dans .github/, où GitHub les reconnaît de la même façon (onglet Security, lien « Contribuer ») et d'où le snapshot public les embarque toujours. Au total 36 → 31 fichiers .md, 165 liens internes vérifiés, 0 cassé.

  • Accueil vitrine ancré dans le thème : le hero / features / roadmap ne sont plus servis par le widget landing (supprimé) mais rendus directement par le thème vitrine (themes/vitrine/views/landing.tpl.php via body.tpl.php sur la home). Plus robuste (jamais perdu à la réinstallation), au prix de l'édition Live Editor de l'accueil. Inventaire : 39 → 38 widgets. Stats du hero corrigées (54 modules / 38 widgets / 7 thèmes).

Ajouté

  • Chaque addon montre à quoi il ressemble (2026-09-22). La page « Thèmes & addons » et la place de marché affichent désormais une VIGNETTE pour 85 des 117 addons : une capture d'écran réelle du module, du widget ou de la langue tel qu'il tourne, prise sur la démonstration, où il y a des données à montrer. Ce qui n'a rien à montrer garde son icône : les connecteurs Discord, GitHub et Google, dont l'icône EST le logo, et les widgets qui n'affichent rien sans réglage.

  • Dix addons ont enfin une description (2026-09-22) : les six langues — chacune dans sa propre langue — et les quatre connecteurs de connexion externe.

Corrigé

  • La sauvegarde du Monitoring fonctionne de nouveau (2026-09-23). Depuis le 21 septembre, elle s'arrêtait dès la première ligne de la base : le bouton « Sauvegarde », et la mise à jour du cœur qui commence par une sauvegarde, échouaient sur tout site. Trouvé en éprouvant la mise à jour avant de la publier.

  • Changer de langue sur une actualité traduite ne mène plus à une page introuvable (2026-09-23). Le sélecteur de langue garde l'adresse, donc le titre de la langue d'avant : la version traduite la refusait. Elle renvoie désormais vers sa propre adresse.

  • Une page traduite s'affiche dans la bonne langue (2026-09-23). Les pages publiques étaient servies dans la première version que la base rendait, quelle que soit la langue du visiteur.

  • Le thème choisi sur la démonstration ne suit plus le visiteur sur le site vitrine (2026-09-23). Le choix était retenu pour tout le domaine : un visiteur qui passait la démonstration en Forge voyait aussi le site vitrine en Forge, avec le menu des thèmes en pied de page. Le choix est désormais retenu pour chaque site séparément, et un nouveau réglage, Préférences générales → Choix du thème, permet de le fermer : le menu disparaît et le thème par défaut s'impose à tous. Il est fermé sur le site vitrine, qui n'a qu'une apparence.

  • La démonstration se remet de nouveau à zéro (2026-09-23). Une erreur dans son jeu de données faisait échouer la remise à zéro des quinze minutes depuis la veille : ce que les visiteurs y modifiaient restait. Un contrôle refuse désormais ce défaut avant toute mise en ligne, et un échec de remise à zéro s'écrit au journal au lieu de passer inaperçu.

  • Le compte de secours de la démonstration n'apparaît plus nulle part (2026-09-23) : ni dans la liste des membres, ni dans la recherche, ni parmi les membres en ligne, ni dans l'administration ; sa page répond « introuvable ». Son mot de passe a été remplacé par un secret aléatoire que personne ne connaît.

  • Le site sur un téléphone (2026-09-23), d'après les captures de Luandre :

    • Le menu est toujours atteignable. Sur le thème Nebula, il disparaissait sous 860 pixels de large sans aucun bouton pour le rouvrir : un bouton « burger » l'ouvre désormais. Sur les autres thèmes, un menu trop long pour tenir sur une ligne se replie derrière un bouton « Menu », comme sur le site vitrine, au lieu de passer à la ligne jusqu'à remplir l'écran ; un menu court reste déplié.
    • Les blocs empilés ne se touchent plus : l'espace membre collait au bloc du dessus.
    • Le texte ne colle plus à l'avatar des membres (commentaires, discussions, auteur d'une actualité, candidatures, profil).
    • Les points du diaporama sont de vrais boutons, centrés sous l'image, et restent clairs sur les thèmes sombres, comme les flèches et la légende : Bootstrap les passait en noir.
    • La recherche du thème Extend garde son bouton à côté du champ.
    • Les photos d'un album remplissent leur carte.
    • L'en-tête des commentaires montre le titre avant le compteur, qui le précédait.
    • Les dates abrégées s'écrivent dans la langue du site (« 30 août 2026 », « Sonntag »), et non plus en anglais ; la ligne d'auteur d'une actualité a retrouvé une taille de texte normale.
  • La page d'une photo de la galerie existe (2026-09-23). Le widget « Image aléatoire », le diaporama de la galerie et le lien d'un commentaire posé sur une photo menaient tous à une page introuvable : elle n'avait jamais été déclarée. Elle montre la photo, sa description, le retour à l'album et les commentaires. Une photo suit aussi les règles de son album : plus d'accès par son adresse quand l'album est à la corbeille ou pas encore publié.

  • Derniers restes de Bootstrap 3 et 4 (2026-09-23), que leur nom de classe ne trahissait pas : la structure du diaporama, les croix de fermeture qui affichaient un « × » en plus de l'icône, deux accordéons (FAQ, liens de la navigation), des champs qui s'enveloppaient dans un groupe vide, la flèche des bulles d'aide restée blanche sous une bulle sombre, et deux règles du widget des partenaires. check-classes-bs4 lit désormais aussi la STRUCTURE des composants et les styles écrits dans les vues.

  • Les pages d'erreur qui n'en étaient pas (2026-09-23). Dans la modération, la gestion des rôles, des modèles d'e-mails, des membres et du diaporama, une adresse vers un élément disparu devait répondre « page introuvable » : elle appelait une fonction qui n'existe pas, écrivait un avertissement au journal, et la page de modération s'affichait vide. Trouvé en apprenant à l'analyse statique les fonctions « magiques » du framework — ce qui a allégé de 455 occurrences sa liste d'exceptions.

  • Le bouton des e-mails envoyés aux membres est lisible (2026-09-23) : validation du compte, mot de passe perdu et les autres modèles avaient un bouton blanc sur turquoise clair (2,3:1). Les modèles livrés comme ceux déjà installés — sauf ceux qu'un administrateur a personnalisés.

  • Dernières finitions de la passe complète (2026-09-23) : liens du wiki, des lieux et de la webradio dans la teinte lisible du thème ; boutons « succès » des thèmes sombres ; statut d'une candidature, dont la boîte n'avait aucun fond (classes d'un ancien thème d'administration) ; index alphabétique du glossaire, qui n'avait aucune mise en forme ; réseaux sociaux des cartes de membres ; titres coupés dans l'administration ; un visiteur qui suit le lien « participer » d'un événement est invité à se connecter.

  • Les textes sont lisibles partout, en clair comme en sombre (2026-09-23). Liens et boutons de l'administration, alertes, dates et en-têtes de tableau des thèmes, catégories d'actualités, vitrine du marketplace sur les thèmes clairs, bouton « Tout accepter » du bandeau cookies, pastilles de réputation et de type d'événement : tous atteignent désormais le contraste minimal recommandé (4,5:1). En mode sombre, l'administration a ses propres couleurs d'état, le panneau « Santé du site » et la zone d'envoi d'images de la galerie suivent le thème.

  • L'administration sur un téléphone (2026-09-23). La barre du haut passe sur deux lignes — le fil d'Ariane en entier, puis les boutons — au lieu de couper le titre de la page ; les petites cibles (icône d'accueil, compteurs) sont assez grandes pour le doigt ; l'en-tête des catégories du forum ne passe plus sous ses boutons.

  • Le forum sur un téléphone (2026-09-23) : dans un sujet, l'auteur passe au-dessus du message au lieu de lui laisser 200 px, et les boutons d'un message ne recouvrent plus sa date.

  • Les pluriels traduits (2026-09-23). 47 textes avaient perdu leur forme plurielle en traduction : un site en anglais affichait « 3 topic », « 5 image », « 2 year ». Le compteur des catégories du forum, écrit en français dans toutes les langues, est traduit.

  • La documentation du site est à jour (2026-09-23). Les dix pages du wiki livrées avec le produit et celles de la démonstration avaient une semaine de retard sur les guides ; « Créer un widget » enseignait encore une classe de Bootstrap 4. Un contrôle les compare désormais aux guides.

  • Le formulaire de réponse du forum tient sur un téléphone (2026-09-23) : la liste des types de pièces jointes autorisés, écrite sans espace, formait un seul mot trop large pour l'écran.

  • Tout le site passé au crible, dans chaque thème, chaque mode et à chaque largeur (2026-09-23). Un contrôle automatique rend désormais chaque page publique et d'administration dans les six thèmes, en clair et en sombre, connecté et en visiteur, de 360 à 2560 px, avec et sans contenu. Ce premier passage a corrigé :

    • des icônes cassées dans les listes de catégories de la galerie, des jeux, des équipes et des actualités, quand une catégorie n'avait pas d'icône ;
    • le pays des profils de la démonstration, écrit en toutes lettres : le profil n'affichait aucun pays et cherchait un drapeau introuvable ;
    • les sujets des modèles d'e-mails, qui affichaient {{site_name}} en clair dans la liste ;
    • des tableaux trop larges pour un téléphone (bug tracker, téléchargements, historique du wiki, modération, sessions, participants d'un événement, palmarès) : ils défilent désormais dans leur cadre au lieu d'élargir toute la page ;
    • les listes du forum sur téléphone : quatre colonnes se partageaient 360 px et le dernier message était coupé ; chaque entrée s'empile désormais — icône et titre, puis statistiques et dernier message ;
    • la barre du thème Nebula sur téléphone, qui débordait de l'écran ;
    • trois fichiers réclamés par des pages sans exister, et deux lignes écrites au journal à chaque vérification des mises à jour tant qu'aucune version n'est publiée.
  • Les boutons « Modifier » et « Supprimer » ne sont plus en escalier (2026-09-22). Dans les listes de l'administration à deux colonnes — catégories des actualités, de la galerie, groupes de membres… —, le second bouton passait à la ligne, décalé sous le premier. Ils restent désormais côte à côte.

  • La jauge « Stockage » ne touche plus son arc (2026-09-22). La valeur, en haut du demi-cercle, empiétait sur le trait ; elle est descendue dans le creux, le pourcentage juste en dessous. Et le titre « Informations serveur » de la même page n'est plus coupé net quand la colonne est étroite : il passe sur deux lignes.

  • Un widget posé sans réglages s'affiche avec ses valeurs par défaut (2026-09-22). Quand un thème pose un widget à l'installation, ou qu'une ancienne disposition le restaure, il arrive sans réglages : il écrivait alors des alertes dans le journal à chaque page affichée, et le menu de navigation disparaissait purement et simplement. Il reçoit désormais les valeurs que son formulaire aurait enregistrées par défaut. Les widgets déjà réglés ne changent pas.

  • La page des événements s'affiche de nouveau (2026-09-22). Elle rendait une page d'erreur « introuvable » dès qu'elle avait des événements à répartir sur plusieurs pages — sous un titre parfaitement normal, ce qui la rendait difficile à remarquer. Le réglage « nombre d'événements par page » était enregistré comme du texte, et le code, rendu plus strict la veille, le refusait. Toutes les listes paginées acceptent désormais ce réglage sous les deux formes.

  • Les votes des sondages sont enregistrés (2026-09-22). Le votant lisait « Merci pour ton vote ! », et le vote n'était jamais compté : aucune option n'était reconnue comme appartenant au sondage. Un vote qui ne vise aucune option du sondage est désormais refusé en le disant, au lieu d'être salué.

  • Le widget « Derniers sujets du forum » affiche enfin des sujets (2026-09-22). Il ne retenait aucune catégorie de forum, et restait donc vide sur tous les sites, quel que soit le nombre de messages.

  • Trois autres fonctions tombaient dans le même piège que les sondages (2026-09-22), trouvées par le contrôle écrit pour ce défaut :

    • ouvrir une candidature qui porte des champs personnalisés plantait la page, côté candidat comme dans l'administration ;
    • une notification qui renvoie vers un événement du calendrier plantait au moment de fabriquer son lien ;
    • la profondeur maximale des réponses du forum n'était jamais appliquée : le calcul s'arrêtait au premier niveau.
  • Revenir à la page 1 d'une liste ne mène plus à une page introuvable (2026-09-22). Dans les listes de l'administration dont on peut régler le nombre d'éléments par page, le bouton « 1 » fabriquait une adresse que le site ne reconnaissait pas.

  • L'édition d'un champ de profil personnalisé s'ouvre (2026-09-22). L'écran plantait à chaque ouverture : il ne donnait pas de libellé à son bouton d'enregistrement.

  • Deux boutons « Retour » menaient à une page introuvable (2026-09-22) : depuis la fiche d'une image, vers son album ; depuis un mode de jeu, vers son jeu.

  • « Postuler » n'apparaît plus là où la candidature serait refusée (2026-09-22) : sur une offre fermée ou complète, et pour qui n'a pas le droit de postuler. De même, un administrateur qui lit une conversation réservée à l'équipe peut désormais y inviter et en signaler un message, au lieu d'être refusé après avoir vu le bouton.

  • Les sous-menus du widget de navigation s'ouvrent (2026-09-22). Un lien qui regroupe d'autres liens ne dépliait rien au clic : il portait encore l'attribut de l'ancienne version de Bootstrap, que son propre script ne cherchait plus.

  • Les bulles d'aide des formulaires s'affichent au survol (2026-09-22). L'icône (i) posée à côté des champs de l'administration, et les info-bulles des étiquettes, ne montraient rien : même défaut d'attribut, sur les deux bibliothèques qui les fabriquent.

  • Les libellés accentués du menu sont traduits (2026-09-22). Un lien de menu enregistré depuis l'administration — « Actualités », « Équipes » — est stocké sous forme codée, et sa traduction n'était plus trouvée : il restait en français dans les cinq autres langues, avec une alerte au journal à chaque page affichée.

  • Les archives de la place de marché sont reproductibles (2026-09-22). Elles emportaient les cartes de source .map, que le compilateur de styles régénère sur chaque installation et que le dépôt ignore : deux archives du même addon, bâties sur deux machines, différaient sans que le code ait bougé. Une archive contient désormais ce que le dépôt contient, rien d'autre.

  • Le widget « Palmarès » s'installe enfin par dépôt d'archive (2026-09-22). Il était le seul des 61 addons distribuables à ne pas déclarer sa dépendance au cœur ; l'installeur, qui l'exige pour reconnaître un addon, passait son chemin SANS RIEN DIRE — ni message, ni trace.

  • Le lecteur de flux RSS affiche ses dates au lieu de leur code (2026-09-22). Sous chaque titre s'affichait <time datetime="2026-09-11…">Le 11/09/2026…</time> en toutes lettres : la vue échappait un élément que la bibliothèque de dates avait déjà composé. Les résumés, eux, laissaient passer le balisage des flux qui l'échappent.

  • Le lecteur de flux n'a plus besoin d'écrire son cache pour afficher (2026-09-22). Il renvoyait ce que le cache voulait bien lui relire : quand l'écriture échouait, il affichait le vide alors qu'il avait les articles en main, et sans un mot nulle part.

  • La page de maintenance n'est plus blanche (2026-09-22). Les deux champs « titre » et « texte » sont livrés VIDES, et la page ne montrait donc RIEN d'autre que le nom du site — alors que l'aperçu de l'administration promettait un titre et un texte. Un titre et un message par défaut, traduits dans les six langues, comblent le vide ; dès que les champs sont remplis, ce sont eux qui s'affichent.

  • Les boutons à contour ont retrouvé leur cadre (2026-09-22). Les sept thèmes ne redéfinissaient qu'une seule variante de bouton « contour », et leur propre règle effaçait la bordure des six autres : 90 boutons du produit ressemblaient à de simples liens. Le plus visible était « Ouvrir le site », dans le bandeau de maintenance, qui n'avait de cadre qu'au survol.

  • Le code technique n'est plus rose dans l'administration (2026-09-22). Le thème ne fixait que la police des extraits <code>, qui gardaient donc le rose de Bootstrap — une couleur étrangère à toutes les palettes du produit. Très visible sur la liste des rôles.

  • La carte « Stockage » de la supervision s'affiche correctement (2026-09-22). La valeur et le pourcentage se plaçaient tout en haut de la carte au lieu du creux de la jauge, et la jauge elle-même se dessinait en cercle entier au lieu du demi-cercle prévu, avec un nombre brut en son centre. La ligne « Envoi d'email » affichait [object Object] à la place du nom de la méthode employée.

  • Le bouton « Fermer » des fenêtres ferme enfin la fenêtre (2026-09-22). Dans TOUTES les fenêtres du produit, le bouton « Fermer » (ou « Annuler ») du bas ne faisait rien : il fallait la croix en haut à droite. La cause : Bootstrap 5 a renommé l'attribut qui commande la fermeture, et le cœur posait encore l'ancien nom ; un navigateur ignore un attribut inconnu sans rien dire. Le bouton est aussi devenu un vrai bouton — il était rendu en <span>, donc inaccessible au clavier. Deux gardes l'empêchent de revenir : le contrôle du code refuse l'ancien nom, et un parcours de navigateur CLIQUE sur le bouton à chaque passage.

  • Le thème Nebula n'affiche plus son menu deux fois (2026-09-22). Toute installation neuve montrait le nom du site deux fois et le menu deux fois : le thème dessine sa propre barre, et l'installation posait EN PLUS un titre et un menu juste en dessous. Désormais le menu vit dans la barre du haut, où il n'apparaît qu'une fois — et il reste configurable depuis l'administration, ce qui n'était pas le cas : les liens de la barre étaient écrits en dur, si bien que « Contact » n'y figurait pas et qu'un module désactivé gardait son lien. Les sites déjà installés sont corrigés par une migration, qui ne touche à l'en-tête que s'il est resté tel que livré.

  • Changer de langue ne renvoie plus sur une page de code (2026-09-22). Le sélecteur de langue du pied de page — celui de cinq thèmes, dont celui par défaut — envoyait le visiteur sur une page affichant {"redirect":"/en/…"} en texte brut, au lieu de l'emmener sur la page traduite. Il fallait revenir en arrière pour s'en sortir.

    La cause : ce sélecteur est un vrai formulaire, que rien n'interceptait, et il visait une adresse technique qui répond toujours en JSON. Le serveur distingue désormais l'appel du menu « Choisir ma langue », qui attend du JSON, d'un envoi de formulaire ordinaire, qui attend d'être emmené sur la page. Trouvé en suivant le geste dans un vrai navigateur, ce qu'aucun contrôle ne faisait jusque-là.

  • Un contenu rédigé dans une seule langue ne renvoie plus les autres sur une page d'erreur (2026-09-21). Une actualité écrite en français proposait quand même les cinq autres langues dans son sélecteur, et les cinq rendaient 404 : mesuré sur la démonstration, 60 adresses mortes sur 72. Le geste le plus naturel d'un visiteur étranger tombait sur une erreur. La version qui existe est désormais servie, avec un bandeau qui le dit dans la langue du visiteur et un lien vers l'original. Cela vaut pour les actualités, les articles, les albums, les équipes et leurs catégories.

    Deux précautions indissociables, sans quoi le remède coûterait plus que le mal : la page n'annonce plus aux moteurs de recherche que les langues qui existent vraiment, et elle désigne l'original comme adresse de référence — sinon six adresses seraient indexées pour un seul texte. En administration, aucun repli : une version vide doit se voir vide, c'est ce qu'on vient remplir. tools/check-langues-contenu.php tient les trois propriétés, en intégration continue.

  • Webradio : le bouton « Configurer » menait à un 404 (2026-09-21). Il visait une route qui n'existait pas ; il ouvre désormais la modale de configuration commune à tous les addons, celle du bouton « Configuration » de la barre d'administration. Trouvé par check-liens en CI, où tous les modules sont installés — l'atelier n'a pas la webradio.

  • La CI est de nouveau lisible : elle était rouge depuis le 26 août (2026-09-21). Trois causes, aucune dans le produit. Le verrou Composer, régénéré sous PHP 8.5, exigeait 8.4 pour une bibliothèque : cinq jobs qui tournent sous 8.3 échouaient dès composer install ; la plateforme est désormais fixée à 8.2.0 dans composer.json. check-docs comptait les addons distribuables d'après les zips de marketplace/, qui ne sont pas versionnés : zéro en CI ; il lit le manifeste. Et prepare-test-db créait le compte de test pour localhost et 127.0.0.1 seulement, alors que MariaDB, dans son conteneur, voit le client arriver de la passerelle Docker ; l'ancienne version cachait ce refus derrière un exit à code zéro. Il crée aussi le compte pour l'hôte que le serveur voit, sans joker. Derrière, un quatrième défaut : les suites d'intégration se sautaient depuis setUpBeforeClass(), et --fail-on-skipped n'y voyait rien — le saut se prononce dans setUp(), test par test, et le drapeau le transforme bien en échec. Chaque job a enfin un budget de temps : celui des épreuves JS avait pendu six heures, deux fois. Et la bibliothèque des outils avait son défaut : nf_http() répondait « pas de réponse » à une redirection qu'on lui demandait de ne pas suivre, si bien que check-install-profiles comptait / et /admin en erreur serveur. Enfin, sous PHP 8.3, le serveur intégré présente SCRIPT_NAME égal au chemin demandé pour un asset sans fichier : Url en tirait une base tronquée et redirigeait feuilles et scripts vers la langue. Le routeur des outils se présente désormais comme un vrai serveur, et Url ne déduit la base que d'un SCRIPT_NAME qui finit par index.php.

  • Le logo de l'installateur ne s'affichait pas sur une installation neuve (2026-09-21) : le chemin était relatif au dossier install/, que le serveur ne sert pas sous cette adresse. L'image est désormais embarquée dans la page.

  • Des composants qui ne s'ouvraient plus depuis le passage à Bootstrap 5 (2026-09-21). Bootstrap 5 a préfixé tous ses attributs de données par bs-. Un attribut resté à l'ancien nom ne provoque aucune erreur : le composant se contente de ne rien faire. Étaient morts, en silence :

    Ce qui ne marchait plus Où
    L'accordéon de la FAQ ne s'ouvrait pas modules/faq
    La liste des participants d'un événement ne se dépliait pas modules/events
    Tous les popovers d'aide des formulaires étaient vides cœur — neofrag/libraries/form.php
    Le popover de profil du forum, celui de la suppression d'un lien de navigation modules/forum, widgets/navigation
    Le placement des infobulles de l'éditeur en direct, le délai du carrousel modules/live_editor, widgets/slider
  • Des mises en page fausses, elles aussi silencieuses (2026-09-21). Bootstrap 5 a renommé ses utilitaires directionnels ; une classe renommée n'est plus définie nulle part, et l'élément garde sa disposition par défaut sans que rien ne le signale.

    • Les pieds de panneau et de modale ne s'alignaient pas à droite. Le cœur fabriquait la classe par concaténation ('float-'.$align), donc aucune recherche sur float-right ne pouvait la trouver. Le seul rattrapage du produit ne couvrait que les tableaux du thème d'administration : rien sur les six thèmes publics. Même cause pour l'alignement des cellules de tableau, dans les deux bibliothèques.
    • Aucun champ de formulaire refusé n'était marqué, sur les sept thèmes. Les deux bibliothèques posaient un nom hérité — has-error (Bootstrap 3) et has-danger (une préversion de Bootstrap 4) — qu'aucune feuille servie ne définit. Le message s'affichait bien, mais rien ne désignait lequel des dix champs le concernait. Ils sont remplacés par un marqueur maison, défini une fois.
    • Le pied de page portait <div class="float-right"> : « Propulsé par NeoFrag » tombait à la ligne, à gauche. La valeur vient de loin — une migration de 2019 l'avait passée de pull-right à float-right au moment de Bootstrap 4, et le pas suivant n'a jamais été fait. Une migration (2026_09_20_bootstrap5_float) corrige les sites existants ; le seed livre désormais float-end.
    • Et les dernières classes mortes des vues : la barre de débogage sur téléphone, la gouttière des listes d'événements, les étiquettes de l'éditeur de menu, les deux barres du vote de recrutement — qui sortaient de la même couleur, le graphique ne distinguant plus les avis favorables des défavorables.
  • Onze coercitions de type, dont trois sur des chemins empruntés à chaque page (2026-09-21), mises au jour par la vague strict_types ci-dessous :

    • strnatcmp() recevait l'ordre d'affichage d'une langue et d'un groupe — deux entiers — à l'initialisation de la session. Un site à deux langues actives aurait rendu un 500 sur toutes ses pages ;
    • crypt::hash() passait un flottant à str_split() ; c'est cette méthode qui fabrique le jeton anti-CSRF de toute page à formulaire ;
    • huit autres dans le diagnostic PHP, le flux de sauvegarde, le graphique par semaine et la barre de débogage — toutes dans des branches qu'aucun test n'emprunte.
  • Le corps d'un article pouvait être effacé à l'affichage (2026-09-21). La construction du sommaire écrasait le contenu par le résultat de preg_replace_callback(), qui vaut NULL quand le moteur d'expressions régulières abandonne — ce qu'un article long peut provoquer. La page répondait 200 avec un article vide, sans rien dans les journaux.

  • Un widget placé en haut ou en bas de page disparaissait. Les thèmes Nebula et Vitrine proposaient les emplacements « Header » et « Footer » dans l'éditeur de mise en page, mais ne les affichaient pas. Le widget était bien enregistré, et rien n'apparaissait — sans message. Les quatre autres thèmes n'étaient pas touchés.

  • Des pages s'affichaient normalement tout en répondant « page introuvable » aux moteurs de recherche. Trois pages étaient dans ce cas, de façon intermittente : la même recette s'affichait correctement sans nombre de parts, et en erreur avec.

  • Les dépendances entre addons étaient toujours annoncées comme manquantes, même quand le module requis était bel et bien installé. Deux autres conséquences du même défaut : les statistiques de l'administration se mélangeaient entre modules, et le widget de navigation proposait deux modules internes qu'il aurait dû masquer.

  • Le journal de débogage grossissait sans limite quand il était activé. Il est désormais borné : au-delà de 64 Mo, il repart en conservant la génération précédente.

  • En anglais, « Non » s'affichait « Non » au lieu de « No ».

  • Cinq pages d'administration n'offraient aucun retour (2026-09-20) : la fiche d'un membre, le journal d'audit, l'ajout d'un groupe, la liste des sessions et les fichiers du monitoring. Le fil d'Ariane ne rend le nom du module cliquable que si le module déclare avoir une page d'accueil d'administration ; user et monitoring déclaraient le contraire alors qu'ils en ont une. Une fois arrivé sur ces pages, il fallait passer par le menu latéral ou le bouton du navigateur.

  • Le formulaire de contact perdait les messages. Le serveur du site n'avait aucun moyen d'envoyer du courrier, et son adresse d'expédition était invalide : un visiteur qui écrivait voyait « une erreur s'est produite » et son message disparaissait. Le site envoie désormais, et le message arrive.

  • Les moteurs de recherche recevaient du JSON à la place du plan du site. Demandé à la racine, sans préfixe de langue — la seule façon dont un robot les demande —, /sitemap.xml répondait {"redirect":"/fr/sitemap.xml"} avec un code 200, et robots.txt comme humans.txt faisaient de même. La redirection qui ajoute le préfixe de langue répondait en JSON dès que l'adresse finissait par .txt, .xml ou .json. Ces quatre fichiers n'ayant pas de version par langue, ils ne sont plus redirigés du tout : ils sont servis directement, avec leur vrai type de contenu.

  • /favicon.ico répondait 404. Les navigateurs demandent cette adresse quoi qu'annonce la page ; elle mène désormais au favicon configuré dans les réglages, ou à celui du CMS.

  • Activer la double authentification plantait. Au moment d'enregistrer les codes de récupération, une erreur interrompait la page : le compte se retrouvait marqué « protégé » sans aucun code de secours. Trouvé par un test écrit en durcissant le typage de cette bibliothèque ; corrigé, et le test reste.

  • Une limitation « une seule tentative » ne bloquait jamais. Le seuil n'était vérifié qu'à partir de la deuxième tentative. Sans effet sur les réglages livrés (trois ou cinq tentatives), corrigé pour que le seuil vaille dès la première.

  • Des textes restaient en français dans les autres langues, et le journal se remplissait. Trois causes, toutes corrigées : le titre d'une page du wiki, d'un type d'événement ou d'une campagne de dons passait par la traduction comme s'il était un texte de l'interface (il n'a pas de traduction : c'est du contenu) ; un texte déjà traduit repassait par la traduction — « Espace membre » devenait « Member area », puis « Member area » était cherché comme clé et signalé introuvable à chaque affichage ; et trente textes d'interface (le champ « Pseudo ou adresse email » de la connexion, des titres de panneaux de l'administration des jeux et du recrutement, les réseaux du profil…) n'avaient de traduction dans aucune langue, parce que le contrôle qui vérifie les six langues ne regardait que les appels explicites à la traduction, pas les titres de champs et d'en-têtes traduits en aval. Le contrôle les voit désormais ; les trente textes sont traduits en anglais, allemand, espagnol, italien et portugais.

  • Google Analytics attend désormais votre consentement. Si un identifiant Analytics est configuré, le script de Google ne se charge plus qu'après un clic sur « Tout accepter » dans la bannière de cookies, jamais avant. Par ailleurs la politique de sécurité du site refusait ce script dans tous les cas : Analytics ne fonctionnait pas, sans le dire. Il fonctionne maintenant, et seulement avec l'accord du visiteur.

  • Trois fonctions de l'administration ne répondaient plus depuis juin. Le tri des tables par clic sur un en-tête de colonne, le glisser-déposer pour réorganiser catégories, forums et sous-forums, et les boutons Ouvert/Fermé des pages Maintenance et Inscriptions : leurs scripts supposaient encore la présence de jQuery, retiré du CMS en juin 2026, et s'arrêtaient net au chargement de la page — sans message, les boutons restant simplement inertes. Les trois sont réécrits sans jQuery, couverts par des épreuves en navigateur, et un contrôle automatique refuse désormais tout script qui ferait de nouveau appel à jQuery.

  • Une adresse mal formée provoquait une erreur serveur. Une requête comme /fr/a:80 ou /:80, du genre qu'envoient les robots, faisait planter le calcul de l'extension de l'adresse et rendait une erreur 500 au visiteur. Le site répond désormais par un 404 ordinaire.

  • Le formulaire de contact écrivait « De : » avec l'adresse du visiteur. Envoyer depuis votre serveur un message au nom de visiteur@gmail.com, c'est exactement ce que les protections anti-usurpation des messageries (SPF, DMARC) rejettent : le message finissait en spam ou refusé. L'expéditeur est désormais l'adresse de contact du site, et l'adresse du visiteur est placée en « Répondre à », ce qui revient au même à l'usage. Même correction pour la réponse à une candidature, qui pouvait partir avec l'adresse personnelle de l'administrateur.

  • L'installeur pouvait enregistrer une adresse de contact invalide. Quand le site était installé en y accédant par son adresse IP, l'adresse de contact devenait noreply@<IP>, que les messageries refusent — et plus aucun e-mail ne partait, sans message d'erreur visible. L'adresse n'est plus déduite que d'un vrai nom de domaine.

  • Un bandeau vide s'affichait sur les pages dont une zone de mise en page est vide. En thème Extend, toutes les pages autres que l'accueil montraient une large bande colorée ne contenant rien, ainsi qu'une section vide au-dessus du contenu.

    La cause ne venait pas du thème : une zone déclarée mais sans widget renvoyait des espaces plutôt que rien du tout, et les thèmes dessinaient donc leur cadre autour du vide. Corrigé une fois pour tous les thèmes. Si vous créez un thème, vous pouvez continuer d'écrire if ($zone = $this->output->region('banner')) : la condition dit désormais la vérité.

  • Le thème Extend affichait sa mention « Propulsé par NeoFrag Reborn » deux fois. Sa mise en page livrée posait dans le pied un bloc reprenant ce que le thème écrit déjà lui-même. Les sites déjà installés sont corrigés automatiquement à la mise à jour ; les nouveaux ne l'auront jamais.

  • Un commentaire illisible dans l'exemple de code de la page d'accueil — contraste de 3,01:1 là où l'accessibilité en demande 4,5. Éclairci, tout en restant plus discret que le reste du code.

  • Chercher un mot courant renvoyait une page d'erreur. Une recherche comme « le » ou « de » affichait bien ses résultats à l'écran, mais le serveur répondait 404 — ce que voient les moteurs de recherche, les outils de supervision et les navigateurs, et ce qui faisait passer une page parfaitement valide pour une page disparue.

    La mise en évidence des mots trouvés partait du principe que le mot cherché figurait forcément dans le texte affiché. C'est faux deux fois : la recherche examine plusieurs champs et n'en affiche qu'un — un sujet de forum intitulé « Salut tout le monde ! » répond à « le » par son titre, pas par le message montré dessous — et la base de données compare sans tenir compte des accents là où le code, lui, en tenait compte : « éléphant » ressortait bien pour la recherche « ele », sans qu'aucun mot n'y soit surligné.

    Les deux cas sont traités : le surlignage reconnaît désormais les variantes accentuées d'une lettre, comme la base de données, et quand le mot n'est réellement pas dans le texte affiché, l'extrait commence simplement à son début.

  • La mise à jour automatique du cœur ne pouvait pas fonctionner — cinq défauts, chacun suffisant à lui seul : le réglage qui désigne la source des versions était vide par défaut et déclaré nulle part (le manifeste n'était donc jamais téléchargé, et le bouton n'apparaissait jamais) ; un interrupteur global bloquait la méthode ; l'URL de téléchargement était codée en dur vers la release upstream, qui aurait écrasé le code de ce fork ; le paquet produit rangeait tout sous un dossier racine alors que l'updater écrit à plat, donc une mise à jour aurait créé un sous-dossier sans rien remplacer et sans la moindre erreur ; et l'extraction passait par l'API zip procédurale, dépréciée depuis PHP 8.0.

    L'interrupteur est remplacé par quatre garanties : l'origine vient d'une liste d'hôtes autorisés (la même que le marketplace), le manifeste ne fournit qu'un nom de fichier et jamais une URL, l'empreinte SHA-256 est vérifiée avant qu'un seul fichier ne soit touché, et l'archive est contrôlée entrée par entrée (chemins d'évasion et liens symboliques refusés). config/ et install/ ne sont jamais réécrits quand ils existent déjà.

  • Le catalogue du marketplace était injoignable depuis n'importe quel site. Le serveur renvoyait {"redirect":"/fr/…"} avec un code 200 au lieu du fichier statique — un corps parfaitement valide en JSON, et parfaitement faux, donc un échec silencieux qui faisait retomber chaque site sur son catalogue local. Corrigé côté serveur (les fichiers de distribution sont servis en statique ; la config Caddy de production est désormais versionnée) et côté code (la forme des manifestes est validée, plus seulement leur décodabilité).

  • Un widget sans réglages ne pouvait pas être ajouté au Live Editor. Le contrôle de formulaire exigeait le champ settings comme obligatoire : les widgets qui n'ont aucun réglage (Copyright, Fil d'Ariane, Recherche…) échouaient avant d'atteindre le contrôleur, sans message exploitable. Un nom de champ suffixé de ? le rend facultatif.

  • Le bouton « Supprimer » de l'administration ne protégeait que le thème admin, par son nom écrit en dur. Il s'appuie maintenant sur la déclaration, donc protège aussi nebula et vitrine.

  • Couplages du cœur vers l'optionnel. Six modules du cœur interrogeaient les tables de modules optionnels. Un seul était réellement cassé — dans teams, la garde d'existence de recruits était placée après la requête qu'elle devait protéger. Les autres étaient gardés mais muets : ils portent désormais une annotation vérifiée par la CI. Le seul cycle dur du paquet (games ↔ teams) est rompu.

  • Live Editor : les modifications ne s'affichaient qu'après rechargement. NF.post() parse la réponse en JSON (NF.ajax fait response.json() sauf si dataType: 'text'), or tous les endpoints admin/ajax/live-editor/* répondent en text/html : un fragment de disposition, ou un corps vide pour les mutations. response.json() levait donc sur < (ou sur le corps vide), la promesse était rejetée, et les .then() qui mettent le DOM à jour ne tournaient jamais — alors que le serveur, lui, avait bien enregistré. Concrètement : on supprimait un widget et il restait à l'écran, on ajoutait une ligne et elle n'apparaissait pas, jusqu'au rechargement de la page. Les 16 appels du module passent par un helper nfLePost() qui force dataType: 'text', comme le faisaient déjà js/delete.js et js/popover.js. Régression introduite par la conversion vanilla (ee4300a) : jQuery devinait le type de réponse, fetch non. (Audit des autres appelants de NF.post : monitoring.json, monitoring/sudo et le file-manager visent bien du JSON — seul le Live Editor était touché.)

  • Live Editor : le glisser-déposer des widgets ne fonctionnait pas. SortableJS était branché sur [data-col-id], mais les widgets sont enveloppés un cran plus bas dans .live-editor-col (le wrapper que col.php ajoute avec l'en-tête de colonne quand le mode Colonnes est actif). Or SortableJS ne déplace que les enfants directs de son conteneur, là où jQuery UI acceptait un sélecteur de descendants (items: '[data-widget-id]'). Symptôme : un widget ajouté restait collé en bas de colonne, impossible de le remonter au-dessus du module de la page. Le tri vise désormais le vrai parent (.live-editor-col s'il existe, [data-col-id] sinon — le wrapper n'existe pas quand le mode Colonnes est éteint), et col_id est relu via closest(). Les lignes et les colonnes n'étaient pas concernées : elles sont bien enfants directs de leur conteneur.

  • Live Editor : impossible d'ajouter 10 des 38 widgets (breadcrumb, copyright, downloads, forum, module, news, newsletter, slider, surveys, teams) — widget-add répondait 404. Le checker serveur fait un post_check() qui exige la présence du champ settings ; les widgets qui ont des réglages nomment leurs champs settings[clé] et le remplissent donc, mais ceux sans controllers/admin.php n'envoient rien. Le JS prévoyait ce cas avec un repli settings: null… que le sérialiseur de NF.ajax omet (v !== null), là où $.param() de jQuery écrivait settings=. Le repli est passé en chaîne vide, ce qui rétablit le format d'avant la dé-jQuery. (Le fond — rendre ce champ facultatif côté serveur — est noté dans le backlog.)

  • NF.ajax : un statut HTTP d'erreur ne rejetait pas. fetch ne se contente pas de résoudre, il livre le corps de la page d'erreur : un appelant en dataType: 'text' insérait donc la page « 404 Not Found » dans le DOM comme une réponse valide. jQuery ne déclenchait pas .done() sur un 404 ; le garde response.ok rétablit ce comportement. Touche tout le front — vérifié que les flux qui signalent une erreur applicative (le sudo du file-manager) répondent en 200 avec un JSON.

  • Live Editor : formulaire de réglages du mauvais widget. load_settings() postait vers widget-admin sans numéro de séquence : deux changements rapides de widget ou de type et la réponse la plus lente écrasait la plus récente. La requête dépassée est désormais annulée (AbortController, déjà supporté par NF.ajax) et sa réponse ignorée si elle arrive quand même ; une clé widget::type évite en prime de recharger — donc de perdre la saisie en cours — quand la sélection n'a pas réellement changé.

  • Port de base de données ignoré (connexion possible qu'en 3306) : le driver runtime (neofrag/drivers/mysqli.php) construisait new mysqli(host, user, pass, db) sans port, et write_config ne le persistait pas dans config/db.php. Résultat : impossible d'installer/faire tourner le CMS sur un port MySQL non standard (l'assistant web collectait bien le port mais il était perdu, et install/cli.php --db-port était silencieusement inopérant). Le port est désormais persisté (s'il diffère de 3306, pour garder les config standards propres) et propagé jusqu'à mysqli() (driver + db.php + write_config + les 3 installeurs). Le chemin 3306 par défaut est inchangé (aucune régression).

  • Thèmes vides à l'activation : activer un thème ne faisait que changer nf_default_theme sans appliquer sa mise en page par défaut (son install()), lancée seulement au « Réinstaller par défaut » manuel. Résultat : forge, granite, blockcraft, extend s'affichaient vides (zones sans widgets). enable() applique désormais la disposition par défaut du thème s'il n'en a aucune (sans jamais écraser une personnalisation existante).

  • Changement de thème par défaut ignoré par le cookie visiteur : le sélecteur de thème (footer) pose un cookie nf_theme (1 an) qui prime sur le défaut — l'admin changeait le thème mais les visiteurs (et lui-même) restaient collés sur leur ancien choix, sans moyen d'en sortir depuis un thème sans sélecteur. Introduit une « époque » (nf_theme_epoch) incrémentée à chaque changement de défaut : la préférence n'est honorée que si elle a été posée depuis le dernier changement, sinon le cookie est ignoré et effacé et le visiteur suit le nouveau défaut (l'admin décide). Un choix explicite postérieur reste respecté.

  • Pages dynamiques mises en cache par le navigateur : aucune réponse n'envoyait d'en-tête de cache → cache heuristique du navigateur, d'où du contenu périmé (ex. thème changé côté admin visible seulement au Ctrl+F5). Les réponses HTML/JSON dynamiques envoient désormais Cache-Control: no-store ; les assets statiques (servis par le serveur web, cache-bustés par mtime) ne sont pas concernés.


Sécurité

  • Une valeur enregistrée ne peut plus injecter de balises dans un formulaire (2026-09-23). Le formulaire d'édition posait la valeur d'un champ dans la page en échappant les guillemets à la manière d'une chaîne PHP — sans effet en HTML. Une valeur qui n'était pas passée par le formulaire lui-même (import, données de démonstration, autre module) pouvait donc sortir de son champ et ajouter du code à l'écran d'administration : la source d'une citation valant "><img src=x> l'a montré. Champs texte, zones de texte, listes, cases à cocher et boutons radio encodent désormais leur valeur ; le rendu d'une valeur saisie normalement ne change pas. Les notifications (« Citation modifiée », etc.) sont aussi passées au script de la page sous une forme qui ne peut plus le casser.

  • La politique de sécurité déclare désormais une règle pour les médias (media-src). Elle n'en avait aucune et héritait d'une règle plus large ; la déclarer explicitement la rend plus lisible. L'adresse d'un flux de webradio n'y est ajoutée que si un flux est configuré : un site sans webradio n'autorise rien de plus qu'avant. Le reste de la politique est inchangé.

  • Onze avis de sécurité fermés sur les dépendances (2026-09-20) : league/commonmark passe de 2.8.2 à 2.10.1 (dix avis, dont huit graves) et phpseclib de 3.0.52 à 3.0.57 (un avis moyen).

    Neuf des dix avis de commonmark sont des dénis de service par Markdown fabriqué — analyse en temps quadratique, titres aux ancres colliionnantes, notes de bas de page dupliquées, blocs d'attributs adjacents, sortie XML profondément imbriquée — et le dixième est une faille XSS : le filtre des attributs on* se contournait avec un saut de page U+000C. La bibliothèque rend le Markdown du wiki, des articles et de la FAQ, écrit par des membres ; nos garde-fous (échappement du HTML, liens non sûrs refusés, imbrication bornée) couvraient le XSS mais rien des dénis de service.

    Le rendu ne change pas, et ce n'est pas une impression : douze cas représentatifs — titres, tableau, liste de tâches, bloc de code, HTML injecté, lien javascript:, citation, texte barré, lien automatique, listes imbriquées, image, entités et note de bas de page, imbrication profonde — rendus par les deux versions avec la configuration réelle du produit donnent le même HTML octet pour octet (1568 octets, même empreinte).

    À noter : Composer refuse désormais d'installer la 2.8.2, précisément à cause de ces dix avis. Il a fallu désactiver la politique dans un dossier jetable pour mener la comparaison.

À lire aussi

0
Commentaires