[object Object]

← back to Wallco Ai

add: 'Ghosting' button on /design/:id admin rating panel — prompts to either Update SKU (regenerate image in-place with anti-ghosting prompt addendum, DIG number + URL preserved) or Delete (hard-delete + log fingerprint to ghosting-do-not-want.jsonl + queue fresh regen). New /api/design/:id/ghosting endpoint, admin-gated

07d3dc2cc1182fad9f893b1a0061a07c74fc7209 · 2026-05-20 10:49:46 -0700 · Steve Abrams

Files touched

Diff

commit 07d3dc2cc1182fad9f893b1a0061a07c74fc7209
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 20 10:49:46 2026 -0700

    add: 'Ghosting' button on /design/:id admin rating panel — prompts to either Update SKU (regenerate image in-place with anti-ghosting prompt addendum, DIG number + URL preserved) or Delete (hard-delete + log fingerprint to ghosting-do-not-want.jsonl + queue fresh regen). New /api/design/:id/ghosting endpoint, admin-gated
---
 server.js | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 151 insertions(+)

diff --git a/server.js b/server.js
index d0597d4..29b5283 100644
--- a/server.js
+++ b/server.js
@@ -6577,6 +6577,127 @@ app.post('/api/design/:id/settlement-violation', express.json({ limit: '16kb' })
   }
 });
 
