diff --git a/mcp-server/console.html b/mcp-server/console.html
index f75b293..38e0ef6 100644
--- a/mcp-server/console.html
+++ b/mcp-server/console.html
@@ -54,8 +54,19 @@ details .body{padding-top:10px;font-size:12px;color:#8b949e}
.accent-g{border-left:3px solid #3fb950}
.accent-r{border-left:3px solid #f85149}
-.worker{display:flex;align-items:center;gap:10px;padding:8px 10px;background:#161b22;border-radius:6px;margin-bottom:4px;font-size:12px}
-.worker .av{width:28px;height:28px;border-radius:6px;background:#1a2744;display:flex;align-items:center;justify-content:center;font-weight:600;color:#e6edf3;font-size:10px;flex-shrink:0}
+.worker{display:flex;align-items:center;gap:10px;padding:8px 10px;background:#161b22;border-radius:6px;margin-bottom:4px;font-size:12px;border-left:3px solid #30363d}
+.worker .av{width:32px;height:32px;border-radius:50%;background:#0d1117;border:1px solid #21262d;display:flex;align-items:center;justify-content:center;font-weight:600;color:#c9d1d9;font-size:11px;flex-shrink:0;letter-spacing:0.5px}
+.worker[data-role-band="warehouse"]{border-left-color:#58a6ff}
+.worker[data-role-band="production"]{border-left-color:#d29922}
+.worker[data-role-band="trades"]{border-left-color:#bc8cff}
+.worker[data-role-band="driver"]{border-left-color:#3fb950}
+.worker[data-role-band="lead"]{border-left-color:#f0883e}
+.role-pill{display:inline-block;font-size:9px;padding:1px 7px;border-radius:3px;background:#0d1117;color:#8b949e;margin-right:6px;font-weight:600;letter-spacing:0.4px;text-transform:uppercase;border-left:2px solid #30363d;vertical-align:1px}
+.role-pill[data-rb="warehouse"]{border-left-color:#58a6ff;color:#79c0ff}
+.role-pill[data-rb="production"]{border-left-color:#d29922;color:#e3b341}
+.role-pill[data-rb="trades"]{border-left-color:#bc8cff;color:#d2a8ff}
+.role-pill[data-rb="driver"]{border-left-color:#3fb950;color:#56d364}
+.role-pill[data-rb="lead"]{border-left-color:#f0883e;color:#ffa657}
.worker .info{flex:1;min-width:0}
.worker .nm{color:#e6edf3;font-weight:500}
.worker .why{color:#545d68;font-size:11px;margin-top:1px}
@@ -199,6 +210,57 @@ var A=location.origin+P;
// DOM helpers — all dynamic content goes through these. No innerHTML
// anywhere in the script; every API-derived string passes through
// textContent so no injection path regardless of upstream data.
+// Role classification — mirrors search.html, no emojis. Maps role
+// strings to a band+label used by the worker-card border + role pill.
+var ROLE_BANDS = [
+ { match: /forklift|warehouse|associate|material\s*handler|loader|loading|packag|shipping|logistics|inventory|sanitation|janit/i, band: 'warehouse', label: 'Warehouse' },
+ { match: /production|assembl/i, band: 'production', label: 'Production' },
+ { match: /welder|weld|electric|maint(enance)?\s*tech|cnc|machine\s*op|hvac|plumb|carpenter|mason/i, band: 'trades', label: 'Skilled Trade' },
+ { match: /driver|truck|haul|cdl/i, band: 'driver', label: 'Driver' },
+ { match: /line\s*lead|supervisor|foreman|coordinator/i, band: 'lead', label: 'Lead' },
+ { match: /quality/i, band: 'production', label: 'Quality' },
+];
+function roleBand(role){
+ if(!role) return { band: 'warehouse', label: '' };
+ for (var i = 0; i < ROLE_BANDS.length; i++) {
+ if (ROLE_BANDS[i].match.test(role)) return ROLE_BANDS[i];
+ }
+ return { band: 'warehouse', label: role.split(' ')[0].toUpperCase().slice(0, 12) };
+}
+
+// Build a sober worker card: monogram avatar + colored role band on
+// the left edge + uppercase role pill in the detail line. Used by
+// every chapter that renders worker rows. `name` and `role` drive the
+// classification; `detail` is the full text after the pill.
+function workerRow(name, role, detail, opts){
+ opts = opts || {};
+ var band = roleBand(role||'');
+ var w = el('div','worker');
+ if(band.band) w.dataset.roleBand = band.band;
+ var initials = (name||'?').split(' ').map(function(s){return (s[0]||'').toUpperCase()}).join('').substring(0,2);
+ w.appendChild(el('div','av',initials));
+ var info = el('div','info');
+ var nm = el('div','nm', name||'?');
+ if(opts.endorsed){
+ nm.appendChild(el('span','boost-chip',opts.endorsed));
+ }
+ info.appendChild(nm);
+ var why = el('div','why');
+ if(band.label){
+ var pill = document.createElement('span'); pill.className='role-pill';
+ pill.dataset.rb = band.band;
+ pill.textContent = band.label;
+ why.appendChild(pill);
+ }
+ why.appendChild(document.createTextNode(detail||''));
+ info.appendChild(why);
+ w.appendChild(info);
+ if(opts.score){
+ w.appendChild(el('div','score', opts.score));
+ }
+ return w;
+}
+
function el(tag, cls, text){
var e=document.createElement(tag);
if(cls) e.className=cls;
@@ -380,21 +442,13 @@ function loadChapter4(){
var list=document.createElement('div');list.style.marginTop='6px';
(prop.candidates||[]).slice(0,5).forEach(function(cand,i){
- var w=el('div','worker');
- var initials=(cand.name||'?').split(' ').map(function(s){return (s[0]||'').toUpperCase()}).join('').substring(0,2);
- w.appendChild(el('div','av',initials));
- var info=el('div','info');
- var nm=el('div','nm',cand.name||cand.doc_id||'?');
- if((cand.playbook_boost||0)>0){
- var ncit=(cand.playbook_citations||[]).length;
- nm.appendChild(el('span','boost-chip','Endorsed · '+ncit+' past fill'+(ncit!==1?'s':'')));
- }
- info.appendChild(nm);
- var why=cand.doc_id+' · '+(cand.playbook_boost>0?'boosted +'+cand.playbook_boost.toFixed(3)+' by memory · ':'')+'semantic score '+(cand.score||0).toFixed(3);
- info.appendChild(el('div','why',why));
- w.appendChild(info);
- w.appendChild(el('div','score','#'+(i+1)));
- list.appendChild(w);
+ var detail = cand.doc_id+' · '+(cand.playbook_boost>0?'boosted +'+cand.playbook_boost.toFixed(3)+' by memory · ':'')+'semantic score '+(cand.score||0).toFixed(3);
+ var endorsed = (cand.playbook_boost||0) > 0
+ ? 'Endorsed · '+((cand.playbook_citations||[]).length)+' past fill'+((cand.playbook_citations||[]).length!==1?'s':'')
+ : null;
+ list.appendChild(workerRow(cand.name||cand.doc_id||'?', prop.role||'', detail, {
+ endorsed: endorsed, score: '#'+(i+1)
+ }));
});
card.appendChild(list);
@@ -628,12 +682,8 @@ function loadChapter8(){
bfHdr.textContent='✓ '+d.backfills.length+' local '+(d.worker.role||'workers')+' available — sorted by responsiveness';
host.appendChild(bfHdr);
d.backfills.slice(0,5).forEach(function(c){
- var row=el('div','row');
- var left=document.createElement('div');left.style.flex='1';left.style.minWidth='0';
- left.appendChild(el('div','title',c.name));
- left.appendChild(el('div','meta',(c.role||'?')+' · '+(c.city||'')+', '+(c.state||'')+' · rel '+Math.round((c.rel||0)*100)+'% · resp '+Math.round((c.resp||0)*100)+'%'));
- row.appendChild(left);
- host.appendChild(row);
+ var detail=(c.role||'?')+' · '+(c.city||'')+', '+(c.state||'')+' · rel '+Math.round((c.rel||0)*100)+'% · resp '+Math.round((c.resp||0)*100)+'%';
+ host.appendChild(workerRow(c.name||'?', c.role||'', detail));
});
}
var narr=el('div','narr');
@@ -675,23 +725,16 @@ function runTry(){
var workers=d.sql_results||d.vector_results||d.results||[];
workers.slice(0,5).forEach(function(w,i){
- var row=el('div','worker');
var nm=w.name||(w.text||'').split('—')[0].trim()||w.doc_id||'?';
- var initials=nm.split(' ').map(function(s){return (s[0]||'').toUpperCase()}).join('').substring(0,2);
- row.appendChild(el('div','av',initials));
- var info=el('div','info');
- var n=el('div','nm',nm);
- if((w.playbook_boost||0)>0){
- n.appendChild(el('span','boost-chip','Endorsed · '+((w.playbook_citations||[]).length||'?')+' past fill(s)'));
- }
- info.appendChild(n);
var bits=[];
if(w.role) bits.push(w.role);
if(w.city&&w.state) bits.push(w.city+', '+w.state);
if(w.rel!==undefined) bits.push('reliability '+Math.round(w.rel*100)+'%');
if(w.avail!==undefined) bits.push('availability '+Math.round(w.avail*100)+'%');
- info.appendChild(el('div','why',bits.join(' · ')||'AI semantic match'));
- row.appendChild(info);
+ var endorsed = (w.playbook_boost||0) > 0
+ ? 'Endorsed · '+((w.playbook_citations||[]).length||'?')+' past fill(s)'
+ : null;
+ var row = workerRow(nm, w.role||'', bits.join(' · ')||'AI semantic match', { endorsed: endorsed });
row.appendChild(el('div','score','#'+(i+1)));
card.appendChild(row);
});