Changelog
Historia wersji Kolofonu, wersja po wersji, prosto z gita — z godzinami commitów włącznie. Co weszło, co się zepsuło i co trzeba było naprawić kwadrans później.
Kolofon · 22 września 2026 · wstępna

0.13.29 — zostaje przy wpisie
Poprawka do 0.13.28: zalogowany czytelnik z inną preferencją języka, który otworzy wpis z serii nietłumaczonej, zostaje przy tym wpisie zamiast lądować na stronie startowej drugiej wersji. Wersja 0.13.28 przenosi zalogowanego czytelnika do wersji językowej zapisanej na koncie, na odpowiednik strony, na którą wszedł. Test na żywo pokazał jeden przypadek, w którym to prowadziło w złe miejsce: wpis z serii, która nie ma tłumaczenia. Taki wpis nie podaje przełącznikowi języka klucza, więc jego „odpowiednikiem” jest strona startowa drugiej wersji. Czytelnik, któremu ktoś przysłał odnośnik do takiego tekstu, zamiast go przeczytać, lądował na cudzej stronie głównej. Od 0.13.29 strona bez klucza przekierowuje tylko wtedy, gdy sama jest stroną startową. Wpis bez odpowiednika zostaje na miejscu — w języku, w którym istnieje.
Kolofon · 22 września 2026 · wstępna

0.13.28 — język z konta
Konto pamięta wersję językową: tę, w której je założono, a potem tę, na którą czytelnik się świadomie przełączył. Zalogowany czytelnik trafia do swojej wersji, na odpowiednik strony, na którą wszedł. Konto w instancji dwujęzycznej jest jedno dla obu wersji — ta sama baza, ten sam login. Do tej pory jednak nie pamiętało, w którym języku czytelnik czyta. Kto założył konto po angielsku, a potem wszedł na stronę z zakładki albo z linku, lądował w wersji polskiej i za każdym razem musiał przełączać język. Od 0.13.28 konto dostaje język wersji, w której je założono. Potem każde świadome przełączenie — w menu konta albo w pasku z propozycją drugiej wersji — zapisuje się jako nowa preferencja. Zalogowany czytelnik, który wejdzie do innej wersji niż zapisana, trafia na odpowiednik tej samej strony w swojej: ten sam wpis, tę samą serię, stronę startową zamiast strony startowej. Trzy bezpieczniki. Przekierowanie następuje tylko wtedy, gdy odpowiednik istnieje — wpis z serii nietłumaczonej zostaje na miejscu, zamiast prowadzić na 404. Najwyżej jedno przekierowanie na kilka sekund, więc nie ma jak zrobić pętli. I zapis preferencji nigdy nie wstrzymuje przejścia na dłużej niż ułamek sekundy. Język przeglądarki dalej niczego nie rozstrzyga. Preferencja bierze się wyłącznie z wyboru czytelnika — wersji, w której założył konto, i kliknięć w przełącznik — a czytelnik bez konta widzi to, co pod adresem, na który wszedł.
Kolofon · 22 września 2026 · wstępna

0.13.27 — konto pod przedrostkiem
W wersji językowej pod przedrostkiem ścieżki odnośniki do konta i przekierowania po zalogowaniu gubiły przedrostek. Teraz czytelnik trafia na konto swojej wersji i po zalogowaniu wraca dokładnie do wpisu, który czytał. Druga wersja językowa może mieszkać pod przedrostkiem ścieżki, na przykład `/en`. Router zna trasy bez przedrostka i dokleja go sam do każdego swojego odnośnika. Kłopot jest z adresami, które omijają router: zwykłymi odnośnikami i nagłówkami przekierowań, które przeglądarka czyta wprost. Tak było z kontem. Przycisk w murze, zachęcający do założenia konta, był zwykłym odnośnikiem i prowadził na `/account` — adres wersji domyślnej, która takiej strony nie ma, więc czytelnik dostawał 404, i to w cudzym języku. Adres powrotu też tracił przedrostek. A serwer po zalogowaniu odnośnikiem z maila odsyłał na gołą ścieżkę konta albo na `/`, czyli znowu do wersji domyślnej. Od 0.13.27 wszystkie adresy silnika, które omijają router, dostają przedrostek bieżącej wersji: odnośnik muru, strona konta w przekierowaniach po logowaniu — odnośnikiem i przez dostawców zewnętrznych — oraz strona startowa. Adres powrotu jest traktowany jako adres przeglądarki, z przedrostkiem, więc po kliknięciu w mail czytelnik ląduje dokładnie na wpisie, od którego zaczął. Wersja w korzeniu domeny zachowuje się jak dotąd — jej przedrostek jest pusty.
Kolofon · 22 września 2026 · wstępna