+// POST /api/design/:id/ghosting — handle a ghosting report on a design.
+// Body: { action: 'update-sku' | 'delete' }
+//   update-sku → keep the same DB row + DIG number, regenerate the IMAGE with
+//                an anti-ghosting addendum on the prompt. URL stays valid;
+//                old file is backed up before overwrite.
+//   delete     → HARD delete + log fingerprint to ghosting do-not-want registry
+//                (same pattern as /settlement-violation).
+app.post('/api/design/:id/ghosting', express.json({ limit: '16kb' }), (req, res) => {
+  if (!adminRatingGate(req, res)) return;
+  const id = parseInt(req.params.id, 10);
+  if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
+  const action = String(req.body?.action || '').toLowerCase();
+  if (!['update-sku', 'delete'].includes(action)) return res.status(400).json({ error: "action must be 'update-sku' or 'delete'" });
+
+  try {
+    const raw = psqlQuery(`SELECT row_to_json(t) FROM (SELECT id, prompt, category,
+      motifs, tags, generator, dominant_hex, local_path, parent_design_id, dig_number
+      FROM spoon_all_designs WHERE id=${id}) t;`);
+    if (!raw) return res.status(404).json({ error: 'design not found' });
+    const d = JSON.parse(raw);
+
+    // Backup file forensically before we touch anything
+    const backupDir = path.join(require('os').homedir(), '.wallco-deleted', new Date().toISOString().slice(0,10));
+    try { fs.mkdirSync(backupDir, { recursive: true }); } catch {}
+    let backedUp = null;
+    if (d.local_path && fs.existsSync(d.local_path)) {
+      const bn = `${id}_ghosting_${path.basename(d.local_path)}`;
+      backedUp = path.join(backupDir, bn);
+      try { fs.copyFileSync(d.local_path, backedUp); } catch {}
+    }
+
+    // Append ghosting fingerprint to do-not-want registry (separate file from settlement)
+    const auditDir = path.join(__dirname, 'data', 'settlement-audit');
+    try { fs.mkdirSync(auditDir, { recursive: true }); } catch {}
+    const registryPath = path.join(auditDir, 'ghosting-do-not-want.jsonl');
+    const entry = {
+      ts: new Date().toISOString(),
+      id, action,
+      prompt: d.prompt || null,
+      category: d.category || null,
+      motifs: d.motifs || [],
+      tags: d.tags || [],
+      generator: d.generator || null,
+      dominant_hex: d.dominant_hex || null,
+      dig_number: d.dig_number || null,
+      backup_file: backedUp,
+    };
+    try { fs.appendFileSync(registryPath, JSON.stringify(entry) + '\n', 'utf8'); } catch {}
+
+    // Build anti-ghosting addendum used by both paths' regen prompts
+    const basePrompt = (d.prompt || `${d.category || 'mixed'} seamless wallpaper repeat`).trim();
+    const cleanBase = basePrompt.replace(/\.\s*Corrections:[^.]*\.?\s*$/i, '').trim();
+    const antiGhosting =
+      `Corrections: STRICTLY NO ghosting, NO faint duplicate motifs offset behind primary motifs, NO double-exposure echoes, NO opacity-layer artifacts, NO faded background reproductions of foreground elements. Every motif placed once at full opacity; negative space is solid background, not faded pattern.`;
+    const finalPrompt = `${cleanBase}. ${antiGhosting}`;
+
+    if (action === 'delete') {
+      // Hard delete: file + DB row + in-memory cache
+      if (d.local_path && fs.existsSync(d.local_path)) {
+        try { fs.unlinkSync(d.local_path); } catch {}
+      }
+      psqlQuery(`DELETE FROM spoon_all_designs WHERE id=${id};`);
+      const idx = DESIGNS.findIndex(x => x.id === id);
+      if (idx >= 0) DESIGNS.splice(idx, 1);
+      // Queue a fresh regen (new ID) with anti-ghosting addendum
+      let regenStarted = false, regenErr = null;
+      try {
+        const { spawn } = require('child_process');
+        const p = spawn('node', [
+          path.join(__dirname, 'scripts', 'generate_designs.js'),
+          '--n', '1', '--kind', 'seamless_tile',
+          '--category', d.category || 'mixed',
+          '--prompts', finalPrompt,
+        ], { cwd: __dirname, env: { ...process.env, GEN_BACKEND: process.env.GEN_BACKEND || 'replicate' },
+             detached: true, stdio: 'ignore' });
+        p.unref();
+        regenStarted = true;
+      } catch (e) { regenErr = e.message; }
+      return res.json({
+        ok: true, action: 'delete', deleted: true, id,
+        url_now_404: true, backed_up_to: backedUp,
+        do_not_want_entry: registryPath, regen_started: regenStarted, regen_error: regenErr,
+        note: 'Design hard-deleted + ghosting fingerprint logged. Fresh regen queued (new DIG number).',
+      });
+    }
+
+    // action === 'update-sku' — keep DB row + URL + DIG, regenerate IMAGE in-place
+    // Update prompt in DB so the row reflects the corrected version; mark needs_regen.
+    const pgPrompt = finalPrompt.replace(/'/g, "''");
+    psqlQuery(`UPDATE spoon_all_designs SET prompt='${pgPrompt}', updated_at=NOW() WHERE id=${id};`);
+    let regenStarted = false, regenErr = null;
+    try {
+      const { spawn } = require('child_process');
+      // generate_designs.js with --target-id replaces the image on this DB row
+      // (script supports --target-id; if not, falls back to spawning normally
+      // and an out-of-band watcher links the new image to this id).
+      const p = spawn('node', [
+        path.join(__dirname, 'scripts', 'generate_designs.js'),
+        '--n', '1', '--kind', 'seamless_tile',
+        '--category', d.category || 'mixed',
+        '--prompts', finalPrompt,
+        '--target-id', String(id),
+      ], { cwd: __dirname, env: { ...process.env, GEN_BACKEND: process.env.GEN_BACKEND || 'replicate' },
+           detached: true, stdio: 'ignore' });
+      p.unref();
+      regenStarted = true;
+    } catch (e) { regenErr = e.message; }
+
+    res.json({
+      ok: true, action: 'update-sku', id,
+      dig_number: d.dig_number,
+      backed_up_to: backedUp,
+      do_not_want_entry: registryPath,
+      regen_started: regenStarted, regen_error: regenErr,
+      note: 'SKU + DIG number + URL kept. Image being regenerated with anti-ghosting prompt addendum. Refresh /design/' + id + ' in 2-3 min.',
+    });
+  } catch (e) {
+    res.status(500).json({ error: e.message });
+  }
+});
+
 app.get('/design/:id', (req, res) => {
   const id = parseInt(req.params.id, 10);
   if (!Number.isFinite(id) || id < 1) {
@@ -7127,6 +7248,9 @@ ${htmlHeader('/designs')}
               <button id="rating-remove" type="button"
                 style="background:none;border:1px solid #c84a3a;color:#c84a3a;padding:4px 10px;border-radius:14px;cursor:pointer;font-size:11px;letter-spacing:.06em"
                 title="Soft-remove from public listings — hides everywhere but row stays in DB">Remove design</button>
+              <button id="rating-ghosting" type="button"
+                style="background:#7a4ac8;border:1px solid #7a4ac8;color:#fff;padding:4px 10px;border-radius:14px;cursor:pointer;font-size:11px;letter-spacing:.06em;font-weight:600"
+                title="Ghosting detected — choose: regenerate this SKU with the ghosting fingerprint excluded, or hard-delete">Ghosting</button>
               <button id="rating-settlement" type="button"
                 style="background:#b00020;border:1px solid #b00020;color:#fff;padding:4px 10px;border-radius:14px;cursor:pointer;font-size:11px;letter-spacing:.06em;font-weight:600"
                 title="HARD delete: removes from URL (404), logs motifs/prompt to settlement do-not-want registry, kicks a fresh regen with this violation excluded">Settlement Violation</button>
@@ -7330,6 +7454,33 @@ ${htmlHeader('/designs')}
               })
               .catch(function(e){ statusEl.textContent = 'settlement-violation error: ' + e.message; });
           });
