← back to Wallco Ai
wallco.ai · /api/designs/search?q=&limit= — lightweight in-memory search across catalog scoring motif(5 exact/3 contains), title(2), category(2 exact/1 contains), handle(1); top-N by score then saturation; rejects q<2 chars; emits Last-Modified+Cache-Control public 5min so crawlers + clients can cache search results; verified rose+floral → 81 hits with Rose Bouquet No.161 (score 14) leading, damask → 180 hits
7d5cb786f3fa0c10dd3e0c449a40db72b34e657a · 2026-05-12 07:52:21 -0700 · SteveStudio2
Files touched
Diff
commit 7d5cb786f3fa0c10dd3e0c449a40db72b34e657a
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue May 12 07:52:21 2026 -0700
wallco.ai · /api/designs/search?q=&limit= — lightweight in-memory search across catalog scoring motif(5 exact/3 contains), title(2), category(2 exact/1 contains), handle(1); top-N by score then saturation; rejects q<2 chars; emits Last-Modified+Cache-Control public 5min so crawlers + clients can cache search results; verified rose+floral → 81 hits with Rose Bouquet No.161 (score 14) leading, damask → 180 hits
---
server.js | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/server.js b/server.js
index 3ecd8c2..34b2b51 100644
--- a/server.js
+++ b/server.js
@@ -216,6 +216,59 @@ function maybe304(req, res, lastModifiedDate) {
return false;
}
+// ── Lightweight in-memory search across the catalog.
+// /api/designs/search?q=<term>&limit=20
+// Scores each design on motif (3), title (2), category/handle (1) substring match.
+// Returns top-N by score then by saturation. Pure read-only, no DB.
+app.get('/api/designs/search', (req, res) => {
+ const qRaw = String(req.query.q || '').toLowerCase().trim();
+ const limit = Math.max(1, Math.min(50, parseInt(req.query.limit || '20', 10)));
+ if (!qRaw || qRaw.length < 2) {
+ return res.json({ q: qRaw, limit, count: 0, results: [], note: 'q must be ≥2 chars' });
+ }
+ // Conditional GET on the catalog freshness so crawlers benefit.
+ if (maybe304(req, res, CATALOG_LAST_MODIFIED)) return;
+ res.setHeader('Cache-Control', 'public, max-age=300, must-revalidate');
+ const terms = qRaw.split(/\s+/).filter(Boolean);
+ const out = [];
+ for (const d of DESIGNS) {
+ const title = (d.title || '').toLowerCase();
+ const category = (d.category || '').toLowerCase();
+ const handle = (d.handle || '').toLowerCase();
+ const motifs = (d.motifs || []).map(m => m.toLowerCase());
+ let score = 0;
+ for (const t of terms) {
+ if (motifs.some(m => m === t)) score += 5; // exact motif match — strongest
+ else if (motifs.some(m => m.includes(t))) score += 3;
+ if (title.includes(t)) score += 2;
+ if (category === t) score += 2;
+ else if (category.includes(t)) score += 1;
+ if (handle.includes(t)) score += 1;
+ }
+ if (score > 0) {
+ out.push({
+ id: d.id,
+ title: d.title,
+ category: d.category,
+ handle: d.handle,
+ image_url: d.image_url,
+ dominant_hex: d.dominant_hex,
+ room_mockups: d.room_mockups || [],
+ score,
+ saturation: d.saturation || 0,
+ url: `/design/${d.id}`,
+ });
+ }
+ }
+ out.sort((a, b) => b.score - a.score || b.saturation - a.saturation);
+ res.json({
+ q: qRaw,
+ limit,
+ count: out.length,
+ results: out.slice(0, limit),
+ });
+});
+
// ── Color sort helpers
function hexToHSL(hex) {
if (!hex) return [0, 0, 0.5];
← d6cdb24 wallco.ai · all target=_blank now rel=noopener noreferrer (t
·
back to Wallco Ai
·
wallco.ai · /designs search box → live autocomplete dropdown 3622bf5 →