← back to Approval Command Center
viewer: add reversible fast-track tray (Approve all N) + per-card risk-reason line
478111847f5dfc0f1368b418ee8d1adb8d5d4ba9 · 2026-08-10 12:04:04 -0700 · Steve Abrams
- fast-track tray above the grid batches the reversible/no-hard-gate items
with an 'Approve all N reversible' button (bulk-enqueue, still drained by
the live pass — never fires from this page)
- every card now shows a one-line, gate-derived risk reason (⚠ hard / ✓ soft),
incl. the 'reversible but hard-gated → still officer-only' nuance
- corrected hardcoded 105 -> 109 in title/header to match the real digest
Per DTD verdict B + contrarian's highest-leverage fix.
Files touched
Diff
commit 478111847f5dfc0f1368b418ee8d1adb8d5d4ba9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 10 12:04:04 2026 -0700
viewer: add reversible fast-track tray (Approve all N) + per-card risk-reason line
- fast-track tray above the grid batches the reversible/no-hard-gate items
with an 'Approve all N reversible' button (bulk-enqueue, still drained by
the live pass — never fires from this page)
- every card now shows a one-line, gate-derived risk reason (⚠ hard / ✓ soft),
incl. the 'reversible but hard-gated → still officer-only' nuance
- corrected hardcoded 105 -> 109 in title/header to match the real digest
Per DTD verdict B + contrarian's highest-leverage fix.
---
public/index.html | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 52 insertions(+), 2 deletions(-)
diff --git a/public/index.html b/public/index.html
index 1e1b7b1..f1cec81 100644
--- a/public/index.html
+++ b/public/index.html
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
-<title>Approval Command Center — 105 gated drafts</title>
+<title>Approval Command Center — 109 gated drafts</title>
<style>
:root{ --cols:3; --bg:#0f1115; --panel:#171a21; --line:#252a34; --ink:#e7ebf0; --dim:#98a2b3; --teal:#33c2b8; --gold:#d9b166; }
*{box-sizing:border-box}
@@ -58,11 +58,21 @@
.count-pills{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}
.cp{font-size:11.5px;color:var(--dim);background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:4px 9px;cursor:pointer}
.cp b{color:var(--ink)}
+ /* fast-track tray — the reversible batch */
+ .fasttrack{background:linear-gradient(180deg,#12211f,#0f1b1a);border:1px solid #1f5c52;border-radius:12px;padding:13px 16px;margin-bottom:16px;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
+ .fasttrack.empty{display:none}
+ .fasttrack .ft-h{font-weight:600;color:#7ee0d3;font-size:14px;margin-bottom:2px}
+ .fasttrack .ft-sub{color:var(--dim);font-size:12px;max-width:640px}
+ .fasttrack .ft-actions{margin-left:auto;display:flex;gap:8px;flex-wrap:wrap}
+ /* per-card one-line risk reason */
+ .risk{font-size:11.5px;line-height:1.4;display:flex;gap:6px;align-items:flex-start;color:#e6a5a5}
+ .risk .rk{flex:none}
+ .card.soft .risk{color:#8fcabb}
</style>
</head>
<body>
<header>
- <h1>Approval Command Center <span class="sub">— the 105-item pending-approval digest · 2026-08-09</span></h1>
+ <h1>Approval Command Center <span class="sub">— the 109-item pending-approval digest · 2026-08-09</span></h1>
<div class="sub">Your click is the human gate. <b>Approve</b> logs your go and queues the item for the live officer-yolo pass (DTD → officer → contrarian → verify). Nothing irreversible fires from this page unattended.</div>
<div class="bar">
<input type="search" id="q" placeholder="Search title / body / slug…"/>
@@ -87,6 +97,7 @@
</div>
</header>
<main>
+ <div class="fasttrack empty" id="fasttrack"></div>
<div class="grid" id="grid"></div>
</main>
<div class="bulk" id="bulk">
@@ -103,6 +114,23 @@ const activeGates=new Set(), activeSev=new Set(), activeState=new Set();
const $=s=>document.querySelector(s);
const gates=["destructive","dw_unified canonical","Shopify write","deploy","DNS / domain","send (comms-law — NEVER auto)","other"];
+// one-line, plain-English "why is this gated / what's at stake" per gate.
+const RISK={
+ "destructive":"Deletes or overwrites data/state — no undo once it runs.",
+ "dw_unified canonical":"Writes the canonical catalog DB — a wrong value propagates fleet-wide.",
+ "Shopify write":"Edits the LIVE storefront — customer-facing the instant it lands.",
+ "deploy":"Ships code to prod — changes the live site until reverted.",
+ "DNS / domain":"Reroutes DNS/domain — slow to propagate, can black-hole a site.",
+ "send (comms-law — NEVER auto)":"Sends outbound comms — CAN-SPAM/TCPA exposure, can't be unsent.",
+ "other":"Gated action — open the memo for exactly what it touches."
+};
+function riskLine(it){
+ if(it.severity==='soft') return "Reversible & no hard gate — undoable/local; safe to fast-track.";
+ const base=RISK[it.gate]||RISK.other;
+ // a reversible item that still rides a hard gate: say why it's still officer-only
+ return (it.reversible && it.severity==='hard') ? base+" (reversible, but hard-gated → still officer-only)" : base;
+}
+
function fmtWhen(ms){ if(!ms) return "—"; return new Date(ms).toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}); }
function load(){
@@ -148,6 +176,27 @@ function render(){
addpill(`<b>${ITEMS.filter(i=>i.reversible).length}</b> reversible`, ()=>{activeSev.clear();activeSev.add('soft');render();});
gates.forEach(g=>addpill(`<b>${byGate[g]||0}</b> ${g}`, ()=>{activeGates.clear();activeGates.add(g);render();}));
+ // fast-track tray — reversible & no-hard-gate items still pending
+ const softPending=ITEMS.filter(i=>i.severity==='soft' && i.where==='pending');
+ const ft=$('#fasttrack');
+ if(softPending.length){
+ ft.classList.remove('empty');
+ ft.innerHTML=`
+ <div>
+ <div class="ft-h">⚡ Ready to fast-track — ${softPending.length} reversible</div>
+ <div class="ft-sub">Reversible & local, no hard gate. Approving logs your go and queues them on the <b>reversible-fast-track</b> path — they're still executed by the live drain, never fired from this page.</div>
+ </div>
+ <div class="ft-actions">
+ <button class="btn approve" id="ftapprove">Approve all ${softPending.length} reversible</button>
+ <button class="btn sel" id="ftselect">Select them instead</button>
+ </div>`;
+ $('#ftapprove').onclick=()=>{
+ if(!confirm(`Queue all ${softPending.length} reversible items for the fast-track drain?\n\nThis logs your go + enqueues them. Nothing executes from this page — the live pass drains the queue.`)) return;
+ post('/api/bulk',{slugs:softPending.map(i=>i.slug),action:'approve'}).then(()=>{ toast(`APPROVE · ${softPending.length} reversible queued`); load(); });
+ };
+ $('#ftselect').onclick=()=>{ softPending.forEach(i=>SEL.add(i.slug)); render(); };
+ } else { ft.classList.add('empty'); ft.innerHTML=''; }
+
const grid=$('#grid'); grid.innerHTML='';
const list=sortItems(ITEMS.filter(passesFilter));
list.forEach(it=>grid.appendChild(cardEl(it)));
@@ -167,6 +216,7 @@ function cardEl(it){
<span class="badge state">${it.where}</span>
${it.officer&&it.officer!=='unassigned'?`<span class="officer">${esc(it.officer)}</span>`:''}
</div>
+ <div class="risk"><span class="rk">${it.severity==='soft'?'✓':'⚠'}</span><span>${esc(riskLine(it))}</span></div>
<div class="row">
<button class="btn toggle">▸ memo</button>
${acted?`<span class="badge">${it.where.toUpperCase()}</span>`
← d7fbed4 initial scaffold: approval command center over the 105-draft
·
back to Approval Command Center
·
ACC: dynamic file scan for new pending-approval items (was s 4af2af7 →