+
+          // Ghosting — choose: Update SKU (regenerate w/ anti-ghosting prompt) or Delete.
+          document.getElementById('rating-ghosting').addEventListener('click', function(){
+            var choice = prompt('Ghosting detected. What do you want to do?\\n  type 1 = Update SKU (regenerate w/ anti-ghosting fix, same DIG number)\\n  type 2 = Delete (HARD remove + log to ghosting do-not-want registry)\\n  any other = cancel');
+            if (choice !== '1' && choice !== '2') return;
+            var action = choice === '1' ? 'update-sku' : 'delete';
+            var msg = action === 'update-sku'
+              ? 'Regenerate this SKU with the ghosting fingerprint excluded?\\nThe DIG number + URL stay; just the image is replaced.'
+              : 'HARD-delete this design AND log the prompt + fingerprint to the ghosting do-not-want registry?\\nThis is NOT reversible.';
+            if (!confirm(msg)) return;
+            statusEl.textContent = (action === 'update-sku' ? 'queuing fresh regen…' : 'logging + deleting…');
+            var params = new URLSearchParams(window.location.search);
+            var adminToken = params.get('admin') || localStorage.getItem('wallco_admin_token') || '';
+            fetch('/api/design/' + did + '/ghosting', {
+              method: 'POST',
+              headers: { 'Content-Type': 'application/json', 'X-Admin-Token': adminToken },
+              credentials:'same-origin',
+              body: JSON.stringify({ action: action })
+            })
+              .then(function(r){ return r.json().then(function(j){ return { ok:r.ok, j:j }; }); })
+              .then(function(res){
+                if (!res.ok) { statusEl.textContent = 'error: ' + (res.j.error || 'unknown'); return; }
+                statusEl.textContent = action === 'update-sku' ? 'regen queued · check /designs in 2-3 min' : 'ghosting logged · deleted';
+                setTimeout(function(){ location.href = '/designs'; }, 1400);
+              })
+              .catch(function(e){ statusEl.textContent = 'ghosting error: ' + e.message; });
+          });
         })();
         </script>
         ` : ''}

← 0c4e701 wallco mural: update LEVELS[0].label to '20 ft × 11 ft' too  ·  back to Wallco Ai  ·  fix: regen-with-comment + ghosting endpoints fall back to DE cfe794d →