[object Object]

← 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 &amp; 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 →