[object Object]

← back to Wallco Ai

marketplace YOLO loop · overnight milestone 3 — all 8 parallel tabs landed

9ff4a1a3e023bf25a01f4b786dacea14a23f833b · 2026-05-13 15:14:12 -0700 · SteveStudio2

Final integration: every task from the abramstasks fan-out shipped. Net
~3,500 LOC across 8 parallel iTerm Claude sessions plus the main loop.

Tabs landed:
1. Gemini colorways (real generations via gemini-2.5-flash-image)
2. Ollama qwen3:14b auto-tag + auto-description (ai-text 4/4 LIVE pass)
3. Shopify sample/wallpaper checkout + fulfilled webhook (orders.js)
4. Postgres FTS search + filter UI on /patterns (12/12 search tests)
5. Designer follow + /trade/projects + project boards (10/10 follow tests)
6. Admin payout CSV export + mp_payout_batches (12/12 payout tests)
7. Leaderboard + monthly challenges + auto-CHALLENGE_WIN (19/19 challenge tests)
8. SEO sitemap-marketplace.xml + JSON-LD Person/Product/CreativeWork

Plus bonus: textures.js (texture-overlay AI generation, 9/9 tests)
Plus: designers.html location filter + pill
Plus: /api/marketplace/designers/locations endpoint

Total test count after parallel landings:
- 8 commissions + 11 gamification + 23 smoke + 12 search + 12 payouts
- 19 challenges + 10 follow-projects + 19 orders + 5 ai-colorways
- 4 ai-text (live Ollama) + 9 textures + SEO unit
= 132+ tests passing across the marketplace layer

Status board: http://127.0.0.1:9792/marketplace/status
Live counts: 6 designers / 24 patterns / 8 collections / 8 inquiries / $545.60 pending commission
pm2: wallco-ai (145) + wallco-mp-watcher (149)

Files touched

Diff

commit 9ff4a1a3e023bf25a01f4b786dacea14a23f833b
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 15:14:12 2026 -0700

    marketplace YOLO loop · overnight milestone 3 — all 8 parallel tabs landed
    
    Final integration: every task from the abramstasks fan-out shipped. Net
    ~3,500 LOC across 8 parallel iTerm Claude sessions plus the main loop.
    
    Tabs landed:
    1. Gemini colorways (real generations via gemini-2.5-flash-image)
    2. Ollama qwen3:14b auto-tag + auto-description (ai-text 4/4 LIVE pass)
    3. Shopify sample/wallpaper checkout + fulfilled webhook (orders.js)
    4. Postgres FTS search + filter UI on /patterns (12/12 search tests)
    5. Designer follow + /trade/projects + project boards (10/10 follow tests)
    6. Admin payout CSV export + mp_payout_batches (12/12 payout tests)
    7. Leaderboard + monthly challenges + auto-CHALLENGE_WIN (19/19 challenge tests)
    8. SEO sitemap-marketplace.xml + JSON-LD Person/Product/CreativeWork
    
    Plus bonus: textures.js (texture-overlay AI generation, 9/9 tests)
    Plus: designers.html location filter + pill
    Plus: /api/marketplace/designers/locations endpoint
    
    Total test count after parallel landings:
    - 8 commissions + 11 gamification + 23 smoke + 12 search + 12 payouts
    - 19 challenges + 10 follow-projects + 19 orders + 5 ai-colorways
    - 4 ai-text (live Ollama) + 9 textures + SEO unit
    = 132+ tests passing across the marketplace layer
    
    Status board: http://127.0.0.1:9792/marketplace/status
    Live counts: 6 designers / 24 patterns / 8 collections / 8 inquiries / $545.60 pending commission
    pm2: wallco-ai (145) + wallco-mp-watcher (149)
---
 data/marketplace/events.jsonl       | 14 ++++++++
 data/marketplace/status-watch.json  | 14 ++++----
 data/marketplace/status-watch.jsonl | 20 ++++++++++++
 public/marketplace/designers.html   | 65 +++++++++++++++++++++++++++++++++----
 src/marketplace/index.js            | 41 ++++++++++++++++++++---
 5 files changed, 135 insertions(+), 19 deletions(-)

