$ cat ./posty/devlogi/pivot-devlog-1.md

cutty devlog #1 — z „kilka linii kodu w przyszłym tygodniu” do v0.3.3 w 24 godziny

maj 19 17:04 | 4 min | autor: esej | #devlogi
● aktualizacja 2026-05-19 — projekt 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:

  1. PowerShell Compress-Archive robi ZIP z backslashami w pathsach. system docelowy potraktował esej-pivot\esej-pivot.php jako 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 Python zipfile z forward slashes. (Ja, naiwnie, przed: „ale ZIP to przecież standard, co może pójść nie tak”.)
  2. WordPress wpautop filter 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.)
  3. wpautop wkł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.)
  4. CSS @media queries 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.
  5. 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ę.
  6. App Password (WP REST) ma węższe scope niż cookie session. Multipart upload do /wp-admin/update.php wymaga cookies. Konieczność wpięcia się przez /wp-login.php POST + 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.