[object Object]

← back to build-pages

refactor: extract sparkline(commits, days) helper — DRY across home + /p/:slug

fdee564c8f1fd893e0c4b2ef61ed37614a29691e · 2026-05-13 19:21:18 -0700 · SteveStudio2

Files touched

Diff

commit fdee564c8f1fd893e0c4b2ef61ed37614a29691e
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 19:21:18 2026 -0700

    refactor: extract sparkline(commits, days) helper — DRY across home + /p/:slug
---
 server.js | 78 ++++++++++++++++++++++++++++-----------------------------------
 1 file changed, 35 insertions(+), 43 deletions(-)

diff --git a/server.js b/server.js
index cb1955c..519126c 100644
--- a/server.js
+++ b/server.js
@@ -326,32 +326,14 @@ app.get('/', async (req, res, next) => {
     }));
     const totalCommits = entries.reduce((a, e) => a + e.count, 0);
     const totalIdeas   = entries.reduce((a, e) => a + e.ideas, 0);
-    // Velocity counters — commits in the last 1 day and last 7 days across
-    // the fleet. Uses _projectCache directly so no extra git work.
-    const now = Date.now();
-    const dayMs = 24 * 60 * 60 * 1000;
-    let last24 = 0, last7d = 0;
-    // Per-day buckets for the last 7 days (index 0 = oldest, 6 = today). Used
-    // to render a tiny Unicode sparkline so a glance at the home page shows
-    // the velocity shape, not just two scalars.
-    const dayBuckets = new Array(7).fill(0);
-    for (const e of entries) {
-      const b = _projectCache.get(e.slug); if (!b) continue;
-      for (const c of b.commits) {
-        const age = now - new Date(c.dateISO).getTime();
-        if (age < dayMs)     last24++;
-        if (age < 7 * dayMs) last7d++;
-        if (age < 7 * dayMs && age >= 0) {
-          const dayIdx = 6 - Math.floor(age / dayMs);
-          if (dayIdx >= 0 && dayIdx < 7) dayBuckets[dayIdx]++;
-        }
-      }
-    }
-    // Render Unicode block sparkline. Empty buckets = ' ', full bar height
-    // scales to max non-zero day so the shape is always readable.
-    const blocks = ['▁','▂','▃','▄','▅','▆','▇','█'];
-    const maxBucket = Math.max(...dayBuckets, 1);
-    const sparkline = dayBuckets.map(n => n === 0 ? ' ' : blocks[Math.min(7, Math.floor((n / maxBucket) * 7))]).join('');
+    // Velocity counters + 7-day sparkline across the whole fleet. Concats
+    // per-project commit lists so the helper sees every commit in one shot.
+    const fleetCommits = entries.flatMap(e => _projectCache.get(e.slug)?.commits || []);
+    const fleetSpark = sparkline(fleetCommits);
+    const sparkChars = fleetSpark.spark;
+    const dayBuckets = fleetSpark.buckets;
+    const last24 = fleetSpark.last24;
+    const last7d = fleetSpark.last7d;
     const rows = entries.map(({ slug, p, count, latest, ideas }) => `<li class="bp-card">
         <a href="/p/${esc(slug)}"><h2>${esc(p.name)}</h2></a>
         <p>${esc(p.blurb)}</p>
@@ -396,7 +378,7 @@ app.get('/', async (req, res, next) => {
       <h1>build journals</h1>
       <p class="bp-lede">Every build, every commit, every line. Click into a project to see how it actually got made.</p>
       <p class="bp-meta">${entries.length} projects · ${totalCommits} commits · ${totalIdeas} design-rationale ideas surfaced</p>
-      <p class="bp-meta">velocity: <strong>${last24}</strong> commits in the last 24h · <strong>${last7d}</strong> in the last 7 days · 7-day shape <code class="bp-spark" title="${dayBuckets.join(' / ')}">${sparkline}</code> · ${(() => {
+      <p class="bp-meta">velocity: <strong>${last24}</strong> commits in the last 24h · <strong>${last7d}</strong> in the last 7 days · 7-day shape <code class="bp-spark" title="${dayBuckets.join(' / ')}">${sparkChars}</code> · ${(() => {
         const [l1] = require('os').loadavg();
         const cls = l1 < 8 ? 'bp-load-ok' : l1 < 20 ? 'bp-load-warn' : 'bp-load-hot';
         return `host load <span class="bp-load ${cls}">${l1.toFixed(1)}</span> (1m)`;
@@ -549,22 +531,8 @@ app.get('/p/:slug', async (req, res, next) => {
       <h1>${esc(p.name)}</h1>
       <p class="bp-lede">${esc(p.blurb)}</p>
       <p class="bp-meta">repo: <code>${esc(p.repo.replace(HOME, '~'))}</code> · ${commits.length} commits · ${(() => {
-        const now = Date.now(), dayMs = 86400000;
-        let d24 = 0, d7 = 0;
-        const buckets = new Array(7).fill(0);
-        for (const c of commits) {
-          const age = now - new Date(c.dateISO).getTime();
-          if (age < dayMs) d24++;
-          if (age < 7 * dayMs) d7++;
-          if (age < 7 * dayMs && age >= 0) {
-            const idx = 6 - Math.floor(age / dayMs);
-            if (idx >= 0 && idx < 7) buckets[idx]++;
-          }
-        }
-        const blocks = ['▁','▂','▃','▄','▅','▆','▇','█'];
-        const max = Math.max(...buckets, 1);
-        const spark = buckets.map(n => n === 0 ? ' ' : blocks[Math.min(7, Math.floor((n / max) * 7))]).join('');
-        return `<strong>${d24}</strong> in last 24h, <strong>${d7}</strong> in last 7d · <code class="bp-spark" title="${buckets.join(' / ')}">${spark}</code>`;
+        const s = sparkline(commits);
+        return `<strong>${s.last24}</strong> in last 24h, <strong>${s.last7d}</strong> in last 7d · <code class="bp-spark" title="${s.buckets.join(' / ')}">${s.spark}</code>`;
       })()}</p>
 
       <section class="bp-section">
@@ -838,6 +806,30 @@ for (const facet of ['agents', 'skills']) {
 // /api/recent — most recent N commits across the entire fleet, merged + sorted
 // by date desc. Powers a "today across the fleet" rail. Shares the bundle
 // cache so it's free after the first warm.
+// Render a Unicode block-char sparkline of daily commit counts over the last
+// N days for a given commits[] array. Returns { spark, buckets, last24, last7d }.
+// Used by both home (fleet-wide) and /p/:slug (per-project) so the rendering
+// logic stays in one place.
+const _sparkBlocks = ['▁','▂','▃','▄','▅','▆','▇','█'];
+function sparkline(commits, days = 7) {
+  const now = Date.now();
+  const dayMs = 86400000;
+  const buckets = new Array(days).fill(0);
+  let last24 = 0, last7d = 0;
+  for (const c of commits) {
+    const age = now - new Date(c.dateISO).getTime();
+    if (age < dayMs)      last24++;
+    if (age < 7 * dayMs)  last7d++;
+    if (age < days * dayMs && age >= 0) {
+      const idx = (days - 1) - Math.floor(age / dayMs);
+      if (idx >= 0 && idx < days) buckets[idx]++;
+    }
+  }
+  const max = Math.max(...buckets, 1);
+  const spark = buckets.map(n => n === 0 ? ' ' : _sparkBlocks[Math.min(7, Math.floor((n / max) * 7))]).join('');
+  return { spark, buckets, last24, last7d };
+}
+
 // Normalize git author strings. Steve commits as "SteveStudio2", "Steve",
 // "Steve Abrams" — same person, different .gitconfig values on different
 // machines. Map these to a canonical name so the authors[] tally doesn't

← 59b1208 p/:slug: per-project 7-day sparkline in meta line  ·  back to build-pages  ·  api: /api/fleet/buckets — daily commit buckets with ISO labe e8b65d2 →