0.13.26 — bez obietnic
Po wycofaniu trybu offline silnik przestał go obiecywać: propozycja instalacji nie mówi już o czytaniu bez sieci, a trasa manifestu i strona offline zniknęły razem z resztą martwego kodu. Wersja 0.13.25 wycofała tryb offline, ale obietnica została w kilku miejscach. Propozycja instalacji mówiła czytelnikowi, że będzie czytał „także bez sieci”, opisy funkcji zapowiadały pakiet wpisów pobierany na zapas, a w kodzie stała trasa manifestu i szablon strony offline, których nikt już nie wołał. Od 0.13.26 propozycja instalacji mówi tylko to, co prawdziwe: aplikacja prosto z ekranu głównego. Trasa `/offline-manifest.json` i szablon strony offline zniknęły, a skrypt generujący pliki statyczne przestał ją tworzyć. Dokumentacja silnika i opis funkcji „07 · Aplikacja” opisują stan obecny. Starsze eseje z serii Architektura, które opisywały budowę trybu offline, zostają — dostały na początku notkę, że opisany mechanizm wycofano. To zapis tego, jak silnik powstawał, a nie jego instrukcja obsługi.
Kolofon · 22 września 2026 · wstępna

0.13.25 — tylko na życzenie
Kolofon nie ma już trybu offline. Service worker służy wyłącznie powiadomieniom: niczego nie pobiera z wyprzedzeniem, nie przechwytuje żądań i nie trzyma kopii stron. Wersja 0.13.24 okiełznała tryb offline: zamiast pobierać wszystkie wpisy przy pierwszej wizycie, pobierała dziesięć, z opóźnieniem i z przerwami. Następny krok był prostszy — tryb offline znika zupełnie. Strona ładuje się wtedy, gdy czytelnik o nią prosi, i ani bajta wcześniej. Service worker zostaje, ale z jednym zadaniem: powiadomienia push. Nie pobiera niczego z wyprzedzeniem, nie ma obsługi zdarzenia `fetch`, więc każde żądanie idzie prosto do sieci, jakby service workera nie było, i nie trzyma żadnej pamięci podręcznej. Przy aktywacji nowa wersja usuwa wszystkie pamięci podręczne pozostawione przez poprzednie. Czytelnik, który miał w telefonie zapisane kopie stron sprzed tygodnia, nie zobaczy ich już zamiast aktualnych — przy następnej wizycie wszystko przychodzi świeże. Z silnika zniknął komponent, który prosił service worker o zapełnianie pamięci. Szablon service workera skurczył się z ponad trzystu linii do osiemdziesięciu.
Kolofon · 22 września 2026 · wstępna

0.13.24 — offline bez fali
Zapełnianie pamięci offline przestało obciążać serwer przy pierwszej wizycie. Zamiast wszystkich wpisów naraz: dziesięć najnowszych, dopiero po dwudziestu sekundach, z pauzą między pobraniami i nigdy przy oszczędzaniu… Instancje mogą działać offline: service worker trzyma w pamięci wpisy, żeby dało się je czytać bez sieci. Zapełnianie tej pamięci było jednak za gorliwe. Service worker zaraz po instalacji pobierał wszystkie wpisy z manifestu, kilka sekund później strona prosiła go o to samo drugi raz, a zmiana wersji językowej dokładała trzeci komplet. W jednej z instancji to ponad sto wpisów w każdym języku. Zmierzyliśmy to na żywo, w logach workera: jedno wejście na świeżym telefonie i kliknięcie przełącznika języka dało 267 żądań w ciągu 75 sekund, z czego 256 to strony pobierane w tle. Każda wymaga renderowania po stronie serwera, więc na planie z limitem czasu procesora część żądań — także tych, o które prosił sam czytelnik — kończyła się błędem 1102. Do tego kilka megabajtów danych komórkowych, o które nikt nie pytał. Od 0.13.24 service worker przy instalacji zapisuje tylko szkielet strony. Po wpisy prosi go wyłącznie strona, i to ostrożnie: dopiero dwadzieścia sekund po załadowaniu, najwyżej dziesięć najnowszych, raz na sesję i wersję językową. Przy włączonym oszczędzaniu danych albo na połączeniu 2G — wcale. Sam service worker robi półtorej sekundy pauzy między pobraniami i nie przyjmuje drugiej kolejki, dopóki trwa pierwsza. Tryb offline dalej działa: najnowsze wpisy są pod ręką, a każda odwiedzona strona i tak trafia do pamięci przy zwykłym czytaniu. Szablon service workera w silniku
Kolofon · 22 września 2026 · wstępna

