Steven
Steven6 min czytania

Strona logowania JEST demem

GeekBye v1.7.5 dodał screenshot produktu na stronie logowania i usunął go tego samego dnia — odbudowując go zamiast tego z prawdziwych komponentów. Do tego: użytkownik, który był szybszy niż OCR, i jak naprawdę wygląda 25 commitów stylistycznych w jeden dzień.

Inżynieria
Design
UX
Wydania GeekBye
Strona logowania JEST demem

GeekBye v1.7.5 to tygodniowe wydanie o rozdwojonej osobowości: 37 commitów, z których 28 to redesign strony logowania — a 25 z nich to mikropoprawki style(login), wszystkie wylądowały jednego dnia. To także wydanie, które naprawiło jeden z najbardziej pouczających wyścigów w tej bazie kodu. Obie historie warto opowiedzieć i spotykają się w tej samej idei: strona logowania to pierwsza rzecz, jaką widzi niezalogowany użytkownik, więc lepiej, żeby była produktem, a nie jego obrazkiem.

Screenshot, przez jakieś pięć godzin

Redesign zaczął się konwencjonalnie. Przebudować na dwie kolumny, logowanie po lewej, a produkt pokazać po prawej — jako obrazek. Późnoporanne commity robią dokładnie to: o 11:57 w repo ląduje prawdziwy screenshot podglądu, PNG na 114 KB, i sześć minut później zostaje raz zaktualizowany.

O 17:04 już go nie było. Commit-zwrot zastępuje statyczny obrazek 344 liniami prawdziwych komponentów — panelem LoginDemo złożonym z ośmiu małych kawałków replikujących faktyczne UI overlaya: pigułką nagrywania w navbarze z pulsującymi słupkami EQ i przyciskiem stop, panelem czatu z zakładkami Chat/Transcript, wyrenderowaną odpowiedzią asystenta (tłumaczącą Reactowy useEffect, jakże stosownie), szybkimi akcjami "Tell me more / Simplify / Show example" (powiedz więcej / uprość / pokaż przykład), wierszem live-promptów i polem Assist z ⌘⏎.

Bądźmy precyzyjni co do słowa „demo", bo łatwo tu przesadzić z obietnicami: przyciski mają prawdziwe hover-stany — poświaty, shimmery, tranzycje — ale nic nie jest podpięte pod kliknięcie, a input jest tylko do odczytu. To nie jest sandbox. Jest to natomiast faktyczny słownik UI produktu wyrenderowany na żywo: ten sam asset logo, ten sam gradient-border i backdrop-blur, ten sam import productName, którego używa system white-label — więc demo rebranduje się automatycznie dla każdego produktu budowanego z tej bazy kodu. Screenshot nie potrafi żadnej z tych rzeczy. Screenshot jest nieaktualny w dniu, w którym go robisz, rozmyty na niewłaściwym ekranie i na stałe obrandowany tym produktem, który akurat uchwyciłeś. Komponenty są ostre przy każdym DPI, zawsze aktualne i poprawne dla każdej marki — bo nie są zdjęciem design systemu, one design systemem.

Usunięty PNG, nawiasem mówiąc, wciąż siedzi w repo pod tagiem wydania — osierocony, przez nic niereferowany. Każda baza kodu nosi w sobie kilka skamielin podejścia, które przegrało.

Jak wygląda 25 commitów stylistycznych w jeden dzień

Log commitów tego jednego dnia to szkicownik projektanta, zachowany z niecodzienną szczerością. Proporcja kolumn poszła 40/60, potem 45/55. Etykieta „Works with" (działa z) nad logotypami platform została dodana w południe, przesunięta i usunięta — żyła jakieś pół dnia. Sam wiersz logotypów platform (Google Meet, Teams, Zoom, Slack, Webex), dodany o 10:59 rano, został w całości skasowany o 22:08 tej nocy. A footer przeszedł czteropodejściową sagę wyrównywania: pojedynczy kontener-wrapper, potem usunięcie klas centrujących, potem centrowanie flexboxem, na koniec po prostu dosunięcie go bliżej dołu.

Łatwo byłoby odczytać to jako churn. My czytamy to jako faktyczny kształt pracy projektowej: dochodzisz do rozwiązania renderując, patrząc i poprawiając — a robienie tego w 25 małych commitach zamiast jednego zsquashowanego „redesign login page" oznacza, że samo poszukiwanie zostaje zapisane. Gdy ktoś zapyta „czy nie próbowaliśmy kiedyś wiersza z logotypami?", odpowiedź jest w historii, ze znacznikami czasu.

