← back to Resume Queue Viewer
Add Done/Reopen toggle on parking items — writes discussed flag back to CNCP
6f6a34ae5b36de98d26e6b7d40f38e9888bc5e68 · 2026-05-18 17:34:11 -0700 · SteveStudio2
Files touched
Diff
commit 6f6a34ae5b36de98d26e6b7d40f38e9888bc5e68
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Mon May 18 17:34:11 2026 -0700
Add Done/Reopen toggle on parking items — writes discussed flag back to CNCP
---
server.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 63 insertions(+), 5 deletions(-)
diff --git a/server.js b/server.js
index cba312d..881d1e2 100644
--- a/server.js
+++ b/server.js
@@ -21,11 +21,13 @@ function readJSON(name) {
function buildQueue() {
const parking = (readJSON('cncp-parking-lot.json') || []).map(p => ({
type: 'parking',
+ id: p.id || '',
title: p.id || 'untitled',
body: p.note || '',
date: p.saved ? new Date(p.saved).toISOString().slice(0, 10) : '',
ts: p.saved || 0,
badge: p.discussed ? 'discussed' : 'open',
+ discussed: !!p.discussed,
link: p.url || '',
}));
@@ -114,6 +116,22 @@ function runItem(item) {
});
}
+// Toggle the `discussed` flag on a parking-lot entry, written straight back
+// to the live CNCP store. Returns {ok,message}.
+function setDiscussed(id, discussed) {
+ const file = path.join(CNCP, 'cncp-parking-lot.json');
+ let list;
+ try { list = JSON.parse(fs.readFileSync(file, 'utf8')); }
+ catch (e) { return { ok: false, message: 'cannot read parking lot: ' + e.message }; }
+ if (!Array.isArray(list)) return { ok: false, message: 'parking lot is not an array' };
+ const entry = list.find(p => p.id === id);
+ if (!entry) return { ok: false, message: 'no parking-lot entry "' + id + '"' };
+ entry.discussed = !!discussed;
+ try { fs.writeFileSync(file, JSON.stringify(list, null, 2) + '\n', 'utf8'); }
+ catch (e) { return { ok: false, message: 'write failed: ' + e.message }; }
+ return { ok: true, message: (discussed ? 'Marked done: ' : 'Reopened: ') + id };
+}
+
const PAGE = `<!doctype html>
<html lang="en">
<head>
@@ -167,6 +185,10 @@ const PAGE = `<!doctype html>
padding:7px 14px; font-size:12.5px; font-weight:600; cursor:pointer; }
button.run:hover { filter:brightness(1.12); }
button.run:disabled { opacity:.55; cursor:progress; }
+ button.done { background:transparent; color:var(--dim); border:1px solid var(--line);
+ border-radius:7px; padding:7px 12px; font-size:12.5px; cursor:pointer; }
+ button.done:hover { color:var(--txt); border-color:var(--dim); }
+ button.done:disabled { opacity:.55; cursor:progress; }
#toast { position:fixed; left:50%; bottom:26px; transform:translateX(-50%) translateY(20px);
background:#1f2230; border:1px solid var(--line); color:var(--txt);
padding:11px 18px; border-radius:9px; font-size:13px; opacity:0;
@@ -238,9 +260,11 @@ function render() {
const proj = i.project ? esc(i.project) : '';
const runnable = i.type==='parking' || i.type==='recap';
const runLabel = i.type==='recap' ? 'Resume ▸' : 'Run ▸';
- const actions = runnable
- ? '<div class="actions"><button class="run" data-idx="'+idx+'">'+runLabel+'</button></div>'
- : '';
+ let actionBtns = '';
+ if (runnable) actionBtns += '<button class="run" data-idx="'+idx+'">'+runLabel+'</button>';
+ if (i.type==='parking')
+ actionBtns += '<button class="done" data-idx="'+idx+'">'+(i.discussed?'↩ Reopen':'✓ Done')+'</button>';
+ const actions = actionBtns ? '<div class="actions">'+actionBtns+'</div>' : '';
return '<div class="card">'
+ '<div class="top"><span class="tag '+TAGCLASS[i.type]+'">'+i.type+'</span>'
+ '<span class="badge '+(i.badge==='open'?'open':i.badge==='done'?'done':'')+'">'+esc(i.badge)+'</span></div>'
@@ -283,6 +307,22 @@ async function runItem(idx, btn) {
}
}
+async function markDone(idx, btn) {
+ const item = (window.LASTITEMS || [])[idx];
+ if (!item || item.type !== 'parking') return;
+ btn.disabled = true;
+ try {
+ const r = await fetch('/api/discussed', {
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ id: item.id, discussed: !item.discussed }),
+ });
+ const j = await r.json();
+ toast(j.message || (j.ok ? 'updated' : 'failed'), !j.ok);
+ if (j.ok) return load();
+ } catch (e) { toast('request failed: ' + e.message, true); }
+ btn.disabled = false;
+}
+
function setFilter(f){ filter=f; localStorage.setItem('rq.filter',f);
document.querySelectorAll('.bar button').forEach(b=>b.classList.toggle('on',b.dataset.f===f));
render(); }
@@ -302,8 +342,10 @@ document.getElementById('sort').onchange = e=>{ sort=e.target.value; localStorag
document.getElementById('dens').oninput = e=>{ dens=+e.target.value; localStorage.setItem('rq.dens',dens); render(); };
document.getElementById('grid').addEventListener('click', e => {
- const btn = e.target.closest('button.run');
- if (btn) runItem(+btn.dataset.idx, btn);
+ const run = e.target.closest('button.run');
+ if (run) return runItem(+run.dataset.idx, run);
+ const done = e.target.closest('button.done');
+ if (done) return markDone(+done.dataset.idx, done);
});
document.getElementById('q').addEventListener('input', e => { query = e.target.value; render(); });
@@ -338,6 +380,22 @@ const server = http.createServer((req, res) => {
});
return;
}
+ if (req.url === '/api/discussed' && req.method === 'POST') {
+ let body = '';
+ req.on('data', c => { body += c; if (body.length > 1e5) req.destroy(); });
+ req.on('end', () => {
+ let p;
+ try { p = JSON.parse(body || '{}'); }
+ catch (e) { res.writeHead(400, { 'Content-Type': 'application/json' });
+ return res.end(JSON.stringify({ ok: false, message: 'bad JSON' })); }
+ if (!p || !p.id) { res.writeHead(400, { 'Content-Type': 'application/json' });
+ return res.end(JSON.stringify({ ok: false, message: 'missing id' })); }
+ const result = setDiscussed(p.id, !!p.discussed);
+ res.writeHead(result.ok ? 200 : 500, { 'Content-Type': 'application/json' });
+ res.end(JSON.stringify(result));
+ });
+ return;
+ }
if (req.url === '/healthz') { res.writeHead(200); res.end('ok'); return; }
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(PAGE);
← 39b9848 Add search box + live per-tab counts to the queue viewer
·
back to Resume Queue Viewer
·
Refactor: extract readBody + sendJSON helpers, DRY out POST 620dc5f →