0.13.23 — wektory na żądanie
Wektory wyszukiwarki mogą się teraz wczytywać dopiero przy pierwszym wyszukiwaniu. Główny kawałek paczki serwera w największej instancji spadł z 5 do 1,3 MB, a zimny start workera przestał przetwarzać indeks dla stron… Wektory znaczeniowe instancja podaje silnikowi globem modułów JSON. Do tej pory był to glob gorliwy: wszystkie wektory wchodziły do głównego kawałka paczki serwera i były przetwarzane razem z resztą kodu. W największej instancji to 3,4 z 5 MB tego kawałka — ponad dwie trzecie. Worker przetwarza główny kawałek przy każdym zimnym starcie, czyli za każdym razem, gdy żądanie trafia do świeżego procesu. Działo się to także przy stronach, które z wyszukiwarki w ogóle nie korzystają. Na planie z niskim limitem czasu procesora część takich żądań kończyła się błędem 1102 — najłatwiej było go wywołać, klikając szybko na świeżym telefonie, bo kilka równoległych żądań trafiało wtedy do nowych procesów. Od 0.13.23 moduły wektorów mogą być leniwe: instancja podaje glob bez `eager: true`, a silnik wczytuje je dopiero przy pierwszym wyszukiwaniu w danym procesie. Główny kawałek paczki w największej instancji spadł z 5 do 1,3 MB. Pierwsze wyszukiwanie w nowym procesie płaci za wczytanie indeksu, kolejne idą po pamięci; dwa równoczesne wyszukiwania nie wczytują go dwa razy. Glob gorliwy działa jak dotąd, więc instancja, która nic nie zmieni, zachowuje się po staremu. Zmiana instancji to jedna linijka w `instance.ts`.
Kolofon · 21 września 2026 · wstępna

0.13.22 — wynik w języku strony
Wyszukiwarka znaczeniowa w wersji bez własnych wektorów pokazuje teraz tytuł, adres i fragment z wpisu w języku strony, a nie w języku, z którego policzono wektory. Wektory znaczeniowe mogą być wspólne dla wszystkich wersji językowych instancji. To świadoma decyzja: model jest wielojęzyczny, a klucz wpisu jest tożsamością materiału ponad językiem, więc pytanie po angielsku trafia w ten sam wpis co pytanie po polsku. Osobny indeks dla każdego języka byłby tą samą treścią policzoną drugi raz — i drugi raz ważyłby w paczce. Tyle że kawałek wektora niesie tekst, z którego go policzono, a wynik pokazywał właśnie ten tekst. W wersji bez własnych wektorów czytelnik pytał po angielsku i dostawał fragment po polsku. Od 0.13.22 tytuł, adres i fragment pochodzą zawsze z wpisu w języku strony. Trafiony akapit przekładamy po pozycji: kawałek to dosłownie akapit wpisu źródłowego, a tłumaczenie trzyma układ akapitów, więc wystarczy znaleźć jego miejsce i wziąć akapit spod tego samego numeru. Gdy układ się nie zgadza, zamiast tekstu w obcym języku pokazujemy zajawkę wpisu. Wpis, którego w danym języku nie ma, nie trafia do wyników — jego miejsce zajmuje następny. Przycinanie fragmentu z 0.13.18 działa już na tekście w języku strony, więc okno ustawia się na zdaniu, które dzieli rdzeń słowa z pytaniem zadanym w tym samym języku.
Kolofon · 21 września 2026 · wstępna

0.13.21 — ze startu na start
Na stronie startowej, która pokazuje strumień, przełącznik języka i pasek z propozycją drugiej wersji prowadzą teraz na stronę startową drugiej wersji, a nie pod adres jej strumienia. Przełącznik języka prowadzi do tego samego materiału w drugiej wersji. Robi to po stałym kluczu: wpis, seria albo strona wbudowana ma klucz wspólny dla wszystkich języków, a trasa `/i/<klucz>` zamienia go na adres w danym języku. Strumień też ma taki klucz. Kłopot w tym, że strona startowa często pokazuje strumień — z własnym wstępem nad siatką, ale tym samym widokiem. Widok podawał klucz strumienia, więc przełącznik ze strony startowej prowadził pod adres strumienia w drugiej wersji: z `/` na `/en/stream`, a z `/en` na `/strumien`. Czytelnik, który chciał tylko zmienić język, lądował na innej stronie niż ta, z której przyszedł. Od 0.13.21 widok strumienia rozpoznaje, że stoi na stronie startowej, i wtedy nie podaje klucza. Bez klucza przełącznik i pasek z propozycją drugiej wersji prowadzą na stronę startową: z `/` na `/en` i z powrotem. Pod własnym adresem strumień zachowuje się jak dotąd. Rozpoznanie idzie po adresie, a nie po nowym parametrze. Żadna instancja nie musi niczego dopisywać — poprawka działa wszędzie, gdzie strona startowa pokazuje strumień.
Kolofon · 21 września 2026 · wstępna

0.13.20 — numer części
Wpis może nieść numer części. Kafelek w liście serii i w strumieniu stawia go przed tytułem — „1. Tytuł” — a sam tytuł na stronie wpisu, w kanale i na karcie podglądu zostaje bez numeru. Serie czytane po kolei (`ordering: "sequence"`) stoją na liście od pierwszego wpisu. Kolejność była więc dobra, ale kafelki nie mówiły, która to część. Przy serii w odcinkach czytelnik, który wraca po tygodniu, musiał policzyć kafelki od góry, żeby wiedzieć, gdzie skończył. Od 0.13.20 wpis może mieć pole `part` — numer części. Kafelek stawia go przed tytułem: „1. Tytuł”, „2. Tytuł”. Dzieje się to wszędzie, gdzie jest kafelek, czyli w liście serii i w strumieniu, gdzie numer pomaga szczególnie, bo obok stoją wpisy z innych serii. Numer podaje się jawnie, a nie wylicza z pola `order`. Powód jest praktyczny: seria w odcinkach często zaczyna się od wstępu, który stoi w kolejności jako pierwszy, ale częścią nie jest. Numeracja wyliczona z kolejności przesunęłaby wszystko o jeden. Wpis bez pola `part` wygląda dokładnie tak jak dotąd. Sam tytuł zostaje bez zmian. Strona wpisu, kanał RSS i karty podglądu w mediach społecznościowych niosą tytuł bez numeru — numer jest informacją o miejscu w liście, a nie częścią nazwy tekstu.
Kolofon · 21 września 2026 · wstępna

