<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Kacper Złotorowicz — Blog</title>
    <link>https://zlotorowicz.com/blog/</link>
    <description>Zapiski z pracy nad własnymi projektami — co się zepsuło, dlaczego i co z tego wynika dla firm, które automatyzują swoje procesy.</description>
    <language>pl-PL</language>
    <lastBuildDate>Wed, 05 Aug 2026 12:00:00 GMT</lastBuildDate>
    <atom:link href="https://zlotorowicz.com/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Gdzie sprawdzasz zmianę, zanim zobaczy ją klient</title>
      <link>https://zlotorowicz.com/blog/gdzie-sprawdzasz-zanim-wlaczysz/</link>
      <guid isPermaLink="true">https://zlotorowicz.com/blog/gdzie-sprawdzasz-zanim-wlaczysz/</guid>
      <pubDate>Wed, 05 Aug 2026 12:00:00 GMT</pubDate>
      <description>Drobna poprawka odsłoniła brak środowiska testowego. Dlaczego to ważne także dla firm, które same nie programują.</description>
      <content:encoded><![CDATA[<p><strong>Każda zmiana w programie, z którego korzysta Twoja firma, musi gdzieś zostać sprawdzona, zanim dotknie prawdziwych danych. Jeśli nie ma takiego miejsca, to miejscem sprawdzania stajecie się Wy — na żywo, w godzinach pracy. Ten wpis pokazuje, jak wygląda porządny układ i o co zapytać osobę, która robi dla Ciebie oprogramowanie. To jedno z tańszych zabezpieczeń, jakie możesz sobie zapewnić. Na końcu zostawiam gotowy szablon polecenia dla tych, którzy budują sami.</strong></p>
<p>Prowadzę <strong>TranslateScorm</strong> — aplikację, która tłumaczy szkolenia internetowe na obce języki. Korzystają z niej firmy szkoleniowe: wgrywają gotowy kurs po polsku, a dostają go w kilku wersjach językowych. Żeby tłumaczenia były spójne — żeby ta sama nazwa stanowiska czy przycisku brzmiała wszędzie tak samo — każda firma ma u siebie <strong>słownik: listę pojęć wraz z ich zatwierdzonymi tłumaczeniami</strong>.</p>
<p>I właśnie ten słownik chciałem tamtego ranka poprawić. Do tej pory miał sztywno ustawione języki, te same dla wszystkich klientów. Chciałem, żeby każda firma mogła wybrać własne — bo jedna tłumaczy na czeski i słowacki, a druga na hiszpański. Godzina, może dwie roboty.</p>
<p>Zrobiłem to. Kliknąłem „Zapisz&quot;. Nie zadziałało — nie dlatego, że źle wymyśliłem rozwiązanie, tylko dlatego, że jedną z czynności potrzebnych do uruchomienia zmiany po prostu <strong>pominąłem</strong>. Przygotowałem ją, odłożyłem na później i zapomniałem.</p>
<p>Mogłem to naprawić w minutę i wrócić do pracy. Zamiast tego zadałem sobie inne pytanie: <em>dlaczego w ogóle mogło do tego dojść?</em> I reszta dnia poszła już nie na słownik, tylko na to pytanie. Słownik był tylko zapalnikiem — dalej nie będzie już o nim mowy.</p>
<h2>Jak to wyglądało wcześniej</h2>
<p>Miałem jeden komplet prawdziwych danych i pracowałem bezpośrednio na nim. Każda próba, każdy eksperyment, każde „sprawdzę tylko, czy to działa&quot; dotyczyło od razu tego samego miejsca, z którego korzystali klienci.</p>
<p>To trochę jak remontowanie sklepu w godzinach otwarcia. Zwykle jakoś idzie. Klienci przechodzą obok drabiny i nic się nie dzieje. Ale nie ma tam żadnego marginesu na pomyłkę — a pomyłka w końcu przychodzi, bo zawsze przychodzi.</p>
<p>Najgorsze było to, że <strong>nie miałem gdzie się pomylić bezpiecznie</strong>. Nie istniało miejsce, w którym mógłbym coś zepsuć, obejrzeć skutki i spokojnie naprawić. Skoro takiego miejsca nie ma, to każdy błąd wychodzi dopiero tam, gdzie boli najbardziej: u ludzi, którzy właśnie próbują wykonać swoją pracę.</p>
<figure class="post-figure">
<div class="scroll-x">
<svg viewBox="0 0 720 300" role="img" aria-label="Porównanie: po lewej każda zmiana trafia wprost do danych klientów, po prawej zmiana przechodzi przez trzy oddzielone etapy, zanim do nich dotrze.">
<defs>
<marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="currentColor"/></marker>
<marker id="ar-d" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#B4453A"/></marker>
<marker id="ar-g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#C4992A"/></marker>
</defs>
<text x="14" y="22" font-size="13" font-weight="700" fill="currentColor">WCZEŚNIEJ</text>
<text x="14" y="40" font-size="12" fill="currentColor" opacity=".65">jedno miejsce na wszystko</text>
<rect x="14" y="62" width="150" height="46" rx="7" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="89" y="90" font-size="13" text-anchor="middle" fill="currentColor">Pomysł / zmiana</text>
<line x1="89" y1="112" x2="89" y2="168" stroke="#B4453A" stroke-width="2" marker-end="url(#ar-d)"/>
<text x="99" y="145" font-size="12" fill="#B4453A" font-weight="600">od razu</text>
<rect x="14" y="172" width="150" height="62" rx="7" fill="none" stroke="#B4453A" stroke-width="2"/>
<text x="89" y="197" font-size="13" text-anchor="middle" fill="#B4453A" font-weight="600">Dane klientów</text>
<text x="89" y="216" font-size="12" text-anchor="middle" fill="#B4453A">praca na żywo</text>
<text x="89" y="262" font-size="12.5" text-anchor="middle" fill="#B4453A" font-weight="600">błąd = awaria u klienta</text>
<text x="89" y="280" font-size="12" text-anchor="middle" fill="currentColor" opacity=".6">nie ma gdzie się pomylić</text>
<line x1="212" y1="58" x2="212" y2="286" stroke="currentColor" stroke-width="1" opacity=".25" stroke-dasharray="4 4"/>
<text x="262" y="22" font-size="13" font-weight="700" fill="currentColor">TERAZ</text>
<text x="262" y="40" font-size="12" fill="currentColor" opacity=".65">trzy oddzielone etapy</text>
<rect x="262" y="62" width="132" height="46" rx="7" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="328" y="82" font-size="13" text-anchor="middle" fill="currentColor">U mnie</text>
<text x="328" y="99" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".62">dane wymyślone</text>
<line x1="394" y1="85" x2="434" y2="85" stroke="currentColor" stroke-width="1.6" marker-end="url(#ar)"/>
<rect x="438" y="62" width="132" height="46" rx="7" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="504" y="82" font-size="13" text-anchor="middle" fill="currentColor">Kopia testowa</text>
<text x="504" y="99" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".62">jak prawdziwa</text>
<line x1="504" y1="112" x2="504" y2="166" stroke="currentColor" stroke-width="1.6" marker-end="url(#ar)"/>
<text x="514" y="144" font-size="12" fill="currentColor" opacity=".7">dopiero gdy działa</text>
<rect x="438" y="170" width="132" height="62" rx="7" fill="none" stroke="#C4992A" stroke-width="2"/>
<text x="504" y="195" font-size="13" text-anchor="middle" fill="#A07A18" font-weight="600">Klienci</text>
<text x="504" y="214" font-size="11.5" text-anchor="middle" fill="#A07A18">prawdziwe dane</text>
<text x="504" y="262" font-size="12.5" text-anchor="middle" fill="currentColor" font-weight="600">błąd = wychodzi wcześniej</text>
<text x="504" y="280" font-size="12" text-anchor="middle" fill="currentColor" opacity=".6">jest gdzie się pomylić</text>
</svg>
</div>
<figcaption>Różnica nie polega na tym, że popełniam mniej błędów. Polega na tym, gdzie te błędy wychodzą — u mnie, czy u klienta.</figcaption>
</figure>
<h2>Trzy miejsca zamiast jednego</h2>
<p>Rozwiązanie brzmi banalnie: zamiast jednego miejsca — trzy. Każde ma inne zadanie i inne dane.</p>
<ul>
<li><strong>U mnie na komputerze.</strong> Tu powstaje zmiana. Dane są wymyślone, więc mogę zepsuć absolutnie wszystko i nikogo to nie dotyczy.</li>
<li><strong>Kopia testowa.</strong> Osobne miejsce urządzone tak samo jak to prawdziwe, ale z danymi na niby. Tu sprawdzam, czy zmiana zachowa się tak samo w warunkach zbliżonych do rzeczywistych.</li>
<li><strong>To, z czego korzystają klienci.</strong> Tu trafia wyłącznie coś, co przeszło dwa poprzednie etapy.</li>
</ul>
<p>Sens jest jeden: <strong>zmiana ma szansę zawieść w miejscu, gdzie nic to nie kosztuje</strong>. Nie chodzi o to, żeby się nie mylić. Chodzi o to, żeby pomyłka spotkała mnie, a nie klienta. U mnie zbudowanie tego zajęło część jednego dnia i nie wiązało się z dodatkowymi opłatami — druga baza mieści się w darmowym planie.</p>
<figure class="post-figure">
<div class="scroll-x">
<svg viewBox="0 0 720 210" role="img" aria-label="Droga zmiany od pomysłu do klienta w czterech krokach, z opisem, co jest wyłapywane na każdym etapie.">
<line x1="70" y1="72" x2="176" y2="72" stroke="currentColor" stroke-width="1.6" marker-end="url(#ar)"/>
<line x1="250" y1="72" x2="356" y2="72" stroke="currentColor" stroke-width="1.6" marker-end="url(#ar)"/>
<line x1="430" y1="72" x2="536" y2="72" stroke="currentColor" stroke-width="1.6" marker-end="url(#ar)"/>
<circle cx="40" cy="72" r="26" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="40" y="77" font-size="16" text-anchor="middle" fill="currentColor">1</text>
<text x="40" y="122" font-size="13" text-anchor="middle" fill="currentColor" font-weight="600">Pomysł</text>
<text x="40" y="142" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".6">potrzeba</text>
<circle cx="212" cy="72" r="26" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="212" y="77" font-size="16" text-anchor="middle" fill="currentColor">2</text>
<text x="212" y="122" font-size="13" text-anchor="middle" fill="currentColor" font-weight="600">U mnie</text>
<text x="212" y="142" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".6">czy w ogóle działa</text>
<text x="212" y="160" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".6">dane wymyślone</text>
<circle cx="392" cy="72" r="26" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="392" y="77" font-size="16" text-anchor="middle" fill="currentColor">3</text>
<text x="392" y="122" font-size="13" text-anchor="middle" fill="currentColor" font-weight="600">Kopia testowa</text>
<text x="392" y="142" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".6">czy działa jak u klienta</text>
<text x="392" y="160" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".6">warunki jak prawdziwe</text>
<circle cx="572" cy="72" r="26" fill="none" stroke="#C4992A" stroke-width="2"/>
<text x="572" y="77" font-size="16" text-anchor="middle" fill="#A07A18" font-weight="600">4</text>
<text x="572" y="122" font-size="13" text-anchor="middle" fill="#A07A18" font-weight="600">Klienci</text>
<text x="572" y="142" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".6">prawdziwa praca</text>
<text x="212" y="30" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".55">łapie: literówki, oczywiste błędy</text>
<text x="392" y="30" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".55">łapie: pominięte kroki, różnice w danych</text>
</svg>
</div>
<figcaption>Każdy etap wyłapuje inny rodzaj problemu. Etap 3 jest tym, którego wcześniej nie miałem — i to on wyłapuje właśnie takie pomyłki jak moja.</figcaption>
</figure>
<h2>Jak to jest poskładane naprawdę</h2>
<p>Powyższe schematy są celowo uproszczone. Poniżej to samo, ale z prawdziwymi nazwami — gdyby ktoś chciał zobaczyć, z czego to się składa. <strong>Nie trzeba tego rozumieć, żeby czytać dalej</strong>; ważny jest sam układ.</p>
<p>Aplikacja składa się z dwóch części, które łączą się w parę. Pierwsza to <strong>strona</strong>, którą widzi użytkownik. Druga to <strong>baza danych</strong> — magazyn, w którym trzymane są słowniki, konta i tłumaczenia. Sztuka polega na tym, żeby strona testowa łączyła się z bazą testową, a nigdy z prawdziwą.</p>
<p>Do tego dochodzi trzeci element: <strong>gałąź</strong>. To równoległa wersja projektu — ten sam kod, ale rozwijany osobno, tak jak dwie kopie tego samego dokumentu, w których pracuje się niezależnie. Wysłanie zmiany do gałęzi <code>test</code> automatycznie odświeża stronę testową, a do <code>main</code> — tę, z której korzystają klienci. Dzięki temu <strong>nie ma czegoś takiego jak „ręczne wgrywanie na serwer&quot;</strong>, a więc nie ma też okazji, żeby wgrać coś nie tam, gdzie trzeba.</p>
<figure class="post-figure">
<div class="scroll-x">
<svg viewBox="0 0 720 356" role="img" aria-label="Trzy poziomy: gałąź kodu, adres strony i baza danych. Gałąź test odświeża stronę testową połączoną z bazą testową Supabase, gałąź main odświeża stronę produkcyjną połączoną z bazą główną, a praca na komputerze korzysta z bazy uruchomionej lokalnie.">
<text x="14" y="20" font-size="12" font-weight="700" fill="currentColor">KTÓRA WERSJA KODU</text>
<text x="252" y="20" font-size="12" font-weight="700" fill="currentColor">CO WIDZI UŻYTKOWNIK</text>
<text x="500" y="20" font-size="12" font-weight="700" fill="currentColor">GDZIE SĄ DANE</text>
<text x="14" y="52" font-size="11.5" font-weight="700" fill="currentColor" opacity=".55">1 · TYLKO U MNIE</text>
<rect x="14" y="62" width="212" height="54" rx="7" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="28" y="84" font-size="12.5" fill="currentColor">wersja robocza</text>
<text x="28" y="102" font-size="11" fill="currentColor" opacity=".62">jeszcze nigdzie nie wysłana</text>
<line x1="234" y1="89" x2="244" y2="89" stroke="currentColor" stroke-width="1.6" marker-end="url(#ar)"/>
<rect x="252" y="62" width="212" height="54" rx="7" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="266" y="84" font-size="12.5" fill="currentColor">strona na moim komputerze</text>
<text x="266" y="102" font-size="11" fill="currentColor" opacity=".62">niedostępna z internetu</text>
<line x1="472" y1="89" x2="492" y2="89" stroke="currentColor" stroke-width="1.6" marker-end="url(#ar)"/>
<rect x="500" y="62" width="206" height="54" rx="7" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="514" y="84" font-size="12.5" fill="currentColor">baza uruchomiona lokalnie</text>
<text x="514" y="102" font-size="11" fill="currentColor" opacity=".62">dane wymyślone · można zepsuć</text>
<text x="14" y="150" font-size="11.5" font-weight="700" fill="currentColor" opacity=".55">2 · DO SPRAWDZANIA</text>
<rect x="14" y="160" width="212" height="54" rx="7" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="28" y="182" font-size="12.5" fill="currentColor" font-weight="600">gałąź „test”</text>
<text x="28" y="200" font-size="11" fill="currentColor" opacity=".62">wysłanie zmiany = odświeżenie</text>
<line x1="234" y1="187" x2="244" y2="187" stroke="currentColor" stroke-width="1.6" marker-end="url(#ar)"/>
<rect x="252" y="160" width="212" height="54" rx="7" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="266" y="182" font-size="12.5" fill="currentColor">test.translatescorm.com</text>
<text x="266" y="200" font-size="11" fill="currentColor" opacity=".62">osobny adres, ta sama strona</text>
<line x1="472" y1="187" x2="492" y2="187" stroke="currentColor" stroke-width="1.6" marker-end="url(#ar)"/>
<rect x="500" y="160" width="206" height="54" rx="7" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="514" y="182" font-size="12.5" fill="currentColor">druga baza (Supabase)</text>
<text x="514" y="200" font-size="11" fill="currentColor" opacity=".62">dane na niby · darmowy plan</text>
<text x="14" y="248" font-size="11.5" font-weight="700" fill="#A07A18">3 · DLA KLIENTÓW</text>
<rect x="14" y="258" width="212" height="54" rx="7" fill="none" stroke="#C4992A" stroke-width="2"/>
<text x="28" y="280" font-size="12.5" fill="#A07A18" font-weight="600">gałąź „main”</text>
<text x="28" y="298" font-size="11" fill="currentColor" opacity=".62">tu trafia tylko sprawdzone</text>
<line x1="234" y1="285" x2="244" y2="285" stroke="#C4992A" stroke-width="2" marker-end="url(#ar-g)"/>
<rect x="252" y="258" width="212" height="54" rx="7" fill="none" stroke="#C4992A" stroke-width="2"/>
<text x="266" y="280" font-size="12.5" fill="#A07A18" font-weight="600">translatescorm.com</text>
<text x="266" y="298" font-size="11" fill="currentColor" opacity=".62">adres, który znają klienci</text>
<line x1="472" y1="285" x2="492" y2="285" stroke="#C4992A" stroke-width="2" marker-end="url(#ar-g)"/>
<rect x="500" y="258" width="206" height="54" rx="7" fill="none" stroke="#C4992A" stroke-width="2"/>
<text x="514" y="280" font-size="12.5" fill="#A07A18" font-weight="600">baza główna (Supabase)</text>
<text x="514" y="298" font-size="11" fill="currentColor" opacity=".62">prawdziwe słowniki · nietykalna</text>
<line x1="14" y1="326" x2="706" y2="326" stroke="currentColor" stroke-width="1" opacity=".18"/>
<text x="14" y="346" font-size="12" fill="currentColor" opacity=".72">Poziomy nigdy się nie krzyżują: strona testowa nie ma jak sięgnąć do bazy klientów.</text>
</svg>
</div>
<figcaption>Każdy poziom ma własną gałąź kodu, własny adres i własną bazę. Aplikacja rozpoznaje po adresie strony, z której bazy korzystać — adres nieznany, na przykład literówka, kieruje do bazy lokalnej, czyli w stronę najbezpieczniejszą. Strony stoją na Cloudflare, bazy na Supabase; obie usługi mają darmowe plany wystarczające do prowadzenia takiego układu.</figcaption>
</figure>
<h2>Dlaczego akurat ten pominięty krok był groźny</h2>
<p>Wróćmy na moment do tamtego kliknięcia „Zapisz&quot;. Wiele zmian składa się z dwóch części: samej zmiany oraz <strong>przygotowania miejsca w bazie, w którym mają być trzymane nowe informacje</strong>. To jak wstawienie nowej rubryki do formularza: trzeba dodać i pole, i miejsce na wpisaną w nie treść.</p>
<p>Ja zrobiłem pierwsze, a drugie odłożyłem. I to jest podstępne, bo <strong>na pierwszy rzut oka wszystko wygląda na gotowe</strong> — zmiana napisana, zadanie odhaczone. Program zgłasza problem dopiero wtedy, gdy ktoś faktycznie spróbuje czegoś użyć.</p>
<p>U mnie „ktoś&quot; to byłem ja, pięć minut później. Bez etapu pośredniego tym kimś byłby klient — w środku swojej pracy, bez pojęcia, dlaczego przycisk przestał działać.</p>
<figure class="post-figure">
<div class="scroll-x">
<svg viewBox="0 0 720 250" role="img" aria-label="Zmiana składa się z dwóch części. Gdy druga zostanie pominięta, wszystko wygląda na gotowe, a awaria ujawnia się dopiero przy próbie użycia.">
<text x="14" y="20" font-size="12.5" font-weight="700" fill="currentColor">ZMIANA SKŁADA SIĘ Z DWÓCH CZĘŚCI</text>
<rect x="14" y="38" width="200" height="54" rx="7" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="114" y="60" font-size="13" text-anchor="middle" fill="currentColor">Nowa funkcja</text>
<text x="114" y="79" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".62">zrobione ✓</text>
<rect x="14" y="104" width="200" height="54" rx="7" fill="none" stroke="#B4453A" stroke-width="2" stroke-dasharray="6 4"/>
<text x="114" y="126" font-size="13" text-anchor="middle" fill="#B4453A">Miejsce w bazie</text>
<text x="114" y="145" font-size="11.5" text-anchor="middle" fill="#B4453A">przygotowane, nieuruchomione</text>
<line x1="222" y1="98" x2="286" y2="98" stroke="currentColor" stroke-width="1.6" marker-end="url(#ar)"/>
<rect x="292" y="66" width="176" height="64" rx="7" fill="none" stroke="currentColor" stroke-width="1.5"/>
<text x="380" y="90" font-size="13" text-anchor="middle" fill="currentColor" font-weight="600">Wygląda na gotowe</text>
<text x="380" y="110" font-size="11.5" text-anchor="middle" fill="currentColor" opacity=".62">zadanie odhaczone</text>
<line x1="476" y1="98" x2="540" y2="98" stroke="#B4453A" stroke-width="2" marker-end="url(#ar-d)"/>
<text x="508" y="88" font-size="11.5" text-anchor="middle" fill="#B4453A">użycie</text>
<rect x="546" y="66" width="160" height="64" rx="7" fill="none" stroke="#B4453A" stroke-width="2"/>
<text x="626" y="90" font-size="13" text-anchor="middle" fill="#B4453A" font-weight="600">Nie działa</text>
<text x="626" y="110" font-size="11.5" text-anchor="middle" fill="#B4453A">dopiero teraz widać</text>
<line x1="14" y1="182" x2="706" y2="182" stroke="currentColor" stroke-width="1" opacity=".2"/>
<text x="14" y="208" font-size="12.5" font-weight="700" fill="currentColor">KTO JEST TYM „KTOŚ", KTO PRÓBUJE UŻYĆ</text>
<text x="14" y="232" font-size="13" fill="currentColor">bez etapu pośredniego: <tspan fill="#B4453A" font-weight="600">klient, w środku pracy</tspan>   ·   z etapem pośrednim: <tspan fill="#A07A18" font-weight="600">ja, pięć minut później</tspan></text>
</svg>
</div>
<figcaption>Pominięty krok nie daje o sobie znać od razu. Dlatego potrzebne jest miejsce, w którym ktoś spróbuje użyć zmiany, zanim zrobi to klient.</figcaption>
</figure>
<h2>O co zapytać osobę, która robi dla Ciebie oprogramowanie</h2>
<p>Pięć pytań, na które nie trzeba wiedzy technicznej. Interesuje Cię nie tyle sama odpowiedź, ile to, czy pada bez wahania.</p>
<ol>
<li><strong>Gdzie sprawdzacie zmiany, zanim zobaczą je moi ludzie?</strong> Jeśli odpowiedź brzmi „u siebie&quot; i na tym koniec — brakuje etapu pośredniego.</li>
<li><strong>Czy testujecie na prawdziwych danych mojej firmy?</strong> Powinno paść „nie&quot; — do prób służy kopia, nie oryginał.</li>
<li><strong>Skąd wiecie, że wszystkie potrzebne kroki zostały wykonane?</strong> Szukasz odpowiedzi „mamy to zapisane i sprawdzamy&quot;, nie „pamiętamy&quot;.</li>
<li><strong>Co się dzieje, gdy zmiana okaże się zła po wdrożeniu?</strong> Powinien istnieć sposób powrotu do stanu sprzed — i ktoś powinien go znać.</li>
<li><strong>Czy mamy kopię danych i kiedy ostatnio ktoś ją odtworzył?</strong> Kopia, której nigdy nie odtworzono, jest tylko obietnicą kopii.</li>
</ol>
<h2>Prompt, którym to spiąłem</h2>
<p>Całego układu nie budowałem ręcznie — zleciłem to Claude Code, czyli asystentowi AI, który pracuje bezpośrednio na plikach projektu. Poniżej szablon polecenia, <strong>oczyszczony z moich konkretów, żeby dało się go użyć u siebie</strong>. Uzupełniasz nawiasy kwadratowe i wysyłasz.</p>
<div class="post-prompt">
<div class="head">Szablon — rozdzielenie środowisk</div>
<div class="body">
<pre><code>Kontekst: projekt [NAZWA] to [CO ROBI]. Stos: [FRAMEWORK],
baza [DOSTAWCA BAZY], hosting [DOSTAWCA HOSTINGU].
Dziś istnieje tylko jedno środowisko — produkcyjne — i pracuję
bezpośrednio na nim. Chcę to rozdzielić na: lokalne, testowe
i produkcyjne.

ZASADY (obowiązują przez całe zadanie):
- Nie wykonuj niczego na produkcji bez mojej wyraźnej zgody.
  Produkcja to [ADRES] i baza [IDENTYFIKATOR].
- Zmiany w bazie mają być zapisane jako pliki migracji w repo,
  nie wklejane ręcznie w panelu. Chcę móc odtworzyć stan bazy
  od zera z samego repo.
- Zanim cokolwiek zmienisz, przedstaw plan i poczekaj
  na moje „ok&quot;.

ZADANIA:
1. Zbadaj repozytorium i opisz stan faktyczny: gdzie są
   ustawienia połączenia z bazą, czy istnieje jakikolwiek
   podział środowisk, co jest zaszyte na sztywno w kodzie.
   Nie zgaduj — sprawdź w plikach i pokaż, gdzie co znalazłeś.
2. Ściągnij aktualny schemat bazy produkcyjnej do repo jako
   punkt wyjścia dla migracji.
3. Zaproponuj sposób wyboru środowiska po adresie strony.
   Wymagania: dopasowanie dokładne, nie po fragmencie adresu;
   adres nieznany ma trafiać do środowiska lokalnego,
   nigdy do produkcyjnego.
4. Wypisz kroki, które muszę wykonać ręcznie poza repo
   (założenie drugiej bazy, ustawienia u dostawcy hostingu,
   wpisy DNS) — po kolei, z zaznaczeniem, co jest nieodwracalne.
5. Podaj sposób sprawdzenia, że rozdzielenie faktycznie działa:
   jak potwierdzić, że środowisko testowe NIE łączy się
   z bazą produkcyjną.

Po każdym etapie zatrzymaj się i podsumuj, co zostało zrobione
i co wymaga mojej decyzji. Jeśli któreś polecenie jest
niejednoznaczne — dopytaj, zamiast zakładać.
</code></pre>
<p class="note">U mnie w miejsce nawiasów weszły: TranslateScorm, baza Supabase i hosting Cloudflare Pages. Ale szablon nie zakłada tych konkretnych usług — zmieniasz dwie linijki i działa na innym stosie.</p>
</div>
</div>
<hr>
<h2>Czego się nauczyłem</h2>
<p><strong>Najdroższa nie była nowa funkcja, tylko brak miejsca do jej sprawdzenia.</strong> Sama zmiana to kilka godzin. Reszta dnia poszła na coś, czego brak wyszedł na jaw dopiero wtedy, gdy przestało działać.</p>
<p><strong>Warto sprawdzić, zamiast zakładać.</strong> Tamtego dnia kilka razy „oczywista&quot; diagnoza okazała się fałszywa — raz przez godzinę byłem przekonany, że zepsułem coś w bazie, a okazało się, że sprawdzałem to w niewłaściwy sposób. Za każdym razem ratowało to samo: sprawdzić jeszcze raz, ale inaczej niż poprzednio.</p>
<p><strong>Problem rzadko leży tam, gdzie się objawia.</strong> Zaczęło się od przycisku, który nie zadziałał. Skończyło na wniosku, że nie mam gdzie pracować bezpiecznie. Gdybym naprawił tylko przycisk, ten sam problem wróciłby za miesiąc pod inną postacią.</p>
<hr>
<p>Aplikacja jest dziś w lepszym stanie niż rano. Nie dlatego, że doszła nowa funkcja — choć doszła. Dlatego, że następnym razem, gdy coś pójdzie nie tak, będę miał gdzie to sprawdzić, zanim zobaczą to klienci.</p>
]]></content:encoded>
      <category>Jak pracuję</category>
      <category>Bezpieczeństwo wdrożeń</category>
      <category>Praca z AI</category>
    </item>
  </channel>
</rss>
