<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title># notACMS</title>
        <link>https://notacms.holas.pl/fr/</link>
        <description><![CDATA[AI-friendly static site generator. Zero database. Pure Markdown.]]></description>
        <language>fr</language>
        <atom:link href="https://notacms.holas.pl/fr/feed/" rel="self" type="application/rss+xml"/>
                        <lastBuildDate>Sat, 13 Jun 2026 00:00:00 +0000</lastBuildDate>
                        <item>
            <title><![CDATA[notACMS 1.2.1 — correction charset, avertissements de build plus précis, nouvelles pages de documentation]]></title>
            <link>https://notacms.holas.pl/fr/articles/release-1-2-1/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/fr/articles/release-1-2-1/</guid>
                        <pubDate>Sat, 13 Jun 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Quoi de neuf dans 1.2.1 Un petit suivi de 1.2.0 avec deux corrections de bugs et une documentation étendue. Charset nginx Les réponses texte — /llms.txt, /robots.txt — étaient servies sans déclaration de charset. Les navigateurs affichaient les caractères non-ASCII comme du charabia. charset utf-8; est maintenant défini au niveau du bloc serveur dans docker/nginx.conf.template, de sorte que chaque…]]></description>
            <content:encoded><![CDATA[<h2>Quoi de neuf dans 1.2.1<a id="quoi-de-neuf-dans-121" href="#quoi-de-neuf-dans-121" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Un petit suivi de 1.2.0 avec deux corrections de bugs et une documentation étendue.</p>
<h2>Charset nginx<a id="charset-nginx" href="#charset-nginx" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Les réponses texte — <code>/llms.txt</code>, <code>/robots.txt</code> — étaient servies sans déclaration de charset. Les navigateurs affichaient les caractères non-ASCII comme du charabia. <code>charset utf-8;</code> est maintenant défini au niveau du bloc serveur dans <code>docker/nginx.conf.template</code>, de sorte que chaque type de réponse reçoit automatiquement la déclaration correcte.</p>
<h2>Avertissement de clé de répertoire ambiguë<a id="avertissement-de-clé-de-répertoire-ambiguë" href="#avertissement-de-clé-de-répertoire-ambiguë" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>L'avertissement &quot;Ambiguous directory key&quot; se déclenchait auparavant lors de la construction de l'arbre — au moment où un second élément de contenu enregistrait un basename en conflit — indépendamment du fait que cette clé courte soit jamais utilisée dans un template.</p>
<p>L'avertissement est maintenant différé jusqu'à la recherche effective : il se déclenche une seule fois, et uniquement quand <code>content_item()</code> ou <code>content_url()</code> est appelé avec une clé courte ambiguë. Les sites avec des collisions de noms dans différentes sections de contenu ne voient plus d'avertissements superflus pour des clés qu'ils ne référencent jamais.</p>
<h2>Deux nouvelles pages de documentation<a id="deux-nouvelles-pages-de-documentation" href="#deux-nouvelles-pages-de-documentation" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Le thème demo dispose maintenant de pages pour <strong>Construction de Thème</strong> et <strong>Sécurité</strong>, disponibles dans les quatre locales.</p>
<p><strong><a href="/fr/construction-theme/">Construction de Thème</a></strong> couvre le modèle complet de couches de templates — comment <code>local/templates/</code>, les chemins de thème enregistrés et le bare core sont résolus par ordre de priorité — ainsi qu'une référence pour les contrats de contexte de template, les globales Twig, les fonctions et filtres, l'API ContentItem, les clés de traduction requises et les six règles de portabilité que tout thème devrait suivre.</p>
<p><strong><a href="/fr/securite/">Sécurité</a></strong> explique le modèle de menace qui guide les décisions de sécurité de notACMS : pas de base de données, pas de rendu dynamique, un pipeline de compilation qui n'exécute jamais le contenu. La page couvre les protections intégrées (validation du hostname Turnstile, protection contre la traversée de chemin, types stricts), la conception CSRF délibérée du formulaire de contact et les bonnes pratiques de déploiement pour une instance de production.</p>
<p>Les deux pages apparaissent dans le menu déroulant Documentation et dans la barre latérale.</p>
<h2>Changelog complet<a id="changelog-complet" href="#changelog-complet" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p><a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/CHANGELOG.md#121---2026-06-13">CHANGELOG.md</a></p>
]]></content:encoded>
                                    <category><![CDATA[releases]]></category>
                                    <category><![CDATA[release]]></category>
                        <category><![CDATA[announcement]]></category>
                    </item>
                <item>
            <title><![CDATA[notACMS 1.2.0 — la version de l'audit]]></title>
            <link>https://notacms.holas.pl/fr/articles/release-1-2-0/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/fr/articles/release-1-2-0/</guid>
                        <pubDate>Fri, 12 Jun 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[La version de l'audit 1.2.0 est le résultat d'un audit complet de notACMS — architecture, sécurité, qualité du code et les deux arborescences de templates. Environ 170 problèmes ont été corrigés. Les points forts : Robustesse Un fichier markdown au frontmatter invalide ne fait plus planter une langue entière ni le build — il est ignoré et signalé avec son chemin et le détail de l'erreur. Un slug: …]]></description>
            <content:encoded><![CDATA[<h2>La version de l'audit<a id="la-version-de-laudit" href="#la-version-de-laudit" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>1.2.0 est le résultat d'un audit complet de notACMS — architecture, sécurité, qualité du code et les deux arborescences de templates. Environ 170 problèmes ont été corrigés. Les points forts :</p>
<h3>Robustesse<a id="robustesse" href="#robustesse" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<ul>
<li>Un fichier markdown au frontmatter invalide ne fait plus planter une langue entière ni le build — il est ignoré et signalé avec son chemin et le détail de l'erreur.</li>
<li>Un <code>slug:</code> manquant ne peut plus détourner silencieusement la page d'accueil ; <code>posts_per_page: 0</code> ne plante plus le blog ; les URL dupliquées, les clés de répertoire ambiguës et les tags invalides produisent désormais des avertissements de build explicites.</li>
<li>Les brouillons et les <strong>pages</strong> planifiées sont maintenant réellement exclus des builds, des menus et de <code>/llms.txt</code> — seuls les billets étaient filtrés jusqu'ici.</li>
<li><code>app:build -o &lt;dir&gt;</code> refuse de vider un répertoire autre que le répertoire statique configuré sans <code>--force</code>.</li>
<li>Visiter <code>/pl/</code> pour la première fois ne redirige plus vers la version anglaise — l'URL est interprétée comme un choix de langue et un cookie est enregistré. Le cookie est maintenant conditionnel à <code>Secure</code> pour fonctionner aussi en HTTP.</li>
<li>Les extraits des résultats de recherche affichent désormais les surlignages comme de vraies balises <code>&lt;mark&gt;</code>, et non comme du texte littéral <code>&amp;lt;mark&amp;gt;</code>.</li>
<li>Le <code>_site.yaml</code> du thème de base contient maintenant des valeurs fictives pour <code>contact_form</code> — plus d'erreur de build à la première installation.</li>
</ul>
<h3>Sécurité<a id="sécurité" href="#sécurité" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<ul>
<li>ImageMagick passe maintenant par <code>Symfony\Process</code> avec des tableaux d'arguments — <code>exec()</code> a disparu.</li>
<li>Turnstile valide le hostname de la réponse par rapport à <code>base_url</code> et journalise une erreur quand les clés de test toujours-valides sont actives en production. Un contrôle au démarrage avertit si <code>APP_SECRET</code> est resté le placeholder par défaut.</li>
<li>La sortie JSON-LD est hex-échappée (<code>&lt;/script&gt;</code> dans un titre ne peut plus s'en échapper), les catalogues de traduction ne contiennent plus de HTML, et les en-têtes de sécurité nginx s'appliquent maintenant aussi aux réponses <code>/assets/</code> et <code>/media/</code>.</li>
<li>L'API de contact préfixée par la langue était inaccessible dans le déploiement Docker à cause d'un bug de regex nginx — corrigé.</li>
</ul>
<h3>Pour les créateurs de thèmes<a id="pour-les-créateurs-de-thèmes" href="#pour-les-créateurs-de-thèmes" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<ul>
<li>Nouveau : <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/docs/THEME_BUILDING.md">THEME_BUILDING.md</a> — le contrat complet de l'API de thèmes : contextes de templates par route, fonctions et globales Twig, l'API ContentItem et les clés de traduction que chaque thème doit définir.</li>
<li><code>#[LocalizedRoute]</code> fonctionne maintenant dans <code>local/src/Controller/</code>, le changement de langue fonctionne correctement sur les sites à 3 langues et plus, et le pipeline de build statique est décomposé en services réutilisables.</li>
</ul>
<h3>Nouveautés<a id="nouveautés" href="#nouveautés" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<ul>
<li>Le build statique génère désormais un fichier <code>/llms.txt</code> par langue — les billets les plus récents dans un format lisible par les LLM. Configurable via <code>llms_limit</code> dans <code>_site.yaml</code> (par défaut : 5). Le template est personnalisable par thème via le namespace Twig <code>@base</code>.</li>
</ul>
<h3>Changements cassants<a id="changements-cassants" href="#changements-cassants" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Quelques-uns — chacun avec une migration en une ligne. <code>directoryKey()</code> renvoie maintenant les chemins de contenu complets (la recherche par nom de base fonctionne toujours), <code>getTree()</code> a été déplacé vers <code>ContentTreeProviderInterface</code>, <code>blogPosting()</code> accepte une map nommée, la clé de contexte <code>lang_switch_url</code> a disparu, quatre clés de traduction inutilisées ont été supprimées et le paquet <code>old-template</code> de la 1.0 a été retiré. Le guide complet : <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/UPGRADE-1.2.md">UPGRADE-1.2.md</a>.</p>
]]></content:encoded>
                                    <category><![CDATA[releases]]></category>
                                    <category><![CDATA[release]]></category>
                        <category><![CDATA[security]]></category>
                        <category><![CDATA[refactoring]]></category>
                    </item>
                <item>
            <title><![CDATA[notACMS 1.1.4 — mise à jour de sécurité Symfony 7.4.13 et Twig 3.27.0]]></title>
            <link>https://notacms.holas.pl/fr/articles/release-1-1-4/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/fr/articles/release-1-1-4/</guid>
                        <pubDate>Thu, 28 May 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Mise à jour de sécurité : Symfony 7.4.13 et Twig 3.27.0 Mettez à jour immédiatement. Lancez composer update dans votre projet, puis rebuilder. Symfony 7.4.13 (6 CVEs) CVE-2026-48489 — contournement du firewall de sécurité : le handler failure_forward honorait un paramètre _failure_path contrôlé par l'attaquant dans la sous-requête interne, permettant de contourner le firewall. CVE-2026-48736 — con…]]></description>
            <content:encoded><![CDATA[<h2>Mise à jour de sécurité : Symfony 7.4.13 et Twig 3.27.0<a id="mise-à-jour-de-sécurité--symfony-7413-et-twig-3270" href="#mise-à-jour-de-sécurité--symfony-7413-et-twig-3270" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p><strong>Mettez à jour immédiatement.</strong> Lancez <code>composer update</code> dans votre projet, puis rebuilder.</p>
<h3>Symfony 7.4.13 (6 CVEs)<a id="symfony-7413-6-cves" href="#symfony-7413-6-cves" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<ul>
<li><strong>CVE-2026-48489</strong> — contournement du firewall de sécurité : le handler <code>failure_forward</code> honorait un paramètre <code>_failure_path</code> contrôlé par l'attaquant dans la sous-requête interne, permettant de contourner le firewall.</li>
<li><strong>CVE-2026-48736</strong> — contournement SSRF dans <code>NoPrivateNetworkHttpClient</code> et <code>IpUtils::PRIVATE_SUBNETS</code> via des formes de transition IPv6 (IPv4-mapped, IPv4-compatible, 6to4, Teredo).</li>
<li><strong>CVE-2026-48761</strong> — <code>HtmlSanitizer</code> ne nettoyait pas les attributs URL sur <code>&lt;object&gt;</code>, <code>&lt;applet&gt;</code>, <code>&lt;iframe&gt;</code>, <code>&lt;img&gt;</code> et l'URL dans le contenu <code>&lt;meta http-equiv=&quot;refresh&quot;&gt;</code>.</li>
<li><strong>CVE-2026-48760</strong> — <code>HtmlSanitizer</code> acceptait des marques BiDi encodées en pourcentage et des espaces Unicode dans les URLs, permettant un contournement du sanitizer par usurpation visuelle.</li>
<li><strong>CVE-2026-48784</strong> — <code>UrlGenerator</code> encodait incorrectement les segments <code>../</code> et <code>./</code> chaînés, produisant des URLs pouvant sortir du chemin prévu.</li>
<li><strong>CVE-2026-48747</strong> — Mailer : l'algorithme de signature du webhook Mailomat n'était pas fixé à SHA-256, permettant des attaques par substitution d'algorithme.</li>
</ul>
<p>Liste complète : <a rel="nofollow noopener noreferrer" target="_blank" href="https://symfony.com/blog/symfony-7-4-13-released">symfony.com/blog/symfony-7-4-13-released</a>.</p>
<h3>Twig 3.27.0 (5 CVEs)<a id="twig-3270-5-cves" href="#twig-3270-5-cves" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Les cinq sont des contournements de sandbox. Si votre thème rend des templates contrôlés par l'utilisateur dans un sandbox, ils sont critiques :</p>
<ul>
<li><strong>CVE-2026-46636</strong> — contournement de la liste d'autorisation des filtres, tags et fonctions lorsque l'état du sandbox change entre les rendus dans des workers de longue durée (ex. FrankenPHP, RoadRunner).</li>
<li><strong>CVE-2026-48808</strong> — le filtre <code>column</code> contournait la liste des propriétés autorisées sous <code>SourcePolicyInterface</code>.</li>
<li><strong>CVE-2026-48806</strong> — contournement de la politique <code>__toString()</code> via des clés de mapping dynamiques : <code>{% set arr = {(obj): &quot;value&quot;} %}</code>.</li>
<li><strong>CVE-2026-48807</strong> — contournement de la politique <code>__toString()</code> via des objets <code>Traversable</code> dans les filtres <code>join</code>/<code>replace</code> et les opérateurs <code>in</code>/<code>not in</code>.</li>
<li><strong>CVE-2026-48805</strong> — régression d'état du sandbox dans les wrappers internes dépréciés (<code>twig_check_arrow_in_sandbox()</code>, <code>twig_array_some()</code>, <code>twig_array_every()</code>).</li>
</ul>
<h2>Aussi dans 1.1.4<a id="aussi-dans-114" href="#aussi-dans-114" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<ul>
<li><code>symfony/polyfill-*</code> mis à jour de v1.37.0 à v1.38.1.</li>
<li>Dépendances de développement : <code>phpstan/phpstan</code> 2.1.55 → 2.2.1, <code>phpunit/phpunit</code> 13.1.10 → 13.1.13, <code>rector/rector</code> 2.4.4 → 2.4.5.</li>
</ul>
<h2>Comment mettre à jour<a id="comment-mettre-à-jour" href="#comment-mettre-à-jour" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<pre><code class="language-bash">composer update
ddev build   # ou la commande de build équivalente dans votre environnement
</code></pre>
<p>Aucune modification de configuration ni étape de migration n'est nécessaire.</p>
<h2>Changelog complet<a id="changelog-complet" href="#changelog-complet" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p><a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/CHANGELOG.md#114---2026-05-28">CHANGELOG.md</a></p>
]]></content:encoded>
                                    <category><![CDATA[releases]]></category>
                                    <category><![CDATA[release]]></category>
                        <category><![CDATA[security]]></category>
                        <category><![CDATA[symfony]]></category>
                    </item>
                <item>
            <title><![CDATA[notACMS 1.1.3 — fichiers JSON Schema et mise à jour de sécurité Symfony 7.4.12]]></title>
            <link>https://notacms.holas.pl/fr/articles/release-1-1-3/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/fr/articles/release-1-1-3/</guid>
                        <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Fichiers JSON Schema pour la configuration et le frontmatter Six fichiers JSON Schema draft-07 se trouvent maintenant dans config/schema/ : Fichier de schéma Décrit site.schema.json _site.yaml — paramètres du site, locales, liens sociaux, formulaire de contact routes.schema.json _routes.yaml — surcharges des chemins URL par locale tags.schema.json _tags.yaml — table de traduction des tags post.fro…]]></description>
            <content:encoded><![CDATA[<h2>Fichiers JSON Schema pour la configuration et le frontmatter<a id="fichiers-json-schema-pour-la-configuration-et-le-frontmatter" href="#fichiers-json-schema-pour-la-configuration-et-le-frontmatter" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Six fichiers JSON Schema draft-07 se trouvent maintenant dans <code>config/schema/</code> :</p>
<table>
<thead>
<tr>
<th>Fichier de schéma</th>
<th>Décrit</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>site.schema.json</code></td>
<td><code>_site.yaml</code> — paramètres du site, locales, liens sociaux, formulaire de contact</td>
</tr>
<tr>
<td><code>routes.schema.json</code></td>
<td><code>_routes.yaml</code> — surcharges des chemins URL par locale</td>
</tr>
<tr>
<td><code>tags.schema.json</code></td>
<td><code>_tags.yaml</code> — table de traduction des tags</td>
</tr>
<tr>
<td><code>post.frontmatter.schema.json</code></td>
<td>Frontmatter des articles de blog</td>
</tr>
<tr>
<td><code>page.frontmatter.schema.json</code></td>
<td>Frontmatter des pages statiques</td>
</tr>
<tr>
<td><code>category.frontmatter.schema.json</code></td>
<td>Frontmatter des fichiers d'index de catégorie</td>
</tr>
</tbody>
</table>
<p>Tous les fichiers YAML de template (<code>_site.yaml</code>, <code>_routes.yaml</code>, <code>_tags.yaml</code>) dans <code>docs/bare/</code>, <code>docs/demo/</code> et <code>docs/customization/old-template/</code> portent désormais un commentaire <code># yaml-language-server: $schema=</code> pointant vers l'URL brute sur la branche main. VS Code (et tout éditeur avec YAML Language Server) les détecte automatiquement — validation et autocomplétion fonctionnent sans aucune configuration de projet supplémentaire.</p>
<p>Les schémas sont optimisés pour l'authoring assisté par IA : les descriptions incluent les valeurs par défaut, les contraintes et les explications de comportement, pour qu'un agent IA dans un autre projet puisse récupérer un schéma et savoir exactement ce que fait chaque champ.</p>
<p>Récupérer n'importe quel schéma directement depuis la branche main :</p>
<pre><code>https://raw.githubusercontent.com/holas1337/notACMS/main/config/schema/&lt;name&gt;.schema.json
</code></pre>
<h2>Mise à jour de sécurité : Symfony 7.4.12 et Twig 3.26.0<a id="mise-à-jour-de-sécurité--symfony-7412-et-twig-3260" href="#mise-à-jour-de-sécurité--symfony-7412-et-twig-3260" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p><strong>Mettez à jour immédiatement.</strong> Lancez <code>composer update</code> dans votre projet, puis rebuilder.</p>
<h3>Symfony 7.4.12 (21 CVEs)<a id="symfony-7412-21-cves" href="#symfony-7412-21-cves" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Les plus impactantes pour un déploiement CMS :</p>
<ul>
<li><strong>CVE-2026-45073</strong> — injection SQL dans <code>Cache</code> via un <code>$prefix</code> non validé dans <code>PdoAdapter::doClear()</code>.</li>
<li><strong>CVE-2026-45071</strong> — XXE / divulgation de fichiers locaux dans <code>DomCrawler::addXmlContent()</code> avec <code>validateOnParse</code> activé.</li>
<li><strong>CVE-2026-45075</strong> — les requêtes HEAD contournent le filtre <code>methods</code> des attributs <code>#[IsGranted]</code>, <code>#[IsCsrfTokenValid]</code> et <code>#[IsSignatureValid]</code>.</li>
<li><strong>CVE-2026-45072</strong> — XSS dans <code>CodeExtension::fileExcerpt()</code> de <code>TwigBridge</code>.</li>
<li><strong>CVE-2026-45068</strong> — injection d'en-tête dans <code>SendmailTransport</code> ; les adresses commençant par un tiret sont désormais rejetées.</li>
<li><strong>CVE-2026-45067</strong> — les adresses e-mail contenant des sauts de ligne étaient acceptées par <code>Mime\Address</code> — elles sont maintenant rejetées.</li>
<li><strong>CVE-2026-45305 / 45304 / 45133</strong> — backtracking catastrophique et récursion illimitée dans le parseur YAML.</li>
<li><strong>CVE-2026-45066 / 45064 / 45753</strong> — trois contournements de <code>HtmlSanitizer</code> : caractères BiDi override, différences de parseurs d'URL et attributs <code>action</code>/<code>formaction</code>/<code>poster</code>/<code>cite</code> non assainis.</li>
</ul>
<p>Liste complète : <a rel="nofollow noopener noreferrer" target="_blank" href="https://symfony.com/blog/symfony-7-4-12-released">symfony.com/blog/symfony-7-4-12-released</a>.</p>
<h3>Twig 3.26.0 (4 CVEs)<a id="twig-3260-4-cves" href="#twig-3260-4-cves" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Les quatre sont des contournements de sandbox. Si votre thème utilise le sandbox de Twig pour des templates fournis par les utilisateurs, ils sont critiques :</p>
<ul>
<li><strong>CVE-2026-46635</strong> — contournement de la liste des propriétés autorisées via le filtre <code>column</code> (<code>array_column</code> sur des objets).</li>
<li><strong>CVE-2026-46638</strong> — <code>{% sandbox %}{% include %}</code> ignore <code>checkSecurity()</code> sur les templates mis en cache ; correction incomplète de CVE-2024-45411.</li>
<li><strong>CVE-2026-24425</strong> — contournement du sandbox via une source policy.</li>
<li><strong>CVE-2026-47732</strong> — multiples contournements de la politique <code>__toString()</code> via des points de coercition de chaîne non protégés.</li>
</ul>
<h2>Aussi dans 1.1.3<a id="aussi-dans-113" href="#aussi-dans-113" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<ul>
<li><strong>Fragments Pagefind obsolètes corrigés</strong> — <code>scripts/rebuild-content.sh</code> supprime maintenant <code>public/pagefind/</code> avant de réindexer. Auparavant, supprimer ou renommer du contenu laissait des fichiers de fragments orphelins que Pagefind servait avec les résultats à jour.</li>
<li><strong>Améliorations DESIGN.md</strong> — les valeurs hex codées en dur remplacées par des références aux tokens dans les fichiers <code>DESIGN.md</code> bare et demo ; alias de couleur <code>primary</code> ajouté ; token de composant <code>card-hover</code> ajouté au thème demo.</li>
</ul>
<h2>Comment mettre à jour<a id="comment-mettre-à-jour" href="#comment-mettre-à-jour" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<pre><code class="language-bash">composer update
ddev build   # ou la commande de build équivalente dans votre environnement
</code></pre>
<p>Aucune modification de configuration ni étape de migration n'est nécessaire.</p>
<h2>Changelog complet<a id="changelog-complet" href="#changelog-complet" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p><a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/CHANGELOG.md#113---2026-05-20">CHANGELOG.md</a></p>
]]></content:encoded>
                                    <category><![CDATA[releases]]></category>
                                    <category><![CDATA[release]]></category>
                        <category><![CDATA[security]]></category>
                        <category><![CDATA[symfony]]></category>
                    </item>
                <item>
            <title><![CDATA[notACMS 1.1.2 — URL canoniques et un constructeur pour les données structurées]]></title>
            <link>https://notacms.holas.pl/fr/articles/release-1-1-2/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/fr/articles/release-1-1-2/</guid>
                        <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Une URL canonique par page Si la locale par défaut de votre site est en, /en/blog/ et /blog/ étaient toutes deux accessibles et rendaient le même contenu. Cela fait deux URL indexables pour une seule page — et les moteurs de recherche choisissent celle qu'ils veulent, qui est rarement celle que vous souhaitez. 1.1.2 ajoute le DefaultLocaleRedirectListener. Chaque requête vers /&lt;locale-par-défau…]]></description>
            <content:encoded><![CDATA[<h2>Une URL canonique par page<a id="une-url-canonique-par-page" href="#une-url-canonique-par-page" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Si la locale par défaut de votre site est <code>en</code>, <code>/en/blog/</code> et <code>/blog/</code> étaient toutes deux accessibles et rendaient le même contenu. Cela fait deux URL indexables pour une seule page — et les moteurs de recherche choisissent celle qu'ils veulent, qui est rarement celle que vous souhaitez.</p>
<p>1.1.2 ajoute le <code>DefaultLocaleRedirectListener</code>. Chaque requête vers <code>/&lt;locale-par-défaut&gt;/...</code> retourne une <code>301</code> vers l'URL sans préfixe :</p>
<pre><code>GET /en/blog/         →  301 Location: /blog/
GET /en/about/?ref=x  →  301 Location: /about/?ref=x
GET /pl/blog/         →  200 (locale non par défaut, intacte)
</code></pre>
<p>L'écouteur s'exécute avant le routeur Symfony, de sorte que la redirection a lieu avant même que le framework ne tente de faire correspondre une route — pas de travail gaspillé, pas de bruit 404 dans les logs. Les locales non par défaut (<code>pl</code>, <code>de</code>, <code>fr</code> sur ce site) restent intactes.</p>
<p>Cela ferme également une petite fuite SEO : les liens entrants qui incluent accidentellement le préfixe de locale se consolident désormais vers l'URL canonique au lieu de diviser l'autorité du domaine.</p>
<h2>Le JSON-LD Schema.org comme constructeur fluide<a id="le-json-ld-schemaorg-comme-constructeur-fluide" href="#le-json-ld-schemaorg-comme-constructeur-fluide" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Jusqu'en 1.1.1, chaque template ayant besoin de données structurées intégrait un tableau PHP, le passait à <code>|json_encode|raw</code> et enveloppait le résultat dans une balise <code>&lt;script&gt;</code>. Six templates, six blocs presque identiques — et chacun une nouvelle occasion d'oublier <code>JSON_UNESCAPED_SLASHES</code> ou de livrer <code>&lt;script&gt;false&lt;/script&gt;</code> si un champ du frontmatter contenait un octet invalide.</p>
<p>1.1.2 remplace tout cela par un service et deux fonctions Twig :</p>
<pre><code class="language-twig">{% block structured_data %}{{ json_ld(structured_data().blogPosting(
    content.title(),
    site_base_url ~ content.url(),
    content.date(),
    content.htmlContent,
    site_author.name,
    content.image() ? site_base_url ~ content.image() : null
)) }}{% endblock %}
</code></pre>
<p><code>structured_data()</code> retourne le constructeur ; chaînez une méthode typée (<code>webSite</code>, <code>webPage</code>, <code>blogPosting</code>, <code>collectionPage</code>, <code>contactPage</code>, <code>person</code>, <code>breadcrumbList</code>, <code>organization</code>, <code>imageObject</code>) et le résultat est un simple tableau. <code>json_ld(array)</code> l'encode et l'enveloppe dans une balise <code>&lt;script type=&quot;application/ld+json&quot;&gt;</code>. Les champs vides et null sont supprimés récursivement, donc les entrées optionnelles (un email d'auteur non défini, une image manquante) n'apparaissent simplement pas dans la sortie.</p>
<p>L'encodage utilise désormais <code>JSON_THROW_ON_ERROR</code> — un UTF-8 invalide dans le frontmatter remonte comme une véritable exception au rendu, au lieu de livrer silencieusement une balise <code>&lt;script&gt;</code> cassée.</p>
<p>Les templates de base du cœur pour les pages <code>contact</code>, <code>default</code> et <code>projects</code> émettent désormais aussi le balisage Schema.org. Auparavant, les déploiements bare avaient un SEO plus faible que tous les exemples de personnalisation que nous fournissons ; cet écart est comblé.</p>
<p>Consultez le <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/docs/CUSTOMIZATION.md#structured-data-json-ld">guide de personnalisation → Données structurées</a> pour l'API complète du constructeur et pour savoir comment surcharger le bloc <code>structured_data</code> dans votre propre thème.</p>
<h2>Correctifs de sécurité<a id="correctifs-de-sécurité" href="#correctifs-de-sécurité" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Deux bugs ont été identifiés lors de la revue de 1.1.2 et corrigés avant la sortie :</p>
<ul>
<li><strong>Redirection ouverte via la normalisation des chemins.</strong> La méthode <code>Request::getPathInfo()</code> de Symfony ne réduit pas les barres obliques répétées ; ainsi, une requête vers <code>/&lt;locale-par-défaut&gt;//evil.com</code> aurait sinon produit <code>Location: //evil.com</code> — une redirection relative au protocole que les navigateurs suivent vers une autre origine. Le nouvel écouteur de redirection refuse désormais d'émettre des redirections vers tout ce qui n'est pas un chemin local enraciné par une seule barre oblique.</li>
<li><strong>XSS dans les résultats de recherche.</strong> <code>assets/search.js</code> injectait le champ <code>excerpt</code> de Pagefind dans innerHTML sans échappement. La build de démonstration était déjà correcte ; le cœur avait dérivé. Les deux sont désormais synchronisés. Les balises de surlignage <code>&lt;mark&gt;</code> de Pagefind ne sont plus rendues dans les résultats de recherche du cœur — un compromis volontaire au profit d'une valeur par défaut plus sûre.</li>
</ul>
<h2>Également nouveau en 1.1.2<a id="également-nouveau-en-112" href="#également-nouveau-en-112" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<ul>
<li><strong>Refactorisation interne de Twig.</strong> La logique des barres latérales, des fils d'Ariane, des badges d'articles, des images og et des filtres de blog a été déplacée des templates vers des extensions Twig dédiées. Les templates restent concentrés sur la mise en page ; les tests peuvent cibler chaque fonction d'aide directement. Nouvelle surface de personnalisation : voir les <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/docs/CUSTOMIZATION.md#layout-helpers">Aides de mise en page</a> dans le guide de personnalisation.</li>
<li><strong>Recherches par clé de répertoire en <code>O(1)</code></strong> dans <code>ContentTree</code> — une petite amélioration de performance sur les sites comportant des centaines de pages.</li>
<li><strong><code>docs/customization/old-template/</code> est désormais déprécié</strong> et sera supprimé en 1.2.0. C'était une aide de transition ponctuelle pour 1.0→1.1 ; les nouveaux travaux de personnalisation devraient partir de <code>custom-footer/</code> ou <code>self-hosted-fonts/</code>.</li>
<li><strong>Mises à jour des dépendances :</strong> Symfony 7.4.8 → 7.4.9 dans tout le bundle, PHPStan 2.1.51 → 2.1.54, PHPUnit 13.1.7 → 13.1.8.</li>
</ul>
<h2>Liste complète des modifications<a id="liste-complète-des-modifications" href="#liste-complète-des-modifications" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Toutes les modifications avec leur catégorie : <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/CHANGELOG.md#112---2026-05-04">CHANGELOG.md</a>.</p>
]]></content:encoded>
                                    <category><![CDATA[releases]]></category>
                                    <category><![CDATA[release]]></category>
                        <category><![CDATA[announcement]]></category>
                    </item>
                <item>
            <title><![CDATA[notACMS 1.1.1 — Labels de navigation depuis le frontmatter et deploy sans écrasement]]></title>
            <link>https://notacms.holas.pl/fr/articles/release-1-1-1/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/fr/articles/release-1-1-1/</guid>
                        <pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Labels de navigation depuis le frontmatter Les labels de navigation des pages de contenu proviennent désormais du frontmatter — pas des clés de traduction. Définissez menu.label dans le frontmatter d'une page, et ce texte devient le label du menu ; omettez-le, et le title de la page sera utilisé comme fallback. --- title: &quot;Guide d'architecture&quot; slug: &quot;architecture-guide&quot; menu: …]]></description>
            <content:encoded><![CDATA[<h2>Labels de navigation depuis le frontmatter<a id="labels-de-navigation-depuis-le-frontmatter" href="#labels-de-navigation-depuis-le-frontmatter" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Les labels de navigation des pages de contenu proviennent désormais du frontmatter — pas des clés de traduction. Définissez <code>menu.label</code> dans le frontmatter d'une page, et ce texte devient le label du menu ; omettez-le, et le <code>title</code> de la page sera utilisé comme fallback.</p>
<pre><code class="language-yaml">---
title: &quot;Guide d'architecture&quot;
slug: &quot;architecture-guide&quot;
menu:
  label: &quot;Architecture&quot;
  weight: 30
---
</code></pre>
<p>La nouvelle fonction Twig <code>content_item()</code> résout n'importe quelle page de contenu par sa clé de répertoire :</p>
<pre><code class="language-twig">{{ content_item('architecture-guide', 'fr').menuLabel() }}
{# → &quot;Architecture&quot; (ou le titre de la page si menu.label est absent) #}
</code></pre>
<p><strong>Pourquoi c'est important :</strong> Auparavant, chaque fichier <code>messages.*.yaml</code> devait dupliquer les traductions pour <code>nav.home</code>, <code>nav.blog</code>, <code>nav.about</code>, <code>nav.contact</code>, <code>nav.privacy_policy</code> (core) et <code>site.releases</code>, <code>site.about</code>, <code>site.manual</code>, <code>site.architecture</code>, <code>site.customization</code>, <code>site.locales</code>, <code>site.design_reference</code>, <code>site.contact</code> (démo). Ajouter une page impliquait de mettre à jour N fichiers de traduction. Maintenant, il suffit d'un champ dans le frontmatter. Les templates personnalisés qui référencent ces clés supprimées doivent migrer vers <code>content_item('key', locale).menuLabel()</code>.</p>
<h2>Deploy préserve le contenu existant<a id="deploy-préserve-le-contenu-existant" href="#deploy-préserve-le-contenu-existant" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<h3>Le bug<a id="le-bug" href="#le-bug" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Exécuter <code>./notACMS deploy --prod</code> <strong>sauvegardait et remplaçait</strong> tout le répertoire <code>local/</code> par <code>docs/demo/</code> à chaque déploiement — même lorsqu'il contenait déjà votre contenu, templates et personnalisations. La logique de seeding ne distinguait pas « l'utilisateur a explicitement demandé un re-seed du thème » de « l'utilisateur veut juste redéployer avec le contenu existant. »</p>
<h3>Le correctif<a id="le-correctif" href="#le-correctif" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Deploy fonctionne désormais exactement comme <code>ddev build</code> :</p>
<ul>
<li><strong>Pas de flag <code>--bare</code> / <code>--demo</code></strong> → seede <code>local/</code> uniquement s'il est absent ou vide ; ignore s'il contient du contenu.</li>
<li><strong><code>--bare</code> ou <code>--demo</code> passé explicitement</strong> → sauvegarde le <code>local/</code> existant et seede le thème choisi.</li>
</ul>
<pre><code class="language-bash">./notACMS deploy --prod          # préserve le local/ existant
./notACMS deploy --prod --demo   # force le re-seed depuis docs/demo/
./notACMS deploy --prod --bare   # force le re-seed depuis docs/bare/
</code></pre>
<p><code>--prod</code> ne contrôle désormais que les flags Composer (<code>--no-dev</code>) et l'environnement d'exécution (<code>APP_ENV=prod</code>). Il ne touche jamais à <code>local/</code>.</p>
<h2>Également dans 1.1.1<a id="également-dans-111" href="#également-dans-111" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<ul>
<li><strong>Mise à jour des dépendances :</strong> paquets <code>symfony/polyfill-*</code> mis à jour de v1.36.0 à v1.37.0.</li>
<li><strong>Contenu démo polonais, allemand et français</strong> révisé et amélioré sur toutes les pages et articles du blog.</li>
<li><strong>Guides de style de traduction</strong> ajoutés au système de compétences de l'agent IA pour le polonais, l'allemand et le français afin d'assurer une qualité cohérente pour les traductions futures.</li>
</ul>
<h2>Liste complète des modifications<a id="liste-complète-des-modifications" href="#liste-complète-des-modifications" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Tous les changements avec leur catégorie : <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/CHANGELOG.md#111---2026-04-26">CHANGELOG.md</a>.</p>
]]></content:encoded>
                                    <category><![CDATA[releases]]></category>
                                    <category><![CDATA[release]]></category>
                        <category><![CDATA[announcement]]></category>
                    </item>
                <item>
            <title><![CDATA[notACMS 1.1.0 — Cœur minimal, thème démo, à vous de choisir]]></title>
            <link>https://notacms.holas.pl/fr/articles/release-1-1-0/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/fr/articles/release-1-1-0/</guid>
                        <pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Deux thèmes, un choix au premier build Le changement phare de 1.1.0 : notACMS livre désormais deux thèmes d'origine, et vous en choisissez un dès votre tout premier build : ./notACMS deploy --demo # par défaut — le design amber-phosphor que vous voyez ici ./notACMS deploy --bare # un wireframe minimal : polices système, mode clair, ~200 lignes de CSS ddev build accepte les mêmes flags. Quel que so…]]></description>
            <content:encoded><![CDATA[<h2>Deux thèmes, un choix au premier build<a id="deux-thèmes-un-choix-au-premier-build" href="#deux-thèmes-un-choix-au-premier-build" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Le changement phare de 1.1.0 : notACMS livre désormais deux thèmes d'origine, et vous en choisissez un dès votre tout premier build :</p>
<pre><code class="language-bash">./notACMS deploy --demo    # par défaut — le design amber-phosphor que vous voyez ici
./notACMS deploy --bare    # un wireframe minimal : polices système, mode clair, ~200 lignes de CSS
</code></pre>
<p><code>ddev build</code> accepte les mêmes flags. Quel que soit votre choix, toutes les fonctionnalités fonctionnent : contenu multilingue, blog, RSS, sitemap, recherche, formulaire de contact, images avec variantes responsives, temps de lecture, indicateur de progression. La différence ne se joue que sur l'apparence — et, surtout, sur ce que vous héritez avant de commencer à personnaliser.</p>
<h3>Cœur minimal<a id="cœur-minimal" href="#cœur-minimal" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Le cœur situé dans <code>templates/</code>, <code>assets/</code>, <code>translations/</code> est désormais un wireframe. Il est volontairement minimal pour que surcharger un seul bloc ne traîne pas derrière lui un langage visuel contre lequel il faudrait lutter. Si vous construisez un design sur mesure, partez du bare et vous maîtrisez l'intégralité du rendu dès le premier jour.</p>
<h3>Thème démo<a id="thème-démo" href="#thème-démo" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Le démo se trouve sous <code>docs/demo/</code> et est copié dans <code>local/</code> quand vous choisissez <code>--demo</code>. C'est le design amber-phosphor complet que vous lisez en ce moment — mode sombre, overlay de recherche, sidebar docs, bouton de thème, tout y est. Partez de là si vous voulez un design abouti dès aujourd'hui et prévoyez d'ajuster, pas de reconstruire.</p>
<h2>Le système de surcharges <code>local/</code><a id="le-système-de-surcharges-local" href="#le-système-de-surcharges-local" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Les deux thèmes utilisent le même mécanisme : chaque fichier dans <code>local/</code> prend le pas sur le chemin correspondant du cœur.</p>
<table>
<thead>
<tr>
<th>Couche</th>
<th>Emplacement de surcharge</th>
<th>Resolver</th>
</tr>
</thead>
<tbody>
<tr>
<td>Templates Twig</td>
<td><code>local/templates/*.html.twig</code></td>
<td>Kernel Symfony</td>
</tr>
<tr>
<td>Point d'entrée SCSS</td>
<td><code>local/assets/styles/app_local.scss</code></td>
<td>Bundle Sass</td>
</tr>
<tr>
<td>Point d'entrée JS</td>
<td><code>local/assets/app.js</code></td>
<td>Importmap AssetMapper</td>
</tr>
<tr>
<td>Traductions</td>
<td><code>local/translations/messages.*.yaml</code></td>
<td>Translator Symfony</td>
</tr>
<tr>
<td>Contenu</td>
<td><code>local/content/**</code></td>
<td>Paramètre <code>notacms_content</code></td>
</tr>
<tr>
<td>Fragments Nginx</td>
<td><code>local/docker/nginx/*.conf</code></td>
<td>Entrypoint du conteneur</td>
</tr>
</tbody>
</table>
<p>Le cœur n'est jamais modifié. <code>git pull</code> reste propre. Les personnalisations se trouvent dans le dépôt de votre site, pas dans un fork de celui-ci.</p>
<h2>Mise à niveau depuis 1.0.0<a id="mise-à-niveau-depuis-100" href="#mise-à-niveau-depuis-100" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Si vous tourniez en 1.0.0 et voulez que votre site ait exactement la même apparence, le paquet de compatibilité tient en une ligne :</p>
<pre><code class="language-bash">cp -r docs/customization/old-template/. local/
ddev build
</code></pre>
<p>Cela dépose le thème pré-1.1.0 complet — templates, SCSS, polices, images, traductions — dans <code>local/</code>. Votre site s'affiche exactement comme avant. Vous pouvez ensuite retirer des fichiers de <code>local/</code> au fur et à mesure que vous adoptez de nouveaux éléments de design.</p>
<p>Les vraies ruptures auxquelles vous ferez face si vous aviez personnalisé l'ancien cœur :</p>
<ul>
<li><strong>Point d'entrée SCSS renommé</strong> : <code>local/assets/styles/local.scss</code> → <code>local/assets/styles/app_local.scss</code>. Renommez le fichier et mettez à jour l'import dans <code>local/assets/app.js</code>.</li>
<li><strong>Variables SCSS de couleur du cœur remplacées par des CSS custom properties</strong> : <code>$color-body</code> → <code>var(--text)</code>, <code>$color-link</code> → <code>var(--accent)</code>, etc. Correspondance complète dans <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/UPGRADE-1.1.md">UPGRADE-1.1.md</a>.</li>
<li><strong>Clés de traduction retirées</strong> du cœur (toujours présentes dans le thème démo) : <code>header.tagline</code>, <code>nav.projects</code>, <code>nav.search</code>, <code>blog.published_on</code>, <code>blog.comments_disabled</code>. Si vos templates appellent <code>'...'|trans</code> dessus, mettez-les à jour.</li>
<li><strong>Répertoire <code>docs/examples/</code> retiré</strong> : les éléments utiles ont été déplacés vers <code>docs/customization/old-template/</code> ; les exemples de surcharge ont été supplantés par le modèle bare/démo.</li>
</ul>
<p>Voir <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/UPGRADE-1.1.md">UPGRADE-1.1.md</a> pour la liste complète avec extraits avant/après.</p>
<h2>Également nouveau en 1.1.0<a id="également-nouveau-en-110" href="#également-nouveau-en-110" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<ul>
<li><strong>Temps de lecture</strong> et <strong>indicateur de progression</strong> sur les posts, docs et releases.</li>
<li><strong>Sélecteur de langue</strong> comme extension Twig (<code>lang_switch_urls</code>) avec des chaînes de fallback correctes pour les archives, listes paginées et pages d'accueil.</li>
<li><strong>Les extraits de posts retirent les ancres de titres</strong> — plus de <code>#</code> parasites dans les cartes de liste.</li>
<li><strong>Ossature de batterie de tests</strong> sous <code>tests/Unit/</code>, <code>tests/Integration/</code>, <code>tests/Fixtures/</code> avec une couverture initiale et une commande <code>test</code> exécutable depuis l'hôte.</li>
<li><strong>Scripts d'agent IA</strong> sous <code>.claude/skills/</code> pour travailler sur le dépôt : ajout de locales, vérifications d'alignement des docs, balayages de site, traductions et génération de guides de mise à niveau.</li>
</ul>
<h2>Liste complète des modifications<a id="liste-complète-des-modifications" href="#liste-complète-des-modifications" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Chaque changement avec sa catégorie : <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/CHANGELOG.md#110---2026-04-24">CHANGELOG.md</a>.</p>
]]></content:encoded>
                                    <category><![CDATA[releases]]></category>
                                    <category><![CDATA[release]]></category>
                        <category><![CDATA[announcement]]></category>
                    </item>
                <item>
            <title><![CDATA[notACMS v1.0.0 — Première version stable]]></title>
            <link>https://notacms.holas.pl/fr/articles/release-1-0-0/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/fr/articles/release-1-0-0/</guid>
                        <pubDate>Thu, 09 Apr 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Ce que contient la v1.0.0 Après plusieurs mois d'utilisation interne sur des projets personnels, je publie la première version stable. L'ensemble de fonctionnalités est suffisamment solide pour construire de vrais sites. Pipeline de contenu Le pipeline de contenu est le cœur de notACMS. Il lit le répertoire local/content/, analyse le frontmatter et génère un site statique complet en une seule comm…]]></description>
            <content:encoded><![CDATA[<h2>Ce que contient la v1.0.0<a id="ce-que-contient-la-v100" href="#ce-que-contient-la-v100" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Après plusieurs mois d'utilisation interne sur des projets personnels, je publie la première version stable. L'ensemble de fonctionnalités est suffisamment solide pour construire de vrais sites.</p>
<h3>Pipeline de contenu<a id="pipeline-de-contenu" href="#pipeline-de-contenu" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Le pipeline de contenu est le cœur de notACMS. Il lit le répertoire <code>local/content/</code>, analyse le frontmatter et génère un site statique complet en une seule commande.</p>
<ul>
<li>Contenu Markdown avec frontmatter YAML</li>
<li>Rendu CommonMark avec permaliens sur les titres</li>
<li>Génération d'extraits à partir du contenu textuel</li>
<li>Calcul du temps de lecture</li>
</ul>
<h3>Routage multilingue<a id="routage-multilingue" href="#routage-multilingue" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Les locales sont définies dans <code>_site.yaml</code>. Chaque locale dispose de son propre espace d'URL, avec des surcharges de chemin optionnelles dans <code>_routes.yaml</code>. Les balises hreflang sont générées automatiquement.</p>
<pre><code class="language-yaml">site:
  locales:
    en:
      label: &quot;English&quot;
      date_format: &quot;M d, Y&quot;
    pl:
      label: &quot;Polski&quot;
      date_format: &quot;d.m.Y&quot;
</code></pre>
<h3>Recherche Pagefind<a id="recherche-pagefind" href="#recherche-pagefind" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>La recherche en texte intégral est intégrée à la sortie statique via <a rel="nofollow noopener noreferrer" target="_blank" href="https://pagefind.app/">Pagefind</a>. La commande de compilation génère l'index de recherche automatiquement. Pas d'API externe, pas de recherche côté serveur — juste un index statique qui fonctionne hors ligne.</p>
<h3>Traitement des images<a id="traitement-des-images" href="#traitement-des-images" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Les images colocées avec le contenu sont traitées lors de la compilation. notACMS génère des variantes WebP à plusieurs largeurs, met à jour automatiquement les attributs <code>src</code> avec un <code>srcset</code> responsive et gère la correspondance de chemins entre les répertoires de contenu et de sortie.</p>
<h3>Développement local avec DDEV<a id="développement-local-avec-ddev" href="#développement-local-avec-ddev" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>L'environnement de développement est entièrement conteneurisé avec DDEV. <code>ddev start</code> vous donne PHP 8.5, Nginx et tous les outils de compilation. <code>ddev build</code> produit la sortie statique. <code>ddev code-check</code> lance PHPStan et PHP CS Fixer.</p>
<h2>Changements cassants<a id="changements-cassants" href="#changements-cassants" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Il s'agit de la première version stable. Si vous utilisiez une version antérieure à la 1.0, vérifiez le schéma de <code>_site.yaml</code> — la clé <code>social</code> est passée d'une liste à un tableau associatif.</p>
<h2>Mise à niveau<a id="mise-à-niveau" href="#mise-à-niveau" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<pre><code class="language-bash">git pull
ddev composer install
ddev build
</code></pre>
<h2>Et ensuite<a id="et-ensuite" href="#et-ensuite" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>La v1.1.0 sera centrée sur le système de design et la documentation. Ce site — construit avec notACMS — deviendra la documentation officielle et la référence de design.</p>
]]></content:encoded>
                                    <category><![CDATA[releases]]></category>
                                    <category><![CDATA[release]]></category>
                        <category><![CDATA[announcement]]></category>
                    </item>
                <item>
            <title><![CDATA[Pourquoi j'ai créé notACMS]]></title>
            <link>https://notacms.holas.pl/fr/articles/the-idea/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/fr/articles/the-idea/</guid>
                        <pubDate>Sun, 01 Feb 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Contexte plus complet sur mon blog personnel : I left WordPress. Le problème que je rencontrais sans cesse Chaque projet PHP sur lequel je travaillais finissait par avoir besoin d'un site web. De la documentation, une page d'accueil, un blog — les classiques. Et chaque fois que je cherchais un outil, j'aboutissais au même constat : un outil qui voulait que je pense comme lui, pas comme un développ…]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Contexte plus complet sur mon blog personnel : <a rel="nofollow noopener noreferrer" target="_blank" href="https://holas.pl/blog/why-i-left-wordpress/">I left WordPress</a>.</p>
</blockquote>
<h2>Le problème que je rencontrais sans cesse<a id="le-problème-que-je-rencontrais-sans-cesse" href="#le-problème-que-je-rencontrais-sans-cesse" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Chaque projet PHP sur lequel je travaillais finissait par avoir besoin d'un site web. De la documentation, une page d'accueil, un blog — les classiques. Et chaque fois que je cherchais un outil, j'aboutissais au même constat : un outil qui voulait que je pense comme lui, pas comme un développeur PHP.</p>
<p>Hugo est rapide mais son langage de templates est déroutant. Jekyll nécessite Ruby. WordPress n'est pas adapté au contenu statique. Next.js est puissant mais il transforme un problème de publication Markdown en un problème de bundler JavaScript.</p>
<p>Je me répétais : je connais déjà Symfony. Je connais déjà Twig. J'ai déjà Composer. Pourquoi devrais-je apprendre un nouvel écosystème juste pour publier du texte ?</p>
<h2>La décision zéro base de données<a id="la-décision-zéro-base-de-données" href="#la-décision-zéro-base-de-données" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>La première chose que j'ai décidée : pas de base de données. Pas « base de données optionnelle » — aucune base de données du tout. Le contenu vit dans des fichiers. Le processus de compilation lit des fichiers. Le résultat est des fichiers.</p>
<p>Cela impose une certaine discipline. La structure du contenu doit être explicite et prévisible. Il n'y a pas de requête de base de données à laquelle se raccrocher quand vous voulez trouver des articles connexes ou générer un sitemap. Tout doit pouvoir être dérivé de l'arborescence de fichiers.</p>
<p>Cette contrainte s'est avérée être la bonne. Elle rend le système facile à comprendre, facile à sauvegarder et trivial pour les outils d'IA à manipuler.</p>
<h2>Conçu pour l'IA dès l'origine<a id="conçu-pour-lia-dès-lorigine" href="#conçu-pour-lia-dès-lorigine" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>J'ai commencé à y penser sérieusement quand je me suis mis à utiliser les LLM dans mon travail quotidien. Demander à une IA de vous aider à générer du contenu, traduire des pages ou valider des schémas YAML est simple quand le format est du texte brut.</p>
<p>Avec un CMS basé sur une base de données, il faudrait expliquer le schéma, écrire du SQL, gérer les migrations. Avec notACMS, vous remettez à l'IA un répertoire de fichiers Markdown et elle peut lire, générer et modifier le contenu directement — parce que le format est simplement du texte.</p>
<p>Ce n'est pas une fonctionnalité ajoutée après coup. C'est la raison pour laquelle le système est conçu tel qu'il est.</p>
<h2>Les fondations Symfony<a id="les-fondations-symfony" href="#les-fondations-symfony" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>J'ai construit notACMS sur Symfony 7 pour la même raison que j'utilise Symfony pour tout le reste : c'est explicite, typé et bien documenté. Le conteneur d'injection de dépendances, les commandes console, Twig — tout cela est du Symfony standard. Il n'y a rien de novateur dans l'infrastructure.</p>
<p>La partie intéressante, c'est le modèle de contenu. Le système de routage lit <code>_routes.yaml</code> et génère des routes Symfony à partir de votre arborescence de contenu. La commande de compilation rend chaque route et écrit du HTML statique. La couche de services est fine et remplaçable.</p>
<h2>Et ensuite<a id="et-ensuite" href="#et-ensuite" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Cette première version est une base fonctionnelle. Ce que je veux construire :</p>
<ul>
<li>Intégration de la recherche Pagefind (prévue dans la v1.0.0)</li>
<li>Un meilleur pipeline d'images avec génération WebP</li>
<li>Un support multilingue plus complet</li>
<li>Une véritable page de référence de design (c'est ce que ce site est en train de devenir)</li>
</ul>
<p>Le dépôt est public. Si vous souhaitez suivre le projet ou contribuer, le lien se trouve dans le pied de page.</p>
]]></content:encoded>
                                    <category><![CDATA[releases]]></category>
                                    <category><![CDATA[announcement]]></category>
                        <category><![CDATA[open-source]]></category>
                    </item>
            </channel>
</rss>