0.13.19 — tyle kolumn, ile ekranu
Czytelnik, który jeszcze nie wybrał liczby kolumn, dostaje tyle, ile mieści jego ekran: trzy na monitorze, dwie na tablecie, jedną na telefonie. Przełącznik zaznacza to, co faktycznie widać. Listy wpisów mają przełącznik liczby kolumn: jedna, dwie albo trzy — tyle, ile mieści ekran. Wybór czytelnika zapisuje się w przeglądarce, a po zalogowaniu także na koncie. Kłopot był z tymi, którzy jeszcze niczego nie wybrali, czyli z każdym, kto przychodzi pierwszy raz. Seria-lista startowała od jednej kolumny, galeria od dwóch, strumień od jednej. Na monitorze oznaczało to wąski pas tekstu pośrodku pustego ekranu, choć przełącznik obok proponował trzy. Od 0.13.19 brak wyboru znaczy „tyle, ile się zmieści”: trzy kolumny na monitorze, dwie na tablecie, jedna na telefonie. Tej wartości nie zapisujemy. Zapisuje się tylko to, co czytelnik sam kliknie — dlatego ten sam nowy czytelnik dostaje trzy kolumny na komputerze i jedną w telefonie, a nie jedną wszędzie, bo tak wypadło przy pierwszej wizycie. Wartość domyślna serii nie znika, ale decyduje już tylko o pierwszym renderze, zanim przeglądarka zmierzy ekran. HTML z serwera musi być ten sam dla wszystkich, a jedna kolumna na starcie nie przeskakuje na telefonie, gdzie czyta większość ludzi. Przy okazji przełącznik zaczął zaznaczać to, co widać. Wybór „trzy” na ekranie, który mieści dwie, pokazywał dwie kolumny, ale nie podświetlał żadnego przycisku. Teraz podświetla dwójkę — przycisk mówi to samo co ekran.
Kolofon · 21 września 2026 · wstępna

0.13.18 — fragment na miarę
Fragment pod wynikiem wyszukiwania znaczeniowego ma teraz najwyżej 300 znaków. Tnie się po zdaniach, a okno zaczyna od zdania, które dzieli z pytaniem rdzeń słowa. Wyszukiwanie znaczeniowe od początku pokazuje pod każdym tytułem akapit, który zadecydował o trafieniu — żeby czytelnik widział, dlaczego dostał ten tekst. Tyle że indeks liczy wektory z całych akapitów, a akapity bywają bardzo różne: od jednego zdania do kilkunastu. Najdłuższe przekraczają tysiąc znaków. Trzy takie trafienia to kilka ekranów telefonu, zanim czytelnik zobaczy drugi tytuł. Od 0.13.18 fragment ma najwyżej 300 znaków. Cięcie idzie po zdaniach, nie po znakach: do limitu wchodzą pełne zdania, bo urwane w pół słowa zdanie czyta się jak błąd. Tylko pojedyncze zdanie dłuższe od limitu tniemy na granicy słowa. Wielokropek stoi po tej stronie, po której coś zostało pominięte. Zostaje pytanie, które zdania pokazać. Wyszukiwanie jest znaczeniowe i nie wie, które zdanie akapitu przeważyło. Ale kiedy pytanie i akapit dzielą rdzeń słowa — jak „masoneria” i „masońskim” — to zdanie jest najlepszym dowodem trafienia, więc od niego zaczyna się okno. Rdzeń to pierwsze pięć liter po zdjęciu znaków diakrytycznych, liczony tylko dla słów od czterech liter. Bez wspólnego rdzenia okno zaczyna się od początku akapitu. Indeks i wektory się nie zmieniają. Przycięcie dzieje się przy odpowiedzi, więc przebudowa nie jest potrzebna, a limit to jedna stała silnika, `FRAGMENT_MAX`.
Kolofon · 16 września 2026 · wstępna

