[object Object]

← back to Re Flyers

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

780f167481aaf603a95cc74667ce725df9c64278 · 2026-08-27 04:44:16 -0700 · Steve

Files touched

Diff

commit 780f167481aaf603a95cc74667ce725df9c64278
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 27 04:44:16 2026 -0700

    href-drill: add drill primitives to re-flyers (TK-10903)
---
 public/drill.js   | 36 ++++++++++++++++++++++++++++++++++++
 public/index.html | 14 +++++++++++---
 2 files changed, 47 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 c46df6a..8f989a3 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,6 +1,7 @@
 <!doctype html>
 <html lang="en">
 <head>
+<script src="/drill.js"></script>
 <script>
 // Credential-safe fetch guard (fleet drop-in) — if this page is opened with
 // credentials in the URL (a saved bookmark / Chrome-remembered basic-auth:
@@ -62,6 +63,7 @@
   <h1>RE Flyers — Unified Cross-Source Index</h1>
   <div class="sub">TK-10708 · normalized aggregate of broker OMs · usre deal assets · RENTV spotlight recaps · read-only</div>
 </header>
+<input type="hidden" id="pattern" value="">
 <div class="controls">
   <div><label>Sort</label>
     <select id="sort">
@@ -84,6 +86,11 @@
 <div class="grid" id="grid"></div>
 
 <script>
+// href-drill primitives (TK-10903) — FIELDS/FIL must be var globals so drill.js can see them
+var FIELDS=[['source_build','Source'],['asset_type','Type'],['tier','Tier']];
+var FIL={source_build:'',asset_type:'',tier:''};
+function applyFilters(push){if(push)writeURL(true);render();}
+
 const LS = 're-flyers-controls';
 const state = Object.assign({sort:'newest',src:'',density:4,q:''}, JSON.parse(localStorage.getItem(LS)||'{}'));
 let ALL = [];
@@ -112,11 +119,11 @@ function render(){
     const gated=/GATED/i.test(r.rights_basis||'');
     const when=r.generated_at?new Date(r.generated_at).toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'}):'—';
     return `<div class="card${gated?' gated':''}">
-      <div class="type">${esc(r.asset_type)}</div>
+      <div class="type">${drill('asset_type',r.asset_type)}</div>
       <div class="title">${esc(r.title)}</div>
       <div class="meta">
-        <span class="chip">${esc(r.source_build)}</span>
-        <span class="chip t${r.tier||''}">tier ${esc(r.tier)}</span>
+        ${drill('source_build',r.source_build,`<span class="chip">${esc(r.source_build)}</span>`,'')}
+        <span class="chip t${r.tier||''}">${drill('tier',r.tier,'tier '+esc(r.tier),'')}</span>
         ${r.firm?`<span class="chip">${esc(r.firm)}</span>`:''}
         <span class="chip">${when}</span>
       </div>
@@ -130,6 +137,7 @@ $('#src').onchange=e=>{state.src=e.target.value;save();render();};
 $('#density').oninput=e=>{state.density=+e.target.value;document.documentElement.style.setProperty('--cols',state.density);save();render();};
 $('#q').oninput=e=>{state.q=e.target.value;save();render();};
 
+readURL();
 fetch('/api/flyers').then(r=>r.json()).then(d=>{
   ALL=d.flyers||[];
   const srcs=[...new Set(ALL.map(r=>r.source_build))].sort();

← 1475a40 creds-safe fetch guard: resolve relative fetch vs credential  ·  back to Re Flyers  ·  (newest)