diff --git a/data/marketplace/events.jsonl b/data/marketplace/events.jsonl
index 96771ce..e128450 100644
--- a/data/marketplace/events.jsonl
+++ b/data/marketplace/events.jsonl
@@ -1,3 +1,17 @@
 {"type":"startup","host":"127.0.0.1","port":9792,"ts":"2026-05-13T21:48:44.080Z"}
 {"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":3,"total":5,"ts":"2026-05-13T21:50:44.135Z"}
 {"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":3,"total":5,"ts":"2026-05-13T21:51:44.106Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":3,"total":5,"ts":"2026-05-13T21:52:44.110Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":4,"total":6,"ts":"2026-05-13T21:53:44.094Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":5,"total":7,"ts":"2026-05-13T21:54:44.094Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":6,"total":8,"ts":"2026-05-13T21:55:44.113Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":3,"total":8,"ts":"2026-05-13T21:56:44.105Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":3,"total":8,"ts":"2026-05-13T21:57:44.104Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":2,"total":8,"ts":"2026-05-13T21:58:44.109Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":1,"total":8,"ts":"2026-05-13T21:59:44.106Z"}
+{"type":"poll_fail","error":"connect ECONNREFUSED 127.0.0.1:9792","ts":"2026-05-13T22:07:55.524Z"}
+{"type":"delta","key":"designers","label":"new designer","change":1,"total":7,"ts":"2026-05-13T22:08:55.908Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":1,"total":9,"ts":"2026-05-13T22:09:56.480Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":1,"total":9,"ts":"2026-05-13T22:10:58.602Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":1,"total":9,"ts":"2026-05-13T22:11:57.407Z"}
+{"type":"delta","key":"open_inquiries","label":"new licensing inquiry","change":1,"total":9,"ts":"2026-05-13T22:12:57.412Z"}
diff --git a/data/marketplace/status-watch.json b/data/marketplace/status-watch.json
index 7bc37e4..8989932 100644
--- a/data/marketplace/status-watch.json
+++ b/data/marketplace/status-watch.json
@@ -1,5 +1,5 @@
 {
-  "ts": "2026-05-13T21:51:44.105Z",
+  "ts": "2026-05-13T22:12:57.411Z",
   "db": true,
   "counts": {
     "designers": "6",
@@ -13,7 +13,7 @@
     "commission_entries": "6",
     "pending_commission": "545.60",
     "paid_commission": "0",
-    "open_inquiries": "5",
+    "open_inquiries": "9",
     "open_takedowns": "0"
   },
   "deltas": {
@@ -29,7 +29,7 @@
       "commission_entries": 0,
       "pending_commission": 0,
       "paid_commission": 0,
-      "open_inquiries": 3,
+      "open_inquiries": 1,
       "open_takedowns": 0
     },
     "30m": {
@@ -44,7 +44,7 @@
       "commission_entries": 0,
       "pending_commission": 0,
       "paid_commission": 0,
-      "open_inquiries": 3,
+      "open_inquiries": 7,
       "open_takedowns": 0
     },
     "60m": {
@@ -59,7 +59,7 @@
       "commission_entries": 0,
       "pending_commission": 0,
       "paid_commission": 0,
-      "open_inquiries": 3,
+      "open_inquiries": 7,
       "open_takedowns": 0
     },
     "24h": {
@@ -74,9 +74,9 @@
       "commission_entries": 0,
       "pending_commission": 0,
       "paid_commission": 0,
-      "open_inquiries": 3,
+      "open_inquiries": 7,
       "open_takedowns": 0
     }
   },
-  "history_size": 4
+  "history_size": 24
 }
\ No newline at end of file
diff --git a/data/marketplace/status-watch.jsonl b/data/marketplace/status-watch.jsonl
index f0b3f5f..6188478 100644
--- a/data/marketplace/status-watch.jsonl
+++ b/data/marketplace/status-watch.jsonl
@@ -2,3 +2,23 @@
 {"ts":1778708984094,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"2","open_takedowns":"0"},"db":true}
 {"ts":1778709044134,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"5","open_takedowns":"0"},"db":true}
 {"ts":1778709104105,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"5","open_takedowns":"0"},"db":true}
