Architektura
Dwadzieścia siedem wpisów o tym, jak Kolofon doszedł do dzisiejszego kształtu: od trzech warunków postawionych przed pierwszą linijką kodu, przez decyzje, które trzeba było odkręcać, po kierunki, w które to idzie.
Kolofon · 7 sierpnia 2026 · wstępna

01 · Założenia wyjściowe
Trzy warunki postawione zanim powstała pierwsza linijka kodu. Wszystko, co przyszło potem, jest ich konsekwencją. Ta seria opisuje, jak Kolofon doszedł do dzisiejszego kształtu — od pierwszego wdrożenia, przez wszystkie decyzje, które okazały się trafne albo trzeba je było odkręcać, po kierunki, w które to idzie. Piszemy ją, bo architektura opisana dopiero na końcu zawsze wygląda na przemyślaną od początku. Nasza nie była. Zaczęło się od trzech warunków, postawionych zanim powstała pierwsza linijka kodu. Pierwszy: adres należy do autora. Nie subdomena platformy, nie profil, nie konto, które ktoś może zawiesić. Własna domena, do której klucze ma jedna osoba. Drugi: dane należą do autora. Teksty jako pliki w jego repozytorium, komentarze i oceny w jego bazie. Wywóz ma być kopiowaniem katalogu, nie prośbą o eksport skierowaną do działu wsparcia. Trzeci, najbardziej praktyczny: zero kosztu stałego przy zerowym ruchu. Projekt literacki nie zarabia od pierwszego dnia, a często nie zarabia nigdy. Architektura, która wymaga płacenia miesięcznego abonamentu, żeby tekst w ogóle był dostępny, wywiera na autora presję, która nie ma nic wspólnego z pisaniem. Te trzy warunki wyglądają na oczywiste, dopóki nie zauważy się, ile popularnych rozwiązań spełnia dwa z trzech. Platforma blogowa daje wygodę i zero kosztu, ale adres i dane są jej. Własny serwer daje kontrolę, ale kosztuje co miesiąc niezależnie od tego, czy ktokolwiek czyta. Generator stron statycznych daje jedno i drugie, ale nie ma bazy — więc
Kolofon · 7 sierpnia 2026 · wstępna

02 · Granica: pliki i baza
Teksty w plikach, dane czytelników w bazie. Jedna linia podziału, która przeżyła wszystkie późniejsze przebudowy. Najważniejsza decyzja tego projektu jest jednocześnie najprostsza do opisania: treść mieszka w plikach, dane czytelników w bazie. Ta linia nie przesunęła się ani razu przez cały czas rozwoju, choć prawie wszystko inne wokół niej się zmieniło. Wpis to plik JSON w repozytorium. Ma tytuł, datę, serię, autora i tablicę akapitów. Nic więcej. Wynika z tego kilka rzeczy, których nie da się kupić za żadne pieniądze: pełna historia zmian każdego tekstu, możliwość cofnięcia edycji sprzed miesięcy, przegląd różnic przed publikacją, i kopia zapasowa całego dorobku na każdym komputerze, na którym ktoś kiedyś sklonował repozytorium. Komentarze, oceny, liczniki odsłon i dziennik zdarzeń idą do bazy, bo są danymi, których autor nie pisze — powstają w trakcie czytania i nie mają prawa wymagać wdrożenia, żeby się pojawić. W trakcie prac padło raz pytanie, czy komentarze też nie mogłyby być plikami. Odpowiedź jest architektoniczna, nie estetyczna: środowisko wykonawcze na brzegu sieci nie ma zapisywalnego systemu plików. Nie „nie zalecamy” — po prostu nie ma gdzie zapisać. Ta jedna właściwość platformy wymusza podział, który i tak byłby słuszny. Efekt uboczny okazał się cenniejszy od zamierzonego. Skoro treść jest plikami, a silnik ich tylko szuka po ustalonym wzorcu, to podmiana katalogu z treścią daje inny serwis na tym samym kodzie. Cała późniejsza produktyzacja wyrosła z decyzji podjętej w pi
Kolofon · 7 sierpnia 2026 · wstępna

03 · Dlaczego brzeg sieci
Serwis stoi na trzystu maszynach naraz i kosztuje zero. Rachunek za to płaci się w ograniczeniach, nie w pieniądzach. Silnik działa na brzegu sieci — czyli kod nie mieszka na jednym serwerze, tylko jest zwielokrotniony w setkach lokalizacji i uruchamia się w tej, która jest najbliżej czytelnika. Dla serwisu, którego czytelnicy siedzą na trzech kontynentach, to różnica między odpowiedzią natychmiastową a zauważalnym czekaniem. Model rozliczeniowy jest przy tym taki, że przy typowym ruchu projektu literackiego rachunek wynosi zero. Nie „niewiele” — zero. To spełnia trzeci warunek z pierwszego wpisu w sposób, którego żaden serwer wynajmowany na miesiące nie spełni. Rachunek płaci się w ograniczeniach i trzeba je wymienić uczciwie, bo określiły resztę architektury. Nie ma zapisywalnego systemu plików. Nie ma procesów żyjących między żądaniami, więc nie ma zadań w tle w klasycznym rozumieniu. Jest limit czasu procesora na żądanie. Jest limit rozmiaru paczki z kodem. Każde z tych ograniczeń wraca w kolejnych wpisach tej serii jako przyczyna konkretnej decyzji. Brak systemu plików wymusił podział treść–baza. Brak procesów w tle wymusił oparcie wysyłki powiadomień o żądania, które i tak przychodzą. Limit rozmiaru paczki wymusił paginację, bo wysyłanie całego archiwum w jednej odpowiedzi przestaje działać przy odpowiednio dużym archiwum. Warto to powiedzieć wprost, bo brzmi jak wada, a jest zaletą: ograniczenia platformy zadziałały jak recenzent, który nie przepuszcza leniwych rozwiązań. Wiele rzeczy,
Kolofon · 7 sierpnia 2026 · wstępna

