cutty devlog #1 — z „kilka linii kodu w przyszłym tygodniu” do v0.3.3 w 24 godziny
pivot ewoluował do cutty.dev. Ten devlog to historia rozwoju, ale produkt żyje dalej pod nową nazwą i nowym stackiem.
$ git log –oneline pivot/ –since=”24 hours ago”
W devlogu #0 dokładnie 24 godziny temu napisałem to zdanie:
„co jeszcze nie zrobione: [ ] jakiekolwiek linie kodu. Ale spokojnie. W przyszłym tygodniu.”
No więc nie czekałem do następnego tygodnia. Spokojnie też nie było.
== co się wydarzyło ==
Zamiast „kilku linii kodu w wolnym tygodniu” dostaliśmy pełny cykl: v0.1 → v0.1.1 → v0.2 → v0.3.0 → v0.3.1 → v0.3.2 → v0.3.3 w jeden wieczór, jedną noc i jeden poranek. Klasyczny przypadek „tylko jeszcze jedna rzecz”.
Stan na dziś, 20 maja, 22:00:
- ✓ v0.1 MVP — 3 endpointy, 1 tabela,
/p/{slug}301 redirect - ✓ v0.1.1 normalize URL — bez
http://, trim,//x.com, walidacja domeny (7 wariantów inputu) - ✓ v0.2 stats — hit logging, ASCII bar charts, geo breakdown przez Cloudflare
CF-IPCountry - ✓ v0.3.0 public mode — każdy może skracać bez logowania, cookie owner (UUID, 1 rok)
- ✓ v0.3.1 embedded dashboard + sidebar widget
- ✓ v0.3.2 widget polished — zero customowych styli, theme handles chrome
- ✓ v0.3.3 popup copy — bo „LIVE v0.3.2” wyglądało jak commit message, nie jak status
== to działa LIVE ==
Strona projektu /projekty/pivot/ ma formularz public. Wklej cokolwiek, dostaniesz esej.space/p/{slug}. Twoje linki widzisz tylko Ty, bo cookie. Pod formularzem dashboard z inline statystykami — domyślnie pokazuje tylko najnowszy link, reszta za przyciskiem ↓ pokaż wszystkie (bo lista 20 linków zabijała UX).
W prawym pasku, tam gdzie Jellyfin/IRIS/Matomo/Nextcloud — purpurowy badge pivot. Klikasz, popup z opisem, link do GitHub. Wygląda jak część motywu od pierwszego dnia, bo używa tej samej struktury DOM i tych samych zmiennych CSS co reszta.
== a to powinno zabić projekt w pierwszych 3 godzinach ==
Ale jakoś nie zabiło. Lista grzybów po drodze:
- PowerShell
Compress-Archiverobi ZIP z backslashami w pathsach. system docelowy potraktowałesej-pivot\esej-pivot.phpjako jeden plik z dziwną nazwą. Plugin „instalował się” — admin GUI go widział, REST API nie. Aktywacja:404. Phantom folder na serwerze nie do usunięcia z poziomu WP. Rozwiązanie: manualne cleanup na poziomie systemu plików kontenera (przez web terminal) i przesiadka na Pythonzipfilez forward slashes. (Ja, naiwnie, przed: „ale ZIP to przecież standard, co może pójść nie tak”.) - WordPress
wpautopfilter morduje JS w<script>. Convertuje<div>w stringach JS na</p>. JSON parse się sypie. Rozwiązanie: base64-encode payload, decode po stronie klienta. (Bo XKCD 1739, ale wciąż w 2026.) wpautopwkłada<br>między sąsiadujące<span>. Demo na stronie pivota się rozjeżdżał. Rozwiązanie: spany w jednej linii, bez newlinów. (Walka z platformą, którą sam wybrałem, w roku 2026.)- CSS
@mediaqueries truncated by wpautop at{. Layout responsive padał na widok mobile. Rozwiązanie:grid-template-columns: repeat(auto-fit, minmax(460px, 1fr))— responsywne bez media queries. - WP REST API nie pokazuje „phantom” pluginów z popsutymi pathsami. Czyli mogłem mieć fantazję, że stary plugin nie istnieje, podczas gdy istniał. Trzeba było użyć Unraid CLI żeby zobaczyć prawdę.
- App Password (WP REST) ma węższe scope niż cookie session. Multipart upload do
/wp-admin/update.phpwymaga cookies. Konieczność wpięcia się przez/wp-login.phpPOST + utrzymanie sesji.
== kontrowersyjna decyzja: public mode ==
Pierwotnie pivot miał być admin-only. „Moja skracarka, moja”. Ale po pomyśleniu — strona esej.space żyje z ruchu. Jak ktoś wpadnie i może coś skrócić bez logowania, to:
- jest większa szansa że wróci
- jest większa szansa że pokaże komuś
- moje koszty: zero (slug 4 chars, base62, MySQL już mam)
Więc public mode bez signup. Anonymous owner przez cookie UUID — twoje linki widzisz tylko Ty. Login nie wymagany. Rate limit 5 linków / 15 min / IP. Honeypot anti-bot. Self-service delete.
Jak ktoś będzie chciał dashboard z tygodniem aktywności, custom slug, custom domain — to v0.4 i v0.5 (user accounts z magic link login). Ale fundament jest gotowy: Esej_Pivot_Owner::current() zwraca user_id dla zalogowanych, cookie UUID dla anon. Dashboard, REST endpoints, ownership checks — wszystko już future-proof.
== co dalej ==
- [ ] v0.4 power features — custom slug, custom domain via CNAME, bulk shorten, CLI helper, expire dates, password-protected links
- [ ] v0.5 user accounts — magic link login, claim flow (anon cookie → konto)
- [ ] llms.txt — AI crawlers już mają zielone światło w Cloudflare
- [ ] wielojęzyczność strony (PL ↔ EN) — bo GitHub README jest po angielsku, ale landing nie
// PS: ten post wyszedł z planu "w przyszłym tygodniu" do publish 24h po devlog #0. ETA na resztę roadmap: nie ufaj mi.
// PPS: pierwszy link, który skróciłem na produkcji, prowadził do tego posta. esej.space/p/k3n9 → tutaj. Pivot przez mojego hosta do mojego hosta. Idealnie zbędne. Idealnie satysfakcjonujące.