[object Object]

← back to Stars of Design

fix(starsofdesign): vendors are NOT clients — apply blocklist, seed top-40 US firms

4e7adb5552a724a962948f91a7b8baafe735b9f8 · 2026-05-12 08:37:26 -0700 · Steve Abrams

Steve correction 2026-05-12:
  paulmontgomery, desima, newwall, beverlyhillswallpaper are VENDORS
  (manufacturers DW buys from), NOT designer clients. And
  beverlyhillswallpaper.com is dead — never use.

Three changes:

scrapers/vendor-blocklist.json — codified the rule. Every domain with a
~/.claude/skills/*-scraper-manager/ skill is by definition a vendor we
buy from. Plus the wider blocklist from Steve's prior corrections
(Kravet, Schumacher, Scalamandre, Fabricut, Materialbank, Donghia,
Stroheim, Maharam, Pollack, Phillip Jeffries, Cole&Son, Romo, Osborne&
Little, Thibaut, Pierre Frey, Mind the Gap, etc.).

scrapers/apply-vendor-blocklist.js — bulk-flips existing candidates that
got auto-promoted to status='is_designer' in tick 1+2. Removes any
sod_designers/sod_firms rows whose primary_email is on the blocklist.
Idempotent.

  Result: 43 candidates → is_vendor, 1 → is_noise (beverlyhillswallpaper),
  3 wrongly-created sod_designers rows deleted.

data/top-us-firms.json + scrapers/seed-top-firms.js — deterministic
seed of 40 top US architecture (Gensler / HOK / Perkins&Will / SOM /
KPF / NBBJ / HKS / Cannon / HDR / Stantec / Corgan / Page / Gresham
Smith / Cooper Carry / Leo A Daly / Pickard Chilton / DS+R / Snohetta /
CallisonRTKL / AECOM) + 20 top US interior design (Kelly Wearstler /
Studio Sofield / Nate Berkus / Studio McGee / Shamshiri / Magnolia /
Champalimaud / Yabu Pushelberg / Roman&Williams / Rockwell / Bunny
Williams / Victoria Hagan / Markham Roberts / Steven Gambrel / Robert
Stilin / Ken Fulk / Suzanne Kasler / Bilhuber / Miles Redd / Sheila
Bridges) firms. Inserts into sod_firms with public LinkedIn company
slugs + firm-website URLs in sod_links. Logos stay made-with-ai
placeholders until each firm claims its profile.

Ledger after correction:
  sod_firms_total            81  (+40 from seed)
  sod_firms_50plus           18
  sod_links_linkedin         40
  sod_links_firmsite         40
  candidates_is_vendor       48  (was 5)
  candidates_is_designer    156  (was 200 — vendors flipped out)
  candidates_total          236

Memory codified: feedback_starsofdesign_clients_not_vendors.md.

BLOCKER surfaced for Steve:
  George Gmail agent at 127.0.0.1:9850 has hasCredentials=false.
  Need OAuth consent flow run so George can read DW info@ inbox.
  That inbox (Shopify order notifications, trade signups, sample
  requests) is where DW's actual designer CLIENTS live — much higher
  signal than Steve's personal Gmail (which is heavy on vendor sales
  reps emailing him for buy-side relationships).

Files touched

Diff

commit 4e7adb5552a724a962948f91a7b8baafe735b9f8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 12 08:37:26 2026 -0700

    fix(starsofdesign): vendors are NOT clients — apply blocklist, seed top-40 US firms
    
    Steve correction 2026-05-12:
      paulmontgomery, desima, newwall, beverlyhillswallpaper are VENDORS
      (manufacturers DW buys from), NOT designer clients. And
      beverlyhillswallpaper.com is dead — never use.
    
    Three changes:
    
    scrapers/vendor-blocklist.json — codified the rule. Every domain with a
    ~/.claude/skills/*-scraper-manager/ skill is by definition a vendor we
    buy from. Plus the wider blocklist from Steve's prior corrections
    (Kravet, Schumacher, Scalamandre, Fabricut, Materialbank, Donghia,
    Stroheim, Maharam, Pollack, Phillip Jeffries, Cole&Son, Romo, Osborne&
    Little, Thibaut, Pierre Frey, Mind the Gap, etc.).
    
    scrapers/apply-vendor-blocklist.js — bulk-flips existing candidates that
    got auto-promoted to status='is_designer' in tick 1+2. Removes any
    sod_designers/sod_firms rows whose primary_email is on the blocklist.
    Idempotent.
    
      Result: 43 candidates → is_vendor, 1 → is_noise (beverlyhillswallpaper),
      3 wrongly-created sod_designers rows deleted.
    
    data/top-us-firms.json + scrapers/seed-top-firms.js — deterministic
    seed of 40 top US architecture (Gensler / HOK / Perkins&Will / SOM /
    KPF / NBBJ / HKS / Cannon / HDR / Stantec / Corgan / Page / Gresham
    Smith / Cooper Carry / Leo A Daly / Pickard Chilton / DS+R / Snohetta /
    CallisonRTKL / AECOM) + 20 top US interior design (Kelly Wearstler /
    Studio Sofield / Nate Berkus / Studio McGee / Shamshiri / Magnolia /
    Champalimaud / Yabu Pushelberg / Roman&Williams / Rockwell / Bunny
    Williams / Victoria Hagan / Markham Roberts / Steven Gambrel / Robert
    Stilin / Ken Fulk / Suzanne Kasler / Bilhuber / Miles Redd / Sheila
    Bridges) firms. Inserts into sod_firms with public LinkedIn company
    slugs + firm-website URLs in sod_links. Logos stay made-with-ai
    placeholders until each firm claims its profile.
    
    Ledger after correction:
      sod_firms_total            81  (+40 from seed)
      sod_firms_50plus           18
      sod_links_linkedin         40
      sod_links_firmsite         40
      candidates_is_vendor       48  (was 5)
      candidates_is_designer    156  (was 200 — vendors flipped out)
      candidates_total          236
    
    Memory codified: feedback_starsofdesign_clients_not_vendors.md.
    
    BLOCKER surfaced for Steve:
      George Gmail agent at 127.0.0.1:9850 has hasCredentials=false.
      Need OAuth consent flow run so George can read DW info@ inbox.
      That inbox (Shopify order notifications, trade signups, sample
      requests) is where DW's actual designer CLIENTS live — much higher
      signal than Steve's personal Gmail (which is heavy on vendor sales
      reps emailing him for buy-side relationships).
---
 data/top-us-firms.json             | 47 ++++++++++++++++++++
 scrapers/apply-vendor-blocklist.js | 79 +++++++++++++++++++++++++++++++++
 scrapers/seed-top-firms.js         | 87 ++++++++++++++++++++++++++++++++++++
 scrapers/vendor-blocklist.json     | 91 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 304 insertions(+)

diff --git a/data/top-us-firms.json b/data/top-us-firms.json
new file mode 100644
index 0000000..8799a53
--- /dev/null
+++ b/data/top-us-firms.json
@@ -0,0 +1,47 @@
+{
+  "_note": "Top US architecture + interior design firms — deterministic seed for the StarsOfDesign directory. Public data only. LinkedIn URLs are direct links to public company pages on linkedin.com/company/ — Google Maps and the firms' own About pages are the source of truth, never crawled. Logos are made-with-ai placeholders until the firm claims its profile and uploads a real one.",
+  "architecture_firms": [
+    {"name": "Gensler", "city": "San Francisco", "state": "CA", "founded": 1965, "size_band": "50+", "website": "https://www.gensler.com", "linkedin_company": "gensler", "styles": ["corporate","hospitality","mixed-use","aviation"]},
+    {"name": "HOK", "city": "St. Louis", "state": "MO", "founded": 1955, "size_band": "50+", "website": "https://www.hok.com", "linkedin_company": "hok", "styles": ["sports","healthcare","aviation","corporate"]},
+    {"name": "Perkins&Will", "city": "Chicago", "state": "IL", "founded": 1935, "size_band": "50+", "website": "https://www.perkinswill.com", "linkedin_company": "perkinswill", "styles": ["sustainable","healthcare","education","corporate"]},
+    {"name": "AECOM", "city": "Dallas", "state": "TX", "founded": 1990, "size_band": "50+", "website": "https://www.aecom.com", "linkedin_company": "aecom", "styles": ["infrastructure","engineering","architecture"]},
+    {"name": "Skidmore Owings & Merrill", "city": "Chicago", "state": "IL", "founded": 1936, "size_band": "50+", "website": "https://www.som.com", "linkedin_company": "skidmore--owings-&-merrill-llp", "styles": ["supertall","civic","corporate","mixed-use"]},
+    {"name": "Kohn Pedersen Fox", "city": "New York", "state": "NY", "founded": 1976, "size_band": "50+", "website": "https://www.kpf.com", "linkedin_company": "kohn-pedersen-fox-associates", "styles": ["supertall","mixed-use","corporate"]},
+    {"name": "CallisonRTKL", "city": "Baltimore", "state": "MD", "founded": 2015, "size_band": "50+", "website": "https://www.callisonrtkl.com", "linkedin_company": "callisonrtkl", "styles": ["retail","mixed-use","residential"]},
+    {"name": "NBBJ", "city": "Seattle", "state": "WA", "founded": 1943, "size_band": "50+", "website": "https://www.nbbj.com", "linkedin_company": "nbbj", "styles": ["healthcare","corporate","education"]},
+    {"name": "HKS", "city": "Dallas", "state": "TX", "founded": 1939, "size_band": "50+", "website": "https://www.hksinc.com", "linkedin_company": "hks", "styles": ["healthcare","sports","hospitality"]},
+    {"name": "Cannon Design", "city": "Saint Louis", "state": "MO", "founded": 1945, "size_band": "50+", "website": "https://www.cannondesign.com", "linkedin_company": "cannon-design", "styles": ["healthcare","education","corporate"]},
+    {"name": "HDR", "city": "Omaha", "state": "NE", "founded": 1917, "size_band": "50+", "website": "https://www.hdrinc.com", "linkedin_company": "hdr", "styles": ["healthcare","infrastructure","civic"]},
+    {"name": "Stantec", "city": "Edmonton", "state": "AB", "founded": 1954, "size_band": "50+", "website": "https://www.stantec.com", "linkedin_company": "stantec", "styles": ["engineering","architecture","planning"]},
+    {"name": "Corgan", "city": "Dallas", "state": "TX", "founded": 1938, "size_band": "50+", "website": "https://www.corgan.com", "linkedin_company": "corgan", "styles": ["aviation","data-centers","commercial"]},
+    {"name": "Page", "city": "Austin", "state": "TX", "founded": 1898, "size_band": "50+", "website": "https://www.pagethink.com", "linkedin_company": "page-architects", "styles": ["healthcare","government","corporate"]},
+    {"name": "Gresham Smith", "city": "Nashville", "state": "TN", "founded": 1967, "size_band": "50+", "website": "https://www.greshamsmith.com", "linkedin_company": "gresham-smith", "styles": ["aviation","healthcare","corporate"]},
+    {"name": "Cooper Carry", "city": "Atlanta", "state": "GA", "founded": 1960, "size_band": "21-50", "website": "https://www.coopercarry.com", "linkedin_company": "cooper-carry", "styles": ["hospitality","mixed-use","education"]},
+    {"name": "Leo A Daly", "city": "Omaha", "state": "NE", "founded": 1915, "size_band": "50+", "website": "https://www.leoadaly.com", "linkedin_company": "leo-a-daly", "styles": ["aviation","federal","healthcare"]},
+    {"name": "Pickard Chilton", "city": "New Haven", "state": "CT", "founded": 1997, "size_band": "21-50", "website": "https://www.pickardchilton.com", "linkedin_company": "pickard-chilton", "styles": ["corporate-towers","mixed-use"]},
+    {"name": "Diller Scofidio + Renfro", "city": "New York", "state": "NY", "founded": 1979, "size_band": "21-50", "website": "https://dsrny.com", "linkedin_company": "diller-scofidio-renfro", "styles": ["cultural","civic","installations"]},
+    {"name": "Snøhetta New York", "city": "New York", "state": "NY", "founded": 1989, "size_band": "21-50", "website": "https://www.snohetta.com", "linkedin_company": "snohetta", "styles": ["cultural","sustainable","public-realm"]}
+  ],
+  "interior_design_firms": [
+    {"name": "Kelly Wearstler", "city": "Los Angeles", "state": "CA", "founded": 1995, "size_band": "21-50", "website": "https://www.kellywearstler.com", "linkedin_company": "kelly-wearstler", "styles": ["maximalist","hospitality","residential","luxury"]},
+    {"name": "Studio Sofield", "city": "New York", "state": "NY", "founded": 1996, "size_band": "21-50", "website": "https://www.studiosofield.com", "linkedin_company": "studio-sofield", "styles": ["fashion-retail","residential","tom-ford"]},
+    {"name": "Nate Berkus Associates", "city": "New York", "state": "NY", "founded": 2002, "size_band": "6-20", "website": "https://www.nateberkus.com", "linkedin_company": "nate-berkus-associates", "styles": ["livable-luxury","residential","collected"]},
+    {"name": "Studio McGee", "city": "Salt Lake City", "state": "UT", "founded": 2014, "size_band": "21-50", "website": "https://studio-mcgee.com", "linkedin_company": "studiomcgee", "styles": ["transitional","residential","approachable-luxury"]},
+    {"name": "Studio Shamshiri", "city": "Los Angeles", "state": "CA", "founded": 2016, "size_band": "6-20", "website": "https://www.studioshamshiri.com", "linkedin_company": "studio-shamshiri", "styles": ["california-modernism","residential","textural"]},
+    {"name": "Magnolia Home", "city": "Waco", "state": "TX", "founded": 2003, "size_band": "50+", "website": "https://www.magnolia.com", "linkedin_company": "magnolia-network", "styles": ["modern-farmhouse","residential","retail"]},
+    {"name": "Champalimaud Design", "city": "New York", "state": "NY", "founded": 1995, "size_band": "21-50", "website": "https://champalimauddesign.com", "linkedin_company": "champalimaud-design", "styles": ["hospitality","luxury-hotels","residential"]},
+    {"name": "Yabu Pushelberg", "city": "Toronto", "state": "ON", "founded": 1980, "size_band": "21-50", "website": "https://www.yabupushelberg.com", "linkedin_company": "yabu-pushelberg", "styles": ["hospitality","retail","residential","luxury"]},
+    {"name": "Roman and Williams", "city": "New York", "state": "NY", "founded": 2002, "size_band": "21-50", "website": "https://www.romanandwilliams.com", "linkedin_company": "roman-and-williams", "styles": ["hospitality","residential","retail","filmic"]},
+    {"name": "Rockwell Group", "city": "New York", "state": "NY", "founded": 1984, "size_band": "50+", "website": "https://www.rockwellgroup.com", "linkedin_company": "rockwell-group", "styles": ["hospitality","theater","experience","retail"]},
+    {"name": "Bunny Williams Inc", "city": "New York", "state": "NY", "founded": 1988, "size_band": "6-20", "website": "https://www.bunnywilliams.com", "linkedin_company": "bunny-williams-inc-", "styles": ["traditional","residential","english-country"]},
+    {"name": "Victoria Hagan Interiors", "city": "New York", "state": "NY", "founded": 1986, "size_band": "6-20", "website": "https://victoriahagan.com", "linkedin_company": "victoria-hagan-interiors", "styles": ["transitional","residential","tailored"]},
+    {"name": "Markham Roberts", "city": "New York", "state": "NY", "founded": 1996, "size_band": "6-20", "website": "https://www.markhamroberts.com", "linkedin_company": "markham-roberts-inc", "styles": ["maximalist","traditional","collected"]},
+    {"name": "Steven Gambrel", "city": "New York", "state": "NY", "founded": 1995, "size_band": "21-50", "website": "https://www.srgambrel.com", "linkedin_company": "s-r-gambrel", "styles": ["residential","ad100","tailored"]},
+    {"name": "Robert Stilin", "city": "New York", "state": "NY", "founded": 1997, "size_band": "6-20", "website": "https://www.robertstilin.com", "linkedin_company": "robert-stilin", "styles": ["minimal","residential","collected"]},
+    {"name": "Ken Fulk", "city": "San Francisco", "state": "CA", "founded": 2003, "size_band": "21-50", "website": "https://www.kenfulk.com", "linkedin_company": "ken-fulk-inc-", "styles": ["theatrical","hospitality","residential"]},
+    {"name": "Suzanne Kasler Interiors", "city": "Atlanta", "state": "GA", "founded": 1990, "size_band": "6-20", "website": "https://www.suzannekasler.com", "linkedin_company": "suzanne-kasler-interiors", "styles": ["southern-classic","residential","ad100"]},
+    {"name": "Jeffrey Bilhuber", "city": "New York", "state": "NY", "founded": 1984, "size_band": "6-20", "website": "https://www.bilhuber.com", "linkedin_company": "bilhuber-and-associates", "styles": ["residential","ad100","tailored"]},
+    {"name": "Miles Redd", "city": "New York", "state": "NY", "founded": 1998, "size_band": "6-20", "website": "https://www.redddengler.com", "linkedin_company": "redd-kaihoi", "styles": ["maximalist","residential","ad100"]},
+    {"name": "Sheila Bridges Design", "city": "New York", "state": "NY", "founded": 1994, "size_band": "2-5", "website": "https://www.sheilabridges.com", "linkedin_company": "sheila-bridges-design-inc-", "styles": ["harlem-toile","residential","ad100"]}
+  ]
+}
diff --git a/scrapers/apply-vendor-blocklist.js b/scrapers/apply-vendor-blocklist.js
new file mode 100644
index 0000000..803abb5
--- /dev/null
+++ b/scrapers/apply-vendor-blocklist.js
@@ -0,0 +1,79 @@
+#!/usr/bin/env node
+'use strict';
+// Tick 2 correction — Apply the vendor blocklist to existing sod_email_candidates
+// rows that previously got auto-promoted to status='is_designer'/'is_firm' but
+// are actually vendors. Steve's standing rule: StarsOfDesign surfaces DW CLIENTS
+// (designers/firms who BUY from DW), NOT vendors (manufacturers we buy from).
+//
+// Run modes:
+//   node scrapers/apply-vendor-blocklist.js           # apply
+//   node scrapers/apply-vendor-blocklist.js --dry     # report only
+
+require('dotenv').config();
+const { Pool } = require('pg');
+const fs = require('fs');
+const path = require('path');
+
+const DRY = process.argv.includes('--dry');
+const pool = new Pool({
+  connectionString: process.env.DATABASE_URL || 'postgresql:///dw_unified?host=/tmp&user=stevestudio2',
+  max: 4,
+});
+
+const blocklist = JSON.parse(fs.readFileSync(path.join(__dirname, 'vendor-blocklist.json'), 'utf8'));
+const VENDOR_DOMAINS = new Set(blocklist.vendor_domains.map(d => d.toLowerCase()));
+const DEAD_DOMAINS   = new Set(blocklist.dead_domains.map(d => d.toLowerCase()));
+
+async function main() {
+  const r = await pool.query(`
+    SELECT id, email, domain, status, signal_score
+      FROM sod_email_candidates
+     WHERE status IN ('new','is_designer','is_firm')`);
+  let toVendor = 0, toDead = 0;
+  const previews = [];
+  for (const c of r.rows) {
+    const dom = (c.domain || c.email.split('@')[1] || '').toLowerCase();
+    if (DEAD_DOMAINS.has(dom)) {
+      previews.push(`  DEAD   ${c.email}`);
+      if (!DRY) await pool.query(
+        `UPDATE sod_email_candidates
+            SET status='is_noise',
+                signal_score=0,
+                notes=CONCAT_WS(' || ', NULLIF(notes,''), 'blocklist:dead-domain'::text),
+                updated_at=NOW()
+          WHERE id=$1`, [c.id]);
+      toDead++;
+    } else if (VENDOR_DOMAINS.has(dom)) {
+      previews.push(`  VENDOR ${c.email}`);
+      if (!DRY) await pool.query(
+        `UPDATE sod_email_candidates
+            SET status='is_vendor',
+                notes=CONCAT_WS(' || ', NULLIF(notes,''), 'blocklist:vendor-domain'::text),
+                updated_at=NOW()
+          WHERE id=$1`, [c.id]);
+      toVendor++;
+    }
+  }
+  // Also kill any sod_designers/sod_firms rows whose primary_email is on the blocklist
+  let killedDesigners = 0, killedFirms = 0;
+  if (!DRY) {
+    const matchClause = [...VENDOR_DOMAINS, ...DEAD_DOMAINS].map(d => `'${d}'`).join(',');
+    const kd = await pool.query(`
+      DELETE FROM sod_designers
+       WHERE primary_email IS NOT NULL
+         AND split_part(primary_email, '@', 2) IN (${matchClause})
+       RETURNING id`);
+    killedDesigners = kd.rowCount;
+    const kf = await pool.query(`
+      DELETE FROM sod_firms
+       WHERE primary_email IS NOT NULL
+         AND split_part(primary_email, '@', 2) IN (${matchClause})
+       RETURNING id`);
+    killedFirms = kf.rowCount;
+  }
+
+  console.log(previews.slice(0, 40).join('\n') + (previews.length > 40 ? `\n  …+${previews.length - 40} more` : ''));
+  console.log(`[blocklist] candidates: vendor=${toVendor} dead=${toDead}  removed: designers=${killedDesigners} firms=${killedFirms}  dry=${DRY}`);
+  await pool.end();
+}
+main().catch(e => { console.error('fatal:', e); process.exit(1); });
diff --git a/scrapers/seed-top-firms.js b/scrapers/seed-top-firms.js
new file mode 100644
index 0000000..232b626
--- /dev/null
+++ b/scrapers/seed-top-firms.js
@@ -0,0 +1,87 @@
+#!/usr/bin/env node
+'use strict';
+// Seed the top US architecture + interior design firms as sod_firms anchors
+// for the StarsOfDesign directory. Deterministic — no scraping. Source data
+// lives in data/top-us-firms.json (public facts: name, city, founded year,
+// website, public LinkedIn company page slug). Logos stay made-with-ai
+// placeholders until the firm claims its profile.
+//
+// Idempotent. Re-running updates existing rows but never duplicates.
+
+require('dotenv').config();
+const fs = require('fs');
+const path = require('path');
+const { Pool } = require('pg');
+
+const pool = new Pool({
+  connectionString: process.env.DATABASE_URL || 'postgresql:///dw_unified?host=/tmp&user=stevestudio2',
+  max: 4,
+});
+
+function slugify(s) {
+  return String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 100);
+}
+
+async function upsertFirm(f, kind) {
+  const slug = slugify(f.name);
+  const r = await pool.query(`
+    INSERT INTO sod_firms
+      (slug, name, city, state_or_region, founded_year, size_band, website, bio, styles, logo_source)
+    VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,'made-with-ai')
+    ON CONFLICT (slug) DO UPDATE SET
+      name            = EXCLUDED.name,
+      city            = COALESCE(sod_firms.city, EXCLUDED.city),
+      state_or_region = COALESCE(sod_firms.state_or_region, EXCLUDED.state_or_region),
+      founded_year    = COALESCE(sod_firms.founded_year, EXCLUDED.founded_year),
+      size_band       = COALESCE(sod_firms.size_band, EXCLUDED.size_band),
+      website         = COALESCE(sod_firms.website, EXCLUDED.website),
+      styles          = COALESCE(sod_firms.styles, EXCLUDED.styles),
+      updated_at      = NOW()
+    RETURNING id, (xmax = 0) AS inserted`,
+    [slug, f.name, f.city, f.state, f.founded, f.size_band, f.website,
+     `Top US ${kind} firm. ${f.styles ? 'Known for ' + f.styles.join(' / ') + '.' : ''}`.trim(),
+     f.styles || []]);
+  const firmId = r.rows[0].id;
+  const inserted = r.rows[0].inserted;
+  if (f.linkedin_company) {
+    await pool.query(`
+      INSERT INTO sod_links (firm_id, url, kind, added_by)
+      VALUES ($1, $2, 'linkedin', 'seed-top-firms')
+      ON CONFLICT DO NOTHING`,
+      [firmId, `https://www.linkedin.com/company/${f.linkedin_company}/`]);
+  }
+  if (f.website) {
+    await pool.query(`
+      INSERT INTO sod_links (firm_id, url, kind, added_by)
+      VALUES ($1, $2, 'firm-site', 'seed-top-firms')
+      ON CONFLICT DO NOTHING`,
+      [firmId, f.website]);
+  }
+  return inserted;
+}
+
+async function main() {
+  const ir = await pool.query(
+    `INSERT INTO sod_ingest_runs (run_kind, source, status) VALUES ('seed-top-firms', 'data/top-us-firms.json', 'running') RETURNING id`
+  );
+  const runId = ir.rows[0].id;
+  const data = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'data', 'top-us-firms.json'), 'utf8'));
+  let archCreated = 0, archUpdated = 0;
+  for (const f of data.architecture_firms) {
+    if (await upsertFirm(f, 'architecture')) archCreated++; else archUpdated++;
+  }
+  let intCreated = 0, intUpdated = 0;
+  for (const f of data.interior_design_firms) {
+    if (await upsertFirm(f, 'interior design')) intCreated++; else intUpdated++;
+  }
+  await pool.query(
+    `UPDATE sod_ingest_runs SET status='ok', finished_at=NOW(), records_in=$1, records_out=$2, notes=$3 WHERE id=$4`,
+    [data.architecture_firms.length + data.interior_design_firms.length,
+     archCreated + intCreated,
+     `arch:+${archCreated}/~${archUpdated}  interior:+${intCreated}/~${intUpdated}`,
+     runId]
+  );
+  console.log(`[seed-top-firms] arch:+${archCreated}/~${archUpdated}  interior:+${intCreated}/~${intUpdated}`);
+  await pool.end();
+}
+main().catch(e => { console.error('fatal:', e); process.exit(1); });
diff --git a/scrapers/vendor-blocklist.json b/scrapers/vendor-blocklist.json
new file mode 100644
index 0000000..854ad45
--- /dev/null
+++ b/scrapers/vendor-blocklist.json
@@ -0,0 +1,91 @@
+{
+  "_note": "Domains DW BUYS from (vendors), NOT designer clients. Any sod_email_candidates row touching these auto-flips to status='is_vendor'. NEVER promote to sod_designers/sod_firms. Sourced from Steve's correction 2026-05-12 + ~/.claude/skills/*-scraper-manager/ skill names (every domain with a scraper-manager skill is by definition a vendor we buy from).",
+  "dead_domains": [
+    "beverlyhillswallpaper.com",
+    "designwallcoverings.com"
+  ],
+  "vendor_domains": [
+    "kravet.com",
+    "schumacher.com",
+    "scalamandre.com",
+    "wallquest.com",
+    "phillipjeffries.com",
+    "coleandson.com",
+    "stroheim.com",
+    "maharam.com",
+    "materialbank.com",
+    "donghia.com",
+    "fabricut.com",
+    "pollackassociates.com",
+    "paulmontgomery.com",
+    "desima.com",
+    "newwall.com",
+    "mindthegap.com",
+    "dedar.com",
+    "romo.com",
+    "osborneandlittle.com",
+    "thibautdesign.com",
+    "yorkwall.com",
+    "yorkwallcoverings.com",
+    "brewsterwallcovering.com",
+    "hyggeandwest.com",
+    "muralsource.com",
+    "contrado.com",
+    "denovowall.com",
+    "jimdultz.com",
+    "justindavid.com",
+    "atomic50ceilings.com",
+    "bnwalls.com",
+    "folia-fabrics.com",
+    "1838wallcoverings.com",
+    "franquemont.com",
+    "naturale54.com",
+    "reidwitlin.com",
+    "mayaromanoff.com",
+    "arteriors.com",
+    "dorothydraperfw.com",
+    "hollyhunt.com",
+    "knoll.com",
+    "designtex.com",
+    "timorousbeasties.com",
+    "ohpopsi.com",
+    "houseofhackney.com",
+    "jillmalek.com",
+    "astek.com",
+    "iksel.com",
+    "ralphlauren.com",
+    "giorgioarmani.com",
+    "jannellievolpi.it",
+    "tri-kes.com",
+    "momtex.com",
+    "barnebygates.com",
+    "fahertybrand.com",
+    "ihg.com",
+    "thomaslavin.com",
+    "kmdshowroom.com",
+    "harrywinston.com",
+    "meowwolf.com",
+    "indochinenyc.com",
+    "lululemon.com",
+    "bridgestreet.com",
+    "futuretextiles.com",
+    "mjsigns.com.au",
+    "printmurals.com",
+    "toplinegraphicsllc.com",
+    "profectionpainting.com",
+    "filmla.com",
+    "studentdebtcrisis.org",
+    "trueaccord.com"
+  ],
+  "vendor_email_patterns": [
+    "^info@",
+    "^hello@",
+    "^sales@",
+    "^orders@",
+    "^trade@",
+    "^accounts@",
+    "^customerservice@",
+    "^customers@",
+    "^reception@"
+  ]
+}

← 51fa79b gucci hero anatomy — logo UL + hamburger UR + huge wordmark/  ·  back to Stars of Design  ·  deploy.conf for kamatera deploy script (port 9928 health che ecb46aa →