
Перепроектирование дашборда вокруг одного волнового графика
GeekBye v1.8.2 превратил дашборд из пульта управления записью в аналитический вид только-для-чтения — полоса статистики и 7-дневный волновой график. Оба фирменных элемента были вторыми черновиками, выпущенными в течение двадцати минут после первых, в тот же день после обеда.
Некоторые релизы добавляют фичу. GeekBye v1.8.2 в основном одну удалил — и это удаление и есть история. За один день после обеда в феврале 2026 восемнадцать коммитов превратили главную дашборда из пульта управления записью в аналитический вид только-для-чтения: четыре статистики и один график активности. По пути он выпустил столбчатый график, проживший двадцать две минуты, сетку карточек статистики, прожившую двадцать, и самодельную волну в SVG, которая заменила обе. Весь этот поиск сохранён в таймстампах коммитов, а это лучший вид проектной документации, какой только бывает.
Дашборд перестал быть пультом
До этого релиза главная дашборда была панелью управления. Её открывала hero-карточка — большой виджет записи с бейджем LIVE, кнопками Pause/Resume/Stop, градиентными пятнами и хуком useSessionState, связывавшим всё это с рекордером. Дашборд пытался быть двумя вещами разом: местом, где ты смотришь свои встречи, и местом, откуда ты управляешь рекордером. Два продукта, один экран, тихо конкурирующие.
v1.8.2 выбрал одно. Он удалил hero-карточку и всю обвязку useSessionState — включая эффект, который автоматически перекидывал тебя на страницу деталей встречи в момент завершения сессии, а это ровно тот сорт «полезного» скачка, что сбивает людей посреди работы. Запуск и скрытие рекордера переехали в обычный тумблер навбара (переделанная кнопка, похудевшая примерно на сотню строк по пути от контроллера сессии к выключателю on/off), опираясь на новый IPC-обработчик navbar:hide. Осталось то, что делает одно: показывает тебе, что ты записал. Только для чтения. Спокойно.
В этом переходе прячется маленький, человеческий баг, о котором стоит сказать, потому что это как раз то, что редизайн тихо привносит, а хороший глаз ловит в тот же час. Две новые кнопки боковой панели — Start и Hide — вышли с одной и той же дженериковой иконкой сетки, так что были визуально идентичны. Двенадцать минут спустя доработка дала Start иконку play, дала Hide иконку перечёркнутого глаза и подкрасила Hide янтарным, чтобы «убрать приложение с глаз» читалось как намеренное действие, а не нейтральный близнец «старта». Иконки — это тоже текст. Две кнопки, которые выглядят одинаково, говорят одно и то же, а эти не значили одно и то же.
Столбчатый график, проживший двадцать две минуты
Вот часть, которую я обожаю, и она полностью читается из git-лога. В 16:29:34 приземлился первый черновик редизайна: 7-дневный график активности, нарисованный столбиками — анимированные колонки, минимальная высота, чтобы крошечные дни оставались видны, 2-пиксельный тусклый огрызок для нулевых дней. Разумно. Обычно. Такой график, к которому тянешься не задумываясь.
В 16:51:21 — двадцать две минуты спустя — он был удалён. Заменяющий коммит говорит ровно, что он сделал: «replace bar chart with smooth wave area chart for 7-day activity». А в 16:52:21, минутой позже, ещё один коммит снял скруглённую карточку и рамку вокруг волны — «for cleaner look».
Почему? Коммиты говорят только, что изменилось, так что почему — это прочтение, но форма данных делает его очевидным. Это инструмент, где у типичного пользователя ноль, одна или две встречи в любой отдельный день. Семь столбиков, большинство высотой 2 пикселя, выглядят как почти пустая парковка. Те же семь чисел, нарисованные как сплошная заливаемая область, выглядят как тренд — плавная волна, поднимающаяся там, где неделя была загружена. Ничего не подделали; значения по y идентичны. Редизайн просто выбрал кодирование, которое позволяет разреженным, честным данным читаться как форма, а не как отсутствие.
Статистика получила то же обхождение по тем же часам. Первый черновик (16:29) поместил четыре числа в сетку карточек 2×4. В 16:49 сетка схлопнулась в одну встроенную полосу статистики — единый обрамлённый ряд с волосяными разделителями, шрифтом помельче, одним контейнером вместо четырёх. Пять коробок на экране (четыре карточки плюс карточка графика) стали двумя (полоса и волна). Следующий PR добавил ещё один честный штрих: подпись «Total Time» (Всего времени) стала «Time in Meetings» (Время на встречах), потому что всего-чего было справедливым вопросом, который никто не должен быть вынужден задавать.
Двадцать минут, от первого черновика до финала, всё внутри одного pull request. Если бы это сквошнули в один коммит «redesign dashboard», поиск был бы невидим. Поскольку не сквошнули, ты можешь наблюдать, как дизайнер сходится к решению в реальном времени.
Волна, с точки зрения механики
Тут нет никакой графической библиотеки. Волна — это встроенный SVG, собранный в JSX, и на неё стоит взглянуть, потому что это маленький, честный рецепт, который ты можешь стащить для любого sparkline.
Сглаживание — вот весь трюк. Для каждой пары соседних точек путь использует сегмент кубической кривой Безье, две контрольные точки которого сидят в горизонтальной середине между ними — cpx = (prev.x + curr.x) / 2 — при этом контрольные точки держатся на высоте своего конца. Это классическое сглаживание с плоской касательной: кривая плавно проходит через каждую точку данных, никогда не выстреливая выше самого высокого или ниже самого низкого значения, что сделал бы наивный сплайн Катмулла-Рома. Для графика о количестве встреч «никогда не рисуй бугор, подразумевающий значение, которого не было» — не украшательство; это корректность. Заливаемая область — та же кривая, замкнутая по низу и закрашенная вертикальным градиентом (циан, угасающий от 30% почти до нуля), а линия сама себя рисует анимацией pathLength из framer-motion, слева направо.
Под SVG — водопровод данных, и он тихий герой. Новый метод getAggregateStats() в репозитории транскриптов гоняет три подготовленных запроса по локальной таблице SQLite transcript_sessions: итоги за всё время (количество, суммарная длительность, средняя длительность), скользящий счёт за семь дней и корзину на день GROUP BY date(created_at). Затем — и это та деталь, что делает график устойчивым — маленький цикл на JavaScript проходит по последним семи календарным дням и дозаполняет каждый день, который запрос не вернул, вталкивая ноль. График поэтому гарантированно имеет ровно семь точек, всегда.
Именно эта гарантия заставляет крайние случаи исчезнуть, вместо того чтобы разбрасывать проверки по всему виду. Пустая неделя? Семь нулей, а Math.max(count, 1) в знаменателе держит линию плоской у низа, а не делит на ноль. Одна точка данных, из-за которой формула шага по оси x i / (n − 1) выдала бы NaN? Не может случиться — репозиторий никогда не выдаёт меньше семи. Подписи дней недели даже парсят каждую корзину на T12:00:00, а не на голую полночь, чтобы строка YYYY-MM-DD не соскользнула на предыдущий день в часовом поясе с отрицательным UTC. Почини форму данных у источника — и код рендеринга может быть наивным нарочно.
Всё это остаётся на машине. Запрос статистики читает локальную базу, возвращается через типизированный IPC-канал transcript:get-stats и анимируется в четыре отсчитывающих вверх числа при монтировании. Ничто из твоей истории встреч не покидает ноутбук, чтобы нарисовать этот график — что для инструмента, который записывает твои разговоры, единственный приемлемый способ построить аналитический вид.
Ещё один баг, потому что редизайны вытаскивают их на свет
В тот же релиз был вложен фикс, не имевший ничего общего с дашбордом и всё общее — с тем родом долга, о который редизайн спотыкается. Стилизация тёмного скроллбара в приложении разъехалась на два конкурирующих CSS-определения одного класса — старое фиолетовое и новое бело-на-тёмном — и то, какое побеждало, зависело от порядка в каскаде. Хуже того, стилизовались только те элементы, что не забыли добавить класс; всё остальное откатывалось к дефолтному светлому скроллбару, а горизонтальные скроллбары не стилизовались никогда, потому что старые правила задавали только ширину, но не высоту. Фикс удалил оба определения, стилизовал скроллбар глобально на каждом элементе и убрал теперь-мёртвый класс из десяти компонентов, что его таскали. Это двухстрочный дифф, повторённый десять раз — негламурный налог за то, что одно и то же было стилизовано дважды, а обе версии оставлены в живых.
Три вещи, которым нас научил этот релиз
- Вычитай, чтобы сфокусироваться. Дашборд стал лучше, делая меньше — потеряв управление записью, которое ему никогда не было нужно, и став одной ясной вещью. Экран, который одновременно и просмотрщик, и пульт, — это два полупродукта; выбери один, а другой перенеси туда, где ему место.
- Выбирай кодирование, которого заслуживают твои данные. Разреженные счётчики читаются как отсутствие в столбиках и как тренд на area chart — из одинаковых чисел. Это не подтасовка; это выбор честной формы, которая даёт настоящим, тощим данным что-то сообщить.
- Гарантируй форму данных выше по потоку. Цикл дозаполнения до семи — вот почему в коде графика нет разбросанных проверок на null: деление на ноль, NaN на одной точке и пропущенные дни исчезают, потому что запрос обещает ровно семь корзин. Устойчивый рендеринг начинается слоем ниже.
Про предыдущую главу истории v1 — четыре релиза за двадцать шесть часов (v1.7.6–v1.8.1); а про весь путь — анатомию доведения софта до совершенства.