Kolorowe linie kodu HTML na ciemnym ekranie edytora

EmDash 1.2 — smaczki z changeloga i co szykuje 1.3

Nowy edytor, wideo i zmiana domeny to nagłówki EmDash 1.2. Ciekawsze są drobiazgi: cron mieszczący się w limicie Workers Free, poprawka XSS, route cache bez stron zalogowanych i koniec z „too many SQL variables” na D1. Do tego zapowiedź 1.3 z pełnym polskim tłumaczeniem panelu.

EmDash 1.2 wyszedł 6 października 2026, pięć dni po wersji 1.1. To duże wydanie: według oficjalnej zapowiedzi 100 scalonych pull requestów, w tym od 21 osób spoza głównego zespołu. Zapowiedź opisuje główne funkcje: nowy edytor, wideo, paginację i zmianę domeny. Ja przejrzałem cały changelog pakietów emdash, @emdash-cms/admin i @emdash-cms/cloudflare i wybrałem drobniejsze rzeczy, które łatwo przeoczyć, a które mają znaczenie, szczególnie na Cloudflare Workers i D1. Na końcu zajrzałem do otwartego PR-a z wydaniem 1.3.0, żeby sprawdzić, co jest w kolejce.

Najważniejsze w 1.2 w skrócie

  • Nowy edytor treści. Tytuł jest dużym nagłówkiem, pod nim edytor bez ramki, a jeden pasek na górze trzyma Live View, Preview i Publish. Slash menu podpowiada skróty Markdown i rozumie skróty typu /bl.
  • Blok wideo. Wpisujesz /video albo upuszczasz plik w tekście. Filmy z /_emdash/api/media/file/ wreszcie grają w Safari i na iOS, bo trasa obsługuje żądania Range (206 Partial Content).
  • Paginacja list. Kolekcja otwiera się z 20 wpisami zamiast pobierać 100.
  • Zmiana domeny z panelu. Okno Change domain w Settings → General najpierw sprawdza, czy nowa domena serwuje stronę, potem przestawia Site URL. Jest też mail do użytkowników z nowym adresem i przycisk Continue on, bo passkeye działają tylko na domenie, na której powstały.
  • upgrade-emdash. npx upgrade-emdash@latest aktualizuje wszystkie paczki EmDasha i zapisuje .emdash/UPGRADE.md z wpisami changeloga, które przeskakujesz. To dobry wsad dla agenta AI robiącego aktualizację.
  • Brak migracji bazy. Między 1.1.0 a 1.2.0 nie doszła żadna migracja rdzenia, więc aktualizacja nie zmienia schematu D1.

Smaczki z changeloga 1.2

