Steven
Steven7 min czytania

Przeprojektowanie dashboardu wokół jednego wykresu falowego

GeekBye v1.8.2 zamienił dashboard z pilota do nagrywania w widok analityczny tylko-do-odczytu — pasek statystyk i 7-dniowy wykres falowy. Oba charakterystyczne elementy były drugimi wersjami, wydanymi w ciągu dwudziestu minut od pierwszych, tego samego popołudnia.

Inżynieria
Design
Dashboard
Wydania GeekBye
Przeprojektowanie dashboardu wokół jednego wykresu falowego

Niektóre wydania dodają feature. GeekBye v1.8.2 przede wszystkim jeden usunął — i to usunięcie jest tu historią. W ciągu jednego popołudnia w lutym 2026 osiemnaście commitów zamieniło stronę główną dashboardu z pilota do nagrywania w widok analityczny tylko-do-odczytu: cztery statystyki i jeden wykres aktywności. Po drodze wydało wykres słupkowy, który przeżył dwadzieścia dwie minuty, siatkę kart ze statystykami, która przeżyła dwadzieścia, i ręcznie sklecony wykres falowy w SVG, który zastąpił oba. Całe to poszukiwanie jest zachowane w timestampach commitów, a to najlepszy rodzaj dokumentacji projektowej, jaki istnieje.

Dashboard przestał być pilotem

Przed tym wydaniem strona główna dashboardu była panelem sterowania. Otwierała ją karta-bohater — duży widżet nagrywania z plakietką LIVE, przyciskami Pause/Resume/Stop, gradientowymi plamami i hookiem useSessionState, który spinał to wszystko z rekorderem. Dashboard próbował być dwiema rzeczami naraz: miejscem, w którym oglądasz swoje spotkania, i miejscem, z którego sterujesz rekorderem. Dwa produkty, jeden ekran, po cichu ze sobą konkurujące.

v1.8.2 wybrał jedno. Skasował kartę-bohatera i całe okablowanie useSessionState — łącznie z efektem, który automatycznie przenosił cię na stronę szczegółów spotkania w momencie zakończenia sesji, a to dokładnie ten rodzaj „pomocnego" skoku, który wybija ludzi z rytmu. Uruchamianie i chowanie rekordera przeniosło się do zwykłego przełącznika w navbarze (przerobiony przycisk, który schudł o jakąś setkę linii, przechodząc z kontrolera sesji w wyłącznik on/off), wsparte nowym handlerem IPC navbar:hide. To, co zostało, to dashboard, który robi jedno: pokazuje ci, co nagrałeś. Tylko do odczytu. Spokojny.

W tym przejściu kryje się mały, ludzki bug wart wspomnienia, bo to typ rzeczy, którą redesign po cichu wprowadza, a dobre oko wyłapuje w tej samej godzinie. Dwa nowe przyciski na pasku bocznym — Start i Hide — wyszły z tą samą generyczną ikoną siatki, więc były wizualnie identyczne. Dwanaście minut później poprawka dała Startowi ikonę play, Hide ikonę przekreślonego oka i podbarwiła Hide na bursztynowo, żeby „spraw, by aplikacja zniknęła" czytało się jako celowe działanie, a nie neutralny bliźniak „startu". Ikony to też tekst. Dwa przyciski, które wyglądają tak samo, mówią to samo — a te nie znaczyły tego samego.

Wykres słupkowy, który przeżył dwadzieścia dwie minuty

Oto część, którą uwielbiam, i jest w pełni czytelna z gita. O 16:29:34 wylądował pierwszy szkic redesignu: 7-dniowy wykres aktywności narysowany jako słupki — animowane kolumny, minimalna wysokość, żeby maleńkie dni pozostały widoczne, 2-pikselowy przygaszony kikut dla dni zerowych. Rozsądny. Konwencjonalny. Ten rodzaj wykresu, po który sięgasz bez zastanowienia.

