[object Object]

← 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 →