← back to AbramsEgo
style: apply design-research top items (androoagi/norma patterns)
63d36862d24fc320a65346832c1c0856387d3556 · 2026-07-01 18:45:57 -0700 · Steve
- top metrics bar: segmented 10-cell gauges + bracketed [CPU]-style labels,
cell fill keeps good/warn/bad threshold colors
- stable per-agent golden-angle hue chips (agentHue) on local fleet, Kamatera
fleet, activity repos, sub-agent run projects
- [ PANEL NAME ] boxed terminal-style headers on every card (h2::before/after)
- click-to-focus: panel glows, siblings dim; Esc or re-click clears
- memo: items 1-4 marked ✅, item 5 noted server-side-blocked
Hard rules spot-checked live (Chromium): 177 🕓 chips, 5 sort+density bars,
cost labels intact, 0 pageerrors on index + landing.
Files touched
M memos/design-research.mdM public/index.html
Diff
commit 63d36862d24fc320a65346832c1c0856387d3556
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jul 1 18:45:57 2026 -0700
style: apply design-research top items (androoagi/norma patterns)
- top metrics bar: segmented 10-cell gauges + bracketed [CPU]-style labels,
cell fill keeps good/warn/bad threshold colors
- stable per-agent golden-angle hue chips (agentHue) on local fleet, Kamatera
fleet, activity repos, sub-agent run projects
- [ PANEL NAME ] boxed terminal-style headers on every card (h2::before/after)
- click-to-focus: panel glows, siblings dim; Esc or re-click clears
- memo: items 1-4 marked ✅, item 5 noted server-side-blocked
Hard rules spot-checked live (Chromium): 177 🕓 chips, 5 sort+density bars,
cost labels intact, 0 pageerrors on index + landing.
---
memos/design-research.md | 26 ++++++++++++++------
public/index.html | 63 +++++++++++++++++++++++++++++++++++++++---------
2 files changed, 69 insertions(+), 20 deletions(-)
diff --git a/memos/design-research.md b/memos/design-research.md
index 6944866..b52d855 100644
--- a/memos/design-research.md
+++ b/memos/design-research.md
@@ -280,14 +280,24 @@ are tracked per message so you always know what is working"*).
## Apply next (updated 2026-07-01, task 31 — supersedes the task-30 list)
-1. Segmented 10-cell gauges + bracketed monospace labels in the top metrics
- bar (keep threshold colors).
-2. Per-agent golden-angle hue chips in Fleet health, reused everywhere that
- agent appears.
-3. `[ PANEL NAME ]` boxed headers with a per-panel summary metrics row
- (count / healthy% / live N).
-4. Focus state: click card → glow + dim siblings.
-5. One-stat caption line on session/sub-agent cards (model · tokens · $).
+1. ✅ Segmented 10-cell gauges + bracketed monospace labels in the top metrics
+ bar (keep threshold colors). *(task 40 — CPU/RAM/SWAP/DISK now render a
+ 10-cell `.seg` gauge under the value, labels bracketed `[CPU]`, cell fill
+ inherits the good/warn/bad threshold class.)*
+2. ✅ Per-agent golden-angle hue chips in Fleet health, reused everywhere that
+ agent appears. *(task 40 — `agentHue()` name-hash × 137.508° → stable hsl
+ chip on local-fleet rows, Kamatera-fleet rows, activity commit repos, and
+ sub-agent run projects.)*
+3. ✅ `[ PANEL NAME ]` boxed headers with a per-panel summary metrics row
+ (count / healthy% / live N). *(task 40 — CSS `h2::before/::after` brackets
+ frame every panel header edge-to-edge; the per-panel summary metrics rows
+ already lived in each h2's muted span.)*
+4. ✅ Focus state: click card → glow + dim siblings. *(task 40 — click a panel
+ → accent glow, siblings dim to .45; click again or Esc clears; clicks on
+ links/inputs/sliders inside panels are ignored.)*
+5. One-stat caption line on session/sub-agent cards (model · tokens · $) —
+ *needs the sessions collector to expose model+tokens per card; server-side,
+ not done in task 40's frontend pass.*
6. Slide-in + source-panel pulse on new activity-feed items.
7. Build-loop **per-task manifest JSON** (status / derived_state /
current_blocker / lifecycle events / commit) written by build-loop.sh,
diff --git a/public/index.html b/public/index.html
index 04b5ca9..2bba8ce 100644
--- a/public/index.html
+++ b/public/index.html
@@ -28,6 +28,19 @@
border-radius:12px;padding:14px 16px;min-height:60px}
.card h2{margin:0 0 10px;font-size:11px;letter-spacing:1px;text-transform:uppercase;color:var(--dim);
display:flex;align-items:center;gap:8px}
+ /* androoagi "[ OUTPUT TERMINAL ]" boxed-label panel headers */
+ .card h2::before{content:'[';color:#3b4f68;font-weight:400}
+ .card h2::after{content:']';color:#3b4f68;font-weight:400;margin-left:auto}
+ /* androoagi segmented 10-cell mini-gauges (top metrics bar) */
+ .seg{display:inline-flex;gap:1.5px;margin-top:2px}
+ .seg i{width:5px;height:7px;border-radius:1px;background:#ffffff14}
+ .seg i.on{background:currentColor}
+ /* stable per-agent hue chip (golden-angle) — same entity, same color, every panel */
+ .hue{display:inline-block;width:7px;height:7px;border-radius:2px;margin-right:5px;flex-shrink:0;vertical-align:baseline}
+ /* androoagi focus cue: clicked panel glows, siblings dim */
+ main.focused section.card{opacity:.45;transition:opacity .15s ease}
+ main.focused section.card.focus{opacity:1;border-color:#4d9fff88;
+ box-shadow:0 0 0 1px #4d9fff33,0 0 26px -8px #4d9fff99}
.span12{grid-column:span 12}.span8{grid-column:span 8}.span7{grid-column:span 7}.span6{grid-column:span 6}
.span5{grid-column:span 5}.span4{grid-column:span 4}.span3{grid-column:span 3}
@media(max-width:1000px){.span8,.span7,.span6,.span5,.span4,.span3{grid-column:span 12}}
@@ -283,16 +296,21 @@ function feedGrid(name, items){ const g=GRIDS[name]; if(!g) return; g.items=Arra
async function load(){
let d; try{ d = await (await fetch('/api/data')).json(); }catch(e){ return; }
if(!d || !d.builtAt) return;
- // metrics bar
+ // metrics bar — segmented 10-cell gauges + bracketed labels (androoagi);
+ // cell fill keeps the threshold colors (green must still mean healthy)
const s=d.system||{};
+ const gauge=(pct)=>{ if(pct==null) return '';
+ const on=Math.max(0,Math.min(10,Math.round(pct/10)));
+ return `<span class="seg">${Array.from({length:10},(_,i)=>`<i${i<on?' class="on"':''}></i>`).join('')}</span>`; };
+ const swapMB=(s.swap && s.swap.usedMB!=null)?s.swap.usedMB:null;
$('metrics').innerHTML = [
- ['CPU', (s.loadPct??'—')+'%', cls(s.loadPct,60,85)],
- ['RAM', (s.memUsedPct??'—')+'%', cls(s.memUsedPct,70,88)],
- ['SWAP', (s.swap && s.swap.usedMB!=null) ? (s.swap.usedMB>=1024 ? (s.swap.usedMB/1024).toFixed(1)+'G' : Math.round(s.swap.usedMB)+'M') : '—', (s.swap && s.swap.usedMB!=null) ? cls(s.swap.usedMB, 2048, 8192) : ''],
- ['DISK', (s.diskUsedPct??'—')+'%', cls(s.diskUsedPct,80,90)],
- ['NODE', s.node||'—',''],
- ['UP', fmtDur(s.dashboardUptimeSec)||'—',''],
- ].map(([k,v,c])=>`<div class="metric"><div class="v ${c}">${v}</div><div class="k">${k}</div></div>`).join('');
+ ['CPU', (s.loadPct??'—')+'%', cls(s.loadPct,60,85), s.loadPct],
+ ['RAM', (s.memUsedPct??'—')+'%', cls(s.memUsedPct,70,88), s.memUsedPct],
+ ['SWAP', swapMB!=null ? (swapMB>=1024 ? (swapMB/1024).toFixed(1)+'G' : Math.round(swapMB)+'M') : '—', swapMB!=null ? cls(swapMB, 2048, 8192) : '', swapMB!=null?Math.min(100,swapMB/8192*100):null],
+ ['DISK', (s.diskUsedPct??'—')+'%', cls(s.diskUsedPct,80,90), s.diskUsedPct],
+ ['NODE', s.node||'—','',null],
+ ['UP', fmtDur(s.dashboardUptimeSec)||'—','',null],
+ ].map(([k,v,c,g])=>`<div class="metric ${c}"><div class="v">${v}</div>${gauge(g)}<div class="k">[${k}]</div></div>`).join('');
$('refresh').textContent = 'snapshot '+new Date(d.builtAt).toLocaleTimeString()+' · auto 15s · $0 (local)'+(d.version?' · v'+d.version:'');
// ---- P&L ----
@@ -340,7 +358,7 @@ async function load(){
$('kam-up').innerHTML=`<span class="${cls(100-(k.up/(k.total||1)*100),20,50)}">${k.up??'—'}</span><span class="dim" style="font-size:16px">/${k.total??'—'} up</span> `+(whenChip(k.fetchedAt)||'')+(k.note?` <span class="warn" style="font-size:11px" title="${esc(k.error||'')}">⚠ ${esc(k.note)}</span>`:'');
renderKamFleet(k);
$('loc-up').innerHTML=`<span class="good">${l.up??'—'}</span><span class="dim" style="font-size:16px">/${l.total??'—'} up</span>`+(l.note?` <span class="warn" style="font-size:11px" title="${esc(l.error||'')}">⚠ ${esc(l.note)}</span>`:'');
- $('loc-list').innerHTML=(l.procs||[]).map(pr=>lrow(`${statusDot(pr.status)}${esc(pr.name)}`,`${pr.memMB!=null?pr.memMB+'M':''}`,whenChip(pr.startedAt))).join('')||`<span class="muted">${esc(l.note||'no local pm2 procs')}</span>`;
+ $('loc-list').innerHTML=(l.procs||[]).map(pr=>lrow(`${statusDot(pr.status)}${hueChip(pr.name)}${esc(pr.name)}`,`${pr.memMB!=null?pr.memMB+'M':''}`,whenChip(pr.startedAt))).join('')||`<span class="muted">${esc(l.note||'no local pm2 procs')}</span>`;
// ---- canaries ---- (sort + density + infinite scroll like every DW grid)
const cans=(d.canaries&&d.canaries.canaries)||[];
@@ -471,7 +489,7 @@ function renderUsage(d){
{k:'model',label:'Model A→Z', cmp:(a,b)=>(a.model||'').localeCompare(b.model||'')},
],
render:(r)=>`<div class="subcard" title="${esc(r.startedAt||r.updatedAt||'')}">
- <div class="subcard-top"><span class="subcard-proj">${esc(r.project||'?')}</span>`+
+ <div class="subcard-top"><span class="subcard-proj">${hueChip(r.project)}${esc(r.project||'?')}</span>`+
`<span class="pill">${esc(r.model||'unknown')}</span></div>`+
`<div class="subcard-task">${esc(r.task||'')}</div>`+
`<div class="subcard-when">${whenChip(r.startedAt,r.updatedAt)||'🕓 —'}</div></div>`,
@@ -494,7 +512,7 @@ function renderActivity(d){
{k:'repo',label:'Repo A→Z',cmp:(x,y)=>(x.repo||'').localeCompare(y.repo||'')||tsOf(y.date)-tsOf(x.date)},
],
render:(cm)=>`<div class="subcard" title="${esc(cm.date||'')}">
- <div class="subcard-top"><span class="subcard-proj">${esc(cm.repo||'?')}</span>`+
+ <div class="subcard-top"><span class="subcard-proj">${hueChip(cm.repo)}${esc(cm.repo||'?')}</span>`+
`<span class="pill">${esc(cm.hash||'')}</span></div>`+
`<div class="subcard-task">${esc(cm.subject||'')}</div>`+
`<div class="subcard-when">${whenChip(cm.date)||'🕓 —'}${cm.author?' · '+esc(cm.author):''}</div></div>`,
@@ -578,13 +596,34 @@ function renderKamFleet(k){
{k:'restarts',label:'Restarts ↓', cmp:(a,b)=>(b.restarts||0)-(a.restarts||0)},
{k:'status', label:'Status', cmp:(a,b)=>srank(a)-srank(b)||(a.name||'').localeCompare(b.name||'')},
],
- render:(pr)=>lrow(`${statusDot(pr.status)}${esc(pr.name)}`,`${pr.restarts??''}↺`,whenChip(pr.startedAt)),
+ render:(pr)=>lrow(`${statusDot(pr.status)}${hueChip(pr.name)}${esc(pr.name)}`,`${pr.restarts??''}↺`,whenChip(pr.startedAt)),
});
// empty text can change per snapshot (unreachable note vs default)
GRIDS['kamfleet'].cfg.empty=(k.note||'kamatera unreachable');
feedGrid('kamfleet', k.procs||[]);
}
function statusDot(st){const c=st==='online'?'var(--good)':st?'var(--bad)':'var(--dim)';return `<span class="dot" style="background:${c}"></span>`;}
+// stable per-agent identity color: name hash spun around the golden angle
+// (137.508°, same trick as /color) so every process/repo keeps one hue everywhere
+function agentHue(name){let h=0;const s=String(name||'');for(let i=0;i<s.length;i++)h=(h*31+s.charCodeAt(i))>>>0;return Math.round((h%997)*137.508)%360;}
+function hueChip(name){return `<span class="hue" style="background:hsl(${agentHue(name)},62%,56%)"></span>`;}
+// click a panel → it glows, siblings dim (click again or Esc to clear);
+// clicks on links/controls inside a panel are left alone
+document.getElementById('main').addEventListener('click',(e)=>{
+ const card=e.target.closest('section.card'); if(!card) return;
+ if(e.target.closest('a,select,input,button,textarea,label')) return;
+ const main=document.getElementById('main');
+ const was=card.classList.contains('focus');
+ main.querySelectorAll('section.card.focus').forEach(c=>c.classList.remove('focus'));
+ main.classList.toggle('focused',!was);
+ if(!was) card.classList.add('focus');
+});
+document.addEventListener('keydown',(e)=>{
+ if(e.key!=='Escape') return;
+ const main=document.getElementById('main');
+ main.classList.remove('focused');
+ main.querySelectorAll('section.card.focus').forEach(c=>c.classList.remove('focus'));
+});
function fmtDur(s){if(s==null)return null;if(s<60)return s+'s';if(s<3600)return Math.floor(s/60)+'m';return Math.floor(s/3600)+'h';}
function esc(x){return (x==null?'':String(x)).replace(/[<>&]/g,m=>({'<':'<','>':'>','&':'&'}[m]));}
← 1c59231 auto-save: 2026-07-01T18:38:06 (8 files) — data/local-fleet.
·
back to AbramsEgo
·
auto-save: 2026-07-01T19:08:14 (10 files) — build-queue/task fb9b33f →