[object Object]

← back to Cronjobs Viewer

feat: wire liveness pill → /api/liveness/:label on-demand re-check (in-place patch, no full re-render; keyboard-accessible). Closes contrarian dead-endpoint finding

93fa8a73adfb7c0a0f1beee8f373168856cd33f2 · 2026-08-25 11:48:22 -0700 · steve

Files touched

Diff

commit 93fa8a73adfb7c0a0f1beee8f373168856cd33f2
Author: steve <steve@designerwallcoverings.com>
Date:   Tue Aug 25 11:48:22 2026 -0700

    feat: wire liveness pill → /api/liveness/:label on-demand re-check (in-place patch, no full re-render; keyboard-accessible). Closes contrarian dead-endpoint finding
---
 5x/click-liveness.js | 21 +++++++++++++++++++++
 public/index.html    | 41 ++++++++++++++++++++++++++++++++++++++---
 2 files changed, 59 insertions(+), 3 deletions(-)

diff --git a/5x/click-liveness.js b/5x/click-liveness.js
new file mode 100644
index 0000000..5bf144f
--- /dev/null
+++ b/5x/click-liveness.js
@@ -0,0 +1,21 @@
+const pw=require('/Users/macstudio3/.npm-global/lib/node_modules/playwright');
+const CHROME='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
+(async()=>{
+  const b=await pw.chromium.launch({executablePath:CHROME});
+  const ctx=await b.newContext({httpCredentials:{username:'admin',password:'DW2024!'}});
+  const p=await ctx.newPage();
+  const errs=[]; p.on('console',m=>{if(m.type()==='error')errs.push(m.text())}); p.on('pageerror',e=>errs.push('PAGEERR '+e.message));
+  const hits=[]; p.on('request',r=>{ if(r.url().includes('/api/liveness/')) hits.push(r.url()); });
+  await p.goto('http://127.0.0.1:9772/',{waitUntil:'networkidle',timeout:20000});
+  await p.waitForSelector('#grid .card .live',{timeout:8000});
+  const before=await p.$eval('#grid .card .live',el=>el.textContent.trim());
+  await p.click('#grid .card .live');                 // click the FIRST card's pill
+  await p.waitForTimeout(1500);
+  const after=await p.$eval('#grid .card .live',el=>el.textContent.trim());
+  const runsAfter=await p.$eval('#grid .card .runsv',el=>el.textContent.trim());
+  console.log('liveness fetch fired:', hits.length, hits[0]?('→ '+hits[0].split('/api/')[1]):'');
+  console.log('pill before:', JSON.stringify(before), '→ after:', JSON.stringify(after));
+  console.log('runs cell after:', JSON.stringify(runsAfter));
+  console.log('console errors:', errs.length?errs.join(' || '):'none');
+  await b.close();
+})();
diff --git a/public/index.html b/public/index.html
index 61ed577..a3485e8 100644
--- a/public/index.html
+++ b/public/index.html
@@ -68,6 +68,11 @@
   .live.firing{background:#0f2e26;color:var(--green)}
   .live.stale{background:#3a2410;color:var(--amber)}
   .live.unknown{background:#20272f;color:var(--dim)}
+  .live{cursor:pointer;user-select:none}
+  .live:hover{filter:brightness(1.28)}
+  .live:focus-visible{outline:2px solid var(--teal);outline-offset:1px}
+  .live .reck{opacity:.5;font-size:9px}
+  .live.checking{background:#20272f;color:var(--dim)}
   .card.stale{border-color:#4a3410}
   .purpose{font-size:12px;color:#b8c2ce;line-height:1.4}
   .rowset{display:flex;flex-direction:column;gap:3px;font-size:12px}
@@ -214,14 +219,14 @@ function apply(){
       : `${j.runs.toLocaleString()}${j.runsDelta>0?` <span class="when">(+${j.runsDelta} since last snapshot)</span>`:''}`;
     // 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':''}">
+    return `<div class="card ${j.state} ${live==='stale'?'stale':''}" data-label="${j.label}">
       <span class="badge ${j.state}">${j.state}</span>
       <div class="label">com.steve.<span class="short">${short}</span></div>
-      <div><span class="live ${live}" title="true launchd run-accounting signal, not log mtime">${liveLabel}</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>`:''}
       <div class="rowset">
         <div class="kv"><span class="k">Schedule</span><span class="v"><span class="chip">${j.schedule||'—'}</span></span></div>
-        <div class="kv"><span class="k">Runs</span><span class="v">${runsTxt}</span></div>
+        <div class="kv"><span class="k">Runs</span><span class="v runsv">${runsTxt}</span></div>
         <div class="kv"><span class="k">Last exit</span><span class="v ${exitClass(ex)}">${ex===null?'not loaded':ex==='-'?'—':ex}${ex==='0'?' ✓':(ex&&ex!=='-'?' ✕':'')}</span></div>
         <div class="kv"><span class="k">Last log</span><span class="v" title="log mtime — frozen for silent jobs, NOT a real-run signal · ${j.lastLogWrite||''}">${fmt(j.lastLogWrite)||'—'} <span class="when">${rel(j.lastLogWrite)}</span></span></div>
         <div class="kv"><span class="k">Next run</span><span class="v" title="${j.nextRun||''}">${fmt(j.nextRun)||'(interval)'} <span class="when">${rel(j.nextRun)}</span></span></div>
@@ -256,6 +261,36 @@ async function load(){
   }catch(e){ $('#grid').innerHTML='<div class="empty">Failed to load /api/crons — '+e.message+'</div>'; }
 }
 
+// Liveness pill → on-demand re-check. Wires the /api/liveness/:label endpoint:
+// ONE fetch, patches just this card's pill + Runs counter in place (no full
+// grid re-render), then refreshes the firing/stale tallies. The snapshot's
+// liveness is up to 5 min old; this bypasses the cache for a single job.
+async function recheckLiveness(pill){
+  const card = pill.closest('.card');
+  const label = pill.dataset.label || (card && card.dataset.label);
+  if(!label || pill.dataset.busy) return;
+  pill.dataset.busy='1';
+  const prevClass = pill.className, prevHtml = pill.innerHTML;
+  pill.className='live checking'; pill.textContent='… checking';
+  try{
+    const d = await (await fetch('api/liveness/'+encodeURIComponent(label),{credentials:'same-origin'})).json();
+    const job = (DATA.jobs||[]).find(x=>x.label===label);
+    const alive = d.running===true || (d.runsDelta||0)>0;
+    const live = alive ? 'firing'
+      : ((d.runs===null||d.runs===undefined) ? 'unknown' : ((job&&job.liveness)||'unknown'));
+    if(job){ if(typeof d.runs==='number') job.runs=d.runs; job.runsDelta=d.runsDelta||0; job.liveness=live; }
+    pill.className='live '+live; pill.dataset.label=label;
+    pill.innerHTML=(d.running?'🟢 running now':live==='firing'?'🟢 firing':live==='stale'?'⚠ stale':'? unknown')+' <span class="reck">↻</span>';
+    const rv = card && card.querySelector('.runsv');
+    if(rv) rv.innerHTML=(d.runs===null||d.runs===undefined) ? '—'
+      : `${Number(d.runs).toLocaleString()}${d.runsDelta>0?` <span class="when">(+${d.runsDelta} since last snapshot)</span>`:''}`;
+    stats();
+  }catch(_){ pill.className=prevClass; pill.innerHTML=prevHtml; }
+  finally{ delete pill.dataset.busy; }
+}
+$('#grid').addEventListener('click', e=>{ const p=e.target.closest('.live'); if(p) recheckLiveness(p); });
+$('#grid').addEventListener('keydown', e=>{ if(e.key==='Enter'||e.key===' '){ const p=e.target.closest('.live'); if(p){ e.preventDefault(); recheckLiveness(p); } } });
+
 // wire controls
 ['sort','fstate','fcad','flive'].forEach(id=>$('#'+id).addEventListener('change',()=>{LS.set(id,$('#'+id).value);apply()}));
 $('#q').addEventListener('input',()=>{LS.set('q',$('#q').value);apply()});

← e60d08e 5x contrarian gate: remove duplicate weaker window.fetch gua  ·  back to Cronjobs Viewer  ·  chore: v1.1.0 (session close) fd2b353 →