[object Object]

← back to Wallco Ai

replace-bg: accept any of the 5k library textures, not just the 10 curated bg_textures rows

774fe3053ed81d06431a409e43f7a6c273ee8369 · 2026-05-26 15:18:42 -0700 · Steve Abrams

Picker passes texture_image_url+name; endpoint falls back to the ad-hoc image path when slug isn't a curated bg_textures row.

Files touched

Diff

commit 774fe3053ed81d06431a409e43f7a6c273ee8369
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 26 15:18:42 2026 -0700

    replace-bg: accept any of the 5k library textures, not just the 10 curated bg_textures rows
    
    Picker passes texture_image_url+name; endpoint falls back to the ad-hoc image path when slug isn't a curated bg_textures row.
---
 server.js | 46 +++++++++++++++++++++++++++++++++++-----------
 1 file changed, 35 insertions(+), 11 deletions(-)

diff --git a/server.js b/server.js
index d137de4..429c113 100644
--- a/server.js
+++ b/server.js
@@ -8908,17 +8908,24 @@ app.post('/api/design/:id/replace-bg', express.json({ limit: '8kb' }), async (re
   const ad_hoc_name = String(req.body?.texture_name || 'custom').trim();
   if (!slug && !ad_hoc_url) return res.status(400).json({ error: 'texture_slug or texture_image_url required' });
   try {
-    // Resolve texture: either DB slug OR ad-hoc URL (e.g., DW product image)
-    let tex;
-    if (ad_hoc_url) {
-      tex = { slug: 'adhoc', name: ad_hoc_name, material: 'natural-wallcovering',
+    // Resolve texture. The picker (/api/bg-textures) unifies THREE sources —
+    // the curated bg_textures table PLUS wallco_textures + shopify_products —
+    // but only the ~10 bg_textures rows have a slug THIS endpoint can look up.
+    // So: try the curated slug first (best — it carries a hand-written
+    // description); otherwise fall back to the texture_image_url the picker now
+    // sends for every library item (ad-hoc path — works for any of the 5k+
+    // textures, not just the 10 curated ones).
+    let tex = null;
+    if (slug) {
+      const traw = psqlQuery(`SELECT row_to_json(t) FROM (SELECT slug, name, material, hex, description, image_url FROM bg_textures WHERE slug=${"'" + slug.replace(/'/g,"''") + "'"} LIMIT 1) t;`);
+      if (traw && traw.length > 2) tex = JSON.parse(traw);
+    }
+    if (!tex && ad_hoc_url) {
+      tex = { slug: slug || 'adhoc', name: ad_hoc_name, material: 'natural-wallcovering',
               hex: null, description: `Use the supplied texture image AS-IS as the background. Keep its natural fiber/weave look visible.`,
               image_url: ad_hoc_url };
-    } else {
-      const traw = psqlQuery(`SELECT row_to_json(t) FROM (SELECT slug, name, material, hex, description, image_url FROM bg_textures WHERE slug=${"'" + slug.replace(/'/g,"''") + "'"} LIMIT 1) t;`);
-      if (!traw || traw.length < 3) return res.status(404).json({ error: 'texture not found: ' + slug });
-      tex = JSON.parse(traw);
     }
+    if (!tex) return res.status(404).json({ error: 'texture not found: ' + (slug || ad_hoc_url) });
     // Lookup design
     let d = null;
     try {
@@ -13415,7 +13422,7 @@ ${(() => {
             if (readout) readout.textContent = 'Applying ' + tex.name + '… (~10–20s)';
             fetch('/api/design/' + DESIGN_ID + '/replace-bg', {
               method:'POST', headers:{'Content-Type':'application/json'},
-              body: JSON.stringify({ texture_slug: tex.slug })
+              body: JSON.stringify({ texture_slug: tex.slug, texture_image_url: tex.image_url, texture_name: tex.name })
             }).then(function(r){
               if (!r.ok) return r.text().then(function(t){ throw new Error(r.status+': '+t.slice(0,160)); });
               return r.json();
@@ -19694,6 +19701,16 @@ const RECOLOR_HUES = {
   charcoal:{ label: 'Charcoal', tail: 'pale grey ground; deep charcoal motif; tonal monochrome' },
 };
 
+// Representative mid-tone hex per hue family. A recolor variant has no measured
+// dominant_hex at insert time, so hue-mode variants borrow this so titleFor()
+// (→ hueName) names the right colour instead of falling back to 'Bone'.
+const HUE_REP_HEX = {
+  red:'#B23A3A', orange:'#C2662E', yellow:'#C9A227', lime:'#8FAE3A',
+  green:'#4E7A4E', teal:'#2E7D7D', cyan:'#3AA6B0', blue:'#3A5FAE',
+  indigo:'#4B3FA0', violet:'#7A4FA0', magenta:'#A03A7A', pink:'#C97A8C',
+  neutral:'#9A8C78', charcoal:'#3A3A3A',
+};
+
 app.post('/api/design/:id/recolor', express.json(), async (req, res) => {
   if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
   try {
@@ -19718,6 +19735,12 @@ app.post('/api/design/:id/recolor', express.json(), async (req, res) => {
     }
     const targetKey   = hex ? hex.slice(1).toLowerCase() : (brandKey || hueKey);
     const targetLabel = hex ? hex : (brand ? brand.label : hue.label);
+    // Dominant hex for the new variant row — drives the title colour word via
+    // titleFor -> hueName. Without it the title falls back to Bone.
+    const domHex = hex
+      || (brand && Array.isArray(brand.hex) && brand.hex.length ? brand.hex[0] : null)
+      || HUE_REP_HEX[hueKey]
+      || null;
 
     // Load source image bytes from local_path or by reading the file behind image_url
     const d = DESIGNS.find(x => x.id === id);
@@ -19872,7 +19895,7 @@ app.post('/api/design/:id/recolor', express.json(), async (req, res) => {
       (kind, width_in, height_in, generator, prompt, seed, dominant_hex,
        local_path, image_url, category, motifs, tags, is_published, request_text)
       VALUES ('seamless_tile', 24, 24, 'gemini-2.5-flash-image-edit',
-        '${promptDesc}', ${seed}, NULL,
+        '${promptDesc}', ${seed}, ${domHex ? "'" + domHex + "'" : 'NULL'},
         '${outPath.replace(/'/g, "''")}', '/designs/img/${filename}',
         ${"'" + (d.category || 'recolor') + "'"},
         ${motifsSql}, ${tagSql}, TRUE,
@@ -19886,7 +19909,8 @@ app.post('/api/design/:id/recolor', express.json(), async (req, res) => {
       id: newId,
       kind: 'seamless_tile',
       category: d.category || 'recolor',
-      title: newTitle,
+      dominant_hex: domHex || undefined,
+      title: domHex ? titleFor(d.category || 'recolor', domHex, newId) : newTitle,
       handle: 'wallco-' + newId,
       image_url: '/designs/img/' + filename,
       filename,

← bb2d819 docs: correct CLAUDE.md — designs.json IS deployed, not excl  ·  back to Wallco Ai  ·  wallco recolor: set dominant_hex on variants so title color d2af6bb →