0.13.17 — liczniki bez zer
Dwa liczniki na stronie wpisu przestają pokazywać zniechęcające zera. Licznik odsłon pojawia się dopiero od dziesiątej odsłony; pod komentarzami znika wiersz z liczbą — komentarze widać tylko wtedy, gdy są. Pod świeżo wystawionym wpisem stały dwie liczby, obie działające przeciwko niemu. „1 odsłona” w metryczce i „0 komentarzy” nad sekcją komentarzy. Każda z nich mówi czytelnikowi to samo: nikogo tu nie ma. To jest dokładnie ten sygnał, którego świeży tekst nie powinien wysyłać. Od 0.13.17 licznik odsłon pojawia się dopiero od progu — dziesięciu odsłon. Poniżej znika w całości. Nie chowamy pomiaru, tylko wyświetlacz: odsłona jest dalej rejestrowana tak samo jak wcześniej, po prostu nie pokazujemy liczby, dopóki nie urośnie do wielkości, która niczego nie zdradza. Próg stoi w jednej stałej silnika, `MIN_VIEWS_SHOWN`. Sekcja komentarzy traci wiersz z liczbą. Nie ma już „0 komentarzy” ani „3 komentarze” — liczby nie pokazujemy w ogóle. Komentarze widać wyłącznie wtedy, gdy są, a pod nimi, jak dotąd, stoi zaproszenie do napisania. Przy okazji z silnika wypada ostatni napis trzymany w nim na sztywno: awaryjne „Komentarze” w czasie ładowania. Odmiana liczby mnogiej komentarzy przestała być do czegokolwiek potrzebna i też znika. Obie zmiany mają jedno uzasadnienie. Licznik jest ozdobą, nie funkcją; kiedy pokazuje kilka albo zero, przestaje być ozdobą, a staje się świadectwem pustki. Lepiej nie pokazywać nic, niż pokazywać liczbę, która zniechęca. Kiedy będzie co liczyć, liczby wrócą same — od progu, na którym zaczynają znaczyć.
Kolofon · 14 września 2026 · wstępna

0.13.16 — strona konta bez cennika
Formularz logowania otwierały kafelki z planami kont i ich cenami. Cennik przed polem na adres każe rozstrzygać, ile to kosztuje, zanim czytelnik zrozumie, po co miałby zakładać konto. Strona konta zaczynała się od kafelków z planami. Każdy niósł nazwę, cenę i krótki dopisek, a cena mogła mieć obok siebie poprzednią, przekreśloną — wszystko z napisów instancji. Kafelki stały tam z rozsądnego powodu: żeby czytelnik wiedział, że konto darmowe nie jest okrojoną wersją czegoś, czego nie widzi. Powód okazał się słabszy niż koszt. Cennik postawiony przed polem na adres każe rozstrzygnąć pytanie o pieniądze, zanim padnie pytanie właściwe: po co w ogóle to konto. Przekreślona cena dokłada do tego ton wyprzedaży, a plan oznaczony jako jeszcze niedostępny obiecuje coś, czego nie da się kupić nawet wtedy, gdyby ktoś chciał. Strona, która ma przyjąć adres, otwierała się reklamą. Od 0.13.16 kafelków nie ma. Zniknęły razem z siedmioma napisami z rodziny `plan*` — nazwy, ceny, ceny poprzedniej i dopiski obu planów — więc instancje nie muszą ich już deklarować w bloku `ui`. To nie jest ukrycie przez wyzerowanie konfiguracji: kodu po prostu nie ma, a strażnik napisów nie pyta o klucze, których silnik nie zna. Mur dla wpisów wykraczających poza konto podstawowe zostaje nietknięty. `wallPremiumBody` i `wallPremium` to osobny mechanizm — działa na wpisie, nie na stronie logowania, i z kafelkami planów nie miał nigdy nic wspólnego.
Kolofon · 13 września 2026 · wstępna

0.13.15 — wynik wyszukiwania widać, nie tylko jest
Wyniki dostawiały się pod polem bez ruchu i bez przewinięcia. Na telefonie wyglądało to jak przycisk, który nie działa — lista leżała poniżej krawędzi ekranu, a strona stała w miejscu. Zgłoszenie brzmiało tak, jak brzmią najlepsze zgłoszenia: nacisnąłem „szukaj” i nie zauważyłem, że coś się pojawiło. Wyniki były na stronie, komplet trzech, każdy z fragmentem. Tylko że dostawiały się pod polem bez żadnego ruchu, a strona stała w miejscu — więc na telefonie, gdzie pole siedzi w połowie ekranu, cała lista lądowała poniżej krawędzi. Przycisk wyglądał na zepsuty. Od 0.13.15 dzieją się po odpowiedzi dwie rzeczy. Strona przewija się do pola, tak żeby pytanie i pierwszy wynik stały obok siebie, a odstęp od paska nagłówka trzyma `scroll-mt-24` — bez tego pasek zasłaniałby własne pole. Wyniki wchodzą jeden po drugim, co siedemdziesiąt milisekund, krótkim wjazdem z przezroczystości. Ruch trwa trzy dziesiąte sekundy, bo ma powiedzieć „tu patrz”, a nie zabawiać. Komunikat o braku wyników wchodzi dokładnie tak samo. Brak odpowiedzi też jest odpowiedzią i też trzeba go zauważyć — inaczej cisza po naciśnięciu przycisku znaczy jednocześnie „nic nie znalazłem” i „nic się nie stało”. Przy ograniczeniu ruchu zgłoszonym przez system zostaje samo przewinięcie, wykonane skokiem, bez animacji. Element, który zjeżdża i gaśnie, jest właśnie tym, przed czym to ustawienie ma chronić — ale ukrycie wyniku poza ekranem nie jest dostępnością, tylko inną awarią.
Kolofon · 13 września 2026 · wstępna

