
대시보드를 하나의 웨이브 차트를 중심으로 다시 설계하다
GeekBye v1.8.2는 대시보드를 녹음 리모컨에서 읽기 전용 분석 뷰로 바꿨습니다 — 통계 스트립과 7일 웨이브 차트. 이 두 간판 요소는 모두 두 번째 초안이었고, 첫 초안이 나온 지 20분 안에, 같은 오후에 출시됐습니다.
기능을 더하는 릴리스도 있습니다. GeekBye v1.8.2는 대부분 하나를 덜어냈고 — 그 덜어냄이 바로 이야기입니다. 2026년 2월의 어느 오후, 18개의 커밋이 대시보드 홈을 녹음 리모컨에서 읽기 전용 분석 뷰로 바꿨습니다: 통계 네 개와 활동 차트 한 개. 그 과정에서 22분을 산 막대 차트, 20분을 산 통계 카드 그리드, 그리고 그 둘을 대체한 손수 만든 SVG 웨이브를 출시했습니다. 탐색 전체가 커밋 타임스탬프에 보존되어 있는데, 이것이야말로 세상에서 가장 좋은 종류의 설계 문서입니다.
대시보드는 리모컨이길 멈췄다
이 릴리스 전, 대시보드 홈은 컨트롤 패널이었습니다. 맨 앞에 히어로 카드가 있었죠 — LIVE 배지, Pause/Resume/Stop 버튼, 그라데이션 덩어리, 그리고 그 모두를 녹음기에 연결하는 useSessionState 훅을 갖춘 커다란 녹음 위젯. 대시보드는 한 번에 두 가지가 되려 하고 있었습니다: 미팅을 바라보는 곳이자 녹음기를 조종하는 곳. 두 개의 프로덕트가 하나의 화면에서 조용히 경쟁하고 있었습니다.
v1.8.2는 하나를 골랐습니다. 히어로 카드와 useSessionState 배선을 삭제했습니다 — 세션이 끝나는 순간 미팅의 상세 페이지로 자동으로 데려가던 이펙트까지 포함해서요. 이건 딱, 작업 흐름 도중에 사람을 놀라게 하는 그 "친절한" 점프의 전형입니다. 녹음기의 시작과 숨기기는 수수한 내비바 토글로 빠져나갔고(세션 컨트롤러에서 온/오프 스위치로 바뀌면서 백 줄쯤을 잃은, 용도를 바꾼 버튼입니다), 뒤에서는 새 navbar:hide IPC 핸들러가 받쳐 줍니다. 남은 것은 한 가지만 하는 대시보드입니다: 당신이 녹음한 것을 보여 준다. 읽기 전용. 차분합니다.
이 전환에는 짚고 넘어갈 만한 작고 인간적인 버그가 하나 있습니다. 재설계가 조용히 들여오고, 눈 좋은 사람이 같은 시간 안에 잡아내는, 딱 그런 종류이기 때문입니다. 두 개의 새 사이드바 버튼 — Start와 Hide — 은 같은 범용 그리드 아이콘으로 출시되어 시각적으로 동일했습니다. 12분 뒤의 후속 작업이 Start에 재생 아이콘을, Hide에 눈-끔(눈에 사선) 아이콘을 주고 Hide를 호박색으로 물들여서, "앱을 사라지게 한다"가 "시작"의 중립적인 쌍둥이가 아니라 의도적인 행동으로 읽히게 했습니다. 아이콘은 카피(문구)입니다. 똑같이 생긴 두 버튼은 똑같은 말을 하는데, 이 둘은 똑같은 걸 뜻하지 않았습니다.
22분을 산 막대 차트
여기가 제가 사랑하는 부분이고, 게다가 git 로그에서 완전히 읽어 낼 수 있습니다. 16:29:34에 재설계의 첫 초안이 착지했습니다: 막대로 그린 7일 활동 차트 — 애니메이션되는 열, 아주 작은 날도 보이도록 하는 최소 높이, 0인 날에는 2픽셀짜리 희미한 그루터기. 합리적입니다. 관습적입니다. 아무 생각 없이 손이 가는 종류의 차트죠.
16:51:21에 — 그 22분 뒤 — 그것은 삭제됐습니다. 대체 커밋은 자기가 한 일을 그대로 말합니다: "replace bar chart with smooth wave area chart for 7-day activity". 그리고 16:52:21에, 그 1분 뒤, 또 다른 커밋이 웨이브 주위에서 둥근 카드와 테두리를 벗겨 냈습니다. "for cleaner look".
왜? 커밋은 무엇이 바뀌었는지만 말하므로 _왜_는 해석입니다 — 하지만 데이터의 형태를 보면 명백합니다. 이건 전형적인 사용자가 어느 날이든 미팅이 0, 1, 2개인 도구입니다. 일곱 개의 막대, 그 대부분이 2픽셀 높이면, 거의 텅 빈 주차장처럼 보입니다. 같은 일곱 개의 숫자를 연속된 채워진 영역으로 그리면 _트렌드_처럼 보입니다 — 그 주가 바빴던 자리에서 부드럽게 솟아오르는 웨이브. 아무것도 조작하지 않았습니다. y값은 동일합니다. 재설계는 그저, 희소하고 정직한 데이터를 부재가 아니라 하나의 형태로 읽히게 하는 인코딩을 골랐을 뿐입니다.
통계도 같은 시계 위에서 같은 처방을 받았습니다. 첫 초안(16:29)은 네 개의 숫자를 2×4 카드 그리드에 넣었습니다. 16:49에 그리드는 한 줄짜리 인라인 통계 스트립으로 접혔습니다 — 가느다란 구분선, 더 작은 글자, 넷이 아니라 하나의 컨테이너로 된, 테두리 있는 한 행입니다. 화면 위 다섯 개의 상자(카드 넷과 차트 카드)가 둘(스트립과 웨이브)이 됐습니다. 다음 PR은 정직한 손질을 하나 더 얹었습니다: "Total Time" 라벨이 "Time in Meetings"가 된 것입니다. 대체 무엇의 _합계_냐는, 아무도 물어야 할 필요가 없어야 할 정당한 질문이었기 때문입니다.
20분, 첫 초안에서 최종본까지, 전부 하나의 풀 리퀘스트 안에서. 만약 이것들이 "redesign dashboard"라는 하나의 커밋으로 스쿼시됐다면 탐색은 보이지 않았을 겁니다. 그러지 않았기에, 당신은 디자이너가 실시간으로 수렴해 가는 것을 볼 수 있습니다.
웨이브, 그 작동 원리
여기엔 차트 라이브러리가 없습니다. 웨이브는 JSX로 짠 인라인 SVG이고, 한 번 볼 가치가 있습니다. 어떤 스파크라인에도 훔쳐 갈 수 있는, 작고 정직한 레시피이기 때문입니다.
스무딩이 요령입니다. 인접한 각 점의 쌍에 대해, 경로는 3차 베지어 구간을 쓰는데, 그 두 제어점은 둘 사이의 수평 중점에 놓이고 — cpx = (prev.x + curr.x) / 2 — 제어점은 각 끝 자신의 높이에 붙들려 있습니다. 이것이 고전적인 평평한 접선 스무딩입니다: 곡선은 모든 데이터 점을, 최고값 위로도 최저값 아래로도 절대 오버슈트하지 않으면서 부드럽게 통과합니다. 순진한 Catmull-Rom 스플라인이라면 저질렀을 일이죠. 미팅 횟수에 관한 차트에서 "일어나지 않은 값을 암시하는 봉우리를 절대 그리지 않는다"는 것은 세심한 배려가 아니라 정확성 그 자체입니다. 채워진 영역은 같은 곡선을 아래쪽에서 닫고 수직 그라데이션(시안, 30%에서 거의 0까지 페이드)으로 칠한 것이고, 선은 framer-motion pathLength 애니메이션으로 왼쪽에서 오른쪽으로 스스로를 그려 냅니다.
SVG 아래에는 데이터 배관이 있고, 이것이 조용한 주인공입니다. 트랜스크립트 리포지토리의 새 getAggregateStats() 메서드는 로컬 SQLite transcript_sessions 테이블에 대해 세 개의 준비된 문(prepared statement)을 실행합니다: 전 기간 합계(개수, 합산 시간, 평균 시간), 롤링 7일 카운트, 그리고 날짜별 GROUP BY date(created_at) 버킷. 그리고 — 이것이 차트를 견고하게 만드는 디테일인데 — 작은 JavaScript 루프가 최근 일곱 달력일을 걸으며 쿼리가 반환하지 않은 날을 전부 0으로 백필합니다. 따라서 차트는 언제나 정확히 일곱 점임이 _보장_됩니다.
그 보장이 바로, 뷰 여기저기에 가드를 흩뿌리는 대신 엣지 케이스를 사라지게 만드는 것입니다. 텅 빈 주? 일곱 개의 0, 그리고 분모에 걸린 Math.max(count, 1)이 0으로 나누는 대신 선을 맨 아래에서 평평하게 유지합니다. i / (n − 1) x-간격 공식에 NaN을 만들어 낼 단일 데이터 점? 일어날 수 없습니다 — 리포지토리는 일곱 점보다 적게 내보내는 법이 없습니다. 요일 라벨조차 각 버킷을 맨 자정이 아니라 T12:00:00으로 파싱하므로, YYYY-MM-DD 문자열이 음수 UTC 시간대에서 전날로 미끄러지지 않습니다. 데이터의 형태를 원천에서 고치면, 렌더링 코드는 일부러 순진해질 수 있습니다.
그 모든 것이 기기 위에 머뭅니다. 통계 쿼리는 로컬 데이터베이스를 읽고, 타입이 붙은 transcript:get-stats IPC 채널을 통해 돌아오며, 마운트 시 네 개의 카운트업 숫자로 애니메이션됩니다. 이 차트를 그리기 위해 당신의 미팅 기록이 노트북을 떠나는 일은 전혀 없습니다 — 당신의 대화를 녹음하는 도구에게, 이것이 분석 뷰를 짓는 유일하게 허용 가능한 방식입니다.
버그 하나 더, 재설계가 그것들을 빛 아래로 끌고 나오니까
같은 릴리스에는 대시보드와는 아무 상관 없고, 재설계가 걸려 넘어지는 종류의 빚과는 온통 상관 있는 수정이 함께 묶여 있었습니다. 앱의 다크 스크롤바 스타일링은 같은 클래스에 대한 서로 경쟁하는 두 개의 CSS 정의로 갈라져 있었습니다 — 오래된 보라색 하나와, 새로운 검정 위 흰색 하나 — 그리고 어느 쪽이 이기는지는 캐스케이드 순서에 달려 있었습니다. 더 나쁘게도, 그 클래스를 잊지 않고 붙인 요소만 아예 스타일이 먹혔고, 나머지 전부는 기본 밝은 스크롤바로 되돌아갔으며, 수평 스크롤바는 한 번도 스타일된 적이 없었습니다. 옛 규칙이 너비만 설정하고 높이는 결코 설정하지 않았기 때문입니다. 수정은 두 정의를 모두 삭제하고, 스크롤바를 모든 요소에 대해 전역적으로 스타일하고, 그것을 짊어지고 있던 10개 컴포넌트에서 이제는 죽은 그 클래스를 제거했습니다. 10번 반복된 두 줄짜리 diff입니다 — 같은 것을 두 번 스타일하고 둘 다 살려 둔 것에 대한, 화려할 것 없는 세금이죠.
이번 릴리스가 우리에게 가르쳐 준 세 가지
- 집중하려면 빼라. 대시보드는 덜 함으로써 더 좋아졌습니다 — 애초에 필요 없던 녹음 조작을 잃고 하나의 명확한 것이 됐습니다. 뷰어이면서 리모컨이기도 한 화면은 두 개의 반쪽짜리 프로덕트입니다. 하나를 고르고 나머지는 제자리로 옮기세요.
- 데이터가 마땅히 받아야 할 인코딩을 골라라. 희소한 카운트는 같은 숫자에서, 막대에서는 부재로 읽히고 area chart에서는 트렌드로 읽힙니다. 이건 포장이 아닙니다 — 진짜, 얇은 데이터가 말하게 하는 정직한 형태를 고르는 일입니다.
- 데이터의 형태를 상류에서 보장하라. 일곱까지 백필하는 루프가 바로, 차트 코드에 흩뿌려진 null 체크가 없는 이유입니다: 0으로 나누기, 단일 점 NaN, 누락된 날이 전부 사라집니다. 쿼리가 정확히 일곱 버킷을 약속하기 때문입니다. 견고한 렌더링은 한 층 아래에서 시작됩니다.
v1 이야기의 이전 장은 26시간 동안 네 번의 릴리스(v1.7.6–v1.8.1)를 보시고; 전체 궤적은 소프트웨어를 완벽하게 출시하는 것의 해부학을 보세요.