[object Object]

← back to build-pages

api/fleet/authors + Authors chip rail on /p/:slug — uses canonAuthor() for alias collapse + 2 smoke

b806ffd809b13172571871af5f64c9314946401a · 2026-05-13 19:10:00 -0700 · SteveStudio2

Files touched

Diff

commit b806ffd809b13172571871af5f64c9314946401a
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 19:10:00 2026 -0700

    api/fleet/authors + Authors chip rail on /p/:slug — uses canonAuthor() for alias collapse + 2 smoke
---
 server.js     | 31 +++++++++++++++++++++++++++++++
 test/smoke.js |  4 ++--
 2 files changed, 33 insertions(+), 2 deletions(-)

diff --git a/server.js b/server.js
index 6ed9f3e..f7790c1 100644
--- a/server.js
+++ b/server.js
@@ -560,6 +560,18 @@ app.get('/p/:slug', async (req, res, next) => {
         </ol>
       </section>
 
+      <section class="bp-section">
+        <h2>Authors</h2>
+        <ul class="bp-chips">${(() => {
+          const t = new Map();
+          for (const c of commits) {
+            const a = canonAuthor(c.author);
+            t.set(a, (t.get(a) || 0) + 1);
+          }
+          return [...t.entries()].sort((a, b) => b[1] - a[1]).map(([n, c]) => `<li><span>${esc(n)}</span><b>${c}</b></li>`).join('');
+        })()}</ul>
+      </section>
+
       <section class="bp-section bp-grid">
         <div>
           <h2>Agents used</h2>
@@ -689,6 +701,24 @@ app.get('/p/:slug/c/:hash', async (req, res, next) => {
   } catch (e) { next(e); }
 });
 
+// /api/fleet/authors — fleet-wide author tallies (post-alias-normalization).
+app.get('/api/fleet/authors', async (_req, res, next) => {
+  try {
+    const tallies = new Map();
+    for (const [slug, p] of Object.entries(PROJECTS)) {
+      const bundle = await getProjectBundle(slug, p).catch(() => null);
+      if (!bundle) continue;
+      for (const c of bundle.commits) {
+        const a = canonAuthor(c.author);
+        tallies.set(a, (tallies.get(a) || 0) + 1);
+      }
+    }
+    const out = [...tallies.entries()].map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
+    res.setHeader('Cache-Control', 'public, max-age=60, stale-while-revalidate=120');
+    res.json({ authors: out });
+  } catch (e) { next(e); }
+});
+
 // /api/fleet/all — one-shot aggregate for dashboards that don't want to make
 // 5 separate calls. Pulls velocity + top agents + top skills + recent commits
 // in parallel. Each piece is already cache-warm via the per-repo bundle, so
@@ -1054,6 +1084,7 @@ app.get('/api', (_req, res) => {
       'GET /api/fleet/agents':                 'Fleet-wide agent tallies merged across every project',
       'GET /api/fleet/skills':                 'Fleet-wide skill tallies merged across every project',
       'GET /api/fleet/velocity':               'Commits-per-rolling-window across the fleet — last 24h + last 7d, with per-project breakdown',
+      'GET /api/fleet/authors':                'Fleet-wide author tallies (post-alias-normalization, so SteveStudio2/Steve/Steve Abrams collapse to one)',
       'GET /api/fleet/all':                    'One-shot dashboard aggregate — velocity + recent + top skills/agents + per-project counts',
       'GET /p/:slug':                          'Project journal HTML page (?q= deep-link supported)',
       'GET /p/:slug/c/:hash':                  'Commit detail page — full diff + body',
diff --git a/test/smoke.js b/test/smoke.js
index 3f3f8a5..da228f3 100644
--- a/test/smoke.js
+++ b/test/smoke.js
@@ -216,8 +216,8 @@ function check(name, cond, hint = '') {
   check('recent ?since=24h: 200',  r.status === 200);
   check('recent ?since=24h: filter', /"since":"[\d-]+T/.test(r.body));
 
-  // 13b2. fleet-wide facet endpoints (agents + skills)
-  for (const f of ['agents', 'skills']) {
+  // 13b2. fleet-wide facet endpoints (agents + skills + authors)
+  for (const f of ['agents', 'skills', 'authors']) {
     r = await fetch(`/api/fleet/${f}`);
     check(`fleet/${f}: 200`,         r.status === 200);
     check(`fleet/${f}: field`,       new RegExp(`"${f}":\\[`).test(r.body));

← c7c7763 api: canonAuthor() collapses 'SteveStudio2'/'Steve Abrams'/e  ·  back to build-pages  ·  home: 7-day velocity sparkline (Unicode block-chars, scales b6fe68d →