--- title: 'Redesigning the Dashboard Around One Wave Chart' excerpt: 'GeekBye v1.8.2 turned the dashboard from a recording remote control into a read-only analytics view — a stats strip and a 7-day wave chart. Both signature elements were second drafts, shipped within twenty minutes of their first drafts, in the same afternoon.' date: '2026-07-15' author: 'Steven' authorAvatar: '/images/blog/authors/steven.jpg' coverImage: '/images/blog/covers/redesigning-the-dashboard-around-one-wave-chart.svg' tags: ['Engineering', 'Design', 'Dashboard', 'GeekBye Releases'] keywords: - 'react dashboard stats strip design' - 'svg wave area chart without library' - 'bezier smoothing sparkline react' - 'sqlite aggregate stats last 7 days' - 'fill missing days zero count chart' - 'bar chart vs area chart sparse data' lastModified: '2026-07-15' tldr: 'GeekBye v1.8.2 (18 commits, one afternoon) rebuilt the dashboard home. It deleted the recording-control hero card — the dashboard stopped being a remote for the recorder and became a read-only analytics view: four animated stats and a 7-day activity chart, fed by a new SQLite aggregate query through a typed IPC call, all on-device. The interesting part is the iteration, and it is fully timestamped: the first draft (16:29) shipped a bar chart and a 2×4 grid of stat cards; twenty minutes later (16:49–16:52) the grid became one inline stats strip and the bars became a hand-rolled SVG wave with the card chrome stripped off. No chart library — just cubic-bezier smoothing with midpoint control points and a back-fill loop that guarantees exactly seven day-buckets so an empty week draws a flat line instead of crashing.' keyTakeaways: - 'A dashboard that is also a control panel is two products fighting over one screen; v1.8.2 deleted the recording hero card and let the dashboard be one thing — read-only analytics — with start/hide moved to a navbar toggle' - 'Sparse data reads better as a trend than as bars: seven buckets where most days are 0–2 meetings look like lonely stubs as a bar chart and like a line as an area chart — same numbers, and the redesign chose the shape that flatters the data honestly' - "You don't need a charting library for a 7-point sparkline: an inline SVG with cubic-bezier segments using midpoint control points gives monotone-safe smoothing that never overshoots the data" - 'Always emit the buckets you promise: the stats query back-fills missing days to zero so the chart always receives exactly seven points — the divide-by-zero and single-point-NaN edge cases disappear because the shape of the data is guaranteed upstream' - "The iteration IS the design work: a bar chart that lived twenty-two minutes and a stat-card grid that lived twenty aren't churn — they're the search, recorded with timestamps in the same PR" --- Some releases add a feature. GeekBye v1.8.2 mostly _removed_ one — and the removal is the story. In a single afternoon in February 2026, eighteen commits turned the dashboard home from a recording remote control into a read-only analytics view: four stats and one activity chart. Along the way it shipped a bar chart that lived twenty-two minutes, a grid of stat cards that lived twenty, and a hand-rolled SVG wave that replaced both. The whole search is preserved in the commit timestamps, which is the best kind of design documentation there is. ## The dashboard stopped being a remote control Before this release, the dashboard home was a control panel. It led with a hero card — a big recording widget with a LIVE badge, Pause/Resume/Stop buttons, gradient blobs, and a `useSessionState` hook wiring it all to the recorder. The dashboard was trying to be two things at once: the place you _look at_ your meetings and the place you _drive_ the recorder. Two products, one screen, quietly competing. v1.8.2 picked one. It deleted the hero card and the `useSessionState` wiring — including an effect that auto-navigated you to a meeting's detail page the moment a session ended, which is exactly the kind of "helpful" jump that surprises people mid-flow. Starting and hiding the recorder moved out to a plain navbar toggle (a repurposed button that lost about a hundred lines going from session controller to on/off switch), backed by a new `navbar:hide` IPC handler. What remained was a dashboard that does one thing: show you what you've recorded. Read-only. Calm. There's a small, human bug in that transition worth calling out, because it's the sort of thing a redesign quietly introduces and a good eye catches the same hour. The two new sidebar buttons — Start and Hide — shipped with the _same_ generic grid icon, so they were visually identical. Twelve minutes later a follow-up gave Start a play icon, gave Hide an eye-off icon, and tinted Hide amber, so "make the app go away" reads as a deliberate action rather than a neutral twin of "start." Icons are copy. Two buttons that look the same say the same thing, and these didn't mean the same thing. ## The bar chart that lived twenty-two minutes Here is the part I love, and it is entirely legible from the git log. At **16:29:34** the redesign's first draft landed: a 7-day activity chart drawn as **bars** — animated columns, a minimum height so tiny days stayed visible, a 2-pixel dim stub for zero days. Reasonable. Conventional. The kind of chart you reach for without thinking. At **16:51:21** — twenty-two minutes later — it was deleted. The replacement commit says exactly what it did: "replace bar chart with smooth wave area chart for 7-day activity." And at **16:52:21**, one minute after that, another commit stripped the rounded card and border from around the wave, "for cleaner look." Why? The commits only say _what_ changed, so the _why_ is a reading — but the data shape makes it obvious. This is a tool where a typical user has zero, one, or two meetings on any given day. Seven bars, most of them 2 pixels tall, look like a mostly-empty parking lot. The same seven numbers drawn as a continuous filled area look like a _trend_ — a gentle wave that rises where the week was busy. Nothing was faked; the y-values are identical. The redesign just chose the encoding that lets sparse, honest data read as a shape instead of as absence. The stats got the same treatment on the same clock. The first draft (16:29) put four numbers in a 2×4 grid of cards. At **16:49** the grid collapsed into one inline **stats strip** — a single bordered row with hairline dividers, smaller type, one container instead of four. Five boxes on the screen (four cards plus a chart card) became two (a strip and a wave). The next PR added one more honest touch: the "Total Time" label became "Time in Meetings," because total-of-_what_ was a fair question nobody should have to ask. Twenty minutes, first draft to final, all inside one pull request. If those had been squashed into a single "redesign dashboard" commit, the search would be invisible. Because they weren't, you can watch a designer converge in real time. ## The wave, mechanically There is no charting library here. The wave is an inline SVG built in JSX, and it's worth a look because it's a small, honest recipe you can steal for any sparkline. The smoothing is the trick. For each pair of adjacent points, the path uses a cubic-bezier segment whose two control points sit at the **horizontal midpoint** between them — `cpx = (prev.x + curr.x) / 2` — with the control points held at each end's own height. That's the classic flat-tangent smoothing: the curve eases through every data point without ever overshooting above the highest or below the lowest value, which a naive Catmull-Rom spline would do. For a chart about meeting counts, "never draw a bump that implies a value that didn't happen" is not a nicety; it's correctness. The filled area is the same curve closed along the bottom and painted with a vertical gradient (cyan, fading from 30% to nearly nothing), and the line draws itself on with a framer-motion `pathLength` animation, left to right. Under the SVG is the data plumbing, and it's the quiet hero. A new `getAggregateStats()` method on the transcript repository runs three prepared statements against the local SQLite `transcript_sessions` table: all-time totals (count, summed duration, average duration), a rolling seven-day count, and a per-day `GROUP BY date(created_at)` bucket. Then — and this is the detail that makes the chart robust — a small JavaScript loop walks the last seven calendar days and **back-fills** every day the query didn't return, pushing a zero. The chart is therefore _guaranteed_ exactly seven points, always. That guarantee is what makes the edge cases vanish instead of needing guards scattered through the view. An empty week? Seven zeros, and a `Math.max(count, 1)` on the denominator keeps the line flat at the bottom instead of dividing by zero. A single data point that would make the `i / (n − 1)` x-spacing formula produce NaN? Can't happen — the repository never emits fewer than seven. The weekday labels even parse each bucket at `T12:00:00` rather than bare midnight, so a `YYYY-MM-DD` string doesn't slip to the previous day in a negative-UTC timezone. Fix the data's shape at the source and the rendering code gets to be naive on purpose. All of it stays on the machine. The stats query reads the local database, returns through a typed `transcript:get-stats` IPC channel, and animates into four count-up numbers on mount. Nothing about your meeting history leaves the laptop to draw this chart — which, for a tool that records your conversations, is the only acceptable way to build an analytics view. ## One more bug, because redesigns drag them into the light Bundled into the same release was a fix that had nothing to do with the dashboard and everything to do with the kind of debt a redesign trips over. The app's dark scrollbar styling had drifted into **two competing CSS definitions** of the same class — an older purple one and a newer white-on-dark one — and which won depended on cascade order. Worse, only elements that remembered to add the class got styled at all; everything else fell back to the default light scrollbar, and horizontal scrollbars were never styled because the old rules set only a width, never a height. The fix deleted both definitions, styled the scrollbar _globally_ on every element, and removed the now-dead class from the ten components that had been carrying it. It's a two-line diff repeated ten times — the unglamorous tax of having styled the same thing twice and letting both survive. ## Three things this release taught us 1. **Subtract to focus.** The dashboard got better by doing _less_ — losing the recording controls it never needed and becoming one clear thing. A screen that is both a viewer and a remote is two half-products; pick one and move the other where it belongs. 2. **Choose the encoding your data deserves.** Sparse counts read as absence in bars and as a trend in an area chart, from identical numbers. That's not spin — it's picking the honest shape that lets real, thin data communicate. 3. **Guarantee the data's shape upstream.** The back-fill-to-seven loop is why the chart code has no scattered null checks: divide-by-zero, single-point NaN, and missing days all disappear because the query promises exactly seven buckets. Robust rendering starts one layer down. For the previous chapter in the v1 story, [four releases in twenty-six hours](/blog/four-releases-in-twenty-six-hours) (v1.7.6–v1.8.1); for the next, [shipping thirty languages without a safety net](/blog/shipping-thirty-languages-without-a-safety-net) (v1.8.3); and for the whole arc, [the anatomy of shipping software to perfection](/blog/anatomy-of-perfection).