[object Object]

← back to Re Coverage Dashboard

href-drill: add drill primitives to re-coverage-dashboard (TK-10903)

8c5586967adafe75444533552043d478d5ea65f9 · 2026-08-27 04:46:38 -0700 · Steve

Files touched

Diff

commit 8c5586967adafe75444533552043d478d5ea65f9
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 27 04:46:38 2026 -0700

    href-drill: add drill primitives to re-coverage-dashboard (TK-10903)
---
 public/drill.js   | 36 ++++++++++++++++++++++++++++++++++++
 public/index.html | 16 +++++++++++++---
 2 files changed, 49 insertions(+), 3 deletions(-)

diff --git a/public/drill.js b/public/drill.js
new file mode 100644
index 0000000..e63cdd3
--- /dev/null
+++ b/public/drill.js
@@ -0,0 +1,36 @@
+// ── href-to-deeper-data primitives — SINGLE SOURCE OF TRUTH (HARD rule, 2026-07-31) ──
+// Every displayed data point must be an href to its deeper (filtered) data. These are the
+// reusable atoms; a viewer's index.html loads this file, then defines FIELDS, FIL, $, esc,
+// and applyFilters (which these reference at call time via the shared classic-script global
+// scope). Canonical copy: ~/Projects/_shared/web/drill.js → each viewer's public/drill.js.
+// Memory: all-data-points-href-to-deeper-data. Reference viewers: astek-landing, schumacher-internal.
+
+// Build the URL for a given filter set — any count/atom becomes a real, shareable link.
+function qstr(fil, q, pat) {
+  const u = new URLSearchParams();
+  for (const [k] of FIELDS) { if (fil[k]) u.set(k, fil[k]); }
+  if (q) u.set('q', q); if (pat) u.set('pat', pat);
+  const s = u.toString(); return s ? ('?' + s) : location.pathname;
+}
+// Read the URL's filter state back into FIL + the search inputs (deep-link / back-button).
+function readURL() {
+  const u = new URLSearchParams(location.search);
+  for (const [k] of FIELDS) FIL[k] = u.get(k) || '';
+  $('#q').value = u.get('q') || ''; $('#pattern').value = u.get('pat') || '';
+}
+// Reflect the current filter state into the URL (push = new history entry, else replace).
+function writeURL(push) {
+  const url = qstr(FIL, $('#q').value.trim(), $('#pattern').value.trim());
+  history[push ? 'pushState' : 'replaceState']({}, '', url);
+}
+// drill(field,value,label,cls) → an <a> whose href IS the filtered view for that value.
+// cmd/ctrl/middle-click opens it in a new tab; plain click filters in place (delegated listener).
+function drill(field, val, label, cls) {
+  if (val == null || val === '') return '';
+  const href = qstr({ ...FIL, [field]: String(val) }, $('#q').value.trim(), $('#pattern').value.trim());
+  return `<a class="drill ${cls || ''}" href="${href}" data-filter="${field}" data-v="${esc(val)}" title="Filter to ${esc(val)}">${label != null ? esc(label) : esc(val)}</a>`;
+}
+// Atom drill target: SET the filter to this value (drill in), vs pickF which TOGGLES.
+function setF(k, v) { if (FIL[k] === String(v)) return; FIL[k] = String(v); applyFilters('push'); }
+
+window.qstr = qstr; window.readURL = readURL; window.writeURL = writeURL; window.drill = drill; window.setF = setF;
diff --git a/public/index.html b/public/index.html
index 0d75418..a1e6999 100644
--- a/public/index.html
+++ b/public/index.html
@@ -53,8 +53,10 @@
   .empty { color:var(--mut); font-size:13px; padding:8px 0; }
 </style>
   <link rel="stylesheet" href="/nav-agent/nav-agent.css"><!-- nav-agent -->
+  <script src="/drill.js"></script>
 </head>
 <body>
+<input type="hidden" id="q" value=""><input type="hidden" id="pattern" value="">
 <header>
   <h1>🌊 RE Contact Coverage</h1>
   <div class="sub">Filling in phone · firm · site · agent-site across usre + CRCP — $0/local discovery. <span id="upd"></span></div>
@@ -64,6 +66,12 @@
 <div class="wins"><h2>🏆 Wins / milestones</h2><div id="winlist"><div class="empty">no milestones yet — they appear as coverage crosses thresholds</div></div></div>
 
 <script>
+// href-drill primitives (TK-10903)
+var FIELDS=[['group','Group']];
+var FIL={group:''};
+var $=function(s){return document.querySelector(s);};
+function applyFilters(push){if(push)writeURL(true);built=false;refresh();}
+
 // XSS-safe HTML escaper — used on any server-sourced string interpolated into innerHTML.
 function esc(s) {
   return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
@@ -114,15 +122,16 @@ class LiquidGauge {
 
 const COLORS = { usre: '#3fb950', CRCP: '#58a6ff', HomesOnSpec: '#d29922' };
 const gauges = {};
-let built = false;
+var built = false;  // var so applyFilters (drill) can reset it
 
 function build(metrics) {
+  const filtered = FIL.group ? metrics.filter(m=>m.group===FIL.group) : metrics;
   const groups = {};
-  metrics.forEach(m => { (groups[m.group] = groups[m.group] || []).push(m); });
+  filtered.forEach(m => { (groups[m.group] = groups[m.group] || []).push(m); });
   const host = document.getElementById('groups'); host.innerHTML = '';
   for (const [g, ms] of Object.entries(groups)) {
     const sec = document.createElement('div'); sec.className = 'grp';
-    sec.innerHTML = `<h2>${esc(g)}</h2><div class="grid"></div>`;
+    sec.innerHTML = `<h2>${drill('group',g)}</h2><div class="grid"></div>`;
     const grid = sec.querySelector('.grid');
     ms.forEach(m => {
       const card = document.createElement('div'); card.className = 'card';
@@ -155,6 +164,7 @@ async function refresh() {
   if (w.wins && w.wins.length) wl.innerHTML = w.wins.map(x =>
     `<div class="win"><span>🏆</span><span class="t">${esc(x.title)}</span><span class="when">${new Date(x.ts).toLocaleString()}</span></div>`).join('');
 }
+readURL();
 refresh(); setInterval(refresh, 15000);
 </script>
 <!-- Own GA4 + FB Pixel (DW build standard). Ids come from server-injected window.__A

← c806220 creds-safe fetch guard: resolve relative fetch vs credential  ·  back to Re Coverage Dashboard  ·  auto-data-snapshot: 2026-09-01T12:55:30 (1 data files) — pac a79a4b4 →