0.13.14 — lista przykładów bierze tablicę zamrożoną
Nowy napis z 0.13.13 miał typ `string[]`, a konfiguracja instancji stoi za `as const`. Pierwsza instancja, która chciała z listy skorzystać, nie przeszła kontroli typów. Lista przykładów wprowadzona w 0.13.13 miała typ `string[]`. Konfiguracja instancji stoi w całości za `as const` — to ona pilnuje, żeby wartości z konfiguracji nie dało się przypadkiem podmienić w czasie działania — więc tablica przychodzi stamtąd jako `readonly`. Kontrola typów odrzucała każdą instancję, która chciała z nowej listy skorzystać, i nie dało się tego obejść inaczej niż rezygnacją z `as const` w całym pliku. Od 0.13.14 `searchExamples` przyjmuje `readonly string[]`. Silnik i tak tylko czyta tę listę, więc nic na tym nie traci. Poprawka wyszła przy pierwszym użyciu, jeszcze zanim 0.13.13 gdziekolwiek stanęło, i idzie osobnym numerem. Wydanie, które już wyszło, opisuje to, co robiło — poprawek do niego nie doklejamy.
Kolofon · 13 września 2026 · wstępna

0.13.13 — zachęta w wyszukiwarce wpisuje się sama
Wyszukiwarka znaczeniowa chce zdania, a puste pole wygląda jak każde inne pole wyszukiwania w internecie — więc dostaje słowo. Zamiast prosić o zdanie instrukcją, silnik pokazuje je przykładem, który wpisuje się sam. Wyszukiwarka znaczeniowa, którą silnik dostał w 0.13.7, nie szuka po słowach: zapytanie idzie do modelu i porównuje się z akapitami wpisów. Działa najlepiej wtedy, gdy czytelnik napisze całym zdaniem, po co przyszedł. Kłopot w tym, że puste pole z lupą wygląda jak każde inne pole wyszukiwania w internecie, a tam wpisuje się jedno słowo. Czyli dokładnie to, co w tej wyszukiwarce działa najgorzej. Pierwszym odruchem była instrukcja w zachęcie — „całym zdaniem, własnymi słowami”. Zdanie jest prawdziwe i nic nie daje: brzmi jak polecenie z ćwiczenia, a ludzie i tak czytają pole, nie podpis nad nim. Od 0.13.13 puste pole podpowiada inaczej. Przykładowe zapytanie wpisuje się w nie znak po znaku, przystaje na chwilę, kasuje się i ustępuje następnemu. Nikt niczego nie każe; widać, jak to ma wyglądać. Animacja gaśnie przy pierwszym dotknięciu pola — kliknięciu, ustawieniu kursora, dowolnym klawiszu — i nie wraca, nawet gdy czytelnik wyjdzie z pola i zostawi je puste. Zachęta wracająca pod cudzym kursorem to już nagabywanie. Gdy system zgłasza wyłączony ruch (`prefers-reduced-motion`) albo pole ma treść, animacji nie ma w ogóle: stoi zwykła zachęta statyczna, ta sama, która idzie w HTML-u i zostaje u kogoś z wyłączonym JavaScriptem. Zdania są napisem interfejsu, więc należą do instancji (`ui.searchExamples`). Silnik nie zna ani jednego — domyślnie lista jest pusta, co znaczy „pole stoi n
Kolofon · 11 września 2026 · wstępna

0.13.5 — czwarta skórka: atelier
Zamówienie zmieściło się w jednym zdaniu: czysta biel, elegancko, z dodatkami szczotkowanej miedzi i butelkowej zieleni. Silnik dostał czwartą skórkę i ani jednego nowego komponentu. Na silniku stanęła druga wizytówka zawodowa, a zamówienie na jej wygląd zmieściło się w jednym zdaniu: strona ma być czysta, biała i elegancka, z dodatkami szczotkowanej miedzi i butelkowej zieleni. Żadna z trzech istniejących skórek tego nie umiała. „book” ma ciepły papier, „dev” jest ciemna, a „docs” chłodna i granatowa. Od 0.13.5 jest czwarta, `theme: "atelier"`. Tło to prawdziwa biel. Zieleń robi za tusz, tytuły i stopkę. Miedź pojawia się tylko w szczegółach: jako kreska pod paskiem i inicjał we wpisie, bo na dużej powierzchni przestaje być szlachetna. Nagłówki są w Bodoni Moda, a tekst w Jost, bo antykwa o takim kontraście męczy w długim akapicie drobnym stopniem. Kroje ładuje instancja w `fontsHref`, a bez nich skórka spada na systemowe i dalej działa. Szczotkowanie to dwie warstwy w jednej zmiennej: przelew metalu i drobne rysy na nim. Pierwszy zrzut ekranu pokazał od razu, czego z tym nie robić. W skrócie `background` podany rozmiar dotyczy tylko ostatniej warstwy, więc kreska pod cytatem wyszła poprawnie, a rysy rozlały się po całym akapicie w szary prostokąt. Kreski rysuje się teraz pseudoelementem. Jak przy „dev” i „docs”, to są same tokeny i kaskada. Żaden komponent nie wie, że skórka istnieje, a trzy pozostałe nie zmieniły się ani o bajt.
Kolofon · 11 września 2026 · wstępna

