← back to Ticket System
Add five-word viewer summaries
811c80f2b3fe048fbc7a4443343b19b0615a1741 · 2026-09-02 14:01:15 -0700 · Steve Abrams
Files touched
Diff
commit 811c80f2b3fe048fbc7a4443343b19b0615a1741
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 2 14:01:15 2026 -0700
Add five-word viewer summaries
---
board.html | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/board.html b/board.html
index 434a2673..43f35bd2 100644
--- a/board.html
+++ b/board.html
@@ -149,6 +149,7 @@
<div id="dmwrap"></div>
<script>
const $=s=>document.querySelector(s), esc=s=>String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
+const fiveWords=s=>String(s||'').replace(/\bTK-\d+\b/gi,'').replace(/[^\p{L}\p{N}$&+/-]+/gu,' ').trim().split(/\s+/).filter(Boolean).slice(0,5).join(' ')||'No summary available yet';
const dt=s=>{if(!s)return '—';const d=new Date(s);if(isNaN(d))return '—';return '<span title="'+esc(new Date(s).toISOString())+'">'+esc(d.toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}))+'</span>';};
const agoH=s=>s?((Date.now()-new Date(s).getTime())/3600000):null;
const elapsed=s=>{const ms=Math.max(0,Date.now()-new Date(s||Date.now()).getTime()),m=Math.floor(ms/60000),h=Math.floor(m/60),d=Math.floor(h/24);return d?`${d}d ${h%24}h`:h?`${h}h ${m%60}m`:`${m}m`;};
@@ -181,6 +182,7 @@ const COLS=[
{k:'priority',l:'Prio', g:'Priority', w:64, def:1, cell:t=>`<span class="prio">${t.priority!=null?t.priority.toFixed(1):'—'}</span>`, raw:t=>t.priority||0},
{k:'ratings',l:'Ratings',g:'Priority',w:150, def:1, cell:t=>barsCell(t), raw:t=>t.priority||0},
{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:'status',l:'Status', g:'Core', w:96, def:1, cell:t=>`<span class="stpill s-${t.status}"><span class="dot" style="background:currentColor"></span>${t.status==='stopped'?'TicketStopped':t.status}</span>`, raw:t=>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||''},
@@ -308,15 +310,15 @@ function render(){
renderPills(); syncBar();
}
function catalogRows(){return SECTION==='agents'?AGENTS:SKILLS;}
-function catalogFields(){return SECTION==='agents'?[['name','Agent'],['tickets','Tickets'],['assigned','Assigned'],['actions','Actions'],['comments','Comments'],['updated_at','Updated']]:[['name','Skill'],['root','Root'],['slug','Slug'],['description','Description'],['updated_at','Updated']];}
+function catalogFields(){return SECTION==='agents'?[['name','Agent'],['about','About'],['tickets','Tickets'],['assigned','Assigned'],['actions','Actions'],['comments','Comments'],['updated_at','Updated']]:[['name','Skill'],['about','About'],['root','Root'],['slug','Slug'],['updated_at','Updated']];}
function renderCatalog(){
$('.tblwrap').style.display='none';$('#catalog').classList.add('show');$('#actionbar').classList.remove('show');
- let rows=catalogRows().filter(r=>!q||Object.values(r).join(' ').toLowerCase().includes(q.toLowerCase()));
+ let rows=catalogRows().map(r=>({...r,about:SECTION==='agents'?`Works across ${r.tickets} durable tickets`:fiveWords(r.description||r.name)})).filter(r=>!q||Object.values(r).join(' ').toLowerCase().includes(q.toLowerCase()));
rows.sort((a,b)=>{const x=a[CATALOG_SORT]??'',y=b[CATALOG_SORT]??'';return (typeof x==='number'&&typeof y==='number'?(x-y):String(x).localeCompare(String(y),undefined,{numeric:true}))*CATALOG_DIR;});
const fields=catalogFields(), opts=fields.map(([k,l])=>`<option value="${k}" ${CATALOG_SORT===k?'selected':''}>${l}</option>`).join('');
const meta=r=>fields.slice(1).map(([k,l])=>r[k]==null||r[k]===''?'':`<span class="kv"><b>${esc(l)}</b>${k.endsWith('_at')?dt(r[k]):esc(r[k])}</span>`).join('');
const stamp=r=>`<span class="stamp" title="${esc(r.created_at||'')}">🕓 ${r.created_at?new Date(r.created_at).toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}):'unknown'}</span>`;
- const body=LAYOUT==='kv'?rows.map(r=>`<article class="kvrow"><h3>${esc(r.name)}</h3><div class="kvmeta">${meta(r)}${stamp(r)}</div></article>`).join(''): `<div class="catalog-grid">${rows.map(r=>`<article class="catalog-card"><h3>${esc(r.name)}</h3>${r.description?`<p>${esc(r.description)}</p>`:''}<div class="kvmeta">${meta(r)}</div>${stamp(r)}</article>`).join('')}</div>`;
+ const body=LAYOUT==='kv'?rows.map(r=>`<article class="kvrow" title="${esc(r.description||r.about)}"><h3>${esc(r.name)}</h3><div class="kvmeta">${meta(r)}${stamp(r)}</div></article>`).join(''): `<div class="catalog-grid">${rows.map(r=>`<article class="catalog-card" title="${esc(r.description||r.about)}"><h3>${esc(r.name)}</h3><p>${esc(r.about)}</p><div class="kvmeta">${meta(r)}</div>${stamp(r)}</article>`).join('')}</div>`;
$('#catalog').innerHTML=`<div class="catalog-head"><h2>${SECTION==='agents'?'Agent activity':'Installed skills'}</h2><small>${rows.length} shown</small><span class="grow"></span><select class="gsel" id="catalogSort">${opts}</select><button class="btn" id="catalogDir">${CATALOG_DIR>0?'▲ Asc':'▼ Desc'}</button></div>${body||'<div class="c none">No matching records.</div>'}`;
$('#catalogSort').onchange=e=>{CATALOG_SORT=e.target.value;save('tkCatalogSort',CATALOG_SORT);renderCatalog();};$('#catalogDir').onclick=()=>{CATALOG_DIR*=-1;save('tkCatalogDir',CATALOG_DIR);renderCatalog();};
}
← 438a9874 Add unified ticket agent and skill viewer
·
back to Ticket System
·
Add bounded ticket runner override 7a35be1d →