[object Object]

← back to Ticket System

office: seat working agents in their doing room (rank active work over latest touch) + persistent working glow

ee1e135b05127fd0c0d70fb9282203aff70c19f7 · 2026-07-27 09:24:54 -0700 · Steve Abrams

Files touched

Diff

commit ee1e135b05127fd0c0d70fb9282203aff70c19f7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 27 09:24:54 2026 -0700

    office: seat working agents in their doing room (rank active work over latest touch) + persistent working glow
---
 office.html | 38 ++++++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 10 deletions(-)

diff --git a/office.html b/office.html
index aa1314e..65fce39 100644
--- a/office.html
+++ b/office.html
@@ -337,11 +337,27 @@ let seenMsgMids=null, lastActionTs=0, lastFetch=0;
 let showLabels=true, showDone=true, followDMs=false;
 let laneSigns=null;
 
-function agentLatestRoom(tk){ const at=new Map();
-  const put=(a,ts,id)=>{ if(!a)return; const c=at.get(a); if(!c||c.ts<ts) at.set(a,{ts,ticketId:id}); };
-  for(const t of tk){ (t.actions||[]).forEach(a=>put(a.agent,a.ts,t.id));
-    (t.comments||[]).forEach(c=>put(c.agent,c.ts,t.id)); if(t.assignee) put(t.assignee,t.updated_at,t.id); }
-  return at; }
+// Where each agent stands. An agent that is WORKING — owns or is acting on a
+// live doing/blocked ticket — belongs in THAT room, even if they more-recently
+// dropped a stray comment on some done ticket. So we rank active lanes (and being
+// the assignee) above pure recency, and only fall back to latest-touch for agents
+// with no active work. Returns name → { ticketId, working }.
+function agentPlacement(tk){
+  const ACTIVE={ doing:3, blocked:2, open:1, done:0 };
+  const best=new Map();
+  const put=(name,ts,t,assigned)=>{ if(!name) return;
+    // rank: active lane dominates; assignee of that ticket beats a mere toucher
+    const rank=(ACTIVE[t.status]??0)*10 + (assigned?2:0);
+    const cur=best.get(name);
+    if(!cur || rank>cur.rank || (rank===cur.rank && ts>cur.ts))
+      best.set(name,{ ts, ticketId:t.id, rank, working:t.status==='doing' });
+  };
+  for(const t of tk){
+    (t.actions ||[]).forEach(a=>put(a.agent,+new Date(a.ts),t, t.assignee===a.agent));
+    (t.comments||[]).forEach(c=>put(c.agent,+new Date(c.ts),t, t.assignee===c.agent));
+    if(t.assignee) put(t.assignee,+new Date(t.updated_at),t,true);
+  }
+  return best; }
 
 function layout(tk){
   const byLane={doing:[],blocked:[],open:[],done:[]};
@@ -383,17 +399,18 @@ function updateRoom(room,t){ const u=room.userData, prev=u.ticket; u.ticket=t;
 }
 function slotInRoom(p,i,n){ if(n<=1) return new THREE.Vector3(p.x,0,p.z+1.2);
   const r=Math.min(2.6,1.2+n*0.15),a=(i/n)*Math.PI*2; return new THREE.Vector3(p.x+Math.cos(a)*r,0,p.z+Math.sin(a)*r); }
-function placeAvatars(tk){ const latest=agentLatestRoom(tk), perRoom=new Map(), assign=[];
-  for(const [name,info] of latest){ const room=roomByTicket.get(info.ticketId);
-    const rid=(room&&room.visible)?info.ticketId:null; assign.push({name,rid});
+function placeAvatars(tk){ const place=agentPlacement(tk), perRoom=new Map(), assign=[];
+  for(const [name,info] of place){ const room=roomByTicket.get(info.ticketId);
+    const rid=(room&&room.visible)?info.ticketId:null; assign.push({name,rid,working:info.working&&!!rid});
     if(rid) perRoom.set(rid,(perRoom.get(rid)||0)+1); }
   const lobby=[], idx=new Map();
   for(const a of assign){ let av=avatarByName.get(a.name);
     if(!av){ av=buildAvatar(a.name); avatarByName.set(a.name,av); avatarsGrp.add(av);
       av.position.set(a.rid?roomByTicket.get(a.rid).position.x:-46, 0, a.rid?roomByTicket.get(a.rid).position.z:30); }
+    av.userData.atWork=a.working;   // persistent "working" glow while in a doing room
     if(a.rid){ const room=roomByTicket.get(a.rid),n=perRoom.get(a.rid),i=idx.get(a.rid)||0; idx.set(a.rid,i+1);
       av.userData.target.copy(slotInRoom(room.position,i,n)); av.userData.room=a.rid; } else lobby.push(av); }
-  lobby.forEach((av,i)=>{ av.userData.target.set(-48+(i%10)*7,0,26+Math.floor(i/10)*7); av.userData.room=null; });
+  lobby.forEach((av,i)=>{ av.userData.atWork=false; av.userData.target.set(-48+(i%10)*7,0,26+Math.floor(i/10)*7); av.userData.room=null; });
 }
 
 async function poll(){
@@ -489,8 +506,9 @@ function tick(){
     u.body.position.y=1.7+bob; u.head.position.y=3.3+bob;
     if(moving){ const d=u.target.clone().sub(av.position); av.rotation.y=Math.atan2(d.x,d.z); }
     if(u.working>0) u.working-=dt;
-    const w=u.working>0; u.ring.material.opacity=w?(0.5+0.4*Math.sin(el*10)):0.18;
+    const w=u.working>0||u.atWork; u.ring.material.opacity=w?(0.5+0.4*Math.sin(el*10)):0.18;
     u.ring.scale.setScalar(1+(w?0.18*Math.sin(el*10):0)); u.light.intensity=w?2.0+Math.sin(el*12):0.0;
+    u.body.material.emissiveIntensity=0.35+(w?0.28+0.22*Math.sin(el*6+u.phase):0); // working breath
     u.label.visible=showLabels; }
 
   // rooms

← 9e47637 canvas-design: LUMEN INDEX plate — Fleet Office as a scienti  ·  back to Ticket System  ·  office: harden agent placement — clear stale atWork glow on 338a454 →