04 · Baza bez migracji
Nowa instancja dostaje pustą bazę i sama się w niej urządza. Onboarding, którego nie ma, jest najlepszym onboardingiem. Baza to SQLite działający na brzegu sieci. Trzyma pięć rodzajów danych: liczniki odsłon, oceny, komentarze, dziennik zdarzeń i — od niedawna — konta czytelników wraz z sesjami. Pierwsze tabele powstawały ręcznie, wklejaniem instrukcji do konsoli w panelu. Działało to dokładnie tak długo, jak długo instancja była jedna. W momencie, w którym pojawiła się perspektywa drugiej, ten sposób przestał być dopuszczalny: nie da się sprzedać narzędzia, którego uruchomienie zaczyna się od wklejania SQL-a. Rozwiązaniem jest leniwe tworzenie schematu. Przy pierwszym zapisie silnik sprawdza, czy tabele istnieją, i tworzy brakujące. Instrukcje są napisane tak, że wielokrotne uruchomienie niczego nie psuje. Nie ma systemu migracji, wersji schematu ani polecenia, które trzeba pamiętać. Podłączasz pustą bazę i ona sama się urządza. Jest w tym jedna pułapka, którą odkryliśmy dopiero przy rozbudowie schematu o konta. Wszystkie instrukcje leciały w jednej pętli, w jednej obsłudze błędu. Wystarczyło, żeby jedna z nich zawiodła — na przykład dlatego, że indeks już istniał w nieco innej postaci — a pętla przerywała się i po cichu pomijała wszystko poniżej. Baza wyglądała na gotową, będąc gotową w połowie. Teraz każda instrukcja ma własną obsługę błędu. To jedna z tych poprawek, które w opisie zajmują zdanie, a odróżniają narzędzie działające od narzędzia działającego u wszystkich.
Kolofon · 7 sierpnia 2026 · wstępna

05 · Treść jako dane
Nie ma edytora ani panelu. Wpis to plik, a nowa seria to nowy katalog. Wady tego rozwiązania są realne i warto je nazwać. Silnik nie ma panelu administracyjnego. Nie ma edytora, kreatora wpisów ani przycisku „opublikuj”. Wpis to plik, seria to katalog, a metadane serii — nazwa, opis, hasło, kolejność — leżą w osobnym pliku wewnątrz tego katalogu. Dzięki temu dodanie nowej serii jest operacją na jednym katalogu, a nie przebudową strony. Silnik znajduje serie sam, skanując katalog treści po ustalonym wzorcu. Nie ma nigdzie listy serii, którą trzeba by aktualizować — a to znaczy, że nie ma listy, o której można by zapomnieć. Zachowanie serii też jest daną. Czy seria wyświetla się jako galeria, jak brzmi zaproszenie do oceny pod tekstem, jaki tekst czyta czytnik ekranu — to wszystko stoi w pliku serii. Wcześniej siedziało w kodzie jako porównania do konkretnych nazw i było to jedno z tych rozwiązań, które działają idealnie do dnia, w którym pojawia się drugi serwis. Wady tego podejścia są realne i nie ma sensu ich chować. Osoba nietechniczna nie doda wpisu sama. Nie ma podglądu na żywo. Nie ma wgrywania zdjęć z przeglądarki. To narzędzie zakłada, że po drugiej stronie jest ktoś, kto potrafi obsłużyć repozytorium — albo ktoś, kto to robi w jego imieniu. Świadomie wybraliśmy tę wadę, bo panel administracyjny jest obietnicą utrzymywania interfejsu przez lata, a pierwsze wdrożenia mają nas czegoś nauczyć. Formularz rejestracji nie uczy niczego. Wracamy do tego w ostatnim wpisie serii, bo kierunek jest tu
Kolofon · 7 sierpnia 2026 · wstępna

06 · Serie i strumień
Esej, fotografia z podpisem, wpis z filmem i zapis snu to ten sam obiekt z różnymi wypełnionymi polami. Serwis dzieli treść na serie, ale ma też jeden wspólny strumień, w którym wszystko układa się chronologicznie. To rozróżnienie okazało się ważniejsze, niż zakładaliśmy: seria to jest deklaracja formy, a strumień to jest sposób czytania. Czytelnik regularny wchodzi w strumień. Czytelnik z wyszukiwarki trafia w serię. Pod spodem jest jeden typ wpisu. Nie ma osobnego typu dla fotografii i osobnego dla eseju. Jest jeden obiekt z opcjonalnymi polami: obraz, identyfikator filmu, obrazy wplecione w akapity, znacznik wydarzenia inny niż data publikacji. Wypełnione pola decydują o tym, jak wpis się wyświetli. Ta decyzja zapadła, kiedy doszła pierwsza seria fotograficzna, i przez chwilę wyglądała na pójście na łatwiznę. W praktyce uratowała kolejne trzy serie. Kiedy pojawił się pomysł na wpisy z filmem, nie trzeba było dokładać typu — wystarczyło jedno pole. Kiedy doszły wpisy z obrazami w środku tekstu, doszła jedna tablica. Kiedy trzeba było rozdzielić datę publikacji od daty wydarzenia, doszło jedno pole. Gdyby każda z tych rzeczy była osobnym typem wpisu, każda wymagałaby własnej trasy, własnego szablonu i własnego wiersza w każdym miejscu, gdzie wpisy się wylicza. Zamiast tego wszystkie serie jadą jedną trasą i jednym szablonem. Koszt jest taki, że typ wpisu ma sporo pól opcjonalnych, a szablon ma sporo rozgałęzień. To jest uczciwa cena i płacimy ją świadomie.
Kolofon · 7 sierpnia 2026 · wstępna