O 16:51:21 — dwadzieścia dwie minuty później — został skasowany. Zastępujący commit mówi dokładnie, co zrobił: „replace bar chart with smooth wave area chart for 7-day activity". A o 16:52:21, minutę po tym, kolejny commit zdjął zaokrągloną kartę i obramowanie wokół fali — „for cleaner look".

Dlaczego? Commity mówią tylko, co się zmieniło, więc dlaczego to już interpretacja — ale kształt danych czyni to oczywistym. To narzędzie, w którym typowy użytkownik ma zero, jedno albo dwa spotkania danego dnia. Siedem słupków, większość wysokości 2 pikseli, wygląda jak w większości pusty parking. Te same siedem liczb narysowane jako ciągły wypełniony obszar wygląda jak trend — łagodna fala, która wznosi się tam, gdzie tydzień był zajęty. Niczego nie sfałszowano; wartości y są identyczne. Redesign po prostu wybrał kodowanie, które pozwala rzadkim, uczciwym danym czytać się jako kształt zamiast jako nieobecność.

Statystyki dostały to samo traktowanie na tym samym zegarze. Pierwszy szkic (16:29) umieścił cztery liczby w siatce kart 2×4. O 16:49 siatka zwinęła się w jeden liniowy pasek statystyk — pojedynczy obramowany wiersz z włoskowatymi separatorami, mniejszą czcionką, jednym kontenerem zamiast czterech. Pięć pudełek na ekranie (cztery karty plus karta wykresu) stało się dwoma (pasek i fala). Następny PR dodał jeszcze jeden uczciwy akcent: etykieta „Total Time" (Łączny czas) stała się „Time in Meetings" (Czas na spotkaniach), bo łączny-czego było słusznym pytaniem, którego nikt nie powinien musieć zadawać.

Dwadzieścia minut, od pierwszego szkicu do finału, wszystko w jednym pull requeście. Gdyby to zsquashować w jeden commit „redesign dashboard", poszukiwanie byłoby niewidoczne. Ponieważ tego nie zrobiono, możesz oglądać, jak projektant zbiega się do rozwiązania w czasie rzeczywistym.

Fala, od strony mechaniki

Nie ma tu żadnej biblioteki do wykresów. Fala to inline'owy SVG zbudowany w JSX i warto na nią zerknąć, bo to mały, uczciwy przepis, który możesz ukraść do dowolnego sparkline'a.

Wygładzanie to cały trik. Dla każdej pary sąsiednich punktów ścieżka używa segmentu krzywej sześciennej Béziera, którego dwa punkty kontrolne siedzą w poziomym punkcie środkowym między nimi — cpx = (prev.x + curr.x) / 2 — przy czym punkty kontrolne trzymane są na wysokości własnego końca. To klasyczne wygładzanie z płaską styczną: krzywa przechodzi łagodnie przez każdy punkt danych, nigdy nie wystrzeliwując ponad najwyższą ani poniżej najniższej wartości, co robiłby naiwny splajn Catmulla-Roma. Dla wykresu o liczbie spotkań „nigdy nie rysuj wybrzuszenia sugerującego wartość, która się nie wydarzyła" to nie ozdobnik; to poprawność. Wypełniony obszar to ta sama krzywa zamknięta wzdłuż dołu i pomalowana pionowym gradientem (cyjan, zanikający od 30% do prawie zera), a linia sama się rysuje animacją pathLength z framer-motion, od lewej do prawej.

Pod SVG jest hydraulika danych i to ona jest cichym bohaterem. Nowa metoda getAggregateStats() w repozytorium transkryptów odpala trzy prepared statementy na lokalnej tabeli SQLite transcript_sessions: sumy od zawsze (liczba, zsumowany czas trwania, średni czas trwania), kroczącą liczbę z siedmiu dni i kubełek na dzień GROUP BY date(created_at). Potem — i to jest detal, który czyni wykres odpornym — mała pętla JavaScriptu przechodzi przez ostatnie siedem dni kalendarzowych i uzupełnia wstecz każdy dzień, którego zapytanie nie zwróciło, wpychając zero. Wykres ma więc gwarantowanych dokładnie siedem punktów, zawsze.

