← back to Ticket System
board: spinning working-orb on blocked tickets with live agent activity (<15m) or a due saved schedule
547d69dcbc9c26eeaeb1d495ef083214d540622f · 2026-09-26 08:25:45 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBQ38pbQxdBao8SVKBRb84
Files touched
Diff
commit 547d69dcbc9c26eeaeb1d495ef083214d540622f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sat Sep 26 08:25:45 2026 -0700
board: spinning working-orb on blocked tickets with live agent activity (<15m) or a due saved schedule
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBQ38pbQxdBao8SVKBRb84
---
board.html | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/board.html b/board.html
index 1a31d5d4..90ce3d6e 100644
--- a/board.html
+++ b/board.html
@@ -72,6 +72,12 @@
.segsel{font:inherit;background:transparent;border:1px solid var(--line);border-radius:6px;padding:1px 4px;cursor:pointer}.segsel.seg-auto{opacity:.6;font-style:italic}.segsel.seg-set{font-weight:600}.segsel.seg-dw{color:var(--gold)}.segsel.seg-non{color:var(--mut)}
.stsel.s-open{color:var(--open)}.stsel.s-doing{color:var(--doing)}.stsel.s-blocked{color:var(--blocked)}.stsel.s-done{color:var(--done)}.stsel.s-stopped{color:var(--stopped);text-decoration:none}.stsel.s-parked{color:#e879b9}
.stsel option{color:var(--ink);background:var(--card)}
+ /* blocked-but-working orb: spins when a blocked ticket still has live agent activity (<15m) or a saved schedule that is due */
+ .stwrap{display:inline-flex;align-items:center;gap:5px}
+ .orb{width:11px;height:11px;border-radius:50%;flex:none;border:2px solid rgba(143,212,154,.25);border-top-color:#8fd49a;animation:orbspin .9s linear infinite}
+ .orb.saved{border-color:rgba(127,184,255,.25);border-top-color:#7fb8ff}
+ @keyframes orbspin{to{transform:rotate(360deg)}}
+ @media (prefers-reduced-motion:reduce){.orb{animation-duration:3s}}
.vb{display:inline-flex;align-items:center;gap:4px;font-size:11px;padding:1px 7px;border-radius:20px;border:1px solid var(--line)}
.vb.yes{color:#34d399;border-color:#2b6f52} .vb.no{color:#e06c75} .vb.unknown{color:var(--faint)} .vb.none{color:var(--faint);opacity:.6}
.vb small{color:var(--faint)}
@@ -209,7 +215,7 @@ const COLS=[
{k:'id', l:'Ticket', g:'Core', w:150, def:1, cell:t=>ticketCell(t), raw:t=>t.id},
{k:'about', l:'About · 5 words',g:'Core',w:230,def:1,cell:t=>`<span title="${esc(t.title)}">${esc(fiveWords(t.title))}</span>`,raw:t=>fiveWords(t.title)},
{k:'segment',l:'DW?',g:'Core',w:118,def:1,cell:t=>segCell(t),raw:t=>isDW(t)?'DW':'Non-DW'},
- {k:'status',l:'Status', g:'Core', w:120, def:1, cell:t=>{const cur=t.parked?'parked':t.status;const o=['open','doing','blocked','done','stopped','parked'].map(s=>`<option value="${s}"${s===cur?' selected':''}>${s==='stopped'?'TicketStopped':s==='parked'?'⏸ parked':s}</option>`).join('');return `<select class="stsel s-${cur}" data-id="${t.id}" data-cur="${cur}" title="Reassign status" onpointerdown="event.stopPropagation()" onmousedown="event.stopPropagation()" onclick="event.stopPropagation()" onchange="reassignStatus(event)">${o}</select>`;}, raw:t=>t.parked?'parked':t.status},
+ {k:'status',l:'Status', g:'Core', w:120, def:1, cell:t=>{const cur=t.parked?'parked':t.status;const o=['open','doing','blocked','done','stopped','parked'].map(s=>`<option value="${s}"${s===cur?' selected':''}>${s==='stopped'?'TicketStopped':s==='parked'?'⏸ parked':s}</option>`).join('');const w=workSignal(t);const orb=w?`<span class="orb${w.k==='saved'?' saved':''}" role="img" aria-label="${esc(w.tip)}" title="${esc(w.tip)}"></span>`:'';return `<span class="stwrap">${orb}<select class="stsel s-${cur}" data-id="${t.id}" data-cur="${cur}" title="Reassign status" onpointerdown="event.stopPropagation()" onmousedown="event.stopPropagation()" onclick="event.stopPropagation()" onchange="reassignStatus(event)">${o}</select></span>`;}, raw:t=>t.parked?'parked':t.status},
{k:'blocker',l:'Blocker lane',g:'Blocker',w:150,def:1,cell:t=>blockerCell(t),raw:t=>t.blocker?.type||''},
{k:'next_action',l:'Next unblock',g:'Blocker',w:320,def:1,cell:t=>esc(t.blocker?.next_action||'—'),raw:t=>t.blocker?.next_action||''},
{k:'blocker_owner',l:'Blocker owner',g:'Blocker',w:145,def:1,cell:t=>esc(t.blocker?.owner||'—'),raw:t=>t.blocker?.owner||''},
@@ -227,6 +233,9 @@ const COLS=[
{k:'nact',l:'#Act', g:'Activity',w:64,def:1,cell:t=>`<span class="num">${(t.actions||[]).length}</span>`,raw:t=>(t.actions||[]).length},
{k:'last',l:'Last activity',g:'Activity',w:460,def:0,cell:t=>esc((lastText(t)||'').slice(0,160)),raw:t=>lastText(t)||''},
];
+// blocked-but-working (Steve 2026-09-26): 'running' = agent action/comment in the last 15m;
+// 'saved' = a saved cadence/cron schedule whose next run is still due (or ran in the last hour).
+function workSignal(t){if(t.status!=='blocked'||t.parked)return null;let ms=0;for(const a of (t.actions||[]))ms=Math.max(ms,+new Date(a.ts));for(const c of (t.comments||[]))ms=Math.max(ms,+new Date(c.ts));const age=ms?(Date.now()-ms)/60000:Infinity;if(age<15)return {k:'running',tip:'working — last agent activity '+Math.round(age)+'m ago'};const sc=t.schedule||{};if(sc.cadence||sc.cron){const nx=sc.next_run_at?+new Date(sc.next_run_at):0,lr=sc.last_run_at?+new Date(sc.last_run_at):0;if(nx>Date.now()||(lr&&Date.now()-lr<3600000))return {k:'saved',tip:'saved schedule running ('+(sc.cron||sc.cadence)+')'+(nx?' — next '+new Date(nx).toLocaleString():'')};}return null;}
function lastTs(t){let ms=+new Date(t.updated_at||t.created_at||0);for(const a of (t.actions||[]))ms=Math.max(ms,+new Date(a.ts));for(const c of (t.comments||[]))ms=Math.max(ms,+new Date(c.ts));return ms;}
function lastText(t){const acts=(t.actions||[]).slice().sort((a,b)=>+new Date(b.ts)-+new Date(a.ts));if(acts[0])return acts[0].text;const cs=(t.comments||[]).slice().sort((a,b)=>+new Date(b.ts)-+new Date(a.ts));return cs[0]?cs[0].text:'';}
const CK=COLS.map(c=>c.k);
← ed74adfe auto-data-snapshot: 2026-09-25T16:33:34 (1 data files) — tmp
·
back to Ticket System
·
TK-11855: read-only Stage 1 post-reboot verifier for pm2 boo d7dcc35d →