[object Object]

← back to Approval Viewer

fix approval button note lookup

c44df226567fa50126a1444ad2a7426c8fa20a92 · 2026-08-30 22:54:22 -0700 · Steve Abrams

Files touched

Diff

commit c44df226567fa50126a1444ad2a7426c8fa20a92
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Aug 30 22:54:22 2026 -0700

    fix approval button note lookup
---
 public/index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/index.html b/public/index.html
index de8d3ef..5179bc4 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,4 +1,4 @@
 <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Gated Approval Viewer</title><style>
 :root{color-scheme:dark;--bg:#101216;--panel:#191d24;--line:#303744;--ink:#f1f3f5;--muted:#aab3c0;--gold:#d9b166;--ok:#64d1ba;--red:#ed8e8e}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.45 system-ui,sans-serif}header{padding:24px max(18px,4vw);border-bottom:1px solid var(--line);background:#15181e}h1{margin:0 0 5px;font-size:25px}p{color:var(--muted);margin:5px 0}.notice{margin-top:14px;padding:10px 12px;border:1px solid #3e4b60;border-radius:9px;color:#c8d9f4}.tools{display:flex;gap:10px;flex-wrap:wrap;margin-top:17px}input,select,button,textarea{font:inherit;border:1px solid var(--line);border-radius:8px;padding:9px 11px;background:var(--panel);color:var(--ink)}input{min-width:240px}.grid{padding:22px max(18px,4vw);display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:15px}.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:10px}.card.done{opacity:.62}.title{font-weight:700;font-size:17px}.meta{display:flex;gap:7px;flex-wrap:wrap}.badge{font-size:12px;padding:3px 8px;border-radius:999px;border:1px solid var(--line);color:var(--muted)}.hard{color:var(--red);border-color:#704244}.soft{color:var(--ok);border-color:#32675d}.summary{color:#d2d7df;min-height:45px}.actions{display:flex;gap:7px;flex-wrap:wrap;margin-top:auto}.actions button{cursor:pointer}.approve{border-color:#32675d;color:var(--ok)}.revise{border-color:#76602e;color:var(--gold)}.block{border-color:#704244;color:var(--red)}.status{font-size:13px;color:var(--muted)}.empty{padding:30px;color:var(--muted)}textarea{width:100%;min-height:44px;resize:vertical}
 </style></head><body><header><h1>Gated approvals</h1><p>Read the request. Choose one clear next step.</p><div class="notice">Safe mode: buttons record a local decision only. They do not publish, email, spend, deploy, delete, or call outside systems.</div><div class="tools"><input id="search" type="search" placeholder="Search tickets…"><select id="filter"><option value="all">All gates</option><option value="hard">Hard gates</option><option value="soft">Safe local items</option></select><span id="count" class="status"></span></div></header><main id="grid" class="grid"></main><script>
-let data={items:[],decisions:[]};const $=s=>document.querySelector(s);const latest=()=>Object.fromEntries(data.decisions.map(d=>[d.id,d]));function esc(s){return String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));}function render(){let q=$('#search').value.toLowerCase(),f=$('#filter').value,st=latest();let list=data.items.filter(i=>(f==='all'||i.risk===f)&&(!q||JSON.stringify(i).toLowerCase().includes(q)));$('#count').textContent=`${list.length} item${list.length===1?'':'s'} shown`;$('#grid').innerHTML=list.length?list.map(i=>{let d=st[i.id];return `<article class="card ${d?'done':''}"><div class="title">${esc(i.title)}</div><div class="meta"><span class="badge">${esc(i.id)}</span><span class="badge ${i.risk}">${esc(i.gate)}</span><span class="badge">${esc(i.owner)}</span></div><div class="summary">${esc(i.summary)}</div><textarea id="note-${esc(i.id)}" placeholder="Optional note" aria-label="Note for ${esc(i.title)}"></textarea>${d?`<div class="status">Recorded: <b>${esc(d.action)}</b> · ${new Date(d.at).toLocaleString()}</div>`:`<div class="actions"><button class="approve" data-id="${esc(i.id)}" data-action="approve">Approve</button><button class="revise" data-id="${esc(i.id)}" data-action="revise">Revise</button><button class="block" data-id="${esc(i.id)}" data-action="block">Block</button></div>`}</article>`}).join(''):'<div class="empty">No matching items.</div>';document.querySelectorAll('[data-action]').forEach(b=>b.onclick=()=>decide(b.dataset.id,b.dataset.action));}async function decide(id,action){let note=$(`#note-${id}`).value;let r=await fetch('/api/decision',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id,action,note})});let j=await r.json();if(!r.ok)return alert(j.error||'Could not record decision');data.decisions.push(j.decision);render();}async function load(){data=await (await fetch('/api/items')).json();render();}$('#search').oninput=render;$('#filter').onchange=render;load();</script></body></html>
+let data={items:[],decisions:[]};const $=s=>document.querySelector(s);const latest=()=>Object.fromEntries(data.decisions.map(d=>[d.id,d]));function esc(s){return String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));}function render(){let q=$('#search').value.toLowerCase(),f=$('#filter').value,st=latest();let list=data.items.filter(i=>(f==='all'||i.risk===f)&&(!q||JSON.stringify(i).toLowerCase().includes(q)));$('#count').textContent=`${list.length} item${list.length===1?'':'s'} shown`;$('#grid').innerHTML=list.length?list.map(i=>{let d=st[i.id];return `<article class="card ${d?'done':''}"><div class="title">${esc(i.title)}</div><div class="meta"><span class="badge">${esc(i.id)}</span><span class="badge ${i.risk}">${esc(i.gate)}</span><span class="badge">${esc(i.owner)}</span></div><div class="summary">${esc(i.summary)}</div><textarea id="note-${esc(i.id)}" placeholder="Optional note" aria-label="Note for ${esc(i.title)}"></textarea>${d?`<div class="status">Recorded: <b>${esc(d.action)}</b> · ${new Date(d.at).toLocaleString()}</div>`:`<div class="actions"><button class="approve" data-id="${esc(i.id)}" data-action="approve">Approve</button><button class="revise" data-id="${esc(i.id)}" data-action="revise">Revise</button><button class="block" data-id="${esc(i.id)}" data-action="block">Block</button></div>`}</article>`}).join(''):'<div class="empty">No matching items.</div>';document.querySelectorAll('[data-action]').forEach(b=>b.onclick=()=>decide(b.dataset.id,b.dataset.action));}async function decide(id,action){let note=(document.getElementById(`note-`)||{}).value||'';let r=await fetch('/api/decision',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id,action,note})});let j=await r.json();if(!r.ok)return alert(j.error||'Could not record decision');data.decisions.push(j.decision);render();}async function load(){data=await (await fetch('/api/items')).json();render();}$('#search').oninput=render;$('#filter').onchange=render;load();</script></body></html>

← c28b876 connect viewer to live approval queue  ·  back to Approval Viewer  ·  auto-data-snapshot: 2026-08-30T23:23:51 (1 data files) — dec 3e258c5 →