← 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=>({'&':'&','<':'<','>':'>'}[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>