Kwiecień 2024. Właśnie wrzuciłem pierwszą działającą wersję naswoim.org na produkcję. Aplikacja działa. Użytkownicy mogą się logować. Dane są w Supabase. Na papierze — sukces.
Pod maską: dwa konkurujące ze sobą design systemy, siedemnaście zduplikowanych funkcji pomocniczych rozsianych po dwunastu plikach i polityka Supabase RLS, która po cichu zawodzi w jednym konkretnym przypadku brzegowym — który odkryję dopiero za trzy tygodnie.
Vibe coding działa. Ale zawodzi w sposób niewidoczny — aż przestaje być niewidoczny.
Szybki kontekst: co zbudowałem
W ciągu mniej więcej dwunastu miesięcy zbudowałem trzy aplikacje z użyciem vibe codingu — prawie bez zewnętrznych developerów:
- naswoim.org — platforma dla inwestorów budowlanych: checklisty, budżety, dokumenty, marketplace ekspertów, mapy działek, asystent AI. Web + Android + iOS + portal admina.
- industrverse.com — B2B SaaS do szkoleń VR w przemyśle: 7 ról użytkowników, 9 dashboardów na rolę, komunikacja real-time, gateway sesji VR, pełne API backendu.
- marcinpaszkiewicz.com — ta strona. Astro SSR + WordPress headless. Prostsza, ale też pouczająca.
Claude Code był moim głównym narzędziem, z okazjonalną pomocą Cursora. Napisałem w ten sposób gdzieś między 40 000 a 60 000 linii kodu produkcyjnego. Wszystko wyszło. Wszystko działa.
Oto co zrobiłem źle.
Błąd #1: Pozwoliłem AI wybrać architekturę
Kiedy zaczynałem naswoim.org, opisałem projekt Claude’owi i zapytałem, jaki stack poleca. Dostałem solidną odpowiedź: React 19, Vite, Supabase, Tailwind CSS 4. Świetne wybory.
Potem zapytałem o komponenty UI. Zaproponował MUI 7. Zgodziłem się — był szybki, miał wszystko czego potrzebowałem.
Problem: już używałem Tailwind CSS 4. Teraz miałem dwa design systemy:
Diagram — konflikt design systemów / design system conflict
▸ spacing: rem scale
▸ breakpoints: sm/md/lg/xl
▸ tokens: CSS vars
nowy ekran
▸ spacing: 8px grid
▸ breakpoints: xs/sm/md/lg
▸ tokens: theme object
Wniosek: AI nie zna Twojej wizji na osiemnaście miesięcy do przodu. Optymalizuje pod „działające teraz”. Decyzje architekturalne — szczególnie dotyczące design systemów, modeli danych i granic modułów — muszą wychodzić od Ciebie. AI implementuje. Ty decydujesz co implementować.
Co bym zrobił inaczej: napisał jednostronicowy dokument decyzji architekturalnych przed pierwszym promptem. Nie pełna specyfikacja — tylko: co jest jedynym źródłem prawdy o stylach? Jakie jest podejście do zarządzania stanem? Jak dzielimy moduły? Daj AI ograniczenia, nie blank permission.
Błąd #2: AI nigdy nie mówi „nie” — i to jest niebezpieczne
Po trzech miesiącach pracy nad industrverse backend miał 13 modułów NestJS, 7 ról użytkowników i 9 osobnych dashboardów. Każda rola miała własną logikę dostępu do danych, własny system powiadomień, własny workflow.
Nic z tego nie było w oryginalnej specyfikacji.
industrverse — plan MVP vs rzeczywistość
plan 5 → rzeczywistość 13
plan 3 → rzeczywistość 7
plan 3 → rzeczywistość 9
Oto co się dzieje: masz pomysł o 23:00. Opisujesz go Claude’owi. Buduje go w dwadzieścia minut. Działa. Wrzucasz to. Trzy tygodnie później zauważasz, że dodanie tej funkcji zepsuło model mentalny dla następnej funkcji. Ale AI ci tego nie mówi — po prostu buduje to, o co go prosisz.
Każdy developer w zespole ma kolegę, który mówi „chwila — czy na pewno tego potrzebujemy?” AI nie mówi chwila. AI mówi tak.
Wniosek: Musisz być PM-em dla swojego AI. Nie tylko wizjonerem — osobą, która mówi nie. Pytanie nie brzmi „czy AI to zbuduje?” (zbuduje). Pytanie brzmi „czy to w ogóle powinno istnieć?”
Mam teraz regułę przed każdą nową funkcją: piszę jedno zdanie o tym, jaki problem rozwiązuje dla konkretnego użytkownika. Jeśli nie potrafię tego zdania napisać, nie piszę promptu.
Błąd #3: Debugowanie kodu, którego nie pisałeś, jest wolniejsze niż wygląda
W naswoim.org miałem błąd w politykach Supabase Row Level Security. Użytkownicy w jednej roli mogli okazjonalnie widzieć dokumenty, których nie powinni — ale tylko gdy wcześniej zaszła konkretna sekwencja operacji.
Szukanie tego zajęło mi trzy dni.
Nie dlatego, że błąd był skomplikowany. Dlatego, że kod był generowany przez AI i nie przeczytałem go wystarczająco uważnie w momencie powstawania. Polityka RLS wyglądała poprawnie. Była poprawna składniowo. Przechodziła moje podstawowe testy. Przypadek brzegowy był subtelny — kombinacja dwóch różnych warunków polityki, które oddziaływały na siebie w nieoczywisty sposób.
Kiedy piszesz kod sam, budujesz model mentalny w trakcie pisania. Kiedy pisze AI, recenzujesz — co jest szybsze, ale płytsze. Model w głowie jest mniej kompletny. A płytkie modele mentalne spowalniają debugowanie.
Wniosek: Nigdy nie merguj kodu AI, którego nie potrafisz wyjaśnić linijka po linijce. W szczególności dla wszystkiego, co dotyczy auth, dostępu do danych i logiki krytycznej dla biznesu: czytaj jak code reviewer, nie jak ktoś sprawdzający listę zakupów.
Błąd #4: Kontekst się kończy — i AI „zapomina” wszystko
W długiej sesji Claude Code widzi wszystko, co razem zbudowaliście. Zna Twoje konwencje nazewnicze, wzorce, preferencje. Jest spójny.
W następnej sesji zaczyna od zera.
Schemat — pamięć kontekstu między sesjami / context memory across sessions
▸ TanStack Query
▸ Zustand atoms
▸ local useState
▸ API calls inline
▸ Context API
▸ Axios interceptors
W naswoim.org zacząłem nową sesję po dwudniowej przerwie i poprosiłem Claude’a o zbudowanie nowego komponentu. Wygenerował coś, co działało — ale używało zupełnie innych wzorców niż reszta kodu. Inne podejście do zarządzania stanem. Inny styl obsługi błędów. Inne nazewnictwo.
Po czterech miesiącach codebase miał trzy wyraźne „ery” — każda odzwierciedlająca konwencje z rozmów prowadzonych w danym czasie.
Wniosek: Plik CLAUDE.md nie jest opcjonalny. Ustaw go pierwszego dnia. Powinien zawierać: konwencje nazewnicze, wzorce do stosowania, wzorce do unikania, których bibliotek używać do jakich problemów. To jest trwała pamięć między sesjami.
Błąd #5: Security jest niewidoczna — dopóki nie jest
AI generuje działający kod. Nie generuje niezawodnie bezpiecznego kodu.
W industrverse miałem endpoint API dostępny tylko dla użytkowników z rolą „trainer”. Endpoint działał poprawnie. Zwracał właściwe dane. Obsługiwał błędy elegancko.
Nie weryfikował też roszczenia roli JWT przy jednej konkretnej metodzie HTTP. Użytkownik z dowolnym uwierzytelnionym tokenem mógł go wywołać.
Znalazłem to podczas ręcznego przeglądu bezpieczeństwa — nie dlatego, że Claude to oznaczył, nie dlatego, że moje testy to wyłapały. Bo pewnego popołudnia usiadłem i przeczytałem każdy endpoint związany z auth.
Security review — checklista po każdym auth feature
Czy każdy endpoint weryfikuje JWT/session?
Is every endpoint verifying JWT/session?
Czy rola użytkownika jest sprawdzana server-side?
Is the user role checked server-side?
Czy RLS działa dla wszystkich kombinacji ról?
Does RLS work for all role combinations?
Czy input jest walidowany przed zapisem do bazy?
Is input validated before writing to DB?
Czy wrażliwe pola są filtrowane w response?
Are sensitive fields filtered in the response?
Czy edge case (brak roli, wygasły token) jest obsłużony?
Is the edge case (missing role, expired token) handled?
Wniosek: Po każdej funkcji dotykającej autentykacji, autoryzacji lub danych użytkownika — rób ręczny przegląd bezpieczeństwa. Nie vibe. Checklista.
Co bym zrobił inaczej: 6 reguł
Gdybym zaczynał dzisiaj, z całą tą wiedzą:
Czego nie mówię
Nie mówię, że vibe coding jest wadliwy ani że narzędzia AI są zawodne. Wszystkie trzy projekty, które zbudowałem, działają. Mają prawdziwych użytkowników. Rozwiązują prawdziwe problemy. Zysk produktywności jest realny — zbudowałem w rok to, co trzyosobowy zespół zrobiłby w osiemnaście miesięcy.
Mówię, że tryby awarii są konkretne i nieoczywiste na starcie.
Największe ryzyko w vibe codingu nie jest to, że AI pisze zły kod. Jest to, że AI pisze kod, który wygląda dobrze — aż coś idzie nie tak. I wtedy patrzysz na codebase, który na wpół rozumiesz, z bugiem, którego nie napisałeś, i modelem mentalnym mającym luki dokładnie w złych miejscach.
Prędkość jest realna. Zbuduj nawyki, które sprawią, że będzie bezpieczna.