[object Object]

← back to Marketing Command Center

Show last 6 posts per owned account in the #vendors Owned·DW Fleet section

c12730bbc565f60eb94c9b2f4f88231d9e69c390 · 2026-08-25 10:14:13 -0700 · Steve

The owned-fleet section only showed name + link. Now each of the 35 owned accounts
renders its last 6 Instagram posts inline (thumb + title + date · type), sourced from
the fleet post ledger (/api/ig-activity/posts — same data as the IG Activity panel),
reusing the existing .ig-li styling the external-vendor rows use. Best-effort: rows
still render if the ledger is unreachable; the 6 no-creds accounts show 'No posts yet'.

Verified: 35 blocks, 170 post rows, 6 empty-states, thumbnails + styling applied, no
page errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit c12730bbc565f60eb94c9b2f4f88231d9e69c390
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Aug 25 10:14:13 2026 -0700

    Show last 6 posts per owned account in the #vendors Owned·DW Fleet section
    
    The owned-fleet section only showed name + link. Now each of the 35 owned accounts
    renders its last 6 Instagram posts inline (thumb + title + date · type), sourced from
    the fleet post ledger (/api/ig-activity/posts — same data as the IG Activity panel),
    reusing the existing .ig-li styling the external-vendor rows use. Best-effort: rows
    still render if the ledger is unreachable; the 6 no-creds accounts show 'No posts yet'.
    
    Verified: 35 blocks, 170 post rows, 6 empty-states, thumbnails + styling applied, no
    page errors.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public/panels/vendors.js | 40 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/public/panels/vendors.js b/public/panels/vendors.js
index 4b7dd5a..aee619f 100644
--- a/public/panels/vendors.js
+++ b/public/panels/vendors.js
@@ -18,8 +18,11 @@ window.MCC_PANELS['vendors'] = {
     setStats();
 
     // Owned · DW Fleet — all 35 DW-OWNED Instagram accounts from the canonical
-    // source (window.MCC_ACCOUNTS). Rendered as a native vend-block list, clearly
-    // separated ABOVE the external vendor roster. Additive; never touches vendors.
+    // source (window.MCC_ACCOUNTS), each showing its LAST 6 posts inline (thumb +
+    // title + date · type), mirroring the external-vendor rows. The owned posts come
+    // from the fleet post ledger (/api/ig-activity/posts — the same data the IG
+    // Activity panel renders), NOT the vendor Business-Discovery cache. Additive;
+    // never touches the external vendor roster.
     (async () => {
       const host = root.querySelector('#vend-owned-rows');
       if (!host) return;
@@ -27,14 +30,47 @@ window.MCC_PANELS['vendors'] = {
       try { owned = (window.MCC_ACCOUNTS && await window.MCC_ACCOUNTS.load()) || []; }
       catch { host.innerHTML = '<div class="muted">Failed to load owned accounts.</div>'; return; }
       if (!owned.length) { host.innerHTML = '<div class="muted">No owned accounts found.</div>'; return; }
+
+      // Group the fleet ledger by handle → last 6 each (newest first). Best-effort:
+      // if the ledger is unreachable, the rows still render (just without posts).
+      const byHandle = {};
+      try {
+        const j = await (await fetch(location.origin + '/api/ig-activity/posts', { credentials: 'same-origin' })).json();
+        for (const p of (j.posts || [])) { (byHandle[p.handle] = byHandle[p.handle] || []).push(p); }
+        for (const k of Object.keys(byHandle)) byHandle[k].sort((a, b) => new Date(b.ts) - new Date(a.ts));
+      } catch { /* ledger unreachable — rows render without posts */ }
+
+      const ownedPosts = (handle) => {
+        const list = (byHandle[handle] || []).slice(0, 6);
+        if (!list.length) return '<div class="muted" style="font-size:11px;padding:2px 2px 4px">No posts yet.</div>';
+        const items = list.map(p => {
+          const cap = esc((p.product_title || '').replace(/\s+/g, ' ').slice(0, 120)) || '(no title)';
+          const when = p.ts ? String(p.ts).slice(0, 10) : '';
+          const kind = esc(p.kind || 'IMAGE');
+          const thumb = p.image_url
+            ? `<img class="ig-li-thumb" loading="lazy" src="${esc(p.image_url)}" alt="" onerror="this.style.visibility='hidden'">`
+            : `<span class="ig-li-thumb"></span>`;
+          return `<a class="ig-li" href="${esc(p.permalink || '#')}" target="_blank" rel="noopener noreferrer">
+            ${thumb}
+            <span class="ig-li-body">
+              <span class="ig-li-cap">${cap} <span class="ig-li-ext">↗</span></span>
+              <span class="ig-li-meta">${when ? when + '  ·  ' : ''}${kind}</span>
+            </span></a>`;
+        }).join('');
+        return `<div class="ig-list">${items}</div>`;
+      };
+
       const rows = [...owned].sort((a, b) => String(a.name || a.handle).localeCompare(String(b.name || b.handle)));
       host.innerHTML = rows.map(a => {
         const h = esc(a.handle);
+        const n = (byHandle[a.handle] || []).length;
         return `<div class="vend-block" style="border-bottom:1px solid var(--line);padding:10px 2px;background:#faf6ee">
           <div class="row" style="justify-content:space-between;align-items:center">
             <div style="min-width:200px"><b>${esc(a.name || a.handle)}</b> <span class="pill">OURS</span></div>
             <div style="flex:1"><a class="lnk" href="https://www.instagram.com/${h}/" target="_blank" rel="noopener noreferrer">@${h} ↗</a></div>
+            <div style="min-width:70px;text-align:right;font-variant-numeric:tabular-nums" title="posts in the fleet ledger">${n ? n + ' posts' : ''}</div>
           </div>
+          ${ownedPosts(a.handle)}
         </div>`;
       }).join('');
     })();

← 37ff9a8 mcc: credential-safe fetch guard in shell (inoculate root-re  ·  back to Marketing Command Center  ·  Add "Make it ours" DW-original creative system to #vendors a 6b6da55 →