[object Object]

← back to Wallco Ai

admin/edges-review: Aesthetic-gated (hidden) tab — endpoint + UI

4f8e22da374b213c152c368dc60add6386f8754f · 2026-05-30 20:29:57 -0700 · Steve

New GET /api/admin/edges-review/aesthetic-gated re-runs passesAestheticGate's
palette logic (_hueBucket / _isNeonHex) over data/designs.json and returns the
designs the load-time gate HIDES from the public catalog (neon hex / >=4 hue
buckets / neon dominant_hex), with a per-row gate reason. New tab + gate-reason
badge in edges-review.html. Read-only audit view; gate behavior unchanged.
(CNCP _shared). NOTE: re-added after parallel session commit 94df68b1 clobbered
the first attempt. Local only — NOT deployed (prod is the compromised box).

Files touched

Diff

commit 4f8e22da374b213c152c368dc60add6386f8754f
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat May 30 20:29:57 2026 -0700

    admin/edges-review: Aesthetic-gated (hidden) tab — endpoint + UI
    
    New GET /api/admin/edges-review/aesthetic-gated re-runs passesAestheticGate's
    palette logic (_hueBucket / _isNeonHex) over data/designs.json and returns the
    designs the load-time gate HIDES from the public catalog (neon hex / >=4 hue
    buckets / neon dominant_hex), with a per-row gate reason. New tab + gate-reason
    badge in edges-review.html. Read-only audit view; gate behavior unchanged.
    (CNCP _shared). NOTE: re-added after parallel session commit 94df68b1 clobbered
    the first attempt. Local only — NOT deployed (prod is the compromised box).
---
 public/admin/edges-review.html | 10 +++++++--
 server.js                      | 48 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 56 insertions(+), 2 deletions(-)

diff --git a/public/admin/edges-review.html b/public/admin/edges-review.html
index f6312e1..b474027 100644
--- a/public/admin/edges-review.html
+++ b/public/admin/edges-review.html
@@ -203,6 +203,7 @@
   <div class="tabs" id="tabs">
     <button data-src="live" title="spoon_all_designs rows on this server (near-empty on prod)">Live DB<span class="n" id="n-live">—</span></button>
     <button data-src="snapshot" title="flagged designs in the shipped data/designs.json (what prod actually has)">Snapshot (shipped to prod)<span class="n" id="n-snapshot">—</span></button>
+    <button data-src="aesthetic" title="designs the load-time aesthetic gate HIDES from the public catalog (neon/fluoro/rainbow or >3 hues) — gate behavior unchanged, this is audit-only">Aesthetic-gated (hidden)<span class="n" id="n-aesthetic">—</span></button>
   </div>
 
   <div class="controls">
@@ -265,11 +266,12 @@
     source: localStorage.getItem('edges-review-source') || 'snapshot',
     page: 1,
     items: [],
-    cache: { live: null, snapshot: null },  // per-source item cache
+    cache: { live: null, snapshot: null, aesthetic: null },  // per-source item cache
   };
   const ENDPOINT = {
     live: '/api/admin/edges-review/flagged',
     snapshot: '/api/admin/edges-review/snapshot-flagged',
+    aesthetic: '/api/admin/edges-review/aesthetic-gated',
   };
 
   // Created date+time chip (admin-card standing rule). Local tz, date AND time.
@@ -355,7 +357,11 @@
           const cls = v.verdict === 'FAIL' ? 'fail' : (v.verdict === 'WARN' ? 'warn' : '');
           return `<div class="row"><span class="k">${k}</span><span class="v ${cls}">ΔE ${v.score}</span></div>`;
         }).join('');
-        const reason = item.reason || (item.ok === false ? (item.error || 'scan error') : 'edges_fail');
+        // Aesthetic-gated rows carry gate_reasons[] (why the gate hid them) +
+        // hue_count, instead of edges lens scores. Surface that as the reason.
+        const reason = (Array.isArray(item.gate_reasons) && item.gate_reasons.length)
+          ? '⊘ ' + item.gate_reasons.join(' · ')
+          : (item.reason || (item.ok === false ? (item.error || 'scan error') : 'edges_fail'));
         const when = fmtDate(item.created_at);
         const whenChip = when ? `<span class="when" title="${item.created_at}">🕓 ${when}</span>` : '';
         const isSel = selected.has(item.id);
diff --git a/server.js b/server.js
index 4f2e319..7873dfb 100644
--- a/server.js
+++ b/server.js
@@ -2790,6 +2790,54 @@ app.get('/api/admin/edges-review/snapshot-flagged', (req, res) => {
   }
 });
 
+// GET /api/admin/edges-review/aesthetic-gated — the designs the load-time
+// aesthetic gate (passesAestheticGate) silently HIDES from the public catalog:
+// neon palette hex, ≥4 distinct hue buckets, or a neon dominant_hex. The gate
+// runs at load so admins normally can't see what it removed. This re-runs the
+// SAME palette logic over the data/designs.json snapshot and returns the hidden
+// rows with a per-row gate reason, so a curator can audit the gate's decisions.
+// Read-only — viewing only; the gate's behavior is unchanged. (CNCP _shared 2026-05-30)
+app.get('/api/admin/edges-review/aesthetic-gated', (req, res) => {
+  if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
+  try {
+    const fp = path.join(__dirname, 'data', 'designs.json');
+    const all = JSON.parse(fs.readFileSync(fp, 'utf8'));
+    const items = [];
+    for (const d of all) {
+      if (!d || d.user_removed || d.is_published === false) continue; // match load filter
+      if (passesAestheticGate(d)) continue;                          // keep only HIDDEN
+      // Re-derive WHY, mirroring passesAestheticGate's checks.
+      const reasons = [];
+      let hueBuckets = 0;
+      const palette = Array.isArray(d.palette) ? d.palette : null;
+      if (palette) {
+        const buckets = new Set();
+        for (const p of palette) {
+          const b = _hueBucket(p.hex);
+          if (b !== 'neutral' && b !== 'unknown') buckets.add(b);
+          if (_isNeonHex(p.hex, p.pct)) reasons.push('neon palette hex ' + p.hex);
+        }
+        hueBuckets = buckets.size;
+        if (buckets.size >= 4) reasons.push(buckets.size + ' hue buckets (≥4)');
+      } else if (d.dominant_hex && _isNeonHex(d.dominant_hex, 100)) {
+        reasons.push('neon dominant_hex ' + d.dominant_hex);
+      }
+      items.push({
+        id: d.id, ok: true, image_url: `/designs/img/by-id/${d.id}`,
+        category: d.category || null, title: d.title || null,
+        gate_reasons: reasons.length ? reasons : ['aesthetic gate'],
+        hue_count: hueBuckets,
+        dominant_hex: d.dominant_hex || null,
+        removed: !!d.user_removed, published: !!d.is_published,
+        created_at: d.created_at || null,
+      });
+    }
+    res.json({ ok: true, source: 'aesthetic-gate', count: items.length, items });
+  } catch (err) {
+    res.status(500).json({ ok: false, error: err.message });
+  }
+});
+
 // GET /api/ghost-review/design/:id — fetch ANY design's review metadata,
 // even if it's not in the ghost-flagged set. Lets the viewer open arbitrary
 // design ids that Steve found "ugly but not detected" so he can run the

← 94df68b publish drunk-animals 5/28-5/30 (+54 via curator force, sett  ·  back to Wallco Ai  ·  publish next-5 settlement-clean drunk-animals batch2 (Steve 6bc0857 →