07 · Publikacja z wyprzedzeniem
Sto dwadzieścia jeden wpisów napisanych z góry, odsłaniających się dzień po dniu. Cały mechanizm to jedno porównanie dat. Jeden z najbardziej użytecznych mechanizmów w całym silniku ma może dziesięć linijek. Wpis z datą w przyszłości nie jest widoczny. Kiedy data mija, wpis pojawia się sam — w serwisie, w kanale RSS i w powiadomieniach. W praktyce znaczy to, że autor może napisać sto dwadzieścia jeden wpisów w miesiąc, a wypuszczać je przez pół roku, po jednym dziennie. Serwis wygląda na prowadzony codziennie, a praca jest wykonana partiami, wtedy kiedy jest czas i nastrój. Dla projektu prowadzonego obok innych zajęć to nie jest wygoda, to jest warunek istnienia. Ten sam mechanizm rozwiązał przy okazji problem, o którym nie pomyśleliśmy z góry: jak schować wpis, nie kasując go. Odpowiedź jest banalna — data ustawiona na rok 2099. Wpis zostaje w repozytorium wraz z całą historią, ale nie wyświetla się nigdzie. Oryginalna data zostaje zapisana w opisie zmiany, więc przywrócenie to jedna edycja. Doszło do tego pole, które rozdziela datę wyświetlaną od daty w kanale RSS. Wynikło z konkretnego zderzenia: gdy publikuje się archiwum wstecz, wpisy mają prawdziwe daty sprzed miesięcy, ale w kanale RSS mają być nowe — inaczej automatyzacja rozsyłająca je dalej uzna, że nie ma nic nowego. Mechanika trywialna, konsekwencje duże. Data przestała być metadaną opisującą wpis, a stała się mechanizmem sterującym jego widocznością.
Kolofon · 7 sierpnia 2026 · wstępna

08 · Oceny i odsłony
Skala wewnętrzna 0–10, bo połowa gwiazdki musi być liczbą całkowitą. Ocena wstępna od autora, dopóki nie ma głosów czytelników. Pod każdym wpisem jest ocena w gwiazdkach, a na listach — czytelna plakietka ze średnią. Wygląda to na najprostszy element w całym serwisie i było jednym z bardziej podchwytliwych. Pierwsza decyzja: skala wewnętrzna od zera do dziesięciu, gdzie jeden punkt to pół gwiazdki. Wynika z tego, że połowa gwiazdki musi być liczbą całkowitą, jeśli średnia ma być liczona bez błędów zaokrągleń. To jest ten rodzaj decyzji, który albo podejmie się na początku, albo przepisuje się dane później. Druga: jedna ocena na przeglądarkę, ale z możliwością zmiany dowolną liczbę razy. Znaczy to, że serwer musi umieć nie tylko dodać głos, ale i skorygować poprzedni — więc przy zmianie przekazywana jest stara wartość, a serwer odejmuje ją od sumy. Bez tego zmiana oceny podbijałaby licznik głosów w nieskończoność. Trzecia, najciekawsza w skutkach: gwiazdki na listach mają się odświeżać natychmiast po oddaniu głosu, bez przeładowania strony. Rozwiązuje to wspólna pamięć w przeglądarce z prostym powiadamianiem — komponent oceniający ogłasza zmianę, a wszystkie plakietki na stronie ją odbierają. Doszło jeszcze pole na ocenę wstępną od autora. Świeżo opublikowany wpis bez głosów wyświetlałby zero gwiazdek, co wygląda jak ocena, a jest brakiem danych. Teraz do pierwszego głosu czytelnika widać ocenę autorską, wyraźnie oznaczoną jako wstępną.
Kolofon · 7 sierpnia 2026 · wstępna

09 · Komentarze i antyspam
Pułapka na boty, limit częstości i zewnętrzna weryfikacja. A e-mail jest dobrowolny, bo obowiązkowy odstrasza więcej niż spam. Komentarze weszły wcześnie i od początku bez logowania — czytelnik podaje podpis i treść. Warstwa antyspamowa rosła etapami, bo dokładanie jej z góry byłoby zgadywaniem. Najpierw pułapka na boty: ukryte pole formularza, którego człowiek nie widzi, a automat wypełnia. Do tego ograniczenia długości i limit częstości po stronie serwera — jeden komentarz na wpis w krótkim oknie czasu. Dopiero gdy to przestało wystarczać, doszła zewnętrzna weryfikacja antybotowa. Moderacja jest w schemacie od pierwszego dnia, choć długo nie była używana. Komentarz ma znacznik zatwierdzenia ustawiony domyślnie na tak. Przełączenie serwisu na moderację wstępną to zmiana jednej wartości domyślnej, bez przebudowy tabeli i bez migracji danych. To jest wzorzec, który powtarza się w tym silniku wielokrotnie: przygotuj miejsce na decyzję, której jeszcze nie podejmujesz. Pole na adres e-mail przeszło pełny cykl. Najpierw go nie było — mniej tarcia, mniej danych osobowych. Potem stało się obowiązkowe. Ostatecznie jest dobrowolne, służy powiadomieniu o odpowiedzi i nigdy nie jest pokazywane publicznie. Obowiązkowy adres odstraszał więcej czytelników, niż zatrzymywał spamu. Od niedawna doszły konta czytelników i wraz z nimi przełącznik polityki komentowania: wszyscy, tylko konta, albo nikt. Ważne jest to, co przełącznik oznacza w praktyce — wybór „tylko konta” nie usuwa bramki antyspamowej, tylko przesuwa ją z
Kolofon · 7 sierpnia 2026 · wstępna