+{"ts":1778709164109,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"5","open_takedowns":"0"},"db":true}
+{"ts":1778709224093,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"6","open_takedowns":"0"},"db":true}
+{"ts":1778709284093,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"7","open_takedowns":"0"},"db":true}
+{"ts":1778709344112,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778709404104,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778709464103,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778709524108,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778709584105,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778709644117,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778709704112,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778709764119,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778709824108,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778709884174,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778709951679,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778710024129,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778710135751,"counts":{"designers":"7","designers_approved":"6","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"8","open_takedowns":"0"},"db":true}
+{"ts":1778710196471,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"9","open_takedowns":"0"},"db":true}
+{"ts":1778710258441,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"9","open_takedowns":"0"},"db":true}
+{"ts":1778710317406,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"9","open_takedowns":"0"},"db":true}
+{"ts":1778710377411,"counts":{"designers":"6","designers_approved":"5","founding":"3","unclaimed":"4","patterns":"24","patterns_approved":"24","patterns_pending":"0","collections":"8","commission_entries":"6","pending_commission":"545.60","paid_commission":"0","open_inquiries":"9","open_takedowns":"0"},"db":true}
diff --git a/public/marketplace/designers.html b/public/marketplace/designers.html
index c7be387..f5c0632 100644
--- a/public/marketplace/designers.html
+++ b/public/marketplace/designers.html
@@ -10,6 +10,8 @@
   .controls { display:flex; gap: 10px; flex-wrap:wrap; margin: 14px 0 22px; align-items:center; }
   .controls select { padding: 8px 12px; border-radius: 999px; border: 1px solid var(--mp-border); background: rgba(255,255,255,.8); font-size: 14px; }
   .controls input[type=range] { flex: 0 0 200px; }
+  .mp-card .loc { font-size: 12px; color: var(--mp-muted); display:inline-flex; align-items:center; gap:4px; }
+  .mp-card .loc::before { content: '📍'; font-size: 11px; }
 </style>
 </head>
 <body>
@@ -24,6 +26,11 @@
         <option value="featured">Featured first</option>
         <option value="points">Highest points</option>
         <option value="newest">Newest</option>
+        <option value="location">Location (A→Z)</option>
+      </select>
+      <span class="sub" style="margin-left: 14px">Location</span>
+      <select id="locFilter">
+        <option value="">All locations</option>
       </select>
       <span class="sub" style="margin-left: 14px">Density</span>
       <input type="range" id="density" min="180" max="380" value="280" step="10"/>
@@ -34,12 +41,16 @@
 <script>
 const grid = document.getElementById('grid');
 const sortSel = document.getElementById('sort');
+const locFilter = document.getElementById('locFilter');
 const density = document.getElementById('density');
 
-// persist
 const KEY = 'wallco.marketplace.designers';
 const saved = JSON.parse(localStorage.getItem(KEY) || '{}');
 if (saved.sort) sortSel.value = saved.sort;
+if (saved.location) {
+  // Will set after locations load; cached for now.
+  locFilter.dataset.pending = saved.location;
+}
 if (saved.density) density.value = saved.density;
 applyDensity();
 
@@ -47,18 +58,53 @@ function applyDensity() {
   grid.style.gridTemplateColumns = `repeat(auto-fill, minmax(${density.value}px, 1fr))`;
 }
 
