[object Object]

← back to build-pages

ui: fleet-wide search input on home (wired to /api/search, 150ms debounce) + 1 smoke

9494934edecadffa195efc5b4ed349d0aefb6893 · 2026-05-13 13:45:51 -0700 · SteveStudio2

Files touched

Diff

commit 9494934edecadffa195efc5b4ed349d0aefb6893
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 13:45:51 2026 -0700

    ui: fleet-wide search input on home (wired to /api/search, 150ms debounce) + 1 smoke
---
 server.js     | 32 ++++++++++++++++++++++++++++++++
 test/smoke.js |  1 +
 2 files changed, 33 insertions(+)

diff --git a/server.js b/server.js
index d436ffa..ea3272b 100644
--- a/server.js
+++ b/server.js
@@ -310,10 +310,42 @@ 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>
+
+      <section class="bp-section">
+        <h2>Search the whole fleet</h2>
+        <input id="fleet-q" class="bp-q" type="search" placeholder="search every commit across every project…" autocomplete="off">
+        <p id="fleet-tally" class="bp-meta">type ≥ 2 characters to search</p>
+        <ol id="fleet-hits" class="bp-commits"></ol>
+      </section>
+
+      <h2>Projects</h2>
       <ul class="bp-cards">${rows}</ul>
     `, `
       <script type="application/ld+json">${JSON.stringify(jsonld)}</script>
       <script type="application/ld+json">${JSON.stringify(itemList)}</script>
+      <script>
+        const fq = document.getElementById('fleet-q');
+        const ft = document.getElementById('fleet-tally');
+        const fh = document.getElementById('fleet-hits');
+        let pending = null;
+        async function doFleetSearch() {
+          const q = fq.value.trim();
+          if (q.length < 2) { ft.textContent = 'type ≥ 2 characters to search'; fh.innerHTML = ''; return; }
+          ft.textContent = 'searching…';
+          const r = await fetch('/api/search?q=' + encodeURIComponent(q) + '&limit=50');
+          const data = await r.json();
+          ft.textContent = data.total + ' hits across the fleet';
+          fh.innerHTML = data.results.map(h => \`<li>
+            <a href="/p/\${h.slug}/c/\${h.hash}"><code>\${h.hash}</code></a>
+            <span class="bp-d">\${h.date}</span>
+            <span class="bp-s"><strong>\${h.project}</strong> — \${h.subject.replace(/</g,'&lt;')}</span>
+          </li>\`).join('');
+        }
+        fq.addEventListener('input', () => {
+          if (pending) clearTimeout(pending);
+          pending = setTimeout(doFleetSearch, 150);
+        });
+      </script>
     `));
   } catch (e) { next(e); }
 });
diff --git a/test/smoke.js b/test/smoke.js
index 897bb83..5935c4a 100644
--- a/test/smoke.js
+++ b/test/smoke.js
@@ -81,6 +81,7 @@ function check(name, cond, hint = '') {
   check('home: og:title',          /property="og:title"/.test(r.body));
   check('home: WebSite JSON-LD',   /"@type":"WebSite"/.test(r.body));
   check('home: ItemList JSON-LD',  /"@type":"ItemList"/.test(r.body));
+  check('home: fleet search input',/id="fleet-q"/.test(r.body));
 
   // 6. butlr project page renders + has commit count
   r = await fetch('/p/butlr');

← eb2ab40 api: /api/search?q= — cross-project commit search (min 2 cha  ·  back to build-pages  ·  api/search: round-robin merge + by_project breakdown (no sin aafba88 →