10 · Dziennik zdarzeń
Własna analityka zamiast zewnętrznego skryptu śledzącego. Po dziewięćdziesięciu dniach adresy są maskowane, ale wpisy zostają. Serwis nie ma zewnętrznego skryptu analitycznego. Zamiast tego ma własny dziennik zdarzeń w swojej bazie: odsłona wpisu, ocena, komentarz, komentarz zatrzymany przez antybota, wyjście w link zewnętrzny, odtworzenie filmu, udostępnienie. Powód jest prosty i wynika wprost z założeń z pierwszego wpisu. Skrypt analityczny to oddanie danych o czytelnikach firmie trzeciej, w zamian za wykresy. Przy serwisie, który obiecuje autorowi, że dane są jego, byłaby to obietnica z gwiazdką. Przy każdym zdarzeniu zapisujemy czas, adres IP, kraj rozpoznany przez sieć brzegową, przeglądarkę, język i to, skąd czytelnik przyszedł. Ostatnie okazało się najbardziej pouczające i najbardziej podchwytliwe technicznie: pierwsza wersja zapisywała jako źródło adres samego serwisu, bo nagłówek żądania niósł adres poprzedniej podstrony, a nie faktyczne wejście z zewnątrz. Trzeba było przekazać tę informację z przeglądarki. Kwestia prywatności została rozstrzygnięta jednoznacznie i wbrew pierwszej wersji. Pierwotnie wpisy starsze niż dziewięćdziesiąt dni miały być kasowane. Zasada brzmi teraz: niczego nie kasujemy, po dziewięćdziesięciu dniach maskujemy adres. Adres IPv4 traci ostatni człon, adres IPv6 zostaje obcięty do prefiksu. Zdarzenie zostaje, statystyka historyczna zostaje, możliwość powiązania z osobą znika. Ta zmiana kierunku jest warta odnotowania, bo pokazuje różnicę między zgodnością z przepisami
Kolofon · 7 sierpnia 2026 · wstępna

11 · Aplikacja zamiast aplikacji
Instalowalna ikona na telefonie, tryb offline i powiadomienia — bez sklepu, bez recenzji, bez drugiego repozytorium. Aktualizacja z 22 września 2026: tryb offline opisany niżej został wycofany w wersji 0.13.25. Tekst zostaje jako zapis tego, jak ta część silnika powstawała — obecny stan opisuje wpis „07 · Aplikacja”. W pewnym momencie padło pytanie o aplikację na telefon. Odpowiedź brzmi: jest, tylko nie ma jej w sklepie. Serwis jest aplikacją progresywną — instaluje się z przeglądarki, dostaje ikonę na ekranie głównym, otwiera się na pełnym ekranie i działa bez zasięgu. Rachunek jest tu jednostronny do tego stopnia, że aż podejrzany. Aplikacja natywna to osobny projekt w innej technologii, opłaty dla dwóch sklepów, recenzja przy każdej aktualizacji i drugi zbiór kodu do utrzymania przez lata. Aplikacja progresywna to kilka plików dołożonych do serwisu, który już istnieje: opis aplikacji, skrypt działający w tle i obsługa powiadomień. Dla projektu prowadzonego przez jedną albo dwie osoby to nie jest kompromis, tylko właściwy wybór. Trzy rzeczy, które to odblokowuje ponad zwykłą stronę: instalacja bez sklepu, czytanie bez zasięgu i powiadomienia o nowym tekście. Trzecia jest tą, która realnie sprowadza czytelnika z powrotem — kanał RSS tego nie robi. Jest jedna rzecz, której aplikacja progresywna nie da: obecności w sklepie jako kanału odkrywania. Dla serwisu, który i tak buduje zasięg linkiem i wyszukiwarką, to strata pozorna. Wszystkie pliki tej warstwy — opis aplikacji, skrypt w tle, strona
Kolofon · 7 sierpnia 2026 · wstępna

12 · Siedem wersji jednego pliku
Skrypt działający w tle przeszedł siedem wersji w jednej sesji. Każda naprawiała problem widoczny dopiero po naprawieniu poprzedniego. Aktualizacja z 22 września 2026: tryb offline opisany niżej został wycofany w wersji 0.13.25. Tekst zostaje jako zapis tego, jak ta część silnika powstawała — obecny stan opisuje wpis „07 · Aplikacja”. Skrypt działający w tle przeglądarki to najbardziej podstępny element całego stosu. Przeszedł siedem wersji, w większości w ciągu jednej sesji, i każda naprawiała problem, który stawał się widoczny dopiero po naprawieniu poprzedniego. Docelowy podział strategii jest taki. Nawigacja po stronach: najpierw sieć, a z pamięci dopiero gdy sieci nie ma. Dzięki temu czytelnik online zawsze widzi świeżą treść, a offline dostaje ostatnią znaną wersję. Zasoby budowane i obrazy: najpierw pamięć, bo mają w nazwie skrót zawartości — nowa wersja to nowa nazwa, więc stara kopia nigdy nie jest nieaktualna. Ta druga zasada jest ważniejsza, niż wygląda. Cała poprawność mechanizmu opiera się na tym, że nazwa pliku zmienia się razem z jego zawartością. Gdyby zasoby miały stałe nazwy, strategia „najpierw pamięć” serwowałaby stary kod w nieskończoność. Najtrudniejszy okazał się nie sam tryb offline, tylko przechodzenie między podstronami bez przeładowania, gdy sieci nie ma. Aplikacja doczytuje wtedy fragmenty kodu na żądanie, a te fragmenty nie zawsze były w pamięci podręcznej. Twarde przeładowanie działało, przejście „w locie” nie. Rozwiązaniem — i mówimy to wprost, bo to obejście, nie usunięcie przyc
Kolofon · 7 sierpnia 2026 · wstępna

