<?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/pl/</link>
        <description><![CDATA[AI-friendly static site generator. Zero database. Pure Markdown.]]></description>
        <language>pl</language>
        <atom:link href="https://notacms.holas.pl/pl/feed/" rel="self" type="application/rss+xml"/>
                        <lastBuildDate>Sat, 13 Jun 2026 00:00:00 +0000</lastBuildDate>
                        <item>
            <title><![CDATA[notACMS 1.2.1 — charset, mądrzejsze ostrzeżenia builda, nowe strony dokumentacji]]></title>
            <link>https://notacms.holas.pl/pl/wpisy/release-1-2-1/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/pl/wpisy/release-1-2-1/</guid>
                        <pubDate>Sat, 13 Jun 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Co nowego w 1.2.1 Mała aktualizacja następująca po 1.2.0 — dwie poprawki i rozszerzona dokumentacja. nginx charset Odpowiedzi tekstowe — /llms.txt, /robots.txt — były serwowane bez deklaracji zestawu znaków. Przeglądarki renderowały znaki spoza ASCII jako krzaki. Dyrektywę charset utf-8; ustawiono teraz na poziomie bloku serwera w docker/nginx.conf.template, więc każdy typ odpowiedzi automatycznie…]]></description>
            <content:encoded><![CDATA[<h2>Co nowego w 1.2.1<a id="co-nowego-w-121" href="#co-nowego-w-121" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Mała aktualizacja następująca po 1.2.0 — dwie poprawki i rozszerzona dokumentacja.</p>
<h2>nginx charset<a id="nginx-charset" href="#nginx-charset" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Odpowiedzi tekstowe — <code>/llms.txt</code>, <code>/robots.txt</code> — były serwowane bez deklaracji zestawu znaków. Przeglądarki renderowały znaki spoza ASCII jako krzaki. Dyrektywę <code>charset utf-8;</code> ustawiono teraz na poziomie bloku serwera w <code>docker/nginx.conf.template</code>, więc każdy typ odpowiedzi automatycznie otrzymuje poprawną deklarację.</p>
<h2>Ostrzeżenie o niejednoznacznym kluczu katalogu<a id="ostrzeżenie-o-niejednoznacznym-kluczu-katalogu" href="#ostrzeżenie-o-niejednoznacznym-kluczu-katalogu" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Ostrzeżenie &quot;Ambiguous directory key&quot; wywoływało się wcześniej w czasie budowania drzewa — w chwili rejestracji drugiego elementu treści z tym samym basename — niezależnie od tego, czy ten skrócony klucz był kiedykolwiek rzeczywiście używany w szablonie.</p>
<p>Teraz ostrzeżenie jest odroczone do momentu faktycznego wyszukiwania: pojawia się raz i tylko wtedy, gdy <code>content_item()</code> lub <code>content_url()</code> jest wywołane z niejednoznacznym skróconym kluczem. Strony z kolizjami nazw w różnych sekcjach treści nie zobaczą już zbędnych ostrzeżeń dla kluczy, których nigdy nie używają.</p>
<h2>Dwie nowe strony dokumentacji<a id="dwie-nowe-strony-dokumentacji" href="#dwie-nowe-strony-dokumentacji" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Motyw demo ma teraz strony <strong>Theme Building</strong> i <strong>Security</strong>, dostępne we wszystkich czterech wersjach językowych.</p>
<p><strong><a href="/pl/budowanie-motywu/">Budowanie Motywu</a></strong> omawia pełny model warstw szablonów — jak <code>local/templates/</code>, zarejestrowane ścieżki motywu i bare core rozwiązywane są według priorytetu — oraz zawiera referencję kontraktów kontekstu szablonów, globalnych Twig, funkcji i filtrów, API ContentItem, wymaganych kluczy tłumaczeń i sześciu zasad przenośności, których powinien przestrzegać każdy motyw.</p>
<p><strong><a href="/pl/bezpieczenstwo/">Bezpieczeństwo</a></strong> wyjaśnia model zagrożeń, który kształtuje decyzje bezpieczeństwa notACMS: brak bazy danych, brak dynamicznego renderowania, pipeline budowania, który nigdy nie wykonuje treści. Strona opisuje wbudowane zabezpieczenia (walidacja hostname Turnstile, ochrona przed path traversal, ścisłe typy), celowe podejście do CSRF w formularzu kontaktowym oraz najlepsze praktyki wdrożeniowe.</p>
<p>Obie strony pojawiają się w menu nawigacyjnym Documentation i pasku bocznym.</p>
<h2>Pełny changelog<a id="pełny-changelog" href="#pełny-changelog" 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 — wydanie po audycie]]></title>
            <link>https://notacms.holas.pl/pl/wpisy/release-1-2-0/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/pl/wpisy/release-1-2-0/</guid>
                        <pubDate>Fri, 12 Jun 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Wydanie po audycie 1.2.0 to wynik kompletnego audytu notACMS — architektury, bezpieczeństwa, jakości kodu i obu drzew szablonów. Naprawiono około 170 problemów. Najważniejsze zmiany: Odporność Plik markdown z błędnym frontmatterem nie psuje już całej wersji językowej ani nie przerywa builda — zostaje pominięty i zgłoszony ze ścieżką oraz treścią błędu. Brakujący slug: nie może już po cichu zastąpi…]]></description>
            <content:encoded><![CDATA[<h2>Wydanie po audycie<a id="wydanie-po-audycie" href="#wydanie-po-audycie" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>1.2.0 to wynik kompletnego audytu notACMS — architektury, bezpieczeństwa, jakości kodu i obu drzew szablonów. Naprawiono około 170 problemów. Najważniejsze zmiany:</p>
<h3>Odporność<a id="odporność" href="#odporność" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<ul>
<li>Plik markdown z błędnym frontmatterem nie psuje już całej wersji językowej ani nie przerywa builda — zostaje pominięty i zgłoszony ze ścieżką oraz treścią błędu.</li>
<li>Brakujący <code>slug:</code> nie może już po cichu zastąpić strony głównej; <code>posts_per_page: 0</code> nie wysypuje bloga; zduplikowane adresy URL, niejednoznaczne klucze katalogów i nieprawidłowe tagi generują teraz czytelne ostrzeżenia builda.</li>
<li>Szkice i zaplanowane <strong>strony</strong> są teraz naprawdę pomijane przy budowaniu, nie trafiają do menu ani do <code>/llms.txt</code> — wcześniej filtrowane były tylko wpisy.</li>
<li><code>app:build -o &lt;katalog&gt;</code> bez flagi <code>--force</code> odmówi wyczyszczenia katalogu innego niż skonfigurowany katalog statyczny.</li>
<li>Pierwsze wejście na <code>/pl/</code> nie przekierowuje już do wersji angielskiej — adres URL jest traktowany jako wybór języka i zapisywany w cookie. Cookie jest teraz warunkowo oznaczane jako <code>Secure</code>, dzięki czemu mechanizm działa też w środowiskach HTTP.</li>
<li>Wyniki wyszukiwania wyświetlają teraz wyróżnienia jako prawdziwe znaczniki <code>&lt;mark&gt;</code>, a nie jako dosłowny tekst <code>&amp;lt;mark&amp;gt;</code>.</li>
<li>Bazowy <code>_site.yaml</code> szablonu startowego zawiera teraz przykładowe wartości <code>contact_form</code> — świeża instalacja nie kończy się już błędem builda.</li>
</ul>
<h3>Bezpieczeństwo<a id="bezpieczeństwo" href="#bezpieczeństwo" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<ul>
<li>ImageMagick działa teraz przez <code>Symfony\Process</code> z tablicami argumentów — <code>exec()</code> znikło.</li>
<li>Turnstile weryfikuje hostname odpowiedzi względem <code>base_url</code> i loguje błąd, gdy w produkcji aktywne są testowe klucze. Przy starcie sprawdzane jest też, czy <code>APP_SECRET</code> nie jest wciąż placeholderem.</li>
<li>Output JSON-LD jest hex-escapowany (<code>&lt;/script&gt;</code> w tytule nie wyłamie ze skryptu), katalogi tłumaczeń nie zawierają już HTML-a, a nagłówki bezpieczeństwa nginx obejmują teraz też odpowiedzi <code>/assets/</code> i <code>/media/</code>.</li>
<li>Lokalizowane API formularza kontaktowego było nieosiągalne w deploymencie Docker przez błąd w regexie nginx — naprawione.</li>
</ul>
<h3>Dla twórców motywów<a id="dla-twórców-motywów" href="#dla-twórców-motywów" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<ul>
<li>Nowy <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/docs/THEME_BUILDING.md">THEME_BUILDING.md</a>: pełny kontrakt API motywów — konteksty szablonów per trasa, funkcje i zmienne globalne Twig, API ContentItem oraz klucze tłumaczeń wymagane przez każdy motyw.</li>
<li><code>#[LocalizedRoute]</code> działa teraz w <code>local/src/Controller/</code>, przełączanie języków działa poprawnie przy 3 i więcej językach, a pipeline budowania statycznego został rozbity na wielokrotnie używalne serwisy.</li>
</ul>
<h3>Nowości<a id="nowości" href="#nowości" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<ul>
<li>Build statyczny generuje teraz plik <code>/llms.txt</code> dla każdej wersji językowej — lista najnowszych wpisów w formacie czytelnym dla modeli językowych. Konfigurowane przez <code>llms_limit</code> w <code>_site.yaml</code> (domyślnie: 5). Szablon jest nadpisywalny per-motyw przez przestrzeń nazw <code>@base</code> Twig.</li>
</ul>
<h3>Breaking changes<a id="breaking-changes" href="#breaking-changes" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Kilka — każda z jednolinijkową migracją. <code>directoryKey()</code> zwraca teraz pełną ścieżkę treści (szukanie po samej nazwie nadal działa), <code>getTree()</code> przeniesione do <code>ContentTreeProviderInterface</code>, <code>blogPosting()</code> przyjmuje nazwaną mapę, klucz kontekstu <code>lang_switch_url</code> znikł, usunięto cztery nieużywane klucze tłumaczeń, a pakiet <code>old-template</code> z wersji 1.0 został wycofany. Pełny przewodnik migracji: <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 — aktualizacja bezpieczeństwa Symfony 7.4.13 i Twig 3.27.0]]></title>
            <link>https://notacms.holas.pl/pl/wpisy/release-1-1-4/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/pl/wpisy/release-1-1-4/</guid>
                        <pubDate>Thu, 28 May 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Aktualizacja bezpieczeństwa: Symfony 7.4.13 i Twig 3.27.0 Zaktualizuj natychmiast. Uruchom composer update w swoim projekcie, a następnie przebuduj. Symfony 7.4.13 (6 CVE) CVE-2026-48489 — obejście firewalla bezpieczeństwa: handler failure_forward honorował parametr _failure_path dostarczony przez atakującego w wewnętrznym podżądaniu, umożliwiając pominięcie firewalla. CVE-2026-48736 — obejście SS…]]></description>
            <content:encoded><![CDATA[<h2>Aktualizacja bezpieczeństwa: Symfony 7.4.13 i Twig 3.27.0<a id="aktualizacja-bezpieczeństwa-symfony-7413-i-twig-3270" href="#aktualizacja-bezpieczeństwa-symfony-7413-i-twig-3270" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p><strong>Zaktualizuj natychmiast.</strong> Uruchom <code>composer update</code> w swoim projekcie, a następnie przebuduj.</p>
<h3>Symfony 7.4.13 (6 CVE)<a id="symfony-7413-6-cve" href="#symfony-7413-6-cve" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<ul>
<li><strong>CVE-2026-48489</strong> — obejście firewalla bezpieczeństwa: handler <code>failure_forward</code> honorował parametr <code>_failure_path</code> dostarczony przez atakującego w wewnętrznym podżądaniu, umożliwiając pominięcie firewalla.</li>
<li><strong>CVE-2026-48736</strong> — obejście SSRF w <code>NoPrivateNetworkHttpClient</code> i <code>IpUtils::PRIVATE_SUBNETS</code> przez formy przejściowe adresów IPv6 (IPv4-mapped, IPv4-compatible, 6to4, Teredo).</li>
<li><strong>CVE-2026-48761</strong> — <code>HtmlSanitizer</code> nie sanityzował atrybutów URL w elementach <code>&lt;object&gt;</code>, <code>&lt;applet&gt;</code>, <code>&lt;iframe&gt;</code>, <code>&lt;img&gt;</code> oraz URL w treści <code>&lt;meta http-equiv=&quot;refresh&quot;&gt;</code>.</li>
<li><strong>CVE-2026-48760</strong> — <code>HtmlSanitizer</code> akceptował procentowo zakodowane znaki BiDi i białe znaki Unicode w URL-ach, umożliwiając obejście sanityzatora przez wizualne podszywanie się.</li>
<li><strong>CVE-2026-48784</strong> — <code>UrlGenerator</code> błędnie kodował łańcuchowe segmenty <code>../</code> i <code>./</code>, generując URL-e mogące wychodzić poza zamierzony ścieżkę.</li>
<li><strong>CVE-2026-48747</strong> — Mailer: algorytm podpisu webhooka Mailomat nie był przypięty do SHA-256, umożliwiając ataki podstawienia algorytmu.</li>
</ul>
<p>Pełna lista: <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 CVE)<a id="twig-3270-5-cve" href="#twig-3270-5-cve" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Wszystkie pięć to obejścia sandboxa. Jeśli Twój motyw renderuje szablony kontrolowane przez użytkownika w sandboxie, są krytyczne:</p>
<ul>
<li><strong>CVE-2026-46636</strong> — obejście listy dozwolonych filtrów, tagów i funkcji przy zmianie stanu sandboxa między renderowaniami w długo działających procesach (np. FrankenPHP, RoadRunner).</li>
<li><strong>CVE-2026-48808</strong> — filtr <code>column</code> omijał listę dozwolonych właściwości pod <code>SourcePolicyInterface</code>.</li>
<li><strong>CVE-2026-48806</strong> — obejście polityki <code>__toString()</code> przez dynamiczne klucze mapowania: <code>{% set arr = {(obj): &quot;value&quot;} %}</code>.</li>
<li><strong>CVE-2026-48807</strong> — obejście polityki <code>__toString()</code> przez obiekty <code>Traversable</code> w filtrach <code>join</code>/<code>replace</code> i operatorach <code>in</code>/<code>not in</code>.</li>
<li><strong>CVE-2026-48805</strong> — regresja stanu sandboxa w przestarzałych wewnętrznych wrapperach (<code>twig_check_arrow_in_sandbox()</code>, <code>twig_array_some()</code>, <code>twig_array_every()</code>).</li>
</ul>
<h2>Pozostałe zmiany w 1.1.4<a id="pozostałe-zmiany-w-114" href="#pozostałe-zmiany-w-114" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<ul>
<li><code>symfony/polyfill-*</code> zaktualizowano z v1.37.0 do v1.38.1.</li>
<li>Zależności deweloperskie: <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>Jak zaktualizować<a id="jak-zaktualizować" href="#jak-zaktualizować" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<pre><code class="language-bash">composer update
ddev build   # lub odpowiednik komendy budowania w Twoim środowisku
</code></pre>
<p>Nie są wymagane żadne zmiany konfiguracji ani kroki migracji.</p>
<h2>Pełny changelog<a id="pełny-changelog" href="#pełny-changelog" 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 — pliki JSON Schema i aktualizacja bezpieczeństwa Symfony 7.4.12]]></title>
            <link>https://notacms.holas.pl/pl/wpisy/release-1-1-3/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/pl/wpisy/release-1-1-3/</guid>
                        <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Pliki JSON Schema dla konfiguracji i frontmatter Sześć plików JSON Schema draft-07 trafiło do config/schema/: Plik schematu Opisuje site.schema.json _site.yaml — ustawienia witryny, lokalizacje, linki społecznościowe, formularz kontaktowy routes.schema.json _routes.yaml — nadpisania ścieżek URL per lokalizacja tags.schema.json _tags.yaml — mapa tłumaczeń tagów post.frontmatter.schema.json Frontmat…]]></description>
            <content:encoded><![CDATA[<h2>Pliki JSON Schema dla konfiguracji i frontmatter<a id="pliki-json-schema-dla-konfiguracji-i-frontmatter" href="#pliki-json-schema-dla-konfiguracji-i-frontmatter" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Sześć plików JSON Schema draft-07 trafiło do <code>config/schema/</code>:</p>
<table>
<thead>
<tr>
<th>Plik schematu</th>
<th>Opisuje</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>site.schema.json</code></td>
<td><code>_site.yaml</code> — ustawienia witryny, lokalizacje, linki społecznościowe, formularz kontaktowy</td>
</tr>
<tr>
<td><code>routes.schema.json</code></td>
<td><code>_routes.yaml</code> — nadpisania ścieżek URL per lokalizacja</td>
</tr>
<tr>
<td><code>tags.schema.json</code></td>
<td><code>_tags.yaml</code> — mapa tłumaczeń tagów</td>
</tr>
<tr>
<td><code>post.frontmatter.schema.json</code></td>
<td>Frontmatter wpisów blogowych</td>
</tr>
<tr>
<td><code>page.frontmatter.schema.json</code></td>
<td>Frontmatter stron statycznych</td>
</tr>
<tr>
<td><code>category.frontmatter.schema.json</code></td>
<td>Frontmatter indeksów kategorii</td>
</tr>
</tbody>
</table>
<p>Wszystkie szablonowe pliki YAML (<code>_site.yaml</code>, <code>_routes.yaml</code>, <code>_tags.yaml</code>) w <code>docs/bare/</code>, <code>docs/demo/</code> i <code>docs/customization/old-template/</code> zawierają teraz komentarz <code># yaml-language-server: $schema=</code> wskazujący na URL na gałęzi main. VS Code (i każdy edytor z YAML Language Server) wykrywa je automatycznie — walidacja i autouzupełnianie działają bez żadnej dodatkowej konfiguracji projektu.</p>
<p>Schematy są zoptymalizowane pod kątem wspomaganego przez AI tworzenia treści: opisy zawierają wartości domyślne, ograniczenia i wyjaśnienie zachowań, dzięki czemu asystent AI pracujący w innym projekcie może pobrać schemat i wiedzieć dokładnie, co robi każde pole.</p>
<p>Pobranie dowolnego schematu bezpośrednio z gałęzi main:</p>
<pre><code>https://raw.githubusercontent.com/holas1337/notACMS/main/config/schema/&lt;name&gt;.schema.json
</code></pre>
<h2>Aktualizacja bezpieczeństwa: Symfony 7.4.12 i Twig 3.26.0<a id="aktualizacja-bezpieczeństwa-symfony-7412-i-twig-3260" href="#aktualizacja-bezpieczeństwa-symfony-7412-i-twig-3260" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p><strong>Zaktualizuj natychmiast.</strong> Uruchom <code>composer update</code> w swoim projekcie, a następnie przebuduj.</p>
<h3>Symfony 7.4.12 (21 CVE)<a id="symfony-7412-21-cve" href="#symfony-7412-21-cve" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Najbardziej istotne dla wdrożeń CMS:</p>
<ul>
<li><strong>CVE-2026-45073</strong> — SQL injection w <code>Cache</code> przez niezwalidowany <code>$prefix</code> w <code>PdoAdapter::doClear()</code>.</li>
<li><strong>CVE-2026-45071</strong> — XXE / ujawnienie lokalnych plików w <code>DomCrawler::addXmlContent()</code> przy włączonym <code>validateOnParse</code>.</li>
<li><strong>CVE-2026-45075</strong> — żądania HEAD omijają filtr <code>methods</code> w atrybutach <code>#[IsGranted]</code>, <code>#[IsCsrfTokenValid]</code> i <code>#[IsSignatureValid]</code>.</li>
<li><strong>CVE-2026-45072</strong> — XSS w <code>CodeExtension::fileExcerpt()</code> w <code>TwigBridge</code>.</li>
<li><strong>CVE-2026-45068</strong> — wstrzyknięcie nagłówka w <code>SendmailTransport</code>; adresy zaczynające się od myślnika są teraz odrzucane.</li>
<li><strong>CVE-2026-45067</strong> — adresy e-mail zawierające znaki nowej linii akceptowane przez <code>Mime\Address</code> — teraz odrzucane.</li>
<li><strong>CVE-2026-45305 / 45304 / 45133</strong> — katastrofalne backtracking i nieograniczona rekurencja w parserze YAML.</li>
<li><strong>CVE-2026-45066 / 45064 / 45753</strong> — trzy obejścia <code>HtmlSanitizer</code>: znaki BiDi, różnice parserów URL oraz niesanityzowane atrybuty <code>action</code>/<code>formaction</code>/<code>poster</code>/<code>cite</code>.</li>
</ul>
<p>Pełna lista: <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 CVE)<a id="twig-3260-4-cve" href="#twig-3260-4-cve" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Wszystkie cztery to obejścia sandboxa. Jeśli Twój motyw używa sandboxa Twiga dla szablonów dostarczanych przez użytkownika, są krytyczne:</p>
<ul>
<li><strong>CVE-2026-46635</strong> — obejście listy dozwolonych właściwości przez filtr <code>column</code> (<code>array_column</code> na obiektach).</li>
<li><strong>CVE-2026-46638</strong> — <code>{% sandbox %}{% include %}</code> pomija <code>checkSecurity()</code> na zbuforowanych szablonach; niekompletna poprawka dla CVE-2024-45411.</li>
<li><strong>CVE-2026-24425</strong> — obejście sandboxa przy użyciu polityki źródłowej.</li>
<li><strong>CVE-2026-47732</strong> — wielokrotne obejścia polityki <code>__toString()</code> przez niezabezpieczone punkty koercji stringów.</li>
</ul>
<h2>Pozostałe zmiany w 1.1.3<a id="pozostałe-zmiany-w-113" href="#pozostałe-zmiany-w-113" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<ul>
<li><strong>Naprawione nieaktualne fragmenty Pagefind</strong> — <code>scripts/rebuild-content.sh</code> usuwa teraz katalog <code>public/pagefind/</code> przed ponownym indeksowaniem. Wcześniej usunięte lub przemianowane treści pozostawiały osierocone pliki fragmentów, które Pagefind serwował razem z aktualnymi wynikami.</li>
<li><strong>Usprawnienia DESIGN.md</strong> — zakodowane na stałe wartości hex zastąpiono odniesieniami do tokenów w plikach <code>DESIGN.md</code> bare i demo; dodano alias koloru <code>primary</code>; dodano token komponentu <code>card-hover</code> do motywu demo.</li>
</ul>
<h2>Jak zaktualizować<a id="jak-zaktualizować" href="#jak-zaktualizować" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<pre><code class="language-bash">composer update
ddev build   # lub odpowiednik komendy budowania w Twoim środowisku
</code></pre>
<p>Nie są wymagane żadne zmiany konfiguracji ani kroki migracji.</p>
<h2>Pełny changelog<a id="pełny-changelog" href="#pełny-changelog" 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 — Kanoniczne adresy URL i builder dla danych strukturalnych]]></title>
            <link>https://notacms.holas.pl/pl/wpisy/release-1-1-2/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/pl/wpisy/release-1-1-2/</guid>
                        <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Jeden kanoniczny adres URL na stronę Jeśli domyślną lokalizacją witryny jest en, zarówno /en/blog/, jak i /blog/ były dostępne i renderowały tę samą treść. To dwa indeksowalne adresy URL dla jednej strony — a wyszukiwarki wybierają ten, który im się podoba, co rzadko jest tym, którego oczekujesz. 1.1.2 dodaje DefaultLocaleRedirectListener. Każde żądanie do /&lt;domyślna-lokalizacja&gt;/... zwraca …]]></description>
            <content:encoded><![CDATA[<h2>Jeden kanoniczny adres URL na stronę<a id="jeden-kanoniczny-adres-url-na-stronę" href="#jeden-kanoniczny-adres-url-na-stronę" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Jeśli domyślną lokalizacją witryny jest <code>en</code>, zarówno <code>/en/blog/</code>, jak i <code>/blog/</code> były dostępne i renderowały tę samą treść. To dwa indeksowalne adresy URL dla jednej strony — a wyszukiwarki wybierają ten, który im się podoba, co rzadko jest tym, którego oczekujesz.</p>
<p>1.1.2 dodaje <code>DefaultLocaleRedirectListener</code>. Każde żądanie do <code>/&lt;domyślna-lokalizacja&gt;/...</code> zwraca <code>301</code> na adres URL bez prefiksu:</p>
<pre><code>GET /en/blog/         →  301 Location: /blog/
GET /en/about/?ref=x  →  301 Location: /about/?ref=x
GET /pl/blog/         →  200 (lokalizacja niedomyślna, nietknięta)
</code></pre>
<p>Listener uruchamia się przed routerem Symfony, więc przekierowanie następuje, zanim framework w ogóle spróbuje dopasować trasę — żadnej zmarnowanej pracy, żadnych szumów 404 w logach. Lokalizacje niedomyślne (<code>pl</code>, <code>de</code>, <code>fr</code> w tej witrynie) pozostają nietknięte.</p>
<p>To również eliminuje drobny wyciek SEO: linki zwrotne, które przypadkowo zawierają prefiks lokalizacji, konsolidują się teraz do kanonicznego adresu URL, zamiast dzielić autorytet domeny.</p>
<h2>JSON-LD Schema.org jako płynny builder<a id="json-ld-schemaorg-jako-płynny-builder" href="#json-ld-schemaorg-jako-płynny-builder" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Do wersji 1.1.1 każdy szablon, który potrzebował danych strukturalnych, osadzał tablicę PHP, przepuszczał ją przez <code>|json_encode|raw</code> i opakowywał wynik w tag <code>&lt;script&gt;</code>. Sześć szablonów, sześć niemal identycznych bloków, z których każdy stwarzał nową okazję do zapomnienia o <code>JSON_UNESCAPED_SLASHES</code> lub do wysłania <code>&lt;script&gt;false&lt;/script&gt;</code>, jeśli frontmatter zawierał nieprawidłowy bajt.</p>
<p>1.1.2 zastępuje to wszystko serwisem i dwiema funkcjami 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> zwraca builder; wywołaj typowaną metodę (<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>), a wynikiem jest zwykła tablica. <code>json_ld(array)</code> koduje ją i opakowuje w tag <code>&lt;script type=&quot;application/ld+json&quot;&gt;</code>. Puste i null-owe pola są usuwane rekursywnie, więc opcjonalne dane wejściowe (nieustawiony e-mail autora, brakujący obraz) po prostu nie pojawią się na wyjściu.</p>
<p>Kodowanie używa teraz <code>JSON_THROW_ON_ERROR</code> — nieprawidłowe UTF-8 we frontmatter ujawnia się jako rzeczywisty wyjątek podczas renderowania, zamiast po cichu wysyłać uszkodzony tag <code>&lt;script&gt;</code>.</p>
<p>Bazowe szablony rdzenia dla stron <code>contact</code>, <code>default</code> i <code>projects</code> również emitują teraz znaczniki Schema.org. Wcześniej wdrożenia bazowe miały słabszy SEO niż każdy przykład dostosowywania, który dostarczamy; ta luka została zamknięta.</p>
<p>Pełne API buildera oraz informacje o nadpisywaniu bloku <code>structured_data</code> we własnym motywie znajdziesz w przewodniku <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/docs/CUSTOMIZATION.md#structured-data-json-ld">Dostosowywanie → Dane strukturalne</a>.</p>
<h2>Poprawki bezpieczeństwa<a id="poprawki-bezpieczeństwa" href="#poprawki-bezpieczeństwa" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Dwa błędy ujawniły się podczas przeglądu 1.1.2 i zostały naprawione przed wydaniem:</p>
<ul>
<li><strong>Otwarte przekierowanie poprzez normalizację ścieżki.</strong> Metoda <code>Request::getPathInfo()</code> w Symfony nie zwija powtórzonych ukośników, więc żądanie do <code>/&lt;domyślna-lokalizacja&gt;//evil.com</code> mogłoby w przeciwnym razie wygenerować <code>Location: //evil.com</code> — przekierowanie względne wobec protokołu, za którym przeglądarki podążają na inne źródło. Nowy listener przekierowań nie wystawia teraz przekierowań na nic, co nie jest lokalną ścieżką z pojedynczym ukośnikiem.</li>
<li><strong>XSS w wynikach wyszukiwania.</strong> <code>assets/search.js</code> osadzał pole <code>excerpt</code> z Pagefind w innerHTML bez escapowania. Wersja demo była już poprawna; rdzeń się rozjechał. Obie są teraz zsynchronizowane. Tagi <code>&lt;mark&gt;</code> z Pagefind nie są już renderowane w wynikach wyszukiwania rdzenia — świadomy kompromis na rzecz bezpieczniejszej wartości domyślnej.</li>
</ul>
<h2>Co jeszcze w 1.1.2<a id="co-jeszcze-w-112" href="#co-jeszcze-w-112" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<ul>
<li><strong>Wewnętrzny refaktoring Twig.</strong> Logika sidebaru, breadcrumbów, plakietek wpisów, obrazów og oraz filtrów bloga została przeniesiona z szablonów do dedykowanych rozszerzeń Twig. Szablony skupiają się na układzie; testy mogą celować bezpośrednio w każdą funkcję pomocniczą. Nowa powierzchnia dostosowywania: zobacz <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/docs/CUSTOMIZATION.md#layout-helpers">Funkcje pomocnicze układu</a> w przewodniku dostosowywania.</li>
<li><strong>Wyszukiwanie po kluczu katalogu w czasie <code>O(1)</code></strong> w <code>ContentTree</code> — drobna optymalizacja wydajności w witrynach z setkami stron.</li>
<li><strong><code>docs/customization/old-template/</code> jest teraz przestarzałe</strong> i zostanie usunięte w 1.2.0. Był to jednorazowy element pomocniczy migracji 1.0→1.1; nowe prace nad dostosowywaniem powinny opierać się na <code>custom-footer/</code> lub <code>self-hosted-fonts/</code>.</li>
<li><strong>Aktualizacje zależności:</strong> Symfony 7.4.8 → 7.4.9 w całym pakiecie, PHPStan 2.1.51 → 2.1.54, PHPUnit 13.1.7 → 13.1.8.</li>
</ul>
<h2>Pełna lista zmian<a id="pełna-lista-zmian" href="#pełna-lista-zmian" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Wszystkie zmiany z kategoriami: <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 — Etykiety nawigacji z frontmatter i deploy bez nadpisywania treści]]></title>
            <link>https://notacms.holas.pl/pl/wpisy/release-1-1-1/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/pl/wpisy/release-1-1-1/</guid>
                        <pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Etykiety nawigacji z frontmatter Etykiety w nawigacji dla stron treści pochodzą teraz z frontmatter — nie z kluczy tłumaczeń. Wystarczy ustawić menu.label w frontmatter strony, a to ono stanie się etykietą w menu; jeśli go nie ma, użyty zostanie title. --- title: &quot;Przewodnik po architekturze&quot; slug: &quot;architecture-guide&quot; menu: label: &quot;Architektura&quot; weight: 30 --- Nowa f…]]></description>
            <content:encoded><![CDATA[<h2>Etykiety nawigacji z frontmatter<a id="etykiety-nawigacji-z-frontmatter" href="#etykiety-nawigacji-z-frontmatter" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Etykiety w nawigacji dla stron treści pochodzą teraz z frontmatter — nie z kluczy tłumaczeń. Wystarczy ustawić <code>menu.label</code> w frontmatter strony, a to ono stanie się etykietą w menu; jeśli go nie ma, użyty zostanie <code>title</code>.</p>
<pre><code class="language-yaml">---
title: &quot;Przewodnik po architekturze&quot;
slug: &quot;architecture-guide&quot;
menu:
  label: &quot;Architektura&quot;
  weight: 30
---
</code></pre>
<p>Nowa funkcja Twig <code>content_item()</code> pozwala pobrać dowolną stronę według klucza katalogu:</p>
<pre><code class="language-twig">{{ content_item('architecture-guide', 'pl').menuLabel() }}
{# → &quot;Architektura&quot; (lub tytuł strony, jeśli menu.label nie jest ustawione) #}
</code></pre>
<p><strong>Dlaczego to ważne:</strong> Wcześniej pliki <code>messages.*.yaml</code> w każdej lokalizacji musiały zawierać tłumaczenia dla <code>nav.home</code>, <code>nav.blog</code>, <code>nav.about</code>, <code>nav.contact</code>, <code>nav.privacy_policy</code> (core) oraz <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> (demo). Dodanie strony oznaczało aktualizację N plików tłumaczeń. Teraz wystarczy jedno pole w frontmatter. Własne szablony odwołujące się do usuniętych kluczy powinny przejść na <code>content_item('key', locale).menuLabel()</code>.</p>
<h2>Deploy nie nadpisuje już istniejącego <code>local/</code><a id="deploy-nie-nadpisuje-już-istniejącego-local" href="#deploy-nie-nadpisuje-już-istniejącego-local" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<h3>Błąd<a id="błąd" href="#błąd" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Uruchomienie <code>./notACMS deploy --prod</code> <strong>tworzyło kopię zapasową i nadpisywało</strong> cały katalog <code>local/</code> zawartością <code>docs/demo/</code> przy każdym deployu — nawet gdy zawierał twoją treść, szablony i dostosowania. Logika seedowania nie rozróżniała jawnego żądania ponownego seedowania motywu (<code>--bare</code>/<code>--demo</code>) od zwykłego redeployu z istniejącą treścią.</p>
<h3>Naprawa<a id="naprawa" href="#naprawa" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Deploy działa teraz dokładnie tak samo jak <code>ddev build</code>:</p>
<ul>
<li><strong>Brak flagi <code>--bare</code> / <code>--demo</code></strong> → seeduje <code>local/</code> tylko jeśli nie istnieje lub jest puste; pomija, jeśli zawiera treść.</li>
<li><strong>Przekazano <code>--bare</code> lub <code>--demo</code></strong> → tworzy kopię zapasową istniejącego <code>local/</code> i seeduje wybrany motyw.</li>
</ul>
<pre><code class="language-bash">./notACMS deploy --prod          # zachowuje istniejące local/
./notACMS deploy --prod --demo   # wymusza ponowne seedowanie z docs/demo/
./notACMS deploy --prod --bare   # wymusza ponowne seedowanie z docs/bare/
</code></pre>
<p><code>--prod</code> kontroluje teraz tylko flagi Composera (<code>--no-dev</code>) i środowisko uruchomieniowe (<code>APP_ENV=prod</code>). Nigdy nie modyfikuje <code>local/</code>.</p>
<h2>Co jeszcze w 1.1.1<a id="co-jeszcze-w-111" href="#co-jeszcze-w-111" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<ul>
<li><strong>Aktualizacja zależności:</strong> pakiety <code>symfony/polyfill-*</code> zaktualizowane z v1.36.0 do v1.37.0.</li>
<li><strong>Treść demo w języku polskim, niemieckim i francuskim</strong> zweryfikowana i ulepszona na wszystkich stronach i wpisach bloga.</li>
<li><strong>Przewodniki stylu tłumaczeń</strong> dodane do systemu umiejętności agenta AI dla języków polskiego, niemieckiego i francuskiego, aby zapewnić spójną jakość przyszłych tłumaczeń.</li>
</ul>
<h2>Pełna lista zmian<a id="pełna-lista-zmian" href="#pełna-lista-zmian" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Wszystkie zmiany z kategoriami: <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 — Minimalny rdzeń, motyw demo, wybierz swój]]></title>
            <link>https://notacms.holas.pl/pl/wpisy/release-1-1-0/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/pl/wpisy/release-1-1-0/</guid>
                        <pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Dwa motywy, jeden wybór przy pierwszym budowaniu Najważniejsza zmiana w 1.1.0: notACMS zawiera teraz dwa motywy, a ty wybierasz jeden już przy pierwszym buildzie: ./notACMS deploy --demo # domyślny — projekt amber-phosphor, który widzisz tutaj ./notACMS deploy --bare # minimalny wireframe: fonty systemowe, jasny tryb, ~200 linii CSS ddev build obsługuje te same flagi. Cokolwiek wybierzesz, działa …]]></description>
            <content:encoded><![CDATA[<h2>Dwa motywy, jeden wybór przy pierwszym budowaniu<a id="dwa-motywy-jeden-wybór-przy-pierwszym-budowaniu" href="#dwa-motywy-jeden-wybór-przy-pierwszym-budowaniu" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Najważniejsza zmiana w 1.1.0: notACMS zawiera teraz dwa motywy, a ty wybierasz jeden już przy pierwszym buildzie:</p>
<pre><code class="language-bash">./notACMS deploy --demo    # domyślny — projekt amber-phosphor, który widzisz tutaj
./notACMS deploy --bare    # minimalny wireframe: fonty systemowe, jasny tryb, ~200 linii CSS
</code></pre>
<p><code>ddev build</code> obsługuje te same flagi. Cokolwiek wybierzesz, działa każda funkcja: treści wielojęzyczne, blog, RSS, sitemap, wyszukiwarka, formularz kontaktowy, obrazy z wariantami responsywnymi, czas czytania, wskaźnik postępu czytania. Różnica leży jedynie w wyglądzie — i, co najważniejsze, w tym, ile dziedziczysz, zanim zaczniesz dopasowywać.</p>
<h3>Minimalny rdzeń<a id="minimalny-rdzeń" href="#minimalny-rdzeń" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Rdzeń w <code>templates/</code>, <code>assets/</code>, <code>translations/</code> jest teraz wireframem. Został świadomie zminimalizowany, żeby nadpisanie pojedynczego bloku nie ciągnęło za sobą języka wizualnego, z którym musiałbyś walczyć. Jeśli budujesz projekt szyty na miarę, startuj z bare i od pierwszego dnia kontrolujesz cały wygląd.</p>
<h3>Motyw demo<a id="motyw-demo" href="#motyw-demo" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Demo znajduje się w <code>docs/demo/</code> i kopiowane jest do <code>local/</code>, kiedy wybierzesz <code>--demo</code>. To pełny projekt amber-phosphor, który właśnie czytasz — ciemny tryb, overlay wyszukiwania, sidebar dokumentacji, przełącznik motywu, wszystko razem. Startuj stąd, jeśli chcesz dopracowany projekt już dziś i planujesz dopracowywać, a nie przebudowywać.</p>
<h2>System nadpisań <code>local/</code><a id="system-nadpisań-local" href="#system-nadpisań-local" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Oba motywy korzystają z tego samego mechanizmu: każdy plik w <code>local/</code> ma pierwszeństwo przed odpowiadającą mu ścieżką w rdzeniu.</p>
<table>
<thead>
<tr>
<th>Warstwa</th>
<th>Lokalizacja nadpisania</th>
<th>Resolver</th>
</tr>
</thead>
<tbody>
<tr>
<td>Szablony Twig</td>
<td><code>local/templates/*.html.twig</code></td>
<td>Kernel Symfony</td>
</tr>
<tr>
<td>Entrypoint SCSS</td>
<td><code>local/assets/styles/app_local.scss</code></td>
<td>Sass-bundle</td>
</tr>
<tr>
<td>Entrypoint JS</td>
<td><code>local/assets/app.js</code></td>
<td>Importmapa AssetMappera</td>
</tr>
<tr>
<td>Tłumaczenia</td>
<td><code>local/translations/messages.*.yaml</code></td>
<td>Translator Symfony</td>
</tr>
<tr>
<td>Treści</td>
<td><code>local/content/**</code></td>
<td>Parametr <code>notacms_content</code></td>
</tr>
<tr>
<td>Snippety Nginx</td>
<td><code>local/docker/nginx/*.conf</code></td>
<td>Entrypoint kontenera</td>
</tr>
</tbody>
</table>
<p>Rdzeń nie jest nigdy edytowany. <code>git pull</code> pozostaje czysty. Personalizacje znajdują się w repozytorium twojej strony, nie w forku tego repo.</p>
<h2>Aktualizacja z 1.0.0<a id="aktualizacja-z-100" href="#aktualizacja-z-100" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Jeśli używałeś 1.0.0 i chcesz, żeby twoja strona wyglądała dokładnie jak wcześniej, pakiet kompatybilności to jedna linijka:</p>
<pre><code class="language-bash">cp -r docs/customization/old-template/. local/
ddev build
</code></pre>
<p>To wrzuca kompletny motyw sprzed 1.1.0 — szablony, SCSS, fonty, obrazy, tłumaczenia — do <code>local/</code>. Twoja strona renderuje się dokładnie jak przedtem. Później możesz selektywnie usuwać pliki z <code>local/</code>, w miarę jak adaptujesz nowe elementy projektu.</p>
<p>Faktyczne breaking changes, które cię dotkną, jeśli dostosowywałeś stary rdzeń:</p>
<ul>
<li><strong>Zmiana nazwy entrypointa SCSS</strong>: <code>local/assets/styles/local.scss</code> → <code>local/assets/styles/app_local.scss</code>. Zmień nazwę pliku i zaktualizuj import w <code>local/assets/app.js</code>.</li>
<li><strong>Zmienne SCSS kolorów w rdzeniu zastąpione CSS custom properties</strong>: <code>$color-body</code> → <code>var(--text)</code>, <code>$color-link</code> → <code>var(--accent)</code> itd. Pełna mapa w <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>Klucze tłumaczeń usunięte</strong> z rdzenia (nadal obecne w motywie demo): <code>header.tagline</code>, <code>nav.projects</code>, <code>nav.search</code>, <code>blog.published_on</code>, <code>blog.comments_disabled</code>. Jeśli twoje szablony wołają <code>'...'|trans</code> na tych kluczach, zaktualizuj je.</li>
<li><strong>Katalog <code>docs/examples/</code> usunięty</strong>: przydatne części trafiły do <code>docs/customization/old-template/</code>; przykłady nadpisań ad hoc zostały wyparte przez model bare/demo.</li>
</ul>
<p>Zobacz <a rel="nofollow noopener noreferrer" target="_blank" href="https://github.com/holas1337/notACMS/blob/main/UPGRADE-1.1.md">UPGRADE-1.1.md</a> po pełną listę ze snippetami przed/po.</p>
<h2>Co jeszcze nowego w 1.1.0<a id="co-jeszcze-nowego-w-110" href="#co-jeszcze-nowego-w-110" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<ul>
<li><strong>Czas czytania</strong> i <strong>wskaźnik postępu czytania</strong> na wpisach, dokumentacji i releasach.</li>
<li><strong>Przełącznik języka</strong> jako rozszerzenie Twiga (<code>lang_switch_urls</code>) z poprawnymi łańcuchami fallbacków dla archiwów, list z paginacją i stron głównych.</li>
<li><strong>Zajawki wpisów usuwają kotwice nagłówków</strong> — żadnych zbłąkanych <code>#</code> na kartach list.</li>
<li><strong>Szkielet zestawu testów</strong> w <code>tests/Unit/</code>, <code>tests/Integration/</code>, <code>tests/Fixtures/</code> z początkowym pokryciem i komendą <code>test</code> uruchamianą z hosta.</li>
<li><strong>Skrypty agenta AI</strong> w <code>.claude/skills/</code> do pracy nad repo: dodawanie wersji językowych, sprawdzanie alignmentu dokumentacji, site-sweepy, tłumaczenia i generowanie przewodników aktualizacji.</li>
</ul>
<h2>Pełna lista zmian<a id="pełna-lista-zmian" href="#pełna-lista-zmian" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Każda zmiana ze swoją kategorią: <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]]></title>
            <link>https://notacms.holas.pl/pl/wpisy/release-1-0-0/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/pl/wpisy/release-1-0-0/</guid>
                        <pubDate>Thu, 09 Apr 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Co zawiera v1.0.0 Po kilku miesiącach wewnętrznego użytkowania w projektach osobistych, oznaczam pierwsze stabilne wydanie. Podstawowy zestaw funkcji jest wystarczająco solidny, aby budować prawdziwe strony. Pipeline treści Pipeline treści to serce notACMS. Odczytuje katalog local/content/, parsuje frontmatter i jedną komendą generuje kompletną stronę statyczną. Treść Markdown z frontmatter YAML R…]]></description>
            <content:encoded><![CDATA[<h2>Co zawiera v1.0.0<a id="co-zawiera-v100" href="#co-zawiera-v100" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Po kilku miesiącach wewnętrznego użytkowania w projektach osobistych, oznaczam pierwsze stabilne wydanie. Podstawowy zestaw funkcji jest wystarczająco solidny, aby budować prawdziwe strony.</p>
<h3>Pipeline treści<a id="pipeline-treści" href="#pipeline-treści" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Pipeline treści to serce notACMS. Odczytuje katalog <code>local/content/</code>, parsuje frontmatter i jedną komendą generuje kompletną stronę statyczną.</p>
<ul>
<li>Treść Markdown z frontmatter YAML</li>
<li>Renderowanie CommonMark z permalinkami nagłówków</li>
<li>Generowanie excerptów z treści prozatorskich</li>
<li>Obliczanie czasu czytania</li>
</ul>
<h3>Wielojęzyczny routing<a id="wielojęzyczny-routing" href="#wielojęzyczny-routing" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Wersje językowe są definiowane w <code>_site.yaml</code>. Każda wersja językowa otrzymuje własną przestrzeń URL, z opcjonalnymi nadpisaniami ścieżek w <code>_routes.yaml</code>.</p>
<pre><code class="language-yaml">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>Wyszukiwarka Pagefind<a id="wyszukiwarka-pagefind" href="#wyszukiwarka-pagefind" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Pełnotekstowe wyszukiwanie jest wbudowane w wynik statyczny przez Pagefind. Komenda budowania automatycznie generuje indeks wyszukiwania. Brak zewnętrznego API, brak wyszukiwania po stronie serwera — tylko statyczny indeks działający offline.</p>
<h3>Przetwarzanie obrazów<a id="przetwarzanie-obrazów" href="#przetwarzanie-obrazów" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Obrazy przechowywane obok treści są przetwarzane podczas budowania. notACMS generuje warianty WebP w wielu szerokościach, automatycznie aktualizuje atrybuty <code>src</code> o responsywne <code>srcset</code> i obsługuje mapowanie ścieżek między katalogami treści i wynikowym.</p>
<h3>Środowisko lokalne DDEV<a id="środowisko-lokalne-ddev" href="#środowisko-lokalne-ddev" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h3>
<p>Środowisko deweloperskie jest w pełni skonteneryzowane z DDEV. <code>ddev start</code> daje PHP 8.5, Nginx i wszystkie narzędzia budowania. <code>ddev build</code> produkuje wynik statyczny. <code>ddev code-check</code> uruchamia PHPStan i PHP CS Fixer.</p>
<h2>Breaking changes<a id="breaking-changes" href="#breaking-changes" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>To pierwsze stabilne wydanie. Jeśli używałeś wersji sprzed 1.0, sprawdź schemat <code>_site.yaml</code> — klucz <code>social</code> zmienił się z listy na mapę.</p>
<h2>Aktualizacja<a id="aktualizacja" href="#aktualizacja" 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>Co dalej<a id="co-dalej" href="#co-dalej" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>v1.1.0 skupi się na systemie designu i dokumentacji. Ta strona — zbudowana z notACMS — stanie się oficjalną dokumentacją i referencją projektową.</p>
]]></content:encoded>
                                    <category><![CDATA[releases]]></category>
                                    <category><![CDATA[release]]></category>
                        <category><![CDATA[announcement]]></category>
                    </item>
                <item>
            <title><![CDATA[Dlaczego zbudowałem notACMS]]></title>
            <link>https://notacms.holas.pl/pl/wpisy/the-idea/</link>
            <guid isPermaLink="true">https://notacms.holas.pl/pl/wpisy/the-idea/</guid>
                        <pubDate>Sun, 01 Feb 2026 00:00:00 +0000</pubDate>
                        <description><![CDATA[Więcej na ten temat na moim blogu: I left WordPress. Problem, na który wciąż natrafiałem Każdy projekt PHP, nad którym pracowałem, w końcu potrzebował strony internetowej. Dokumentacja, landing page, blog — to, co zwykle. I za każdym razem, gdy sięgałem po narzędzie, lądowałem w tym samym miejscu: narzędzie, które chciało, żebym myślał jak ono, a nie jak programista PHP. Hugo jest szybki, ale jego…]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Więcej na ten temat na moim blogu: <a rel="nofollow noopener noreferrer" target="_blank" href="https://holas.pl/blog/why-i-left-wordpress/">I left WordPress</a>.</p>
</blockquote>
<h2>Problem, na który wciąż natrafiałem<a id="problem-na-który-wciąż-natrafiałem" href="#problem-na-który-wciąż-natrafiałem" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Każdy projekt PHP, nad którym pracowałem, w końcu potrzebował strony internetowej. Dokumentacja, landing page, blog — to, co zwykle. I za każdym razem, gdy sięgałem po narzędzie, lądowałem w tym samym miejscu: narzędzie, które chciało, żebym myślał jak ono, a nie jak programista PHP.</p>
<p>Hugo jest szybki, ale jego język szablonów jest obcy. Jekyll wymaga Ruby. WordPress nie nadaje się do treści statycznych. Next.js jest potężny, ale zamienia problem z publikowaniem Markdown w problem z bundlerem JavaScript.</p>
<p>Wciąż myślałem: znam już Symfony. Znam już Twig. Mam już Composer. Dlaczego muszę uczyć się nowego ekosystemu tylko po to, żeby opublikować tekst?</p>
<h2>Decyzja o braku bazy danych<a id="decyzja-o-braku-bazy-danych" href="#decyzja-o-braku-bazy-danych" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Pierwsza rzecz, którą postanowiłem: żadnej bazy danych. Nie „baza danych opcjonalnie&quot; — żadnej bazy danych w ogóle. Treść przechowywana jest w plikach. Proces budowania odczytuje pliki. Wynik to pliki.</p>
<p>To wymusza pewną dyscyplinę. Struktura treści musi być jawna i przewidywalna. Nie ma zapytania do bazy, po które można sięgnąć, gdy chce się znaleźć powiązane wpisy lub wygenerować mapę strony. Wszystko musi wynikać ze struktury plików.</p>
<p>To ograniczenie okazało się właściwym wyborem. Dzięki temu system jest łatwy do zrozumienia, prosty do archiwizacji i banalny dla narzędzi AI.</p>
<h2>AI-friendly z założenia<a id="ai-friendly-z-założenia" href="#ai-friendly-z-założenia" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Zacząłem poważnie o tym myśleć, gdy zacząłem używać LLM-ów w codziennej pracy. Proszenie AI o pomoc przy generowaniu treści, tłumaczeniu stron czy walidacji schematów YAML jest proste, gdy format to zwykły tekst.</p>
<p>W przypadku CMS-a opartego na bazie danych trzeba by wyjaśniać schemat, pisać SQL, zarządzać migracjami. Z notACMS przekazujesz AI katalog plików Markdown i może ona bezpośrednio odczytywać, generować i edytować treści — bo format to po prostu tekst.</p>
<p>To nie jest funkcja dodana po fakcie. Dlatego właśnie system jest zaprojektowany w ten sposób.</p>
<h2>Fundament Symfony<a id="fundament-symfony" href="#fundament-symfony" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>Zbudowałem notACMS na Symfony 7 z tego samego powodu, dla którego używam Symfony do wszystkiego innego: jest jawny, typowany i dobrze udokumentowany. Kontener DI, komendy konsolowe, Twig — to wszystko standardowy Symfony. W infrastrukturze nie ma nic nowatorskiego.</p>
<p>Model treści to interesująca część. System routingu odczytuje <code>_routes.yaml</code> i generuje trasy Symfony z drzewa treści. Komenda budowania renderuje każdą trasę i zapisuje statyczny HTML. Warstwa serwisów jest lekka i wymienialna.</p>
<h2>Co dalej<a id="co-dalej" href="#co-dalej" class="heading-anchor" aria-hidden="true" title="Permalink">#</a></h2>
<p>To pierwsze wydanie to działający fundament. Rzeczy, które chcę zbudować:</p>
<ul>
<li>Integracja wyszukiwarki Pagefind (w v1.0.0)</li>
<li>Lepszy pipeline obrazów z generowaniem WebP</li>
<li>Pełniejsze wsparcie wielojęzyczne</li>
<li>Właściwa strona Design Reference (tym staje się ta strona)</li>
</ul>
<p>Repozytorium jest publiczne. Jeśli chcesz śledzić postępy lub wnieść wkład, link jest w stopce.</p>
]]></content:encoded>
                                    <category><![CDATA[releases]]></category>
                                    <category><![CDATA[announcement]]></category>
                        <category><![CDATA[open-source]]></category>
                    </item>
            </channel>
</rss>
