← back to Approval Command Center
href-drill: add drill primitives to approval-command-center (TK-10903)
da2d754b2181d2c7ccf912076b537ec9ac9cb1a6 · 2026-08-27 04:46:00 -0700 · Steve
Files touched
A public/drill.jsM public/index.html
Diff
commit da2d754b2181d2c7ccf912076b537ec9ac9cb1a6
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Aug 27 04:46:00 2026 -0700
href-drill: add drill primitives to approval-command-center (TK-10903)
---
public/drill.js | 36 ++++++++++++++++++++++++++++++++++++
public/index.html | 20 ++++++++++++++++----
2 files changed, 52 insertions(+), 4 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 1ca8b8e..e3a616b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -94,12 +94,14 @@
.card.soft .risk{color:#8fcabb}
</style>
<link rel="stylesheet" href="/nav-agent/nav-agent.css"><!-- nav-agent -->
+ <script src="/drill.js"></script>
</head>
<body>
<header>
<h1>Approval Command Center <span class="sub">— the 109-item pending-approval digest · 2026-08-09</span></h1>
<div class="sub">Your click is the human gate. <b>Approve</b> logs your go and queues the item for the live officer-yolo pass (DTD → officer → contrarian → verify). Nothing irreversible fires from this page unattended.</div>
<div class="bar">
+ <input type="hidden" id="pattern" value="">
<input type="search" id="q" placeholder="Search title / body / slug…"/>
<select id="sort">
<option value="sev">Sort: Risk (hard first)</option>
@@ -134,6 +136,11 @@
</div>
<div class="toast" id="toast"></div>
<script>
+// href-drill primitives (TK-10903)
+var FIELDS=[['gate','Gate'],['sev','Severity'],['state','State'],['officer','Officer']];
+var FIL={gate:'',sev:'',state:'',officer:''};
+function applyFilters(push){if(push)writeURL(true);render();}
+
let ITEMS=[], SEL=new Set();
const activeGates=new Set(), activeSev=new Set(), activeState=new Set();
const $=s=>document.querySelector(s);
@@ -162,6 +169,10 @@ function load(){
fetch('/api/items').then(r=>r.json()).then(d=>{ ITEMS=d.items; render(); });
}
function passesFilter(it){
+ if(FIL.gate && it.gate!==FIL.gate) return false;
+ if(FIL.sev && it.severity!==FIL.sev) return false;
+ if(FIL.state && it.where!==FIL.state) return false;
+ if(FIL.officer && (it.officer||'')!==FIL.officer) return false;
if(activeGates.size && !activeGates.has(it.gate)) return false;
if(activeSev.size && !activeSev.has(it.severity)) return false;
if(activeState.size && !activeState.has(it.where)) return false;
@@ -236,10 +247,10 @@ function cardEl(it){
<div class="ctitle">${esc(it.title)}</div>
<div class="when">🕓 ${fmtWhen(it.created)}</div>
<div class="meta">
- <span class="badge gate">${esc(it.gate)}</span>
+ ${drill('gate',it.gate,`<span class="badge gate">${esc(it.gate)}</span>`,'')}
<span class="badge ${it.reversible?'rev':'irrev'}">${it.reversible?'reversible':'IRREVERSIBLE'}</span>
- <span class="badge state">${it.where}</span>
- ${it.officer&&it.officer!=='unassigned'?`<span class="officer">${esc(it.officer)}</span>`:''}
+ ${drill('state',it.where,`<span class="badge state">${it.where}</span>`,'')}
+ ${it.officer&&it.officer!=='unassigned'?drill('officer',it.officer,`<span class="officer">${esc(it.officer)}</span>`,''):''}
</div>
<div class="risk"><span class="rk">${it.severity==='soft'?'✓':'⚠'}</span><span>${esc(riskLine(it))}</span></div>
<div class="row">
@@ -281,7 +292,8 @@ function post(url,b){ return fetch(url,{method:'POST',headers:{'Content-Type':'a
function toast(m){ const t=$('#toast'); t.textContent=m; t.style.display='block'; setTimeout(()=>t.style.display='none',2200); }
function esc(s){ return (s||'').replace(/[&<>]/g,c=>({'&':'&','<':'<','>':'>'}[c])); }
-// density + sort persistence
+// density + sort persistence — readURL restores filter state from URL (href-drill, TK-10903)
+readURL();
const dens=$('#dens'); dens.value=localStorage.acc_dens||3; document.documentElement.style.setProperty('--cols',6-dens.value);
dens.oninput=()=>{ localStorage.acc_dens=dens.value; document.documentElement.style.setProperty('--cols',6-dens.value); };
$('#sort').value=localStorage.acc_sort||'sev'; $('#sort').onchange=()=>{ localStorage.acc_sort=$('#sort').value; render(); };
← 72c3664 creds-safe fetch guard: resolve relative fetch vs credential
·
back to Approval Command Center
·
(newest)