13 · Offline bez serwera
Żeby czytanie działało bez zasięgu, trzeba było przestać pytać serwer o rzeczy, które i tak są w paczce z kodem. Aktualizacja z 22 września 2026: tryb offline opisany niżej został wycofany w wersji 0.13.25. Tekst zostaje jako zapis tego, jak ta część silnika powstawała — obecny stan opisuje wpis „07 · Aplikacja”. Tryb offline wymusił rozstrzygnięcie, które okazało się porządkujące dla całej architektury: co jest treścią, a co danymi. Pierwotnie listy wpisów pobierane były wywołaniem do serwera. Wygodne i poprawne — dopóki jest sieć. Bez sieci wywołanie nie przechodzi i strona nie ma czego wyświetlić, mimo że treść fizycznie leży w pamięci przeglądarki, bo jest wkompilowana w paczkę z kodem. Rozwiązanie polegało na zastąpieniu wywołań serwerowych lokalnym odczytem tam, gdzie chodzi o treść. Wpisy są plikami wkompilowanymi w aplikację, więc przeglądarka ma je u siebie i nie musi o nie pytać. Serwer zostaje potrzebny tylko do rzeczy, które z natury żyją po jego stronie: liczników, ocen, komentarzy. Ta granica pokrywa się dokładnie z granicą pliki–baza z drugiego wpisu. To nie przypadek. Podział, który wprowadziliśmy z powodu braku systemu plików na brzegu sieci, okazał się dokładnie tym samym podziałem, którego wymaga poprawne działanie bez sieci. Dwie zupełnie różne siły wskazały tę samą linię — to zwykle znaczy, że linia jest w dobrym miejscu. Efekt uboczny jest wydajnościowy. Skoro listy nie wymagają rozmowy z serwerem, przechodzenie między podstronami jest natychmiastowe także online.
Kolofon · 7 sierpnia 2026 · wstępna

14 · Powiadomienia
Szyfrowanie ładunku, klucze podpisujące i wysyłka doczepiona do ruchu, który i tak przychodzi — bo nie ma gdzie postawić demona. Powiadomienia to najbardziej wymagająca formalnie część silnika, bo standard nie pozwala na skróty. Ładunek musi być zaszyfrowany kluczem konkretnego odbiorcy, żądanie podpisane parą kluczy serwisu, a wszystko zgodnie ze specyfikacją, która nie wybacza pomyłki o jeden bajt. Subskrypcje leżą w bazie instancji. Klucz publiczny jest jawny z natury, bo trafia do przeglądarki; klucz prywatny żyje wyłącznie jako sekret po stronie platformy i nigdy nie ma go w repozytorium. Najciekawsza jest tu decyzja wymuszona przez ograniczenie z trzeciego wpisu: nie ma procesów żyjących w tle, więc nie ma czegoś, co budziłoby się o świcie i sprawdzało, czy jest nowy wpis. Rozwiązaniem jest doczepienie sprawdzenia do ruchu, który i tak przychodzi. Przy okazji żądania silnik sprawdza — z ograniczeniem częstotliwości, żeby nie robić tego non stop — czy pojawił się wpis, o którym nie powiadomiono. Ma to konsekwencję, którą trzeba nazwać uczciwie: powiadomienie o wpisie publikowanym o świcie wyjdzie przy pierwszym żądaniu po tej godzinie, a nie punktualnie. Przy serwisie z jakimkolwiek ruchem różnica jest w minutach. Przy serwisie bez ruchu powiadomienie poczeka na pierwszego czytelnika — co jest logiczne, bo nie ma komu go dostarczyć. Alternatywą byłoby zewnętrzne zadanie cykliczne. Wybraliśmy rozwiązanie bez zależności, bo zależność, która musi żyć, żeby produkt działał, to koszt utrzymania na zawsze
Kolofon · 7 sierpnia 2026 · wstępna

15 · Obrazy
Fotografia wchodzi raz, a wychodzi w trzech postaciach. Wszystko liczone przy przygotowaniu, nic przy odczycie. Serie fotograficzne wymusiły osobny potok przetwarzania obrazów, i to taki, który działa poza serwisem — bo przeskalowanie zdjęcia przy każdym żądaniu byłoby marnowaniem czasu procesora, którego na brzegu sieci jest odmierzona porcja. Zdjęcie wchodzi raz i wychodzi w trzech postaciach. Wariant do treści: dłuższy bok około dwóch tysięcy pikseli, kompresja dobrana tak, żeby na ekranie nie było widać różnicy. Karta do udostępniania: proporcje wymagane przez serwisy społecznościowe i twardy limit wagi, z jakością obniżaną kaskadowo aż plik się zmieści. Nazwa pliku jest zawsze identyfikatorem wpisu, więc nie trzeba nigdzie zapisywać, co do czego należy. Karta do udostępniania jest tym elementem, którego brak widać dopiero na zewnątrz. Link bez niej wygląda w serwisie społecznościowym jak goły tekst i po prostu nie jest klikany. To jest ta część infrastruktury, która nie służy czytelnikom obecnym, tylko przyszłym. Ikony aplikacji też są generowane, a nie rysowane ręcznie — z monogramu i kolorów podanych jako argumenty. Wynikło to z tego, że komplet ikon dla aplikacji progresywnej to kilka rozmiarów w dwóch wariantach, a brak choćby jednego wywraca instalację: skrypt w tle pobiera całą listę naraz i pierwszy brakujący plik przerywa operację. Wniosek ogólniejszy: wszystko, co da się policzyć przy przygotowaniu treści, ma być policzone wtedy. Na brzegu sieci czas procesora jest zasobem,
Kolofon · 7 sierpnia 2026 · wstępna

