← back to Dtd Approval Viewer

index.html

122 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<script>
// Credential-safe fetch guard (fleet drop-in) — if this page is opened with
// credentials in the URL (a saved bookmark / Chrome-remembered basic-auth:
// https://user:pass@host/…), the browser poisons document.baseURI, so any
// relative or root-relative fetch('/api/…') throws "Request cannot be constructed
// from a URL that includes credentials" and callers silently fall to an empty
// state. Resolve every non-absolute request URL against the credential-free
// location instead of baseURI. Placed first so it wraps window.fetch before any
// app script runs. Ref: creds-in-url-fetch-guard-fleet-pattern.
(function () {
  var _fetch = window.fetch.bind(window);
  var cleanBase = function () { return location.origin + location.pathname; };
  window.fetch = function (input, init) {
    try {
      if (typeof input === 'string' && !/^[a-z]+:\/\//i.test(input) && input.indexOf('//') !== 0) {
        input = new URL(input, cleanBase()).href;
      } else if (input instanceof Request && !/^[a-z]+:\/\//i.test(input.url)) {
        input = new Request(new URL(input.url, cleanBase()).href, input);
      }
    } catch (_) { /* fall through to native */ }
    return _fetch(input, init);
  };
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DTD Review — 42 Gated Memos</title>
<style>
  :root{--bg:#0f1115;--card:#191c22;--edge:#2a2f3a;--fg:#e7e9ee;--mut:#9aa2b1;
    --approve:#2fbf71;--revise:#e0a83a;--block:#e5484d;--hold:#7b8494;}
  *{box-sizing:border-box}
  body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.5 -apple-system,Inter,system-ui,sans-serif}
  header{position:sticky;top:0;z-index:10;background:#12141a;border-bottom:1px solid var(--edge);padding:14px 20px}
  h1{margin:0 0 4px;font-size:18px;font-weight:600;letter-spacing:.02em}
  .sub{color:var(--mut);font-size:12px}
  .tallies{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap}
  .pill{border:1px solid var(--edge);border-radius:20px;padding:5px 12px;font-size:12px;cursor:pointer;user-select:none;background:var(--card);color:var(--fg)}
  .pill.active{border-color:#6672ff;box-shadow:0 0 0 1px #6672ff}
  .pill b{font-weight:700}
  .dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;vertical-align:middle}
  .d-approve{background:var(--approve)} .d-revise{background:var(--revise)} .d-block{background:var(--block)} .d-hold{background:var(--hold)}
  #q{margin-top:10px;width:100%;max-width:420px;background:#0c0e12;border:1px solid var(--edge);border-radius:6px;color:var(--fg);padding:8px 12px;font-size:13px}
  main{padding:18px 20px;display:grid;grid-template-columns:repeat(auto-fill,minmax(430px,1fr));gap:14px;align-items:start}
  .card{background:var(--card);border:1px solid var(--edge);border-radius:10px;padding:14px 16px;border-left-width:4px}
  .card.v-APPROVE{border-left-color:var(--approve)} .card.v-REVISE{border-left-color:var(--revise)}
  .card.v-BLOCK{border-left-color:var(--block)} .card.v-HOLD{border-left-color:var(--hold)}
  .crow{display:flex;justify-content:space-between;align-items:flex-start;gap:10px}
  .tk{font-size:11px;color:var(--mut);font-family:ui-monospace,Menlo,monospace}
  .ttl{font-weight:600;font-size:14px;margin:2px 0 8px}
  .verdict{font-weight:800;font-size:12px;letter-spacing:.06em;padding:3px 9px;border-radius:5px;white-space:nowrap}
  .verdict.V-APPROVE{background:rgba(47,191,113,.16);color:var(--approve)}
  .verdict.V-REVISE{background:rgba(224,168,58,.16);color:var(--revise)}
  .verdict.V-BLOCK{background:rgba(229,72,77,.16);color:var(--block)}
  .verdict.V-HOLD{background:rgba(123,132,148,.18);color:#c3cad6}
  .meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}
  .tag{font-size:11px;color:var(--mut);border:1px solid var(--edge);border-radius:4px;padding:2px 7px}
  .tag.gate{color:#cdd3de}
  .tag.irrev{color:var(--block);border-color:rgba(229,72,77,.4)}
  ul{margin:6px 0 0;padding-left:18px} li{margin:3px 0;color:#d3d7e0}
  .lab{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--mut);margin-top:11px;margin-bottom:2px}
  .dissent{color:#e6b98a} .next{color:#9fd3ff}
  .conf{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--mut)}
  .esc{margin:10px 0 2px;padding:7px 10px;border:1px solid #4a52d6;background:rgba(102,114,255,.12);border-radius:6px;font-size:12px}
  .esc b{color:#aeb6ff} .esc .escnote{color:#c6cbe6;margin-top:3px;font-size:11px}
  footer{color:var(--mut);font-size:11px;padding:16px 20px;border-top:1px solid var(--edge)}
</style>
</head>
<body>
<header>
  <h1>DTD Review — Gated Approval Queue</h1>
  <div class="sub">42 memos · adversarial Claude-panel committed verdict each · <span id="stamp"></span> · review-only, nothing executed</div>
  <div class="tallies" id="tallies"></div>
  <input id="q" placeholder="filter by ticket, title, gate, next action…">
</header>
<main id="grid"></main>
<footer>Verdict = engineer/operator/skeptic panel + contrarian, converged. Gate class shown per card. Every item stays Steve-gated — say “do X” to fire one. Source: ~/.claude/yolo-queue/pending-approval/</footer>
<script>
const order={BLOCK:0,HOLD:1,REVISE:2,APPROVE:3};
let DATA=[],filterV=null,q='';
function esc(s){return String(s).replace(/[&<>]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]))}
function tallies(){
  const c={APPROVE:0,REVISE:0,BLOCK:0,HOLD:0};DATA.forEach(d=>c[d.verdict]++);
  const el=document.getElementById('tallies');el.innerHTML='';
  [['APPROVE','approve'],['REVISE','revise'],['HOLD','hold'],['BLOCK','block']].forEach(([v,k])=>{
    const p=document.createElement('div');p.className='pill'+(filterV===v?' active':'');
    p.innerHTML='<span class="dot d-'+k+'"></span>'+v+' <b>'+c[v]+'</b>';
    p.onclick=()=>{filterV=filterV===v?null:v;render()};el.appendChild(p);
  });
  const all=document.createElement('div');all.className='pill'+(filterV===null?' active':'');
  all.innerHTML='ALL <b>'+DATA.length+'</b>';all.onclick=()=>{filterV=null;render()};el.appendChild(all);
}
function render(){
  tallies();
  const g=document.getElementById('grid');g.innerHTML='';
  DATA.filter(d=>!filterV||d.verdict===filterV)
      .filter(d=>{if(!q)return true;const s=(d.ticket+' '+d.title+' '+d.gate_class+' '+d.next_action).toLowerCase();return s.includes(q)})
      .sort((a,b)=>order[a.verdict]-order[b.verdict]||b.confidence-a.confidence)
      .forEach(d=>{
    const c=document.createElement('div');c.className='card v-'+d.verdict;
    c.innerHTML=
      '<div class="crow"><div><div class="tk">'+esc(d.ticket)+'</div><div class="ttl">'+esc(d.title)+'</div></div>'
      +'<div class="verdict V-'+d.verdict+'">'+d.verdict+'</div></div>'
      +'<div class="meta"><span class="tag gate">'+esc(d.gate_class)+'</span>'
      +'<span class="tag conf">conf '+d.confidence+'%</span>'
      +'<span class="tag '+(d.reversible?'':'irrev')+'">'+(d.reversible?'reversible':'IRREVERSIBLE')+'</span>'
      +'<span class="tag">'+esc(d.blast_radius)+'</span></div>'
      +(d.escalated?'<div class="esc">⚖ EXTERNAL 5-LLM PANEL — <b>'+d.escalated.verdict+'</b> · '+esc(d.escalated.vote)+' · '+esc(d.escalated.confidence)+'<div class="escnote">'+esc(d.escalated.note)+'</div></div>':'')
      +'<div class="lab">panel</div><ul>'+d.panel_rationale.map(r=>'<li>'+esc(r)+'</li>').join('')+'</ul>'
      +'<div class="lab">dissent</div><div class="dissent">'+esc(d.dissent)+'</div>'
      +'<div class="lab">next action</div><div class="next">'+esc(d.next_action)+'</div>';
    g.appendChild(c);
  });
}
document.getElementById('q').addEventListener('input',e=>{q=e.target.value.toLowerCase();render()});
fetch('data.json').then(r=>r.json()).then(d=>{DATA=d;document.getElementById('stamp').textContent=DATA.length+' reviewed';render()});
</script>
</body>
</html>