[17]
BAZARTY.COM (SUPABASE)
▒ TIER C · ROK 2026 · STATUS: DZIAŁA · JĘZYKI: TYPESCRIPT
Escrow, wyszukiwanie pełnotekstowe w Postgresie i budowa spec-first
[FIG. 1] MISJA
Projektuję escrow: płatność zamrożoną do potwierdzenia odbioru, z obsługą stanów pośrednich i sporów. bazarty.com istnieje w dwóch architekturach — to wariant na Supabase; wariant Payload + MongoDB opisany jest osobno. bazarty to marketplace rękodzieła i sztuki łączący twórców z kupującymi: sprzedawca zakłada sklep-portfolio, kupujący przegląda katalog w stylu Pinteresta (tablice, polubienia, mozaikowe okładki), a środki trafiają do sprzedawcy dopiero po potwierdzeniu odbioru. Cały post-purchase (zakup → wysłano → dostarczono → odbiór → ocena → spór) żyje w wątku czatu 1:1 jako karty transakcyjne, co reużywa jeden prymityw danych i upraszcza UX. Wyszukiwanie zbudowałem pełnotekstowo w samym Postgresie, bo przy tym katalogu wektory byłyby przerostem formy — i umiem tę granicę wskazać. Pracuję spec-first: przed implementacją powstaje pakiet planistyczny z kontraktem, a backlog prowadzę jak artefakt inżynierski z kryteriami akceptacji, nie jak listę życzeń. Umiem też przenieść prototyp generatywny w zdyscyplinowaną budowę i nazwać, co z prototypu zostaje, a co idzie do przepisania. Stack: TanStack Start (SSR, 34 trasy plikowe) i pełny Supabase: 35 migracji SQL z RLS i RPC SECURITY DEFINER, 9 edge functions w Deno (Stripe Connect, webhooki, wycena i etykiety Furgonetki, kolejka maili, cron auto-release z backstopem 88 dni). Każdą decyzję architektoniczną zapisuję na piśmie — 20 ADR-ów z sekcją „dlaczego + alternatywy” — a fazy premierowe przechodzą przez code-review i security-review, zanim cokolwiek dostanie odhaczenie.
[FIG. 2] ARCHITEKTURA
najedź na blok, by zobaczyć opis
[FIG. 3] WYZWANIA
[+][CH-01]
Rękodzieło to unikaty — qty równe 1 — więc dwie równoległe płatności mogły „sprzedać” ten sam egzemplarz dwa razy, a webhook Stripe przeniósłby oba zamówienia w paid. Pierwsza wersja rezerwacji (hold 15 minut na partial unique index WHERE status='active', kod 23505 jako idempotencja) wyglądała dobrze — dopiero przegląd kodu wykazał luki: holdy nie były zwalniane przy błędach (stranded holds), a samo przejście pending→paid nie miało guardu kolizji. Domknąłem to atomowym przejściem stanu w jednej transakcji RPC apply_paid_transition: lock produktów plus guard unikatu, a wykryta kolizja nie robi automatycznego refundu, tylko flaguje zamówienie needs_review z eventem double_sale_detected — pieniądze stoją, człowiek decyduje. Współbieżność rozstrzyga baza, nie klient.
[+][CH-02]
Onboarding sprzedawcy kończył się statusem enabled w Stripe, a u nas payout_status wisiał na pending — sprzedawca nie mógł wypłacać. Przyczyna siedziała w niuansie Connecta: webhook platformy nie dostaje zdarzeń account.updated z kont połączonych, a w nowym Dashboardzie Stripe nie ma już przełącznika connect — endpoint z connect=true da się utworzyć wyłącznie przez API, i ma on własny, osobny sekret podpisu. Skończyło się na dwóch webhookach pod tym samym URL-em i weryfikacji podpisu przeciw obu sekretom w pętli, aż któryś się zgodzi. Do tego cała maszyna escrow: transfer release przez source_transaction, nieudany transfer cofa releasing→paid, idempotencja po processed_stripe_events, spór kupującego zatrzymuje auto-release, a backstop crona wymusza rozstrzygnięcie po 88 dniach.
[+][CH-03]
Po okresie szybkiego, „klikanego” rozwoju odkryłem, że kluczowe obiekty escrow — RPC zwalniające środki, tabela zdarzeń Stripe, kolumny Connect — istnieją wyłącznie w żywej bazie produkcyjnej, a nie w wersjonowanych migracjach: baza nie była odtwarzalna z repo, a agent patrzący na kod „nie znajdował escrow”. Odzyskanie odtwarzalności zajęło kilka podejść, bo pierwszy backfill domknął główne obiekty, a audyt wykazał kolejne niezwersjonowane (payment_events, kolumny payout, funkcje RLS). Ostatecznie: migracje idempotentne (apply na żywej bazie = no-op), audyt „żywa baza vs migracje” i decyzja na piśmie, że istnieje dokładnie jedna kanoniczna baza. Lekcja o infrastructure-as-code, której nie zapomnę: schemat, którego nie ma w repo, po prostu nie istnieje.
[+][CH-04]
Ten projekt to także historia zmiany metody. Zaczynał jako generatywny prototyp w Lovable — szybki, ale z regresjami, rewertami i dryfującą bazą — a skończył w dyscyplinie spec-first: pakiet planistyczny z fazami F0–F6, dziennik dwudziestu ADR-ów, pętla /loop z definicją ukończenia zawierającą code-review i security-review. Przeszedłem od generowania i klikania do mierzenia i domykania — i ta siatka bezpieczeństwa natychmiast zaczęła łapać realne błędy: przegląd kodu wykrył m.in. wyścig w debounce wyszukiwarki (flaga alive ustawiana w callbacku timeoutu zamiast w cleanupie efektu — starszy request mógł nadpisać nowsze wyniki) oraz podatność cenową, w której klient dyktował koszt wysyłki; dziś kwotę liczy serwer, wołając wycenę kuriera po swojej stronie, z bezpiecznym fallbackiem, który nigdy nie wynosi zero.
[FIG. 4] WARSTWA AI
W produkcie nie działa żaden model — wyszukiwarka to Postgres full-text, nie wektorowa. AI zbudowało za to platformę i to w dwóch epokach: najpierw generatywny prototyp w Lovable, potem dyscyplina spec-first w Claude Code — pakiet planistyczny z MASTER-PROMPT-em, pętla /loop po fazowanym backlogu, kontekst mierzony z pliku statusline zamiast zgadywany, a definicja ukończenia każdej fazy zawiera /code-review i /security-review. Pamięć projektu trzyma 29 notatek z twardymi regułami pracy wypracowanymi po realnych pomyłkach.
[FIG. 2A] ŻYWY SAVECHIP
KLIKNIJ KAFEL — HEURYSTYKA TAGÓW PODPOWIE TABLICĘ
47 ZAPISÓW
NEXT.JS (FERMENT) vs TANSTACK START (OBJET)
| NEXT.JS | TANSTACK START | |
|---|---|---|
| ROUTING | App Router (konwencje) | TanStack Router |
| RENDER | RSC na Vercelu | Vite 7 + Cloudflare |
| STAN | server actions + DB | zustand/persist |
| DANE | tRPC + Payload | TanStack Query + Supabase |
→ najedź na wiersz — konsekwencja decyzji architektonicznej
[FIG. 5] GALERIA