16 · Paginacja i pojemność
Odpowiedź stałej wielkości niezależnie od rozmiaru archiwum. Wcześniej lista wpisów rosła razem z dorobkiem autora. Do pewnego momentu listy wpisów pobierały wszystkie wpisy. Przy kilkunastu to elegancka prostota. Przy stu dwudziestu to już zauważalna waga odpowiedzi. Przy tysiącu przestałoby działać. Paginacja weszła po stronie serwera, nie w przeglądarce. To rozróżnienie jest istotne, bo wersja przeglądarkowa wygląda identycznie i jest łatwiejsza, ale najpierw ściąga wszystko, a potem udaje, że tego nie zrobiła. Rozwiązuje wrażenie, nie problem. Efektem jest odpowiedź o stałej wielkości niezależnie od tego, ile wpisów ma serwis. Zdjęło to jedyne ograniczenie, które rosło razem z dorobkiem autora — a to zmienia charakter narzędzia. Serwis, który po trzech latach pisania zaczyna zwalniać, jest narzędziem na jeden sezon. Zrobiliśmy przy tej okazji rachunek pojemności, bo wolimy znać sufit niż go odkryć. Po zdjęciu limitu rozmiaru odpowiedzi wąskim gardłem został rozmiar paczki z kodem — a wpisy są w nią wkompilowane. Wychodzi z tego rząd wielkości kilku tysięcy wpisów na planie darmowym i kilkukrotnie więcej na płatnym, który kosztuje tyle co kawa miesięcznie. Dla autora piszącego wpis dziennie kilka tysięcy wpisów to lata pracy. Sufit istnieje, ale jest wysoko i wiemy, gdzie dokładnie — a to jest jedyna forma, w jakiej sufit przestaje być problemem.
Kolofon · 7 sierpnia 2026 · wstępna

17 · Kubełkowanie
Ograniczenie, na które trafiliśmy, nie leżało w silniku ani w hostingu. Leżało w liczbie plików w jednym katalogu. Ten wpis jest o ograniczeniu, którego nikt nie szuka, dopóki się o nie nie potknie. Wpisy leżały płasko w katalogu serii. Kilkanaście plików — w porządku. Sto dwadzieścia — nadal w porządku, choć katalog robi się nieczytelny. Kilka tysięcy — i zaczynają się kłopoty, przy czym nie w silniku i nie w hostingu, tylko w narzędziach wokół repozytorium. Rozwiązanie jest banalne: wpisy dzielone na podkatalogi po roku publikacji. Zmiana dotyczy wyłącznie układu plików, bo silnik i tak skanuje katalog treści wzorcem, a nie po liście. Warto zatrzymać się nad tym, że wzorzec skanowania okazał się tu zaletą, o której nie myśleliśmy, wybierając go. Gdyby gdziekolwiek istniała lista plików do zaktualizowania, ta zmiana byłaby przepisaniem tej listy. Ponieważ nie istnieje, była przeniesieniem plików. Ogólniejsza obserwacja: wąskie gardła w takich systemach rzadko siedzą tam, gdzie się ich szuka. Szukaliśmy ich w rozmiarze odpowiedzi i w limitach czasu procesora. Znalazło się w liczbie plików w katalogu — czyli w warstwie, o której nikt nie myśli jako o części architektury. Stąd zasada, którą staramy się teraz stosować: przy każdej rzeczy, która rośnie liniowo z liczbą wpisów, zadaj pytanie, co się stanie przy dziesięciu tysiącach. Nie żeby od razu optymalizować — żeby wiedzieć, gdzie jest ściana.
Kolofon · 7 sierpnia 2026 · wstępna

18 · Rozdzielenie silnika
Operacja na otwartym sercu działającego serwisu, rozłożona na cztery etapy z zielonym budowaniem po każdym. Rozdzielenie silnika od treści było najtrudniejszą operacją w całej historii projektu, bo wykonywaną na serwisie, który działał i miał czytelników. Metoda: przy każdym pliku jedno pytanie — czy ten kod wie, jak nazywa się serwis, dla którego go napisano. Wiedziało zaskakująco dużo plików. Nazwa serwisu w opisie strony, w kanale RSS, w opisie aplikacji. Nazwy serii w porównaniach decydujących o wyglądzie. Pseudonimy autorów w regułach podpisów. Stare adresy w przekierowaniach. Kolory w plikach statycznych, które nie potrafią czytać konfiguracji. Poszło to czterema etapami, każdy z zielonym budowaniem na końcu. Pierwszy: jeden plik konfiguracji instancji jako jedyne źródło marki i kluczy. Drugi: reguły podpisów autorów i historia adresów przeniesione do danych. Trzeci: osiem stron statycznych przeniesionych do treści, a trasy zredukowane do cienkich przejściówek. Czwarty, najważniejszy: odwrócenie zależności, żeby silnik przestał sięgać po treść, a zaczął ją dostawać. Do czwartego etapu można było mówić, że silnik jest oddzielony, i byłaby to prawda o strukturze katalogów, a nie o zależnościach. Dopóki silnik importuje treść po ścieżce, druga instancja nie jest kwestią konfiguracji, tylko odgałęzienia kodu — a odgałęzienie od pierwszego dnia zaczyna się rozjeżdżać z oryginałem. Test tego rozdzielenia był brutalnie prosty: postawić drugą instancję, o zupełnie innym charakterze, i
Kolofon · 7 sierpnia 2026 · wstępna

