← 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
M public/admin/edges-review.htmlM server.js
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 →