← back to Approval Viewer
connect viewer to live approval queue
c28b876669dc114413529222d9ba302b60b82105 · 2026-08-30 20:53:50 -0700 · Steve Abrams
Files touched
Diff
commit c28b876669dc114413529222d9ba302b60b82105
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Aug 30 20:53:50 2026 -0700
connect viewer to live approval queue
---
server.js | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/server.js b/server.js
index 2b270c5..beedab2 100644
--- a/server.js
+++ b/server.js
@@ -4,23 +4,35 @@ const fs = require('fs');
const path = require('path');
const PORT = Number(process.env.PORT || 9788);
const decisionsPath = path.join(__dirname, 'decisions.jsonl');
-const items = [
+const demoItems = [
{ id:'TK-10409-A', title:'Publish campaign to social accounts', gate:'external publish', risk:'hard', owner:'Marketing', summary:'Would publish to selected social accounts. Review targets and copy first.' },
{ id:'TK-10409-B', title:'Deploy pending website change', gate:'production deploy', risk:'hard', owner:'Engineering', summary:'Would change the live site. Confirm tests, rollback, and timing.' },
{ id:'TK-10409-C', title:'Send campaign email', gate:'outbound email', risk:'hard', owner:'Marketing', summary:'Would contact a list. Confirm audience, consent, and message.' },
{ id:'TK-10409-D', title:'Run approval dry-run report', gate:'read-only', risk:'soft', owner:'Operations', summary:'Creates a local report only; no external side effects.' },
{ id:'TK-10409-E', title:'Update campaign draft copy', gate:'local draft', risk:'soft', owner:'Marketing', summary:'Local draft edit. Nothing is published until separately approved.' }
];
+const queueDir = path.join(process.env.HOME || '/Users/macstudio3', '.claude', 'yolo-queue', 'pending-approval');
+function queueItems(){
+ try {
+ return fs.readdirSync(queueDir).filter(n=>n.endsWith('.md')||n.endsWith('.json')).map(name=>{
+ const raw=fs.readFileSync(path.join(queueDir,name),'utf8');
+ const heading=(raw.match(/^#\s+(.+)$/m)||[])[1] || name.replace(/\.[^.]+$/,'').replace(/[-_]+/g,' ');
+ const gate=(raw.match(/(?:GATE|gate|approval(?: required)?)[^\n:]*[:\-]\s*([^\n]+)/i)||[])[1] || 'gated approval';
+ return {id:'QUEUE-'+name,title:heading.slice(0,180),gate:gate.slice(0,180),risk:'hard',owner:'pending-approval',summary:raw.replace(/\s+/g,' ').slice(0,360),source:name};
+ });
+ } catch { return []; }
+}
+function items(){ const live=queueItems(); return live.length ? live : demoItems; }
function readDecisions(){ try{return fs.readFileSync(decisionsPath,'utf8').trim().split('\n').filter(Boolean).map(JSON.parse);}catch(_){return [];} }
function send(res,status,data,type='application/json'){res.writeHead(status,{'Content-Type':type});res.end(type==='application/json'?JSON.stringify(data):data);}
function body(req){return new Promise(resolve=>{let s='';req.on('data',c=>s+=c);req.on('end',()=>resolve(s));});}
const server=http.createServer(async (req,res)=>{
if(req.method==='GET' && (req.url==='/'||req.url==='/index.html')) return send(res,200,fs.readFileSync(path.join(__dirname,'public/index.html'),'utf8'),'text/html; charset=utf-8');
- if(req.method==='GET' && req.url==='/api/items') return send(res,200,{items,decisions:readDecisions(),writePerformed:false});
+ if(req.method==='GET' && req.url==='/api/items') return send(res,200,{items:items(),decisions:readDecisions(),writePerformed:false,source:queueItems().length?'pending-approval':'demo'});
if(req.method==='POST' && req.url==='/api/decision'){
let data; try{data=JSON.parse(await body(req));}catch(_){return send(res,400,{error:'invalid json'});}
if(!data.id||!['approve','revise','block'].includes(data.action)) return send(res,400,{error:'id and action approve|revise|block required'});
- if(!items.some(i=>i.id===data.id)) return send(res,404,{error:'unknown item'});
+ if(!items().some(i=>i.id===data.id)) return send(res,404,{error:'unknown item'});
const rec={id:data.id,action:data.action,note:String(data.note||'').slice(0,500),at:new Date().toISOString(),writePerformed:false};
fs.appendFileSync(decisionsPath,JSON.stringify(rec)+'\n');
return send(res,200,{ok:true,decision:rec,writePerformed:false});
← f1a5365 create local gated approval viewer
·
back to Approval Viewer
·
fix approval button note lookup c44df22 →