19 · Vendoring zamiast pakietu
Instancja nie instaluje silnika — wciąga jego pliki do siebie według spisu. Wady tego rozwiązania są oczywiste, zalety mniej. Kolofon nie jest pakietem instalowanym poleceniem. Instancja wciąga jego pliki do siebie, według spisu ścieżek nazywanego manifestem, i buduje się u siebie. Jest to rozwiązanie, które w każdym poradniku figuruje pod hasłem „tak się nie robi”, więc warto powiedzieć, dlaczego tak robimy. Powód pierwszy: instancja musi być samowystarczalna. Repozytorium instancji zawiera wszystko, co potrzebne do zbudowania serwisu, bez sięgania po prywatny rejestr pakietów. Jeśli jutro zniknie wszystko po naszej stronie, instancja nadal się zbuduje i wdroży. Powód drugi: instancja może podejrzeć silnik. Nie w formie zminifikowanej paczki, tylko w czytelnym kodzie źródłowym, z komentarzami, które tłumaczą decyzje. Dla narzędzia sprzedawanego jako „twoje dane, twój kod” to nie jest szczegół. Wada jest oczywista: aktualizacja to operacja, a nie podbicie numeru wersji. Dlatego do manifestu dochodzi tryb kontrolny, który przed synchronizacją sprawdza, czy w instancji nie zmieniono czegoś po stronie silnika. Bez tego synchronizacja po cichu nadpisuje cudze zmiany — a instancje bywają edytowane także innymi narzędziami niż nasze. Sam manifest przeszedł ostatnio istotną poprawkę: jest na własnej liście, więc synchronizuje się razem z silnikiem. Wcześniej instancja mogła zostać ze starym spisem ścieżek i po cichu przegapić pliki dołożone w nowszej wersji. Historia tej poprawki, wraz z jej skutkami uboczny
Kolofon · 7 sierpnia 2026 · wstępna

20 · Dokąd to idzie
Panel autorski, wersje robocze w bazie, publikacja po stronie serwera. I jedna rzecz, której świadomie nie robimy. Na koniec to, czego jeszcze nie ma — z zastrzeżeniem, że to są kierunki, a nie obietnice z datami. Kierunek pierwszy i najważniejszy: panel autorski. Dziś wpis to plik, a publikacja to operacja na repozytorium. Docelowo autor pisze wersję roboczą, która ląduje w bazie, a publikowanie jest zadaniem po stronie serwera. Jedna decyzja architektoniczna jest tu przesądzona: aplikacja nigdy nie dostanie klucza do repozytorium. Klucz w przeglądarce to klucz wyciekły. Publikuje serwer, w imieniu autora, kluczem, którego autor nie widzi. Ciekawe jest to, że taki układ jest czystszy od obecnego, a nie tylko wygodniejszy. Wersje robocze żyją w bazie, zanim staną się plikami. Autor może napisać dziś, wypuścić za miesiąc, poprawić po drodze — nie dotykając plików ani razu. Kierunek drugi: konta czytelników. Schemat, sesje i logowanie przez zewnętrznego dostawcę już stoją; brakuje interfejsu. Ważne jest tu, czego świadomie nie budujemy — profili, obserwowania, powiadomień o aktywności. Konto ma dawać trwałą tożsamość pod komentarzem i nic ponadto. Każda kolejna funkcja społecznościowa to obietnica moderowania jej przez lata. Kierunek trzeci to pierwsze wdrożenia, które nie są nasze. Do tego czasu wszystko, co piszemy o skalowaniu i o tym, czego ludzie potrzebują, jest hipotezą — czasem dobrze uzasadnioną, ale hipotezą. Na koniec granica, której nie przekraczamy, bo określa ten projekt lepiej
Kolofon · 9 sierpnia 2026 · wstępna

21 · Wersje językowe bez centrali
Dwie wersje językowe muszą się nawzajem znaleźć. Wszystko, co przychodzi do głowy jako pierwsze — mapa adresów albo centralny katalog — jest strukturą, którą ktoś musi pamiętać o aktualizacji. Wersja językowa jest tu osobną instancją: własna subdomena, własny worker, własna baza. Nie jeden serwis z przełącznikiem, tylko dwa serwisy, które o sobie wiedzą. Powód jest ten sam, dla którego adres należy do autora — wersja angielska ma móc żyć, upaść albo zniknąć niezależnie od polskiej. Zostaje pytanie, po czym mają się nawzajem poznawać. Przełącznik języka musi zbudować adres tego samego tekstu u sąsiada, a slug jest w języku treści, więc `zalozenia-wyjsciowe` i `initial-assumptions` nie mają ze sobą nic wspólnego jako ciągi znaków. Pierwsza odpowiedź, jaka przychodzi do głowy, to mapa: plik, w którym stoi, co czemu odpowiada. Odrzucona. Mapa jest strukturą, którą trzeba pamiętać o aktualizacji, a zapomni się o niej dokładnie wtedy, gdy ktoś zmieni slug — czyli w chwili, w której była jedynym powodem, żeby istniała. Druga odpowiedź jest ambitniejsza i gorsza: centralny katalog. Jeden serwis zna wszystkie języki i wszystkie mapowania, instancje pytają jego i nie muszą wiedzieć o sobie nawzajem. Brzmi jak porządek. Jest pośrednikiem — dokładnie tym, przeciwko któremu ten silnik powstał. Serwis, który musi kogoś zapytać, żeby zbudować własne menu, ma nad sobą platformę, choćby prowadził ją autor silnika. Do tego dochodzą dwa skutki praktyczne: gdy centrala leży, przełącznik nie działa nigdzie naraz, a katalog i tak musi skądś znać slugi, więc mapa wraca — tylko teraz w jedn
Kolofon · 9 sierpnia 2026 · wstępna

