← back to Wallco Ai
designer-studio: wire Elements panel to the REAL visual motif library — /api/studio/elements now returns extracted silhouette cutouts (data/elements) as clickable thumbnails grouped by category; selection by motif id resolved to motifs/category text in compose
0b990f6d7608248ba5329a908017bc13a7382e93 · 2026-05-31 16:21:38 -0700 · Steve Abrams
Files touched
Diff
commit 0b990f6d7608248ba5329a908017bc13a7382e93
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun May 31 16:21:38 2026 -0700
designer-studio: wire Elements panel to the REAL visual motif library — /api/studio/elements now returns extracted silhouette cutouts (data/elements) as clickable thumbnails grouped by category; selection by motif id resolved to motifs/category text in compose
---
server.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 54 insertions(+), 15 deletions(-)
diff --git a/server.js b/server.js
index fab0b24..19538ac 100644
--- a/server.js
+++ b/server.js
@@ -21490,8 +21490,28 @@ app.get('/api/studio/elements', (req, res) => {
// pasted from the admin generator and silently blanked the /designer-studio
// Elements panel for every visitor (frontend did .forEach on the 404 object).
try {
- const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.category, t.name), '[]'::json) FROM
- (SELECT id, category, name, prompt_phrase FROM wallco_element_lexicon) t;`);
+ // REAL visual element library (2026-05-31): elements are the extracted
+ // silhouette/cutout MOTIFS of clean published designs (data/elements/
+ // <id>_motif.png — subject alpha-trimmed on transparent BG), grouped by
+ // category, reusable/combinable into new products. Replaces the old text
+ // vocabulary. Thumbnail = /elements/img/<id>_motif.png (serves the bake,
+ // falls back to by-id image). Capped per category so the panel stays
+ // browsable; all source data is already public (published designs).
+ const raw = psqlQuery(`SELECT COALESCE(json_agg(t),'[]'::json) FROM (
+ SELECT id, category, motif_url FROM (
+ SELECT id, category,
+ '/elements/img/' || id || '_motif.png' AS motif_url,
+ row_number() OVER (PARTITION BY category ORDER BY id DESC) AS rn
+ FROM all_designs
+ WHERE is_published = TRUE
+ AND (user_removed IS NULL OR user_removed = FALSE)
+ AND local_path IS NOT NULL
+ AND (notes IS NULL OR (
+ notes NOT LIKE '%EDGES_FAIL:%' AND notes NOT LIKE '%BLEED_GHOST_REGEN%'
+ AND notes NOT LIKE '%SETTLEMENT BLOCK%' AND notes NOT LIKE '%BLEED_GUARD:%'))
+ ) s WHERE rn <= 30
+ ORDER BY category, id DESC
+ ) t;`);
res.json(JSON.parse(raw || '[]'));
} catch (e) { res.status(500).json({ error: e.message }); }
});
@@ -21707,7 +21727,18 @@ app.post('/api/studio/compose', (req, res) => {
const artists = [...new Set(board.flatMap(b => [b.artist].filter(Boolean)))];
const styles = [...new Set(board.flatMap(b => (b.style_tags || []).concat(b.selected_elements || []).filter(Boolean)))];
const colors = [...new Set(board.flatMap(b => b.color_hex_seeds || []))].slice(0, 6);
- const elems = [...new Set((board.flatMap(b => b.selected_elements || [])).concat(extra_elements))];
+ // extra_elements are now VISUAL motif IDs (element library 2026-05-31) or
+ // legacy text. Resolve a numeric id → the design's motifs/category text so
+ // the prompt reads "featuring crane, botanical" not "featuring 54902".
+ const _resolveEl = (v) => {
+ if (!/^\d+$/.test(String(v))) return String(v);
+ try {
+ const r = psqlQuery(`SELECT COALESCE(NULLIF(array_to_string(motifs,', '),''), category, 'motif')
+ FROM all_designs WHERE id=${parseInt(v,10)} LIMIT 1;`).trim();
+ return r || String(v);
+ } catch { return String(v); }
+ };
+ const elems = [...new Set((board.flatMap(b => b.selected_elements || [])).concat(extra_elements).map(_resolveEl))];
const promptParts = [
'A seamless wallpaper pattern',
@@ -22041,30 +22072,38 @@ async function loadRefs(){
// Renders the elements-list pills with active/inactive styling based on EXTRA.
// Pulled out so toggleExtra can re-render to show visual selected state.
+// Elements are now VISUAL motif cutouts (2026-05-31): each is the extracted
+// silhouette of a design, served at e.motif_url. Render as clickable thumbnails
+// grouped by category; selection holds the motif id. EL_BY_ID maps id->element
+// so the selected strip + compose can resolve the image.
+window.EL_BY_ID = {};
function renderElementsList() {
var byCat = window._ELEMENTS_BY_CAT || {};
+ window.EL_BY_ID = {};
document.getElementById('elements-list').innerHTML = Object.keys(byCat).sort().map(function(c){
- return '<div style="margin-bottom:6px"><div class="eyebrow" style="margin-bottom:3px">'+c+'</div><div style="display:flex;flex-wrap:wrap">'+byCat[c].map(function(e){
- var on = EXTRA.has(e.name);
- var bg = on ? '#1a1a1a' : '#fff';
- var fg = on ? '#fff' : '#333';
- var bd = on ? '#1a1a1a' : '#ccc';
- return '<button data-extra="'+e.name+'" onclick="toggleExtra(\\''+e.name+'\\')" style="padding:2px 7px;font-size:10px;border-radius:9px;border:1px solid '+bd+';background:'+bg+';color:'+fg+';cursor:pointer;margin:1px;transition:all .12s">'+e.name+'</button>';
+ return '<div style="margin-bottom:8px"><div class="eyebrow" style="margin-bottom:3px">'+c+'</div><div style="display:flex;flex-wrap:wrap;gap:4px">'+byCat[c].map(function(e){
+ window.EL_BY_ID[String(e.id)] = e;
+ var on = EXTRA.has(String(e.id));
+ return '<button data-extra="'+e.id+'" title="'+c+' · motif #'+e.id+'" onclick="toggleExtra(\\''+e.id+'\\')" style="width:46px;height:46px;padding:0;border-radius:8px;border:2px solid '+(on?'#1a1a1a':'#e0e0e0')+';background:#faf8f2 url('+e.motif_url+') center/cover no-repeat;cursor:pointer;box-shadow:'+(on?'0 0 0 2px #1a1a1a inset':'none')+';transition:all .12s"></button>';
}).join('')+'</div></div>';
}).join('');
}
function renderExtra() {
- var html = Array.from(EXTRA).map(function(e){
- return pill(e + ' ✕', true, "toggleExtra('"+e+"')");
- }).join('') || '<span style="color:#999;font-size:12px">No extra elements selected yet — click pills above.</span>';
+ var ids = Array.from(EXTRA);
+ var html = ids.map(function(id){
+ var e = (window.EL_BY_ID||{})[String(id)];
+ var url = e ? e.motif_url : '/elements/img/'+id+'_motif.png';
+ return '<button title="motif #'+id+' — click to remove" onclick="toggleExtra(\\''+id+'\\')" style="width:40px;height:40px;padding:0;border-radius:7px;border:1px solid #1a1a1a;background:#faf8f2 url('+url+') center/cover no-repeat;cursor:pointer;margin:2px;position:relative"></button>';
+ }).join('') || '<span style="color:#999;font-size:12px">No elements selected yet — click a motif cutout above to reuse it.</span>';
document.getElementById('extra-elements-pills').innerHTML = html;
}
-window.toggleExtra = function(name){
- if (EXTRA.has(name)) EXTRA.delete(name); else EXTRA.add(name);
+window.toggleExtra = function(id){
+ id = String(id);
+ if (EXTRA.has(id)) EXTRA.delete(id); else EXTRA.add(id);
renderExtra();
- renderElementsList(); // re-render so the pill that was just clicked flips state
+ renderElementsList(); // re-render so the tile that was just clicked flips state
};
window.addArtist = function(a){
← 25e05d1 Publish drunk-animals batch 12 (39443-39447) — SQL-direct, s
·
back to Wallco Ai
·
tests: fix playwright integration specs against current UI ( 3964faf →