Cloudflare Workers i D1

  • Cron mieści się w limicie planu Free (#3861). Cron uruchamiany co minutę na zimnym izolacie potrafił przekroczyć 10 ms CPU z planu Workers Free. Sprzątanie systemowe chodzi teraz raz na godzinę. Co minutę zostają tylko publikacja zaplanowanych wpisów, zadania cron wtyczek i heartbeat. Znam ten limit z własnych pomiarów, więc to dla mnie jedna z ważniejszych poprawek.
  • Log 404 nie zjada budżetu odczytów D1 (#3753). Wcześniej co minutę cron skanował całą tabelę _emdash_404_log, także gdy nie było czego usuwać. Teraz sprawdza ograniczoną próbkę.
  • Koniec z „too many SQL variables”. D1 ma limit parametrów w zapytaniu i w 1.2 poprawiono dwa miejsca, które go przekraczały: przypisanie więcej niż ~30 kategorii lub tagów do wpisu (#3889, psuło to też import z WordPressa) oraz ctx.storage.<kolekcja>.getMany()/deleteMany() we wtyczkach przy ponad 98 identyfikatorach (#3776).
  • Zapisy „po odpowiedzi” wreszcie się kończą. Liczniki przekierowań, log 404 (#3287), data ostatniego użycia tokenu API (#3767) i hooki comment:afterCreate (#3631) były urywane, bo Worker kończył pracę zaraz po wysłaniu odpowiedzi. Teraz działają przez after() / waitUntil.
  • Szybszy zimny start (#3573, #3574, #3575). Sprawdzenie bazy idzie równolegle ze startem runtime'u, ustawienia providerów wtyczek ładują się razem z innymi odczytami startowymi, a getWidgetAreas() zrobi o jedno zapytanie mniej dla niezalogowanych.

Cache i bezpieczeństwo

  • Route cache nie trzyma już stron zalogowanych (#3898). Z cacheCloudflare() strona wyrenderowana dla zalogowanego użytkownika (np. formularz komentarza z jego imieniem i e-mailem) mogła trafić do cache'u i pokazać się anonimowym odwiedzającym. Jeśli używasz Workers Cache z regułą typu /[...slug], zaktualizuj.
  • Stored XSS przez pasek edycji (#3681). EmDash wstawiał toolbar przed pierwszym </body> w odpowiedzi, a Astro nie escapuje < i > w atrybutach. Tekst alternatywny obrazka zawierający </body> przesuwał więc pasek do środka atrybutu. Autor mógł w ten sposób uruchomić skrypt u każdego zalogowanego redaktora, a przy toolbar: "client" u każdego odwiedzającego. To najmocniejszy argument za aktualizacją.
  • ETag i 304 dla mediów (#3855). Pliki mediów i transformacje /image mają ETag i Last-Modified, więc przeglądarka może odświeżyć cache jednym żądaniem warunkowym zamiast pobierać plik od nowa.
  • Konflikty zapisu (#3930). PUT z _rev nie nadpisze już zmiany, którą ktoś inny zapisał w trakcie przetwarzania żądania.

Wtyczki

  • plugin:install i plugin:activate wreszcie się uruchamiają (#3785) dla wtyczek z tablicy plugins w astro.config.mjs. Jeśli planowałeś crona w plugin:activate i nic się nie działo, to był ten błąd.
  • Hooki media:beforeUpload i media:afterUpload działają przy uploadzie z panelu (#3914).
  • Grupy w menu bocznym (#3546). Strony admina wtyczek można wkładać do zwijanych folderów obok kolekcji.
  • Szczegóły błędów (#3602). PluginRouteError.badRequest(message, details) zwraca details do klienta, więc formularz może pokazać, które pole jest złe.

Treść i SEO

  • Sitemapy powyżej 50 000 wpisów (#3806). Wcześniej nadmiarowe wpisy po cichu wypadały. Teraz każda /sitemap-{kolekcja}.xml ma do 2000 wpisów i ciąg dalszy w -2.xml, -3.xml itd. Do tego /sitemap-0.xml, o którą pytają crawlery, zwraca 404 zamiast 500 (#3595).
  • Podgląd szkiców na listach (#3802). getEmDashCollection() w trybie edycji i podglądu pokazuje teraz wersję roboczą, tak jak getEmDashEntry(). Edycje inline na stronie listy nie wyglądają już, jakby się cofnęły po zapisie.
  • Brakujący prefiks języka w entry.id (#3922). Na stronach wielojęzycznych z D1 entry.id czasem nie miał prefiksu typu en/, co psuło linki.
  • Import z WordPressa jest lepszy. Tabele z Classic Editora zostają tabelami (#3762), zaplanowane wpisy przychodzą jako zaplanowane (#3895), a linki do starych uploadów poza blokami obrazków są przepinane na zaimportowane media (#3896).

Panel i tłumaczenia

  • admin.locales (#3056). Ograniczasz panel do języków, których używasz, np. admin: { locales: ["en", "pl"] }, i bundle admina robi się mniejszy. Niezależnie od tego date picker ładuje lokalizację na żądanie, co daje około 220 KB mniej JavaScriptu (#3923).
  • Nowe języki: hebrajski z układem RTL (#3293), portugalski europejski (#3796), kompletny tajski, niemiecki, kataloński i chiński tradycyjny.
  • Domyślne wartości pól (#3758). Pole boolean z defaultValue: true w nowym wpisie startowało wyłączone. Już nie.
  • syncName: false (#3875) dla Cloudflare Access i innych zewnętrznych providerów. Imię zmienione w panelu nie jest już nadpisywane przy każdym żądaniu.

Co szykuje 1.3.0

Wydanie 1.3.0 czeka w otwartym PR-ze z release'em , który Changesets aktualizuje przy każdym scaleniu. Lista może się więc jeszcze zmienić, ale na dziś wygląda tak.

Pełne polskie tłumaczenie panelu (#3970)

Najważniejsza rzecz dla polskich użytkowników. Do tej pory 2255 z 3384 tekstów panelu spadało do angielskiego. W 1.3 polski panel ma być kompletny: kalendarz publikacji, edytor, zmiana domeny, bylines, relacje, transfer strony, kopie zapasowe, rejestr wtyczek i konfiguracja passkeyów.

Edycja wizualna dogania panel

  • Edytor rich text na stronie dostaje ten sam pasek zaznaczenia i slash menu co panel (#4117) oraz wybór i pasek narzędzi obrazków (#4137).
  • Puste pola Portable Text, np. treść nowego wpisu, da się wreszcie pisać bezpośrednio na stronie (#3942).
  • Zapis przy utracie fokusu nie dodaje już nowej rewizji przy każdym kliknięciu obok pola, tylko podmienia szkic, tak jak autosave w panelu (#3942).
  • Kliknięcie pola stawia kursor w miejscu kliknięcia zamiast zaznaczać cały tekst, przez co pierwszy znak kasował całą wartość (#4014).

Cache: mniej purge'owania, poprawne przekierowania

  • Utworzenie lub zduplikowanie wpisu nie czyści już cache'u stron (#4128). Nowy wpis jest szkicem, którego żadna publiczna strona nie pokazuje, więc listy kolekcji zostają w cache'u do następnej zmiany w opublikowanej treści.
  • Nowe przekierowanie czyści z Workers Cache stronę pod tą ścieżką (#3982). Do tego middleware przed zapisem do route cache sprawdza, czy pod tym adresem nie ma już przekierowania (#3997), bo izolat ze starymi regułami potrafił z powrotem wpisać do cache'u starą stronę.
  • Nieudany purge nie zgłasza już błędu zapisu, skoro zmiana i tak się zapisała (#3987).

D1 i migracje

  • emdash migrate --take-over-lock <id> (#4068) stosuje oczekujące migracje pod zablokowanym lockiem D1, bez zwalniania go. Przy --release-lock i ponownym migrate Worker w trybie auto mógł przejąć wolny lock w międzyczasie.
  • Migracja normalizacji dat da się wznowić (#4072). Na dużych bazach D1 trwała godziny i po każdym przerwaniu zaczynała od nowa. Teraz zapisuje w partiach i kontynuuje od ostatniej.
  • getComments() w jednym zapytaniu (#3907), gdy zatwierdzone komentarze mieszczą się na jednej stronie. Plus do dwóch zapytań mniej przy zimnym starcie dla stron z rejestrem wtyczek (#3584).
  • Ostrzeżenie o niezgodnych wersjach (#4058). astro build ostrzeże, gdy @emdash-cms/cloudflare ma inną wersję niż emdash. Adapter zależy od dokładnej wersji rdzenia, więc aktualizacja tylko jednego pakietu instaluje drugą kopię emdash.
  • Filtr where z długimi tablicami (#3265). { in: [...] } z długą listą nie wywala się już na limicie parametrów D1.

Uprawnienia i MCP

  • Contributor wreszcie może zapisywać własne szkice (#4133). Wcześniej mógł utworzyć wpis, ale następny zapis kończył się błędem 403.
  • Narzędzie MCP content_create przyjmuje seo i publishedAt w jednym wywołaniu (#4102), więc agent nie musi już robić osobnego content_update.
  • Narzędzia MCP wtyczek z ID typu @acme/calendar dostają nazwy acme__calendar__<tool> (#988). Jeśli masz zapisane wywołania po starych nazwach, trzeba je zmienić.

Panel i reszta

  • Edytor menu pozwala zmienić nazwę menu i ustawić klasy CSS pozycji (#3969).
  • Byline'y tworzysz z wpisu tym samym pełnym formularzem co na stronie Bylines (#4050).
  • Otwarcie wpisu bez żadnej edycji nie zapisuje już szkicu (#4035). Dotyczyło to m.in. treści zapisanych przez MCP lub markdownToPortableText.
  • Wyczyszczone pole liczbowe zapisuje się jako puste, a nie 0 (#3791). Wyczyszczony opcjonalny URL to null, a nie pusty string odrzucany przy walidacji (#4076).
  • Import z WordPressa przez wtyczkę EmDash Exporter zachowuje pola ACF (#4092). Import XML pomija typy acf_field (#4090), a importer wysyła nagłówek User-Agent, bo niektóre hostingi blokowały puste (#4113).
  • Wyszukiwanie pełnotekstowe nie traktuje już słów „and”, „or”, „not” czy „near” jako operatorów FTS5 (#3202).
  • Nowe tłumaczenia: chorwacki (#3934), uzupełniony francuski, niemiecki, hindi i szwedzki, a także pierwszy katalog włoski.

Czy aktualizować?

Do 1.2 tak, i to głównie z powodu bezpieczeństwa: XSS przez pasek edycji (#3681) i strony zalogowanych w route cache (#3898) to błędy, których nie chcesz mieć na produkcji. Na Workers Free dochodzi do tego cron mieszczący się w limicie CPU. Aktualizacja nie zawiera migracji bazy, a npx upgrade-emdash@latest przygotuje listę zmian do przejrzenia. Pamiętaj tylko o podbiciu emdash i @emdash-cms/cloudflare razem. Od 1.3 dostaniesz o tym ostrzeżenie przy buildzie, w 1.2 musisz pilnować tego sam.

Aktualizacja emdashcms.pl: poszło gładko

emdashcms.pl działa już na 1.2.0. npx upgrade-emdash@latest podbił emdash i @emdash-cms/cloudflare (razem, jak trzeba) oraz wtyczkę webhook-notifier. Nie dodał żadnej migracji, a z 68 wpisów changeloga w wygenerowanym .emdash/UPGRADE.md żaden nie wymagał zmian w kodzie strony.

Przy okazji przestawiłem język treści na polski. W astro.config.mjs wystarczy i18n: { defaultLocale: "pl", locales: ["pl"] }. Przy jednym języku EmDash nie włącza filtrowania po języku ani prefiksów /pl/ w adresach, więc nowe wpisy dostają tylko etykietę pl. Istniejące treści trzeba przestemplować samemu, jednym UPDATE na tabelach z kolumną locale, bo panel tego nie zrobi.

Jedyna niespodzianka: upgrader odświeża też skille dla agentów AI w .agents/skills i doinstalowuje nowe (u mnie trzy). Przejrzyj diff przed commitem.

Pomiary przed i po: 10 rund na stronę, z pominięciem cache, z przerwą 1,5 s między żądaniami (Workers Free, D1).

Strona

render p50 · 1.1

render p50 · 1.2

TTFB p50 · 1.1

TTFB p50 · 1.2

Strona główna

94 ms

108 ms

335 ms

380 ms

Kategoria

91 ms

101 ms

336 ms

362 ms

Lista wpisów

102 ms

95 ms

338 ms

337 ms

Wpis

114 ms

122 ms

360 ms

403 ms

Różnice wynoszą od −7 do +14 ms, a p95 skacze w tych samych seriach o 100–200 ms, więc to szum. Pomiar po aktualizacji robiłem chwilę po deployu, na świeżych izolatach, które zwykle są trochę wolniejsze. Wszystkie 80 żądań zwróciło 200. Wniosek: 1.2 nie przyspiesza ani nie spowalnia renderowania. Aktualizujesz dla poprawek bezpieczeństwa i spokoju.

Brak komentarzy