To ta gwarancja sprawia, że przypadki brzegowe znikają, zamiast wymagać strażników rozsianych po całym widoku. Pusty tydzień? Siedem zer, a Math.max(count, 1) w mianowniku trzyma linię płasko przy dole, zamiast dzielić przez zero. Pojedynczy punkt danych, który sprawiłby, że formuła rozstawu w osi x i / (n − 1) wyprodukuje NaN? Nie może się zdarzyć — repozytorium nigdy nie emituje mniej niż siedmiu. Etykiety dni tygodnia parsują nawet każdy kubełek na T12:00:00, a nie na gołą północ, żeby ciąg YYYY-MM-DD nie ześlizgnął się na poprzedni dzień w strefie o ujemnym UTC. Napraw kształt danych u źródła, a kod renderujący może być naiwny celowo.

Wszystko to zostaje na maszynie. Zapytanie o statystyki czyta lokalną bazę, wraca przez otypowany kanał IPC transcript:get-stats i animuje się w cztery odliczające w górę liczby przy montowaniu. Nic z twojej historii spotkań nie opuszcza laptopa, by narysować ten wykres — co dla narzędzia, które nagrywa twoje rozmowy, jest jedynym akceptowalnym sposobem zbudowania widoku analitycznego.

Jeszcze jeden bug, bo redesigny wyciągają je na światło

Do tego samego wydania dorzucono poprawkę, która nie miała nic wspólnego z dashboardem, a wszystko wspólnego z rodzajem długu, o który redesign się potyka. Stylowanie ciemnego paska przewijania w aplikacji rozjechało się w dwie konkurujące definicje CSS tej samej klasy — starszą fioletową i nowszą białą-na-ciemnym — a to, która wygrywała, zależało od kolejności w kaskadzie. Co gorsza, stylowane były tylko te elementy, które pamiętały o dodaniu klasy; cała reszta wracała do domyślnego jasnego paska, a poziome paski przewijania nigdy nie były stylowane, bo stare reguły ustawiały tylko szerokość, nigdy wysokość. Poprawka skasowała obie definicje, ostylowała pasek przewijania globalnie na każdym elemencie i usunęła martwą już klasę z dziesięciu komponentów, które ją nosiły. To dwuwierszowy diff powtórzony dziesięć razy — nieelegancki podatek za to, że ostylowało się to samo dwa razy i pozwoliło obu wersjom przetrwać.

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

  1. Odejmuj, żeby się skupić. Dashboard stał się lepszy, robiąc mniej — tracąc kontrolki nagrywania, których nigdy nie potrzebował, i stając się jedną jasną rzeczą. Ekran, który jest jednocześnie podglądem i pilotem, to dwa pół-produkty; wybierz jeden, a drugi przenieś tam, gdzie jego miejsce.
  2. Wybierz kodowanie, na jakie zasługują twoje dane. Rzadkie liczby czytają się jako nieobecność na słupkach i jako trend na wykresie obszarowym — z identycznych liczb. To nie kręcenie; to wybranie uczciwego kształtu, który pozwala prawdziwym, ubogim danym coś zakomunikować.
  3. Gwarantuj kształt danych u źródła. Pętla uzupełniania wstecz do siedmiu jest powodem, dla którego kod wykresu nie ma rozsianych sprawdzeń nulla: dzielenie przez zero, NaN z jednego punktu i brakujące dni znikają, bo zapytanie obiecuje dokładnie siedem kubełków. Odporne renderowanie zaczyna się o warstwę niżej.

Po poprzedni rozdział historii v1 — cztery wydania w dwadzieścia sześć godzin (v1.7.6–v1.8.1); a po cały łuk — anatomia dostarczania oprogramowania do perfekcji.