0.13.4 — powrót na wersję domyślną
Na angielski dało się przełączyć. Z powrotem na polski — już nie: serwer czytał język z nagłówka Referer i odsyłał czytelnika na /en. Właściciel sprawdził przełącznik na produkcji: z polskiego na angielski działał, z angielskiego na polski — nie. Klik niby prowadził na polską stronę, a kończył się znowu na angielskiej. Server functions idą pod wspólny adres bez przedrostka, więc od 0.13.0 język dostawały z nagłówka, a w zapasie z nagłówka Referer. Ten zapas działał jednak dla każdego żądania, nie tylko dla server functions. Klik „Polski” na stronie angielskiej niósł Referer `…/en/…` i serwer uznawał żądanie o polską stronę za angielskie. Testy tego nie widziały, bo szły `curl`-em bez nagłówka Referer. Od 0.13.4 nagłówek i Referer liczą się wyłącznie pod adresem server functions, a strona ma język tylko w ścieżce. Sprawdzone klikaniem: cztery przełączenia z rzędu, w obie strony.
Kolofon · 11 września 2026 · wstępna

0.13.3 — media z R2 pod przedrostkiem
Nagranie pod /en/audio/… dawało 404. Router zdejmuje przedrostek z tego, co widzi, ale trasa mediów czytała klucz z samego żądania. Drugie scalenie wersji językowych objęło serwis, który trzyma nagrania i część zdjęć w kubełku R2, a nie w repozytorium. Kwadrans po wdrożeniu nagranie pod `/en/audio/…` odpowiadało 404. Trasy mediów biorą klucz obiektu z `request.url`, bo parametry w handlerach serwerowych bywają puste. `rewrite` routera zdejmuje przedrostek `/en` z tego, co widzi router, ale nie z samego żądania — do kubełka szedł klucz `en/audio/…`, którego tam nie ma. Test lokalny tego nie pokazał z dwóch powodów naraz: pliki z repozytorium omijał już wcześniej inny mechanizm, a lokalny R2 jest pusty. Od 0.13.3 `server.ts` zdejmuje przedrostek z adresu każdego żądania o plik, a język zostaje w kontekście żądania. Sprawdzone na obiekcie włożonym do lokalnego R2.
Kolofon · 11 września 2026 · wstępna

0.13.2 — bez nazwy silnika w przeglądarce
Instancja może się silnikiem nie podpisywać. Podpis w stopce znikał, nazwa w bundlu znikała od 0.9.0 — ale zostawała w nazwach klas, zdarzeń i nagłówku, których nikt nie czytał. Od 0.9.0 instancja z wyłączonym podpisem nie wynosi nazwy silnika w paczce JavaScriptu: decyzja zapada przy budowaniu, a martwa gałąź znika. To zamykało jedno miejsce. Przegląd pull requestów z 0.13.1 pokazał, że były jeszcze inne. W paczkach instancji bez podpisu nazwa silnika siedziała w nazwach dwóch klas animacji i ich klatek kluczowych, w czterech nazwach zdarzeń przeglądarki, a od 0.13.0 miała dojść w nagłówku HTTP z językiem. Żadne z tych miejsc nie jest widoczne na stronie. Wszystkie są widoczne dla kogoś, kto otworzy plik — a to jest dokładnie ten ktoś, przed kim podpis się wyłącza. Doszedł strażnik `check-engine-name.mjs`: po buildzie przegląda wszystko, co idzie do przeglądarki, i przerywa, jeśli instancja bez podpisu ma w tym nazwę silnika. Instancja z podpisem przechodzi od razu. Instancja, która używała starych klas we własnej treści, musi je przemianować — to jedyna zmiana wymagana po jej stronie.
Kolofon · 11 września 2026 · wstępna

