Dashboard: let members see what a nudge actually says before and after sending

A member sent a nudge to #102, then went looking for what had gone out. The
only feedback was a small "Delivered to their dashboard" note, so she checked
her clipboard - which still held the unrelated "Copy plain-English summary"
text describing her OWN position. She reasonably concluded the nudge had sent
her position instead of the recipient's.

It had not: the message log shows #34 -> #102 carrying #102's own numbers.
The feature was right and the feedback was wrong, which is its own kind of
bug - a member who cannot verify what they sent will assume the worst.

Every nudge now has a Preview button that names the recipient and shows the
exact text before sending, and on success the panel shows what was sent and
to whom rather than a bare "Delivered".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-31 09:42:59 -05:00
parent 64527af2ab
commit 34480d444e
3 changed files with 72 additions and 5 deletions
+26 -5
View File
@@ -399,7 +399,18 @@
// One-tap nudges: pre-written message per coach recommendation, sent over
// the wallet-verified Messages rails (and bridged to Telegram if linked).
const nudgeTexts={};
function nudgeBtn(key){return `<br><button class="btn btn-teal btn-sm coach-nudge" data-nkey="${key}" style="margin-top:7px">📨 Send this nudge</button><span class="micro coach-nudge-note" style="display:none;margin-left:8px"></span>`}
// Nudges used to send silently with only a "Delivered" note, so nobody could
// see WHAT went out or to whom. A member went hunting in her clipboard, found
// the unrelated "copy my summary" text still sitting there, and reasonably
// concluded the nudge had sent her own position instead of the recipient's.
// Preview before, and the exact sent text after, removes the guesswork.
function nudgeBtn(key){return `<div class="coach-nudge-wrap" style="margin-top:7px">`+
`<button class="btn btn-teal btn-sm coach-nudge" data-nkey="${key}">📨 Send this nudge</button>`+
`<button class="btn btn-secondary btn-sm coach-nudge-preview" data-nkey="${key}" style="margin-left:6px">👁 Preview</button>`+
`<span class="micro coach-nudge-note" style="display:none;margin-left:8px"></span>`+
`<div class="coach-nudge-prev" style="display:none;margin-top:8px;background:rgba(78,214,203,.06);`+
`border:1px solid rgba(78,214,203,.28);border-radius:10px;padding:10px 12px;font-size:12.5px;`+
`line-height:1.6;color:var(--muted);white-space:pre-wrap"></div></div>`}
function regNudge(id,kind,text){const key=id+':'+kind;nudgeTexts[key]={to:id,text};return nudgeBtn(key)}
function renderCoach(d){
const card=document.getElementById('dCoachCard'),el=document.getElementById('dCoach');
@@ -418,10 +429,20 @@
if(!el.__nudgeBound){
el.__nudgeBound=true;
el.addEventListener('click',async ev=>{
const pv=ev.target.closest&&ev.target.closest('button.coach-nudge-preview');
const btn=ev.target.closest&&ev.target.closest('button.coach-nudge');
if(!btn)return;
const rec=nudgeTexts[btn.dataset.nkey];if(!rec)return;
const note=btn.nextElementSibling;
if(!pv&&!btn)return;
const rec=nudgeTexts[(pv||btn).dataset.nkey];if(!rec)return;
const wrap=(pv||btn).closest('.coach-nudge-wrap');
const note=wrap&&wrap.querySelector('.coach-nudge-note');
const prev=wrap&&wrap.querySelector('.coach-nudge-prev');
const showPrev=(label)=>{if(!prev)return;prev.textContent=label+'\n\n'+rec.text;prev.style.display='';};
// Preview: show exactly what will be sent, and to whom, before sending.
if(pv){
if(prev&&prev.style.display!=='none'){prev.style.display='none';pv.textContent='\u{1F441} Preview';}
else{showPrev('This will be sent to #'+rec.to+':');pv.textContent='\u{1F441} Hide preview';}
return;
}
const say=(t,color)=>{if(note){note.style.display='';note.style.color=color||'var(--muted)';note.textContent=t;}};
btn.disabled=true;const old=btn.textContent;btn.textContent='Sending…';
try{
@@ -434,7 +455,7 @@
}
const r=await fetch('/api/public/msg-send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({toId:rec.to,body:rec.text})});
const dd=await r.json().catch(()=>({}));
if(r.ok&&!dd.error){btn.textContent='Sent ✓';say('Delivered to their dashboard — and straight to their Telegram if they’ve linked it.');}
if(r.ok&&!dd.error){btn.textContent='Sent ✓';showPrev('✅ Sent to #'+rec.to+' — this is exactly what they received:');const pb=wrap&&wrap.querySelector('.coach-nudge-preview');if(pb)pb.style.display='none';say('Delivered to #'+rec.to+'’s dashboard — and straight to their Telegram if they’ve linked it.');}
else{btn.textContent=old;btn.disabled=false;say(dd.error||'Could not send — try again.','var(--danger)');}
}catch(e){btn.textContent=old;btn.disabled=false;say('Network hiccup — try again.','var(--danger)');}
});