Building the Dev Stats Chart

Overview

The Nucleate Dev journey page tracks months of iteration in prose—and, under Development stats, in numbers. The original presentation was a screenshot exported from Excel: accurate, but static, off-palette, and impossible to explore.

This page documents the replacement: an embedded accumulated-hours chart that matches the site’s charcoal-and-amber workshop aesthetic, reads real session data at build time, and surfaces milestone events on hover without leaving the page.

Why Not a Screenshot—or a Chart Library?

Three constraints drove the design:

  1. Brand — The plot had to feel like the Observatory and Section Map: restrained, amber-accented, subservient to reading. A default Chart.js or Plotly theme would fight the rest of the docs.
  2. Weight — One page, one chart. Pulling in a general-purpose charting library for a single SVG line plot felt disproportionate.
  3. Data ownership — Hours live in a spreadsheet I already maintain (static/nucleate_dev_stats.xlsx). The site should consume that source of truth, not a manually re-exported PNG.

The result is vanilla SVG + ~550 lines of JavaScript, the same pattern as the Section Map: geometry and interaction in the client, structure and copy baked in at Hugo build time.

What Visitors Experience

On the Dev journey page, the

Accumulated development hours

  • 2025-04-16: Repo established for "LLM Scribe"
  • 2025-06-28: Development for general release begins under the "Echo" project name
  • 2025-09-01: Addition of multiple user modes and supporting system prompts
  • 2025-10-26: Rebranding under a product name, "Nucleate"
  • 2025-11-16: Branch for Tkinter → PySide6 conversion
  • 2025-12-21: Removed Tkinter legacy code from repo, consolidation
  • 2026-01-01: macOS (x86) first development round complete
  • 2026-02-16: macOS (Apple Silicon) first development round complete
  • 2026-02-25: Windows w/ CUDA, Windows w/ CPU, and macOS w/ Apple Silicon packaged successfully
  • 2026-03-13: Mac (Metal) packaged .dmg successfully
  • 2026-04-07: Alpha feedback implemented, beta solicitation
  • 2026-04-14: Mac notarization and signing
  • 2026-04-21: Beta solicitation closed—unfortunately zero users
  • 2026-05-09: Version updated to 1.0 for first packaged release
  • 2026-05-19: First time using agentic AI to help review code before beta
  • 2026-05-25: v1.0 release and publication to Gumroad
  • 2026-06-07: First download—$0 from Germany!
  • 2026-06-21: Version updated to 1.1 for packaged release
  • 2026-08-01: Public visibility release
shortcode renders:

  • A header with title and a live summary (total hours and day span, e.g. 966.8 h across 429 days).
  • An area + line chart of accumulated development hours from first commit through today.
  • Dim horizontal grid and bi-monthly date ticks—enough context without chartjunk.
  • Milestone markers — vertical dashed amber lines with dots where project events occurred (repo creation, Echo, Nucleate rebrand, PySide6 migration, Gumroad release, first download, and others).
  • A legend row of milestone labels below the chart—each pill is keyboard-focusable.

Hover and focus behavior

Interaction is intentionally simple:

InputResult
Pointer over chartVertical crosshair; dot on the line; tooltip with date and interpolated hours at that x-position
Pointer near a milestoneSnaps to the milestone column; tooltip adds label and detail text for that event
Pointer over milestone dotSame as above, with a larger invisible hit target (14px radius) for easier targeting
Hover / focus legend pillHighlights the corresponding marker and shows the milestone tooltip
Pointer leaveCrosshair, dot, and tooltip hide; active states clear

Between logged sessions, hours are linearly interpolated along the time axis so the crosshair always returns a sensible cumulative value—even on days with no explicit row in the spreadsheet.

Milestone copy is not inferred from the xlsx. Dates come from the data series; labels and descriptions are authored in the export script (see below).

Accessibility

  • The SVG carries an aria-label summarizing the date range and total hours.
  • A visually hidden <ul> lists every milestone for screen readers.
  • Legend buttons are real <button> elements with focus handlers mirroring hover.
  • prefers-reduced-motion: reduce disables CSS transitions on tooltips and markers (the chart itself has no animation loop).

Architecture: Build Time vs Runtime

LayerResponsibility
static/nucleate_dev_stats.xlsxSource spreadsheet (dates + accumulated hours)
scripts/export-nucleate-dev-stats.pyReads xlsx, writes data/nucleate-dev-stats.json; milestones defined here
data/nucleate-dev-stats.jsonHugo data file: series array + milestones array
layouts/shortcodes/dev-stats-chart.htmlEmbeds JSON in data-* attributes; sets Scratch flag for assets
assets/js/dev-stats-chart.jsParse data, layout SVG, wire pointer + legend interaction
assets/css/dev-stats-chart.cssChart chrome using --wl-* tokens
layouts/partials/custom/head-end.htmlLoads chart CSS when shortcode used
layouts/partials/scripts.htmlLoads chart JS when shortcode used

No runtime fetch. The chart is fully determined at build time—same philosophy as the Section Map’s data-graph blob.

Data Pipeline

Spreadsheet → JSON