0.13.1 — migracja sama zakłada kolumnę
Migracja z 0.13.0 miała nadać język zdarzeniom sprzed wydania. Na produkcji nie nadała: kolumnę zakłada inny moduł, a ten budzi się później. Pierwsze `status` po wdrożeniu 0.13.0 pokazało dwanaście starych zdarzeń z językiem strony „brak”. Migracja `0013` zapisała się jako wykonana, liczniki odsłon przeniosła poprawnie, a zdarzeń nie ruszyła. Przyczyna jest w kolejności. Kolumnę `events.page_lang` zakłada moduł dziennika, ale dopiero przy pierwszym zapisie zdarzenia. Migracja rusza przy pierwszym odczycie licznika odsłon, czyli wcześniej. `UPDATE` na nieistniejącej kolumnie wpadł w `catch`, który miał łapać brak tabeli, i nikt tego nie zobaczył. Próba generalna na kopii produkcji przeszła, bo szła w innej kolejności: skrypt scalający bazy założył kolumnę, zanim na kopii padła pierwsza odsłona. Test sprawdził poprawny scenariusz i przemilczał ten, który zdarzył się naprawdę. Uzupełnianie zdarzeń wyszło z `0013` do osobnej migracji `0013b`, która najpierw sama zakłada kolumnę. Bazy, na których `0013` już przeszła, dostają `0013b` przy najbliższym starcie. Obie kolejności sprawdzone tym razem naprawdę: na kopii bieżącej produkcji i na czystej bazie sprzed 0.13, bez skryptu. Reguła na przyszłość: migracja zakłada każdą kolumnę, której dotyka, zamiast liczyć na to, który moduł obudzi się w izolacie pierwszy.
Kolofon · 11 września 2026 · wstępna

0.13.0 — dwa języki, jedno wdrożenie
Do 0.12 wersja językowa była osobną instancją: własna subdomena, repozytorium i baza. Od 0.13.0 jedno wdrożenie serwuje kilka wersji — o języku wpisu decyduje folder, o języku strony przedrostek adresu. Przez miesiąc wersja angielska tej strony żyła obok polskiej jak sąsiad zza ściany: osobna subdomena, osobne repozytorium, osobna baza, osobne wdrożenie. Każda poprawka szła dwa razy, a każdy nowy wpis wymagał dwóch pushy i dwóch weryfikacji. 0.13.0 kończy ten układ. Obie wersje stoją teraz w jednym wdrożeniu, a angielska mieszka pod `/en`. Dwie rzeczy i żadna nie jest nowym polem konfiguracji. **O języku wpisu decyduje folder**: `src/content/posts` to wersja domyślna, `src/content/en/posts` to angielska. **O języku strony decyduje adres**: wersja zarejestrowana z adresem `https://…/en` dostaje przedrostek `/en`, bo tak wynika z jej `url`. Wszystkie polskie adresy zostały dokładnie tam, gdzie były. Trudniejsza połowa nie była w routingu, tylko w tym, że `cfg()` woła się w setkach miejsc i dotąd zawsze zwracało to samo. Teraz musi wiedzieć, w której wersji jest żądanie. Zmienna globalna odpadła od razu: jeden izolat Workera obsługuje żądania równolegle, a dwa renderowania przeplatają się na każdym `await`. Na serwerze język niesie AsyncLocalStorage, w przeglądarce przedrostek w pasku adresu. Test: osiemdziesiąt równoległych żądań po polsku i po angielsku, zero napisów z niewłaściwej wersji. Pierwszy pomysł na przedrostek, `basepath` routera, nie przeżył zderzenia z TanStack Start: serwer nadpisuje go przy każdym żądaniu wartością z czasu budowania. Przeżył `rewrite` — router wid
Kolofon · 11 września 2026 · wstępna

0.12.0 — wersja ze skórkami
Numer podniesiony z 0.11 na 0.12, bo zmienił się sposób używania silnika, a nie tylko jego zachowanie. Instancja deklaruje skórkę i kilka ustawień — resztę robi silnik. Linia 0.11 zaczęła się od serwisu, który wyglądał jednakowo wszędzie, a skończyła czymś innym: instancja deklaruje, czym chce być, a silnik się do tego dostraja. To zmiana w sposobie używania, nie w zachowaniu — i właśnie dlatego numer idzie o poziom wyżej. Trzy skórki: **book** — papier i antykwa, dla serwisu do czytania prozy. **dev** — ciemna i techniczna. **docs** — jasna, chłodna, dokumentacyjna. Nie są wariantami siebie nawzajem; to trzy różne obietnice składane czytelnikowi w pierwszej sekundzie. Do tego dwie zachęty zbudowane jednym wzorcem — do zainstalowania aplikacji i do włączenia powiadomień — oraz propagacja wydań do instancji przez pull requesty, ze strażnikami decydującymi, czy pull request jest gotowy, czy zostaje szkicem. Kuszące było podbić od razu do jedynki: skórki brzmią jak dojrzałość produktu. Ale jedynka mówi „to jest skończone i stabilne”, a w silniku wciąż są rzeczy, o których wiem, że się zmienią. Jest też powód odwrotny, mocniejszy. **Żadne z tych pól nie jest obowiązkowe.** Instancja, która nie deklaruje niczego, wygląda i działa dokładnie tak jak w 0.11.0 — sprawdzone po każdym z dziesięciu ostatnich wydań, na żywo, na wszystkich instancjach. Wydanie, które niczego nie psuje i niczego nie wymaga, nie jest wydaniem głównym. Jest kolejnym krokiem. Numer ma opisywać rzeczywistość, a nie ambicje.
