[object Object]

← back to Norma

IG captions: show name+color+SKU (DW model), remove http link from autoCaption

285fb064e9f066c5ea52ef3429c288c37306a9d8 · 2026-08-14 09:34:46 -0700 · Steve

Files touched

Diff

commit 285fb064e9f066c5ea52ef3429c288c37306a9d8
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri Aug 14 09:34:46 2026 -0700

    IG captions: show name+color+SKU (DW model), remove http link from autoCaption
---
 agents/instagram-agent/content.js | 85 +++++++++++++++++++++++++++++++++------
 1 file changed, 73 insertions(+), 12 deletions(-)

diff --git a/agents/instagram-agent/content.js b/agents/instagram-agent/content.js
index bb791b0..0e262ac 100644
--- a/agents/instagram-agent/content.js
+++ b/agents/instagram-agent/content.js
@@ -30,25 +30,86 @@ const SERVICE_LINES = [
   'Designer-grade quality, ready to specify. Samples to your studio and dedicated service from the Designer Wallcoverings trade team.',
   'Beautifully made and built to last. Designer Wallcoverings brings the sampling, specification, and service serious projects demand.',
 ];
-function autoCaption(p, url) {
-  const title = String(p.title || 'Designer Wallcovering')
+
+const titleCase = (s) => String(s || '')
+  .toLowerCase()
+  .replace(/\b([a-z])/g, (m) => m.toUpperCase())
+  .replace(/\b(And|Of|On|The)\b/g, (m) => m.toLowerCase())
+  .replace(/^([a-z])/, (m) => m.toUpperCase())
+  .trim();
+
+// The clean product NAME: the descriptive title, minus the vendor segment (private-label
+// safe) and minus any embedded mfr SKU digits — color + SKU are shown on their own lines.
+function productName(p) {
+  return String(p.title || 'Designer Wallcovering')
     .split('|')[0]
-    .replace(/\b\d{5,}\b/g, '')      // strip internal mfr SKU numbers (e.g. Koroseal "2308790")
+    .replace(/\b\d{5,}\b/g, '')       // strip internal mfr SKU numbers (e.g. Koroseal "2308790")
     .replace(/\s{2,}/g, ' ')
     .replace(/\s+-\s*$/, '')
     .trim() || 'Designer Wallcovering';
+}
+
+// COLORWAY. Steve's rule: always show the color. Preference order:
+//   1) a tag that reads as a pure colorway phrase ("Metallic Gold & Black", "Warm Pewter"),
+//   2) the color that follows " - " in the title ("... - Navy Blue & Metallic Gold"),
+//   3) up to two atomic color tags joined ("Gold & Black").
+const COLOR = /^(white|black|gray|grey|gold|silver|bronze|pewter|charcoal|navy|blue|green|red|pink|purple|mauve|sage|cream|beige|taupe|tan|brown|ivory|olive|teal|aqua|turquoise|copper|brass|platinum|champagne|blush|rose|burgundy|maroon|rust|terracotta|mustard|yellow|orange|coral|lavender|lilac|plum|emerald|sand|stone|slate|smoke|umber|sienna|ochre|oyster|pearl|clay|cinder|salt|baltic|truffle|goldsand|ebony)$/i;
+const GENERIC = /^(natural|multi|neutral|assorted|various)$/i;   // valid colors, but only used as a last resort
+const CONNECT = /^(and|&|on|light|dark|deep|warm|cool|pale|soft|muted|bright|antique|aged|vintage|metallic|shiny|matte|two|tone)$/i;
+// Material / pattern words to strip out of a title-derived colorway so only the color remains.
+const NOISE = /\b(wallcovering|wallpaper|fabric|residential|commercial|damask|geometric|texture|textured|type\s*\d|luxury|suede|sueded|silk|vinyl|grasscloth|linen|cork|mylar|leaf|modern|mid-century|transitional|traditional|contemporary|print|mural|weave|woven|faux|solid)\b/ig;
+function extractColor(p) {
+  const tags = String(p.tags || '').split(',').map((s) => s.trim()).filter(Boolean);
+  const isColorWord = (w) => COLOR.test(w) || GENERIC.test(w) || CONNECT.test(w);
+  const hasRealColor = (s) => s.split(/[\s/&]+/).filter(Boolean).some((w) => COLOR.test(w));
+
+  // 1) TITLE colorway — the authoritative name: text after the last " - ", material words stripped.
+  const seg = String(p.title || '').split('|')[0].split(' - ');
+  if (seg.length > 1) {
+    const c = seg[seg.length - 1].replace(NOISE, '').replace(/\s{2,}/g, ' ').trim();
+    if (c && hasRealColor(c)) return titleCase(c);
+  }
+  // 2) colorway phrase tag — every word a color/connector, at least one REAL (non-generic) color. Longest wins.
+  const phrase = tags
+    .filter((t) => {
+      const words = t.split(/[\s/&]+/).filter(Boolean);
+      return words.some((w) => COLOR.test(w)) && words.every(isColorWord);
+    })
+    .sort((a, b) => b.length - a.length)[0];
+  if (phrase) return titleCase(phrase);
+  // 3) atomic color tags
+  const atoms = tags.filter((t) => COLOR.test(t.replace(/[^a-z]/ig, ''))).slice(0, 2);
+  if (atoms.length) return titleCase(atoms.join(' & '));
+  // 4) last resort — a generic descriptor tag so the Color line is never empty
+  const generic = tags.find((t) => GENERIC.test(t.replace(/[^a-z]/ig, '')));
+  return generic ? titleCase(generic) : '';
+}
+
+// DW MODEL / SKU. Prefer a non-sample variant SKU; strip the sample/size suffix so the
+// customer sees the clean model number (e.g. "GRD-87018-sample" -> "GRD-87018").
+function extractSku(p) {
+  const skus = (p.variants || []).map((v) => String(v.sku || '').trim()).filter(Boolean);
+  let sku = skus.find((s) => !/sample|memo|swatch/i.test(s)) || skus[0] || '';
+  // Strip any trailing size/unit descriptor (hyphen OR space separated): "-sample", "-PER YARD", " - Roll", etc.
+  sku = sku.replace(/[\s-]+(sample|memo|swatch|per[\s-]*roll|per[\s-]*yard|roll|yard|yd)\s*$/i, '').trim();
+  return sku ? sku.toUpperCase() : '';
+}
+
+// Compose the caption. NO http link anywhere (Steve, 2026-08-14). Always: Name, Color, SKU.
+function autoCaption(p /*, url — intentionally unused: no links in captions */) {
+  const name = productName(p);
+  const color = extractColor(p);
+  const sku = extractSku(p);
   const type = (p.product_type || 'wallcovering').toLowerCase();
   const tag = type.replace(/[^a-z0-9]/g, '');
   const tagLine = tag && tag !== 'wallcovering' ? ` #${tag}` : '';
-  const idx = [...title].reduce((a, c) => a + c.charCodeAt(0), 0) % SERVICE_LINES.length;
-  return [
-    title,
-    '',
-    SERVICE_LINES[idx],
-    '',
-    `Explore: ${url}`,
-    `#wallpaper #interiordesign #wallcovering${tagLine} #tothetrade #designerwallcoverings`,
-  ].join('\n');
+  const idx = [...name].reduce((a, c) => a + c.charCodeAt(0), 0) % SERVICE_LINES.length;
+  const lines = [name];
+  if (color) lines.push(`Color: ${color}`);
+  if (sku) lines.push(`Model / SKU: ${sku}`);
+  lines.push('', SERVICE_LINES[idx], '',
+    `#wallpaper #interiordesign #wallcovering${tagLine} #tothetrade #designerwallcoverings`);
+  return lines.join('\n');
 }
 
 // Hidden upstream-vendor tokens that must NEVER be customer-facing. A public IG image

← f9e68db auto-data-snapshot: 2026-08-14T08:19:45 (1 data files) — age  ·  back to Norma  ·  IG redo: caption polish (quote-strip colors, broaden SKU suf 6e37304 →