[object Object]

← back to Cronjobs Viewer

href-drill: add drill primitives to cronjobs-viewer (TK-10903)

dd00e4e010a0000d26fc8447309e667a15408e67 · 2026-08-27 04:45:23 -0700 · Steve

Files touched

Diff

commit dd00e4e010a0000d26fc8447309e667a15408e67
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 27 04:45:23 2026 -0700

    href-drill: add drill primitives to cronjobs-viewer (TK-10903)
---
 public/drill.js   | 36 ++++++++++++++++++++++++++++++++++++
 public/index.html | 11 ++++++++++-
 2 files changed, 46 insertions(+), 1 deletion(-)

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 a3485e8..0e89ffb 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,6 +2,7 @@
 <html lang="en">
 <head>
 <link rel="icon" href="/favicon.ico">
+<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:
@@ -134,6 +135,7 @@
   </label>
   <label>Search
     <input type="search" id="q" placeholder="label / purpose / program…"/>
+    <input type="hidden" id="pattern" value="">
   </label>
   <label class="toggle"><input type="checkbox" id="failonly"/> Failing only</label>
   <label>Density
@@ -152,6 +154,12 @@
 // drop-in — handles string + Request, preserves pathname). A second, weaker
 // copy here used to double-wrap window.fetch; removed 2026-08-25 (/5x contrarian
 // gate). Do NOT re-add — the head guard is the single source of truth.
+// href-drill primitives (TK-10903)
+var FIELDS=[['state','State'],['liveness','Liveness']];
+var FIL={state:'',liveness:''};
+function esc(s){return String(s||'').replace(/[&<>]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));}
+function applyFilters(push){if(push)writeURL(true);apply();}
+
 const $ = (s)=>document.querySelector(s);
 let DATA = {jobs:[]};
 
@@ -220,7 +228,7 @@ function apply(){
     // liveness label: firing shows it IS running even if the log mtime is frozen
     const liveLabel = live==='firing'?'🟢 firing':live==='stale'?'⚠ stale':'? unknown';
     return `<div class="card ${j.state} ${live==='stale'?'stale':''}" data-label="${j.label}">
-      <span class="badge ${j.state}">${j.state}</span>
+      ${drill('state',j.state,`<span class="badge ${j.state}">${j.state}</span>`,'')}
       <div class="label">com.steve.<span class="short">${short}</span></div>
       <div><span class="live ${live}" role="button" tabindex="0" data-label="${j.label}" title="click to re-check this job's live launchd run-accounting now (bypasses the 5-min cache)">${liveLabel} <span class="reck">↻</span></span></div>
       ${j.purpose?`<div class="purpose">${escapeHtml(j.purpose)}</div>`:''}
@@ -300,6 +308,7 @@ let timer=null;
 function autotoggle(){ clearInterval(timer); if($('#auto').checked){timer=setInterval(load,30000);} LS.set('auto',$('#auto').checked?'1':'0'); }
 $('#auto').addEventListener('change',autotoggle);
 
+readURL();
 load(); autotoggle();
 </script>
 </body>

← fd2b353 chore: v1.1.0 (session close)  ·  back to Cronjobs Viewer  ·  auto-data-snapshot: 2026-08-30T10:20:41 (16 data files) — ve d503031 →