+function persist() {
+  localStorage.setItem(KEY, JSON.stringify({
+    sort: sortSel.value,
+    location: locFilter.value,
+    density: density.value,
+  }));
+}
+
+async function loadLocations() {
+  let j;
+  try { j = await fetch('/api/marketplace/designers/locations').then(r => r.json()); }
+  catch (_) { return; }
+  const locs = j.locations || [];
+  locs.forEach(loc => {
+    const opt = document.createElement('option');
+    opt.value = loc.location;
+    opt.textContent = `${loc.location} (${loc.designer_count})`;
+    locFilter.appendChild(opt);
+  });
+  if (locFilter.dataset.pending) {
+    locFilter.value = locFilter.dataset.pending;
+    delete locFilter.dataset.pending;
+  }
+}
+
 async function load() {
   const sort = sortSel.value;
-  localStorage.setItem(KEY, JSON.stringify({ sort, density: density.value }));
-  const j = await fetch(`/api/marketplace/designers?sort=${sort}&limit=120`).then(r => r.json());
+  const loc = locFilter.value;
+  persist();
+  const params = new URLSearchParams({ sort, limit: '120' });
+  if (loc) params.set('location', loc);
+  const j = await fetch('/api/marketplace/designers?' + params.toString()).then(r => r.json());
   grid.innerHTML = '';
-  (j.designers || []).forEach(d => {
+  const rows = j.designers || [];
+  if (!rows.length) {
+    grid.innerHTML = '<div class="sub" style="padding:24px;grid-column:1/-1">No designers match this filter yet.</div>';
+    return;
+  }
+  rows.forEach(d => {
     const a = document.createElement('a'); a.className='mp-card'; a.href=`/designers/${d.slug}`;
+    const locPill = d.location ? `<span class="loc">${d.location}</span>` : '';
     a.innerHTML = `
       <div class="img" style="background: linear-gradient(135deg,#eee,#ddd);"></div>
       <div class="body">
         <div class="title">${d.display_name}</div>
-        <div class="sub">${d.studio_name || ''} · ${(d.style_tags||[]).slice(0,2).join(' · ')}</div>
+        <div class="sub">${d.studio_name || ''}${d.studio_name && (d.style_tags||[]).length ? ' · ' : ''}${(d.style_tags||[]).slice(0,2).join(' · ')}</div>
+        ${locPill}
         <div class="row">
           <span class="mp-pill ${d.is_founding ? 'gold' : ''}">${d.level}</span>
           ${d.is_verified ? '<span class="mp-pill verified">Verified</span>' : ''}
@@ -70,8 +116,13 @@ async function load() {
 }
 
 sortSel.addEventListener('change', load);
-density.addEventListener('input', () => { applyDensity(); localStorage.setItem(KEY, JSON.stringify({ sort: sortSel.value, density: density.value })); });
-load();
+locFilter.addEventListener('change', load);
+density.addEventListener('input', () => { applyDensity(); persist(); });
+
+(async () => {
+  await loadLocations();
+  load();
+})();
 </script>
 </body>
 </html>
diff --git a/src/marketplace/index.js b/src/marketplace/index.js
index 070f46f..db84026 100644
--- a/src/marketplace/index.js
+++ b/src/marketplace/index.js
@@ -277,14 +277,45 @@ function mount(app) {
       newest: 'created_at DESC',
       points: 'points DESC',
       featured: 'is_founding DESC, points DESC',
+      // Location: alphabetical, with locationless designers at the end. Secondary sort by name
+      // so the order is stable within a city.
+      location: "location ASC NULLS LAST, display_name ASC",
     }[req.query.sort] || 'points DESC');
+
+    // Optional ?location=Brooklyn filter — case-insensitive substring.
+    const where = ["status='approved'"];
+    const params = [];
+    const locFilter = (req.query.location || '').toString().trim();
+    if (locFilter) {
+      params.push('%' + locFilter + '%');
+      where.push(`location ILIKE $${params.length}`);
+    }
+    params.push(limit); const limIdx = params.length;
+    params.push(offset); const offIdx = params.length;
+
+    const rows = await many(
+      `SELECT id, display_name, slug, studio_name, avatar_url, cover_url, level, points,
+              is_verified, is_founding, is_claimed, style_tags, location
+         FROM mp_designer_profiles
+        WHERE ${where.join(' AND ')}
+        ORDER BY ${sort}
+        LIMIT $${limIdx} OFFSET $${offIdx}`,
+      params
+    );
+    res.json({ designers: rows, limit, offset, sort: req.query.sort || 'points', location: locFilter || null });
+  });
+
+  // ── distinct locations (public; powers the designers-page location filter)
+  app.get('/api/marketplace/designers/locations', async (_req, res) => {
     const rows = await many(
-      `SELECT id, display_name, slug, studio_name, avatar_url, cover_url, level, points, is_verified, is_founding, is_claimed, style_tags
-       FROM mp_designer_profiles WHERE status='approved'
-       ORDER BY ${sort} LIMIT $1 OFFSET $2`,
-      [limit, offset]
+      `SELECT location, COUNT(*)::int AS designer_count
+         FROM mp_designer_profiles
+        WHERE status='approved' AND location IS NOT NULL AND location <> ''
+        GROUP BY location
+        ORDER BY designer_count DESC, location ASC
+        LIMIT 200`
     );
-    res.json({ designers: rows, limit, offset });
+    res.json({ locations: rows });
   });
 
   // ── designer detail (public)

← d6036b8 marketplace: tests for /api/marketplace/textures (9/9 pass l  ·  back to Wallco Ai  ·  nav: surface Marketplace · Designers · Patterns in wallco.ai 69fa84a →