[object Object]

← back to Dw Marketing Reels

Fix publish-status unwrap: read Norma's .result envelope + explicit r.simulated (kills false 'posted' on no-creds + false 'simulated' on live post) + no-creds badge — contrarian gate

d02c9bb0a94ed092f9612bac938381cd9f2d7ce5 · 2026-07-13 10:29:15 -0700 · Steve Abrams

Files touched

Diff

commit d02c9bb0a94ed092f9612bac938381cd9f2d7ce5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 13 10:29:15 2026 -0700

    Fix publish-status unwrap: read Norma's .result envelope + explicit r.simulated (kills false 'posted' on no-creds + false 'simulated' on live post) + no-creds badge — contrarian gate
---
 public/index.html          |  1 +
 scripts/publish-social.mjs | 12 ++++++++++--
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/public/index.html b/public/index.html
index 606d9da..e0dd198 100644
--- a/public/index.html
+++ b/public/index.html
@@ -100,6 +100,7 @@ const escapeHtml = s => String(s||'').replace(/[&<>]/g,c=>({'&':'&amp;','<':'&lt
 const escapeAttr = s => String(s||'').replace(/"/g,'&quot;').replace(/\n/g,'&#10;');
 const STMAP = { posted:['✓ posted','ok'], simulated:['◐ simulated','sim'], 'pending-creds':['◷ needs creds','wait'],
   'pending-reconnect':['◷ reconnect','wait'], 'ready-pending-arm':['🔒 armed?','sim'], 'pending-review':['👁 review','sim'],
+  'no-creds-for-account':['⚠ no acct creds','err'],
   pending:['◷ pending','wait'], error:['✗ error','err'], skipped:['– skipped','wait'] };
 function pubBadges(pub){
   if(!pub) return '';
diff --git a/scripts/publish-social.mjs b/scripts/publish-social.mjs
index 84ffb7e..645b858 100644
--- a/scripts/publish-social.mjs
+++ b/scripts/publish-social.mjs
@@ -67,8 +67,16 @@ async function postInstagram(reel) {
     const txt = await res.text().catch(() => '');
     let body = {}; try { body = JSON.parse(txt); } catch {}
     if (!res.ok) return { status: res.status === 401 ? 'pending-creds' : 'error', account: acc.id, http: res.status, at: now() };
-    const simulated = body.simulated || /simulat/i.test(txt);
-    return { status: simulated ? 'simulated' : 'posted', account: acc.id, postId: body.id || body.postId || null, at: now() };
+    // Norma wraps the skill output as { success, skill, result } — the real status/ids
+    // live on .result, NOT the envelope. Reading the envelope reported a false 'posted'
+    // for a no-creds account (contrarian-caught). Unwrap, then honor the skill's verdict.
+    const r = body.result || body;
+    if (r.status === 'no-creds-for-account') return { status: 'no-creds-for-account', account: acc.id, note: r.note, at: now() };
+    // r.simulated is an explicit boolean from Norma's reel skill — read it directly.
+    // (The old /simulat/i text-regex falsely matched `"simulated":false`, mislabeling
+    // a real live post as 'simulated' — contrarian-adjacent bug, fixed with the unwrap.)
+    const simulated = r.simulated === true;
+    return { status: simulated ? 'simulated' : 'posted', account: acc.id, postId: r.media_id || r.id || r.postId || null, at: now() };
   } catch (e) { return { status: 'error', account: acc.id, note: e.message, at: now() }; }
 }
 

← fea25bd chore: v0.3.0 — multi-account IG picker + meta-token canary  ·  back to Dw Marketing Reels  ·  Fix console port default 9846->9848 (matches .deploy.conf + 66cb716 →