22 · Tożsamość, którą da się wymazać
Ustawienie wyłączało podpis silnika w stopce. HTML był czysty. Nazwa i adres silnika leżały w pliku JavaScript obok — bo wyłącznik działał w czasie wykonania, a wtedy jest już za późno. Kolofon ma ustawienie, które wyłącza podpis silnika w stopce. Powstało dla instancji publikowanych anonimowo albo pod pseudonimem: serwis nie ma obowiązku ogłaszać, na czym stoi, a informacja o dostawcy bywa pierwszą nitką, za którą ktoś pociągnie. Ustawienie działało. W wygenerowanym HTML nie było ani jednego wystąpienia nazwy silnika. Przez cały czas, gdy tak było, w pliku JavaScript serwowanym z tej samej domeny leżała stała z nazwą silnika i jego adresem — czytelna, bez żadnego zaciemniania, do znalezienia przez każdego, kto otworzy plik. Mechanizm porażki jest pouczający, bo nie ma w nim żadnego błędu w kodzie. Ustawienie czyta się w czasie wykonania, z konfiguracji instancji. Narzędzie budujące nie ma jak stwierdzić, że gałąź `jeśli podpis włączony` nigdy się nie wykona, więc zostawia ją w wyjściu razem z tekstem, który ta gałąź renderuje. Kod był martwy. Tekst był żywy. To jest szersza zasada niż jeden przypadek. Wszystko, co ma nie istnieć w wyniku, musi być rozstrzygnięte przed jego powstaniem. Warunek sprawdzany przy renderowaniu decyduje, czy coś zobaczysz — nie o tym, czy coś tam jest. Do ukrywania obecności trzeba momentu wcześniejszego. Naprawa przenosi decyzję do budowania. Instancja podaje wartość jako stałą przy kompilacji, gałąź zwija się do literału, a tekst po martwej stronie znika z wyjścia. Po zmianie skan całego zbudowanego serwisu — kod serwera, kod klie
Kolofon · 14 sierpnia 2026 · wstępna

23 · Kanał jako wyjście treści
Kanał RSS to nie jest widok strony w innym formacie. To jedyne wyjście treści, którego autor nigdy nie ogląda — a które ktoś inny czyta zamiast strony. Treść wychodzi z Kolofonu trzema drogami. Stroną — tam renderuje ją przeglądarka i wszystko, co względne, rozwiązuje się względem adresu, pod którym czytelnik właśnie stoi. Książką — tam składa ją Typst i wszystko musi być rozstrzygnięte przed kompilacją, bo papier nie ma dokąd sięgnąć. I kanałem — tam treść czyta program, którego nie znamy, na urządzeniu, którego nie widzimy, w kontekście, którego nie kontrolujemy. Ta trzecia droga jest najłatwiejsza do zaniedbania, bo autor nią nie chodzi. Stronę widzi po każdym wdrożeniu. Książkę pobiera, gdy ją wydaje. Kanału nie otwiera nigdy — a nawet gdyby otworzył, zobaczyłby poprawny XML i nie dowiedziałby się niczego. Błąd w kanale objawia się wyłącznie u kogoś innego. Pierwsza reguła: w kanale nie ma adresów względnych. Na stronie `/images/zdjecia/2026/most.jpg` jest w porządku, bo przeglądarka wie, względem czego to liczyć. Wyjęte z kontekstu strony i wstawione do czytnika — nie prowadzi donikąd. Czytnik, który nie dostał obrazu w kanale, próbuje pobrać stronę i wyciągnąć go samodzielnie; wtedy trafia dokładnie na ten problem i pokazuje znak błędu. Wygląda to jak zepsute zdjęcie, a jest zepsuty kanał. Druga: kanał nie może zakładać, że czytnik zrobi cokolwiek poza czytaniem kanału. Nie pobierze strony, nie wykona skryptu, nie rozwinie żadnej abstrakcji. Co ma być widoczne, musi być w pliku — obraz, tekst, autor, data. Kanał, który j
Kolofon · 16 sierpnia 2026 · wstępna

24 · Zdjęcia poza repozytorium
Treść dzieli się nie na tekst i obrazy, tylko na to, co niesie język, i to, co go nie niesie. Ta granica przebiega w innym miejscu, niż się wydaje. Treść w repozytorium to jedno z trzech założeń postawionych przed pierwszą linijką kodu i przez wiele miesięcy nie miało wyjątków. Wpisy, zdjęcia, karty podglądu — wszystko leży obok kodu, wszystko wchodzi na produkcję tym samym ruchem, wszystko ma historię. Druga wersja językowa pierwszego wdrożenia pokazała, gdzie to założenie ma granicę. Odruch podpowiada, że granica biegnie między tekstem a obrazem: tekst trzeba tłumaczyć, obrazu nie. To nieprawda. Karta podglądu jest obrazem, a niesie zdanie z wpisu, więc po angielsku musi być inna. Fotografia jest obrazem i nie niesie nic — ta sama klatka obsługuje wszystkie wersje. Granica przebiega po języku, nie po formacie. Ma to konsekwencję, której nie widać w dniu, w którym się o tym decyduje. Plik binarny wchodzi do systemu kontroli wersji bez kompresji różnicowej i zostaje w historii na zawsze, także po podmianie. Seria fotograficzna, która rośnie, pomnożona przez liczbę języków, daje repozytorium, które z każdym miesiącem klonuje się dłużej — a jedyne, co w tym przyrasta, to kopie tego samego. Więc to, co niesie język, zostaje w repozytorium instancji, a to, co go nie niesie, wychodzi do magazynu obiektowego i stoi tam raz. Wersje językowe wskazują na ten sam magazyn. Silnik podaje z niego pliki własną trasą, spod adresu instancji — nie z osobnej domeny magazynu — żeby adres pozostał w obrębie serwisu i żeby wszystko, co absolut