Użytkownik, który był szybszy niż OCR

Inżynierska historia tego wydania wydarzyła się pięć dni wcześniej, w trzycommitowym łańcuchu poprawek. Od v1.5.12 naciśnięcie Cmd+H kolejkuje screenshot z natychmiastowym feedbackiem: miniaturka pojawia się w kolejce od razu, a ekstrakcja tekstu — lokalny OCR Apple Vision, mniej więcej ~1-2 sekundy — leci w tle. Zdarzenie kolejki dosłownie wychodzi z ocrText: '' i komentarzem obiecującym, że tekst uzupełni się później.

To dobra optymalizacja — i wybiła wyścig. Szybki użytkownik naciska Cmd+H i natychmiast wysyła pytanie. Pole tekstowe screenshota jest wciąż puste. Request wychodzi bez treści ekranu, a model odpowiada — zgodnie z prawdą, doprowadzając do szału — "I cannot see images" (nie widzę obrazów). Naiwna ścieżka nie zawiodła błędem; zawiodła poprawną odpowiedzią na złe pytanie, czyli w najbardziej dezorientujący sposób, w jaki oprogramowanie może zawieść.

Poprawka bramkuje ścieżkę wysyłki na tej asynchronicznej pracy, którą optymalizacja odroczyła: jeśli którykolwiek zakolejkowany screenshot wciąż ma pusty tekst OCR, request parkuje, a UI pokazuje wskaźnik "Reading screen..." (odczytywanie ekranu); useEffect odpala się w chwili, gdy tekst ląduje, i wysyła request z pełną wyekstrahowaną treścią. Dwa reactowe detale z diffa warte ukradzenia: kolejka screenshotów jest mirrorowana do refa synchronizowanego podczas renderu, bo łańcuch closure'ów w handlerach zdarzeń łapał przeterminowany stan; a zaparkowany request jest wysyłany bezpośrednio, a nie przez generyczny hook zakolejkowanych requestów — z tego samego stale-closure'owego powodu.

Jest też uczciwa zmarszczka zapisana wprost w commit message: logi trace'ujące dodane, żeby upolować ten wyścig, ujawniły drugiego buga po stronie backendu — składanie promptu w jednej ze ścieżek kodu całkowicie gubiło tekst screenshota. Jeden objaw, dwa bugi, znalezione przez oprzyrządowanie całego łańcucha i patrzenie, gdzie tekst znika. Łańcuch skończył się decyzją produktową, dziewięć minut po poprawce wyścigu: Cmd+H kolejkuje screenshoty do analizy zbiorczej, a Cmd+Enter zawsze robi świeże ujęcie bieżącego ekranu — dwa odrębne workflow zamiast jednego dwuznacznego. (Co właściwie robi Assist pod Cmd+Enter, to temat na osobny wpis: natychmiastowa pomoc AI na tym, na co właśnie patrzysz.)

Mała koda z tego samego tygodnia dla czytelników rozdziału o white-label: User-Agent Product/version (platform) dostał poprawkę na tryb dev, bo app.getVersion() w niespakowanym Electronie zwraca wersję Electrona, a nie produktu. Łańcuchy tożsamości, jak zawsze, przeciekają w hydraulice.

Trzy rzeczy, których nauczyło nas to wydanie

  1. Renderuj produkt, nie fotografuj go. Demo na stronie logowania zbudowane z prawdziwych komponentów samo się rebranduje, przeżywa każdą gęstość ekranu i nie potrafi się zestarzeć. Podejście screenshotowe zostało wypróbowane i usunięte w ciągu jednego dnia roboczego — tani eksperyment, jasny werdykt.
  2. Każda asynchroniczna optymalizacja potrzebuje bramki w punkcie użycia. „Natychmiastowy feedback z pustym tekstem OCR" był słuszną decyzją UX, ale ścieżka wysyłki musiała się nauczyć czekać na to, co optymalizacja odroczyła. Jeśli odraczasz pracę, znajdź każdego konsumenta jej wyniku i naucz go różnicy między „puste" a „jeszcze niegotowe".
  3. Oprzyrządowuj łańcuch, nie podejrzanego. Logi trace'ujące śledziły tekst screenshota od końca do końca — od handlera skrótu przez IPC po klienta API — i właśnie dlatego jedno śledztwo zamknęło dwa bugi, w tym jeden po drugiej stronie sieci.

Po poprzedni rozdział historii v1 — spotkania, które naprawdę da się znaleźć (v1.7.3); a po cały łuk — anatomia dostarczania oprogramowania do perfekcji.