The export script uses only the Python standard library (zipfile + XML parse of the xlsx internals—no openpyxl dependency). Each row becomes { "date": "YYYY-MM-DD", "hours": <float> }.

Milestones are a separate list in the script:

{
    "date": "2025-10-26",
    "label": "Nucleate",
    "detail": 'Rebranding under a product name, "Nucleate"',
}

After editing the spreadsheet or milestone list:

python scripts/export-nucleate-dev-stats.py

Hugo exposes the file as site.Data under the key nucleate-dev-stats (hyphenated filename).

Shortcode → page

The shortcode jsonifies series and milestones onto the figure element:

<figure class="dev-stats-chart"
        data-series="[...]"
        data-milestones="[...]">

It also calls .Page.Scratch.Set "devStatsChart" true so CSS/JS load only on pages that embed the chart—not on every docs page.

Rendering (SVG)

On DOMContentLoaded, the script:

  1. Parses embedded JSON and sorts the series by date.
  2. Computes minDate, maxDate, and a padded y-axis ceiling (nice 100- or 200-hour ticks).
  3. Builds an SVG with a vertical amber gradient for the area fill under the line.
  4. Draws grid lines, the area path, the stroke path, milestone groups (line + marker + hit circle), and axis labels.
  5. Attaches a transparent rect overlay for pointer tracking.

Layout is recomputed on resize via ResizeObserver—content layers rebuild; the interaction overlay persists so listeners are not lost.

Scales are plain functions:

  • xForDate / dateAtX — linear map between plot margins and the time span.
  • yForHours — invert y because SVG coordinates grow downward.
  • interpolateHours — walk adjacent series points and lerp.

Milestone y-positions use hoursAtDate: the last logged hours at or before the milestone date, so markers sit on the actual curve even when the milestone falls between sessions.

Nearest-milestone detection

When the pointer moves, the script scores each milestone by pixel distance plus a small day penalty, and promotes the winner if within a threshold. That is why hovering near Gumroad release feels magnetic without requiring pixel-perfect aim on the dot.

Styling

Colors come from assets/css/wl-tokens.css:

  • Background: --wl-charcoal-mid
  • Line and accents: --wl-amber-soft
  • Grid and borders: --wl-amber-dim, --wl-line
  • Text: --wl-text, --wl-text-dim

The tooltip is an HTML div positioned absolutely over the canvas (not SVG <foreignObject>), which keeps typography and wrapping straightforward.

Relationship to Other Site Systems

ObservatorySection MapDev Stats Chart
ScopeWhole studio hubOne docs branchOne product timeline
MotionRotation, dust, cometsStaticStatic
LibraryNoneNoneNone
Data at buildGraph partialWalk partialHugo data JSON
Primary jobIdentity + entryIn-branch orientationQuantified journey

Same workshop: quiet instruments, amber ink, no gratuitous animation.

Extending

  • New hours — Update the xlsx, run the export script, rebuild.
  • New milestone — Add an entry to MILESTONES in the export script with date, label, and detail.
  • Another page — Drop

    Accumulated development hours

    • 2025-04-16: Repo established for "LLM Scribe"
    • 2025-06-28: Development for general release begins under the "Echo" project name
    • 2025-09-01: Addition of multiple user modes and supporting system prompts
    • 2025-10-26: Rebranding under a product name, "Nucleate"
    • 2025-11-16: Branch for Tkinter → PySide6 conversion
    • 2025-12-21: Removed Tkinter legacy code from repo, consolidation
    • 2026-01-01: macOS (x86) first development round complete
    • 2026-02-16: macOS (Apple Silicon) first development round complete
    • 2026-02-25: Windows w/ CUDA, Windows w/ CPU, and macOS w/ Apple Silicon packaged successfully
    • 2026-03-13: Mac (Metal) packaged .dmg successfully
    • 2026-04-07: Alpha feedback implemented, beta solicitation
    • 2026-04-14: Mac notarization and signing
    • 2026-04-21: Beta solicitation closed—unfortunately zero users
    • 2026-05-09: Version updated to 1.0 for first packaged release
    • 2026-05-19: First time using agentic AI to help review code before beta
    • 2026-05-25: v1.0 release and publication to Gumroad
    • 2026-06-07: First download—$0 from Germany!
    • 2026-06-21: Version updated to 1.1 for packaged release
    • 2026-08-01: Public visibility release
    anywhere; assets load automatically.
  • Different dataset — Generalizing would mean parameterizing the shortcode (data="other-stats") and duplicating or templating the export script. Not needed yet.

Reflections

The Dev journey page is narrative first; the chart is evidence, not the headline. Keeping it as a single shortcode with conditional assets means the rest of the Nucleate docs stay untouched.

Replacing a screenshot also removed a maintenance trap: every spreadsheet update used to mean re-cropping and re-uploading an image. Now the pipeline is one command.

The milestone layer is the part I care about most. Cumulative hours tell how much; the hover copy tells what happened. That pairing is what the Excel chart could never do on a static site.

See It Live

The chart is embedded on Nucleate → Overview → Dev journey. Hover the line, the milestone dots, or the legend labels below the plot.


AI-generated Hugo documentation—describes how this website is built and customized.