[object Object]

← back to Approvals Viewer

approvals-viewer UI: always show action buttons + fetch error/retry so it never hangs on Loading

f3b563d47b6bc8d98f49bd68e9b9d201face3536 · 2026-08-11 12:59:54 -0700 · Steve Abrams

Files touched

Diff

commit f3b563d47b6bc8d98f49bd68e9b9d201face3536
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Aug 11 12:59:54 2026 -0700

    approvals-viewer UI: always show action buttons + fetch error/retry so it never hangs on Loading
---
 public/index.html | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/public/index.html b/public/index.html
index 2199886..e684c7e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -33,7 +33,7 @@ button{border:0;border-radius:12px;padding:14px 22px;font-size:16px;font-weight:
  <span class=tally id=tally></span>
 </header>
 <main id=main><div class=card><div class=body>Loading…</div></div></main>
-<div class=actions id=actions style=display:none>
+<div class=actions id=actions style=display:flex>
  <button class=b-undo id=undo title="Undo last (U)">↶ Undo</button>
  <button class=b-no  onclick="decide('reject')">❌ Reject <span class=kbd>←</span></button>
  <button class=b-sk  onclick="decide('defer')">⏭ Defer <span class=kbd>↓</span></button>
@@ -72,7 +72,7 @@ function render(){
  m.querySelector('.body').scrollTop=0;
 }
 async function decide(decision){
- const x=memos[i];tally[decision]++;lastAction={file:x.file,decision,idx:i};
+ const x=memos[i];if(!x)return;tally[decision]++;lastAction={file:x.file,decision,idx:i};
  if(decision!=='defer'){try{await fetch('/api/decide',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({file:x.file,decision})});}catch(e){}}
  i++;render();
 }
@@ -89,5 +89,9 @@ addEventListener('keydown',e=>{
  else if(e.key==='ArrowDown'||e.key===' '||e.key==='s'){e.preventDefault();decide('defer');}
  else if(e.key==='u')undo();
 });
-fetch('/api/memos').then(r=>r.json()).then(d=>{memos=d;total=d.length;render();});
+function load(){fetch('/api/memos').then(r=>{if(!r.ok)throw new Error('HTTP '+r.status);return r.json();})
+ .then(d=>{memos=d;total=d.length;i=0;render();})
+ .catch(e=>{document.getElementById('actions').style.display='none';
+   document.getElementById('main').innerHTML='<div class=done><h2>⚠️ Could not load memos</h2><p style=color:#8a8f9a>'+e.message+'</p><p><button onclick="load()">↻ Retry</button></p></div>';});}
+load();
 </script></body></html>

← 06062c8 approvals-viewer: re-enable Basic auth + pin default port 97  ·  back to Approvals Viewer  ·  chore: harden /api/undo (path-traversal guard + decision whi a188fef →