{"slug":"starsofdesign","total":100,"limit":100,"offset":0,"since":null,"commits":[{"hash":"06be86b","date":"2026-05-31 16:32:38 -0700","author":"Steve Abrams","subject":"Add per-site favicon (kills /favicon.ico 404)","body":""},{"hash":"8fc1daa","date":"2026-05-19 15:20:35 -0700","author":"Steve Abrams","subject":"Static-bak guard returns plain text, not EJS template","body":"The previous version of this guard tried to render public/404.ejs, which\nin turn includes partials/head.ejs that depends on res.locals.meta_desc.\nThat local is wired inside the publicRoutes router-level middleware —\nlater in the stack than this guard runs — so a request for a .bak/.pre-\npath threw \"meta_desc is not defined\" and surfaced a 500 instead of a\n404, defeating the point.\n\nPlain-text response sidesteps the template chain entirely. Verified with\na smoke run: /test.html.bak / /test.bak / /something.pre-refactor.html\nall return 404, regular routes still 200.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"e2ee6aa","date":"2026-05-19 15:19:24 -0700","author":"Steve Abrams","subject":"Guard against serving .bak / .pre- snapshot artifacts from public/","body":"Refactor passes across the DW fleet sometimes leave behind .bak /\n.bak.<ts> / .pre-<label> snapshots next to the file they replace. If\none of those slips into public/ it ships to prod as a world-readable\nstatic asset. Two-layer fix:\n\n1. .gitignore — *.bak, *.bak.*, *.pre-* so new snapshots stop entering\n   the tree to begin with.\n2. server.js — small Express middleware in front of express.static()\n   that 404s any URL path containing those patterns, so even an\n   un-ignored slip can't be fetched. Uses 404 (not 403) so the\n   existence of a snapshot can't be confirmed by path-probing.\n\nNo tracked .bak / .pre-* files exist in the repo today; this is a\nforward-looking guard.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"a7ac2a9","date":"2026-05-19 15:18:55 -0700","author":"Steve Abrams","subject":"Add noreferrer to all rel=nofollow noopener external links","body":"External links across /clients, /clients/:slug, /firms, /firms/:slug,\nand /feed previously used rel=\"nofollow noopener\" — noreferrer was\nmissing, which leaks the originating profile URL in the Referer header\nto third-party sites (LinkedIn, Instagram, firm websites) every time a\nvisitor clicks through. Spec-canonical pairing for target=\"_blank\" is\n\"noopener noreferrer\"; nofollow stays for SEO. All six view files\nupdated; videos.ejs and designer.ejs already had the full triple.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"18acbe8","date":"2026-05-19 15:17:57 -0700","author":"Steve Abrams","subject":"Hydrate saved sort BEFORE first grid render","body":"Per the standing rule: when a user has picked a sort in /designers or on\nthe home grid, that preference must apply on the very first render of a\nfresh visit, not only after they touch the dropdown again. Server-side\nsorting is driven by ?sort= in the URL, so this commit makes\npublic/js/grid-controls.js do a one-time replace() redirect to the saved\nsort value when the URL has no sort param yet.\n\nGuards against:\n- cross-page contamination — only redirects if the saved value is one of\n  the options actually present in this page's <select> (so a \"default\"\n  saved from /clients won't crash /designers).\n- redirect loops — gated by a sessionStorage flag.\n- form-driven pages — /clients and /firms wrap their <select> in a <form\n  onchange=\"this.form.submit()\">; the JS now defers to the form submit\n  instead of double-navigating.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"e85f238","date":"2026-05-13 15:00:22 -0700","author":"Steve Abrams","subject":"Makefile: audit target","body":""},{"hash":"af307bc","date":"2026-05-13 14:56:39 -0700","author":"Steve Abrams","subject":"smoke: /api/health perf budget 500→1000ms","body":""},{"hash":"f86aef5","date":"2026-05-13 14:25:08 -0700","author":"Steve Abrams","subject":"polish: editorial 2-line hero + by-era anthology section","body":"Hero — replace single-line tagline with 2-line italic editorial\nheadline (\"The designers who set the room. / The wallcoverings that\nset the tone.\") plus 1-line sans subtitle naming Draper through\nWearstler. Bumps tagline color to #f0e3b8 + text-shadow for stronger\nAPCA on the dark hero ground.\n\nNew \"By the era\" section between the directory grid and the editorial\ntwo-col. Four cards (Early 20th / Mid-Century / Late 20th /\nContemporary) each with a gold hairline rule, era blurb, 3 exemplar\ndesigner names, and a count-deep-link to /designers?era=<bucket>.\nSpecific to the directory vertical — gives the homepage a real\n\"anthology by era\" identity instead of template-fresh blocks.\n\nServer passes eraIndex into the home view as a single in-memory\npass over data.load() — no DB hit. Hero-readability-auditor passes\nAPCA Lc 100/99 desktop/mobile, well above the 75 threshold."},{"hash":"03cea56","date":"2026-05-13 11:25:50 -0700","author":"Steve Abrams","subject":"smoke: assert /api/{designers,firms,clients}/featured SWR Cache-Control (3 checks)","body":""},{"hash":"ea921b6","date":"2026-05-13 11:18:42 -0700","author":"Steve Abrams","subject":"smoke: cover /healthz + HEAD method + harness opts.method","body":""},{"hash":"4817b26","date":"2026-05-13 11:14:41 -0700","author":"Steve Abrams","subject":"smoke: assert Last-Modified on /sitemap.{xml,txt}","body":""},{"hash":"6650847","date":"2026-05-13 11:06:52 -0700","author":"Steve Abrams","subject":"smoke: home perf budget <1500ms","body":""},{"hash":"d4b32db","date":"2026-05-13 11:03:35 -0700","author":"Steve Abrams","subject":"Makefile: add help target listing all available commands","body":""},{"hash":"54ab22f","date":"2026-05-13 10:59:45 -0700","author":"Steve Abrams","subject":"smoke: /api/health perf budget (<500ms)","body":""},{"hash":"fe4a506","date":"2026-05-13 10:56:45 -0700","author":"Steve Abrams","subject":"Makefile: add fleet-status target","body":""},{"hash":"bcf553b","date":"2026-05-13 10:44:48 -0700","author":"Steve Abrams","subject":"smoke: assert /firms + /clients sort + density (grid standard)","body":""},{"hash":"86dc319","date":"2026-05-13 10:41:43 -0700","author":"Steve Abrams","subject":"smoke: assert /designers has sort + density controls (Steve's grid standard)","body":""},{"hash":"73e8ba0","date":"2026-05-13 10:38:42 -0700","author":"Steve Abrams","subject":"smoke: 3 more home assertions — Permissions-Policy, canonical, color-scheme (asim parity)","body":""},{"hash":"4be3a23","date":"2026-05-13 10:35:38 -0700","author":"Steve Abrams","subject":"smoke: assert X-Frame-Options on home (asim parity)","body":""},{"hash":"338bd3e","date":"2026-05-13 10:32:44 -0700","author":"Steve Abrams","subject":"smoke: assert HSTS + CSP headers (helmet regression guard)","body":""},{"hash":"e88edf9","date":"2026-05-13 10:28:57 -0700","author":"Steve Abrams","subject":"smoke: assert Vary: Accept-Encoding","body":""},{"hash":"3f74a86","date":"2026-05-13 10:25:36 -0700","author":"Steve Abrams","subject":"smoke: assert /api/version git !== 'unknown'","body":""},{"hash":"d45d22b","date":"2026-05-13 10:18:42 -0700","author":"Steve Abrams","subject":"smoke: assert /api/version Cache-Control no-store","body":""},{"hash":"fd7960e","date":"2026-05-13 10:15:10 -0700","author":"Steve Abrams","subject":"smoke: 2 assertions for branded 404 page","body":""},{"hash":"95b38f5","date":"2026-05-13 10:07:38 -0700","author":"Steve Abrams","subject":"smoke: 4 assertions for /api/version","body":""},{"hash":"49fd5af","date":"2026-05-13 10:04:40 -0700","author":"Steve Abrams","subject":"api/version: deploy fingerprint endpoint (asim parity)","body":""},{"hash":"90ada6d","date":"2026-05-13 09:59:55 -0700","author":"Steve Abrams","subject":"smoke: uptime_s + counts.designers assertions on /api/health","body":""},{"hash":"eef66c3","date":"2026-05-13 09:56:04 -0700","author":"Steve Abrams","subject":"api/health: include counts (designers/firms) + uptime_s + as_of (asim parity)","body":""},{"hash":"3774c03","date":"2026-05-13 09:51:56 -0700","author":"Steve Abrams","subject":"smoke: 6 new checks for /api/{designers,firms,clients}/featured","body":""},{"hash":"deb329f","date":"2026-05-13 09:46:43 -0700","author":"Steve Abrams","subject":"API: add /api/{designers,firms,clients}/featured (parity with asim featured)","body":""},{"hash":"ce39c6b","date":"2026-05-13 09:43:22 -0700","author":"Steve Abrams","subject":"sitemap.txt: emit Last-Modified header for conditional GET (asim parity)","body":""},{"hash":"4edf44d","date":"2026-05-13 09:35:54 -0700","author":"Steve Abrams","subject":"smoke: 9 new checks for /api/{designers,firms,clients}/random + /random/{designer,firm,client}","body":""},{"hash":"da27142","date":"2026-05-13 09:32:42 -0700","author":"Steve Abrams","subject":"API: add /random/designer + /random/firm + /random/client redirect helpers","body":""},{"hash":"ba5954a","date":"2026-05-13 09:29:17 -0700","author":"Steve Abrams","subject":"API: add /api/designers/random + /api/firms/random + /api/clients/random (parity with asim)","body":""},{"hash":"d54a257","date":"2026-05-13 09:25:22 -0700","author":"Steve Abrams","subject":"sitemap.xml: emit Last-Modified header for conditional GET (asim parity)","body":""},{"hash":"fe83a3f","date":"2026-05-13 09:11:44 -0700","author":"Steve Abrams","subject":"sod robots.txt: also reference /sitemap.txt. 54/54.","body":""},{"hash":"a808c8d","date":"2026-05-13 09:07:43 -0700","author":"Steve Abrams","subject":"sod Makefile — common dev targets (mirrors asim)","body":""},{"hash":"6d21b22","date":"2026-05-13 08:50:14 -0700","author":"Steve Abrams","subject":"sod sitemap.xml + .txt Cache-Control 1h→6h + SWR 24h (mirrors asim). 54/54.","body":""},{"hash":"c0ba760","date":"2026-05-13 08:45:52 -0700","author":"Steve Abrams","subject":"sod /api discovery — list /api/health + smoke +2 /api/health checks. 54/54.","body":""},{"hash":"fd9f828","date":"2026-05-13 08:42:46 -0700","author":"Steve Abrams","subject":"sod GET /api/health — mirrors asim. SELECT 1 liveness via statsPool, no-store, JSON ok+ts.","body":""},{"hash":"660a173","date":"2026-05-13 08:39:38 -0700","author":"Steve Abrams","subject":"sod smoke --json suppresses header","body":""},{"hash":"b90e2bb","date":"2026-05-13 08:38:54 -0700","author":"Steve Abrams","subject":"sod smoke +--json flag (parity)","body":""},{"hash":"a909b13","date":"2026-05-13 08:28:41 -0700","author":"Steve Abrams","subject":"sod .deploy.conf — point HEALTH_URL at /healthz (cheap liveness, no-store, faster than full home render). Mirrors asim.","body":""},{"hash":"f337110","date":"2026-05-13 07:55:38 -0700","author":"Steve Abrams","subject":"sod footer +/api link — matches asim. 52/52.","body":""},{"hash":"920bdd7","date":"2026-05-13 07:52:35 -0700","author":"Steve Abrams","subject":"sod smoke +2 /sitemap.txt assertions. 52/52.","body":""},{"hash":"9136d4a","date":"2026-05-13 07:49:15 -0700","author":"Steve Abrams","subject":"sod GET /sitemap.txt — plain-text sitemap (Google+Bing supported). Also removed duplicate /healthz stub that was overshadowed. 50/50.","body":""},{"hash":"ef12c2c","date":"2026-05-13 07:32:39 -0700","author":"Steve Abrams","subject":"sod /healthz Cache-Control: no-store (parity with asim)","body":""},{"hash":"9a655f6","date":"2026-05-13 06:55:28 -0700","author":"Steve Abrams","subject":"sod smoke +3 ItemList checks on /designers /firms /clients index pages. 50/50.","body":""},{"hash":"43b04f5","date":"2026-05-13 06:52:11 -0700","author":"Steve Abrams","subject":"starsofdesign: ItemList JSON-LD on /designers + /clients index pages — top-60 each as Person ListItems. 47/47.","body":""},{"hash":"2502570","date":"2026-05-13 06:48:03 -0700","author":"Steve Abrams","subject":"starsofdesign: /firms index ItemList JSON-LD — first 60 firms as Organization ListItems with positions. Rich-snippet eligible. 47/47.","body":""},{"hash":"da98378","date":"2026-05-13 06:29:39 -0700","author":"Steve Abrams","subject":"sod head — +<meta name=color-scheme content=\"light dark\"> since sod has runtime theme toggle. Form controls + scrollbars now respect the chosen theme. 47/47.","body":""},{"hash":"5b0cc55","date":"2026-05-13 06:01:30 -0700","author":"Steve Abrams","subject":"sod robots.txt — Disallow /api/ (mirrors asim, prevents wasted crawl on JSON endpoints).","body":""},{"hash":"47f5530","date":"2026-05-13 05:58:48 -0700","author":"Steve Abrams","subject":"sod /favicon.ico Cache-Control max-age=86400 (was 0). Mirrors asim.","body":""},{"hash":"bb0e392","date":"2026-05-13 05:54:56 -0700","author":"Steve Abrams","subject":"sod smoke +gzip /firms check","body":""},{"hash":"5379dd0","date":"2026-05-13 05:51:41 -0700","author":"Steve Abrams","subject":"sod-smoke-quiet","body":""},{"hash":"c9d3997","date":"2026-05-13 05:24:45 -0700","author":"Steve Abrams","subject":"starsofdesign: smoke +4 security-header checks (X-Content-Type-Options, Referrer-Policy, HSTS, CSP via helmet). 46/46.","body":""},{"hash":"0160b3c","date":"2026-05-13 03:26:02 -0700","author":"Steve Abrams","subject":"sodhz","body":""},{"hash":"4682bab","date":"2026-05-12 23:56:21 -0700","author":"Steve Abrams","subject":"sod-comments-cache","body":""},{"hash":"571d289","date":"2026-05-12 23:03:25 -0700","author":"Steve Abrams","subject":"sod-cache-stats","body":""},{"hash":"0954ae8","date":"2026-05-12 21:23:27 -0700","author":"Steve Abrams","subject":"starsofdesign: Cache-Control public max-age=3600 on /sitemap.xml. Mirrors asim.","body":""},{"hash":"13a504c","date":"2026-05-12 21:14:36 -0700","author":"Steve Abrams","subject":"starsofdesign: gzip compression middleware (threshold 1KB) — mirrors asim. JSON-LD-heavy /firms /designers /clients pages drop over the wire. Smoke 42/42.","body":""},{"hash":"a32feca","date":"2026-05-12 20:08:44 -0700","author":"Steve Abrams","subject":"starsofdesign: smoke — add /api discovery doc check (+2 assertions). 42/42.","body":""},{"hash":"6e94cda","date":"2026-05-12 20:05:44 -0700","author":"Steve Abrams","subject":"starsofdesign: GET /api — JSON discovery doc for public API endpoints (stats/comments/like/comment) + PII policy notes. Mirrors asim /api pattern.","body":""},{"hash":"9d16cb3","date":"2026-05-12 19:50:16 -0700","author":"Steve Abrams","subject":"starsofdesign: smoke test harness — test/smoke.js (zero deps, pure node) hits every public route and asserts 200/3xx + key DOM markers (canonical, WebSite/Person/Organization/BreadcrumbList JSON-LD, footer privacy/terms links). 40 checks. npm test wired. 40/40 pass on initial run. Mirrors asim smoke harness.","body":""},{"hash":"00a2a60","date":"2026-05-12 19:42:05 -0700","author":"Steve Abrams","subject":"starsofdesign: GET /privacy + /terms — minimal legal pages (no-collect privacy notice, public-data terms, editorial-only liability). Mirrors asim. Linked from footer + sitemap. Closes parity gap.","body":""},{"hash":"ed2f2d8","date":"2026-05-12 19:29:40 -0700","author":"Steve Abrams","subject":"starsofdesign: BreadcrumbList JSON-LD across all 3 detail page types — /firms/:slug (Home > Firms > {firm}), /designers/:slug (Home > Designers > {name}), /clients/:slug (Home > Clients > {name}). Packaged in @graph alongside entity schema. /firms also now emits employee→Person list for each associated designer.","body":""},{"hash":"7348d91","date":"2026-05-12 18:58:24 -0700","author":"Steve Abrams","subject":"starsofdesign: home page — WebSite + Organization JSON-LD with SearchAction (urlTemplate→/designers?q=…) for Google sitelinks search-box eligibility. Closes the asim/sod parity gap on home-page schema.","body":""},{"hash":"044e06c","date":"2026-05-12 18:41:09 -0700","author":"Steve Abrams","subject":"starsofdesign: JSON-LD Person schema on /clients/:slug (DW client tier) — name + jobTitle + homeLocation + worksFor→Organization. Mirrors firm/designer pattern. Preserves PII rule (city/state only, no street).","body":""},{"hash":"113ac14","date":"2026-05-12 18:37:06 -0700","author":"Steve Abrams","subject":"starsofdesign: JSON-LD schema — Organization on /firms/:slug (name/website/foundingDate/address/areaServed), Person on /designers/:slug (name/jobTitle/homeLocation/knowsAbout); injected via optional json_ld locals in head.ejs partial","body":""},{"hash":"f17b258","date":"2026-05-12 18:25:28 -0700","author":"Steve Abrams","subject":"starsofdesign: GET /favicon.ico fallback — returns public/favicon.svg so bots/legacy clients don't 404 on conventional path","body":""},{"hash":"728ecab","date":"2026-05-12 18:09:29 -0700","author":"Steve Abrams","subject":"starsofdesign: GET /api/stats — JSON counts (designers/firms/claimed/links/likes/comments/candidates) with shared module-level pool; mirrors asim /api/spotted/stats pattern; consumed by status-loop + external dashboards","body":""},{"hash":"346478c","date":"2026-05-12 16:02:28 -0700","author":"Steve Abrams","subject":"starsofdesign: seed data/vendor-domains.txt (~80 lines) — vendor manufacturers + transactional SaaS + DW family + free-mail + testing domains; consumed by any future audit-promote pipeline","body":""},{"hash":"e00c483","date":"2026-05-12 15:58:28 -0700","author":"Steve Abrams","subject":"promote-candidates: harden — restore 'status=new' filter; document why blanket is_designer promote is unsafe (LLM mis-labels Shopify/Twitter/vendor reps as designers)","body":""},{"hash":"cca9c68","date":"2026-05-12 15:40:39 -0700","author":"Steve Abrams","subject":"starsofdesign: +40 firms (20 arch, 20 interior) — Selldorf, Steven Holl, COOKFOX, KieranTimberlake, Lake|Flato, Tod Williams Billie Tsien, Brad Ford ID, Kara Mann, Jonathan Adler, Carrier and Company, S.R. Gambrel etc.","body":""},{"hash":"10f3ae0","date":"2026-05-12 15:34:57 -0700","author":"Steve Abrams","subject":"starsofdesign: sort dropdown on /firms + /clients (per standing sort+density rule)","body":""},{"hash":"f55b3a7","date":"2026-05-12 15:23:02 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): sitemap.xml now includes /firms/:slug × 184 + /clients/:slug × 61 + /feed (403 URLs total)","body":""},{"hash":"23833cd","date":"2026-05-12 15:21:58 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): add /firms /clients /feed to global nav","body":""},{"hash":"adfe6ed","date":"2026-05-12 15:21:38 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): /firms tier — listFirms+getFirm lib, /firms+/firms/:slug routes, firms.ejs + firm.ejs views","body":""},{"hash":"ddd574f","date":"2026-05-12 15:19:05 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): expand top-us-firms seed +39 (arch 39, interior 39); strip Schumacher per vendor rule","body":""},{"hash":"6f7c2dd","date":"2026-05-12 12:00:09 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): bring Big Red back — widget tag + CSP whitelist for :9935","body":"Big Red avatar launcher was missing from StarsOfDesign + AsSeenInMovies\neven though the big-red server at :9935 was up and healthy. Earlier\nembed work only touched corkwallcovering + factory-floor.\n\nviews/partials/footer.ejs — adds the <script src=:9935/widget.js> tag\nin retail mode before </body>. Widget self-mounts to body as a fixed\nlower-right launcher; goes green when chat is live.\n\nserver.js helmet CSP — whitelists http://localhost:9935 + ws://localhost:9935\nacross script-src, img-src (avatar), connect-src (fetch + WebSocket),\nand frame-src (chat iframe). Drops these entries when the named tunnel\nbigred.agentabrams.com goes live."},{"hash":"da8a343","date":"2026-05-12 11:47:12 -0700","author":"Steve Abrams","subject":"feat(starsofdesign/deep-recon-v2): --windows={v1|v2|all} for historical sweeps","body":"Adds the 5y-7y and 7y-10y date windows to scrapers/george-deep-recon.js,\ngated by --windows=v2 (only the deep sweep) or --windows=all. Default\nremains the v1 windows (last-30d through 3y-5y) so prior automation\nbehavior is unchanged.\n\nRun results (--windows=v2 --max-per-q=50):\n  messages_scanned       350\n  clients_new_or_bumped  213\n  vendors_caught          71\n  no_email                66\n\nNet new historical DW clients (2018-2021 traffic to info@) bumped\nsod_email_candidates 793 → 884."},{"hash":"5927cb6","date":"2026-05-12 11:18:47 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): /feed phone UI — vertical scroll, Like + Comment","body":"Facebook / X.com-style mobile-first feed for the /clients tier. One\ndesigner per card stacked vertically, anonymous like (cookie-tracked),\nanonymous comment thread (name + body required, optional email).\n\nSchema (idempotent CREATE TABLE):\n  sod_likes      (designer_id, anon_id, created_at)\n                 UNIQUE (designer_id, anon_id) — one like per visitor per post\n  sod_comments   (designer_id, parent_id, author_name, author_email, body,\n                  is_hidden, anon_id, ip_hash, created_at)\n                 partial index on (designer_id, created_at DESC) WHERE NOT hidden\n\nlib/feed.js\n  - ensureAnonId(req,res) — sets long-lived sod_anon cookie (12 hex,\n    httpOnly=false so client JS can show liked-state immediately)\n  - feedPage({anonId, limit, offset}) — pulls designers with embedded\n    like_count, comment_count, i_liked (boolean per caller), grouped\n    links. Ordered by created_at DESC (newest first).\n  - commentsFor(designerId, limit) — top-level comments only for v1\n  - toggleLike — flips like state, returns new count + liked boolean\n  - postComment — name+body validation, 1500-char body cap, 5/hour\n    burst limit per anon, simple spam regex\n                 (viagra/crypto/loan/casino/etc.), max 2 URLs allowed\n\nRoutes (in routes/public.js):\n  GET  /feed                              — page=N (15 per page)\n  GET  /api/clients/:id/comments          — JSON list, top-level comments\n  POST /api/clients/:id/like              — toggle, JSON {liked, like_count}\n  POST /api/clients/:id/comment           — JSON {author_name, author_email?, body}\n\nviews/public/feed.ejs — max-width 520px column. Each card:\n  - Avatar circle (made-with-ai fallback for unclaimed)\n  - Name (links to /clients/:slug) + firm/role · city,state\n  - Areas-served chips\n  - Site/LinkedIn/Instagram link row\n  - Like / Comment / Share action row\n  - Collapsed comments section that lazy-loads on toggle\n  - Inline comment form (name+email+body, optimistic insert on success)\n  - At <520px width, posts go edge-to-edge (FB-style mobile)\n\nserver.js — added cookie-parser middleware.\n\nSmoke-tested:\n  GET  /feed                                → 200, 15 posts rendered\n  GET  /api/clients/45/comments             → 200, JSON list\n  POST /api/clients/45/like                 → 200 {liked:true, like_count:1}\n  POST /api/clients/45/comment              → 200, comment landed\n\nv1 scope cuts (defer to next ticks if Steve wants):\n  - Nested replies (parent_id is in schema but not used yet)\n  - Infinite scroll (page=N + Older/Newer pagers for now)\n  - User accounts (anon cookie is enough)\n  - Notifications + email-on-comment\n  - Designer claim flow to enable Verified badge on their post"},{"hash":"1f43d2b","date":"2026-05-12 10:39:27 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): regex pass extracts URLs from stored email signatures","body":"scrapers/extract-links-from-signatures.js — cheap free pass over every\nsod_email_candidates.last_signature blob. Regex pulls all http(s) URLs,\nclassifies as:\n  linkedin   linkedin.com/in/* OR linkedin.com/company/*\n  instagram  instagram.com/*\n  houzz      houzz.com/*\n  pinterest  pinterest.com/*\n  facebook   facebook.com/*\n  twitter    twitter.com/* or x.com/*\n  firm-site  any URL whose host matches the candidate's email domain\n             (so jane@studio.com signature with https://studio.com/about\n              gets recognized as firm-site)\nOtherwise dropped (calendar/mailer/asset hosts are noise without curation).\n\nIdempotent via ON CONFLICT DO NOTHING on sod_links. Designer-attached\nwhen sod_designers.primary_email matches the candidate, otherwise\ninserted as orphan (designer_id=NULL, firm_id=NULL) waiting to be\nattached when the designer's profile gets materialized in a later\nsig-extract pass.\n\nFirst run results:\n  scanned 158 signatures\n  inserted 24 new links\n  by-kind: firm-site=11  twitter=5  instagram=4  facebook=2  houzz=1  pinterest=1\n  dupes=3\n\nWhy this matters — the LLM in extract-from-signatures captures 1-2 URLs\nper signature at most (usually just the primary website or the linkedin),\nbut real designer signatures often include 4-6 URLs across Instagram /\nHouzz / personal site / portfolio host / etc. This deterministic pass\nsweeps up the rest for free.\n\nTop-40 firm LinkedIns from seed-top-firms.js confirmed still in place\n(40 LinkedIn URLs at firm_id level for Gensler/HOK/Kelly Wearstler/etc.).\nDesigner-level LinkedIn coverage is the next gap — needs Google CSE\nkeys to resolve via 'site:linkedin.com/in NAME FIRM' search. Steve does\nnot have GOOGLE_CSE_KEY / GOOGLE_CSE_ID registered in secrets-manager\nyet; tick blocked on Steve providing those.\n\nBackground drains running:\n  PID 59491 — asseeninmovies wikidata bg4 (15k movies, popularity-sorted)\n  PID 59492 — starsofdesign sig-extract pass 4 (limit=200)"},{"hash":"feeab56","date":"2026-05-12 10:32:01 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): /clients route — surface real DW client designers in the directory","body":"New tier in the directory:\n  /designers          editorial 150-name JSON marquee (Kelly Wearstler etc.) — unchanged\n  /clients            NEW — materialized real DW client designers from\n                      sig-extract + Gmail recon\n  /clients/:slug      NEW — individual client profile page\n\nFiles:\n  lib/clients.js         PG query layer. listClients({q,area}) does the\n                         sod_designers ↔ sod_designer_firm ↔ sod_firms ↔\n                         sod_links join, groups links by kind (linkedin /\n                         firm-site / instagram), rolls up city/state/areas\n                         preferring firm's over designer's. getClient(slug)\n                         fetches one + its links.\n  routes/public.js       Adds GET /clients (list, search by q + area filter)\n                         and GET /clients/:slug (detail). 80-char slug regex\n                         guard. PG queries via pg Pool.\n  views/public/clients.ejs    List page. Same card pattern as /designers but\n                              with area chips + LinkedIn/Website/Instagram\n                              link row. Hero says 'The designers and firms\n                              who actually buy from Designer Wallcoverings.'\n  views/public/dwclient.ejs   Detail page. Headshot (made-with-ai fallback)\n                              + name + VERIFIED chip (when claimed) + role +\n                              city/state + areas-served tags + Links list +\n                              Claim CTA + Firm section.\n\nGotcha codified to memory (feedback_express_ejs_client_local_reserved.md):\n  Passing { client: c } to res.render silently shadows EJS's include()\n  helper via with(locals). Symptom: 'include is not a function' at line 1\n  of any <%- include('...') %>. Took 6 restarts + minimal-template\n  bisection to find. Workaround: use any local name other than 'client'.\n  Renamed to 'profile' here.\n\nSig-extract pass 3 (--limit=150, ran in background during this UI work):\n  ok=74  withName=11  withFirm=14  skipped=76\n  Real sod_designers: 24 → 31 (+7)\n  New surfaces include: Putri Sarnadi @ Studio Left, Jesus Raya @ Tokiwa\n  Tsusho (Long Beach CA, with 2 areas served — the new field working).\n\nSmoke-tested live:\n  GET /clients               → 200, 21 designer cards\n  GET /clients/jo-hempelmann → 200, Katie's Wallpaper Installation, LA\n  GET /clients/jerry-sanflippo → 200, Fos Interiors, Palm Springs"},{"hash":"fcd8cd8","date":"2026-05-12 10:11:23 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): areas_served + PII guardrails + sig-extract pass 2","body":"Per Steve's correction 2026-05-12: 'city and state ok, no zip, no\nstreet address, and AREAS THEY SERVE.'\n\nSchema change (idempotent ALTER):\n  ALTER TABLE sod_designers ADD COLUMN areas_served TEXT[];\n  ALTER TABLE sod_firms     ADD COLUMN areas_served TEXT[];\n  CREATE INDEX (gin) on each.\n\nLLM extractor prompt rewrite (extract-from-signatures.js SYSTEM):\n  - Asks for {full_name, firm_name, firm_city, firm_state, firm_country,\n    firm_role, areas_served[], linkedin_personal, linkedin_company,\n    website, instagram}.\n  - Explicit rule: NEVER extract street address, NEVER extract zip,\n    NEVER extract phone. Phone is opt-in via claim flow, not scraping.\n  - For areas_served: look for 'Serving:', 'Areas:', 'We cover', or\n    parenthetical region phrases. Otherwise safely infer from city+state.\n  - If a signature contains '123 Main Street, Suite 4B, Brooklyn, NY 11201'\n    we keep 'Brooklyn'/'NY' only — never the street or zip.\n\nServer-side PII cleanup of LLM output (in case the model echoes one\nthrough anyway):\n  - firmCity scrubbed of any 'N <street> Street/Ave/etc.' patterns\n  - firmCity scrubbed of zip patterns (\\d{5} and \\d{5}-\\d{4})\n  - areas_served filtered: no leading digits, no zip-shaped entries,\n    no <2-char or >60-char entries, dedupe + cap at 12.\n\nHallucination hardening (carryover from Steve's earlier note about\n'Designer Wallcoverings' false firm):\n  - Reject bare first-name without local-part match: gemma3:12b\n    repeatedly returns 'Samantha' / 'Steve' / 'Sarah' when it can't find\n    a real name. New guard requires the first-name's first 4 chars to\n    appear in the email local-part.\n  - Reject firms that are just a website ('domaineinteriors.com').\n\nTwo-phase candidate select for the pass:\n  Priority 1 — empty display_name (need name to materialize designer)\n  Priority 2 — has name but no sod_designers row yet (firm/role enrichment)\n\nPersisted in this pass:\n  city / state_or_region / country / areas_served on both sod_firms and\n  sod_designers. Instagram URL gets its own kind='instagram' row in\n  sod_links (in addition to existing linkedin / firm-site kinds).\n\nSig-extract pass 2 results (limit=120):\n  ok=51  withName=9  withFirm=13  skipped=69\n  9 new real designers materialized after hardening cleanup:\n    Lynsey Humphrey  · Alder & Tweed\n    Lara Heffernan   · Berg Development     · Brookfield\n    Ashley Kruchoski · Katiebassett         · Minneapolis\n    Jerry Sanflippo  · Fos Interiors        · Palm Springs\n    Amy L Young      · Eric Hedlund Design  · Coeur d'Alene\n    (+ 4 noise rows wiped pre-hardening)\n\nMemory codified: feedback_designer_directory_pii_policy.md.\n\nLedger now:\n  sod_designers (real)   24   (was 15)\n  sod_firms             104   (was 92)\n  sod_links              86   (was 83)\n  candidates_real       562\n  candidates_with_name  427\n\nBackground jobs:\n  PID 29524 asseeninmovies wikidata bg3 (10k more, 7873/290945 done)"},{"hash":"7dd41c8","date":"2026-05-12 10:08:23 -0700","author":"Steve Abrams","subject":"video gallery — 75 verified YouTube videos from AIA/ASID/AD/House Beautiful/Studio McGee + firm channels, cross-linked to 66 designer profiles via designer_slug, new /videos route with category+source+search filters, per-profile 'Watch X' rail, sitemap inclusion, fixed regex literal U+2028/9 EJS compile bug","body":""},{"hash":"553c74a","date":"2026-05-12 09:38:29 -0700","author":"Steve Abrams","subject":"security hardening: helmet (CSP/HSTS/X-Frame-Options/Referrer-Policy/Permissions-Policy/COOP), express-rate-limit (200/min/IP), JSON-LD <script> escape, javascript: URL filter, designerwallcoverings.com URL allowlist for preferred_dw, slug regex validation, q clamp to 120 chars, rel=noopener noreferrer everywhere, error message hardcoded","body":""},{"hash":"a7bdb25","date":"2026-05-12 09:30:59 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): DEEP George info@ recon (7-window × 8-template walk)","body":"scrapers/george-deep-recon.js — date-sliced sweep across info@\ndesignerwallcoverings.com. Seven date windows (last-30d, 30d-90d,\n90d-180d, 180d-1y, 1y-2y, 2y-3y, 3y-5y) × eight query templates:\n\n  info-replied-to     from:info@dw  — outgoing reply, designer in To:\n  info-replied-steve  from:steve@dw — same pattern for Steve's DW account\n  sample              subject:sample|memo|swatch|free-sample\n  trade               subject:trade|wholesale|trade-account|program\n  quote               subject:quote|pricing|custom|net-price\n  project             subject:project|residence|hospitality|hotel|...\n  role-keyword        subject:designer|interior|residential|specifier|...\n  mailer-reply        subject:Re:|RE: in:inbox  (designer replied to a\n                                                 DW campaign)\n\nFor outgoing-info@ messages (info-replied-to / -steve), pulls the full\nmessage via /api/messages/:id and reads the To: header — that's where\nthe actual designer lives. For incoming, parses From:.\n\nEvery email checked against scrapers/vendor-blocklist.json before\nlanding as is_designer.\n\nResult of this run (max-per-q=50):\n  messages_scanned         2306\n  clients_new_or_bumped    1459\n  vendors_caught            554\n  no_email                  293\n\nBlocklist additions for vendors that slipped this sweep (28 more\ncaught + flipped): bmwallpaper, miltonandking, wolfgordon, wolf-gordon,\nmindtheg, cowtan, surfaceprint, em.spoonflower, spoonflower, fypon,\nquintushome, brewp, lelievreparis, shawcontract, patcraft, interface,\ntarkett, milliken, mannington, armstrong, carnegiefabrics,\nrobertallendesign, lee-jofa, thibautcontract, stoutindustries,\npkaufmann, rmcoco, barrowfabrics, duralee, swavelle, knolltextiles,\ncarnegiexorel, wallpaperworld, wallpaperboulevard, wallpaperdirect,\nrebelwalls, graham-brown, anewall, wallsneedlove.\n\nAlso widened extract-from-signatures.js to fall back through accounts:\ninfo → steve-office. Many candidates only have signal in steve@dw\nmailbox not info@.\n\nFinal ledger:\n  candidates_total          793  (was 343, +450)\n  real_designers            585  (was 200, +385 net)\n  real_designers_with_name  412  (was 77, +335 — direct From: parsing\n                                  caught most of them)\n  real_designers_empty_name 173  (still need sig-extract or manual)\n  vendors_quarantined       150  (was 86, +64 — blocklist working)\n  noise                      58\n\nTop new domains: gmail.com (75, mostly real interior designers using\npersonal gmail addresses), yahoo.com (9), hotmail.com (6), aol.com (6),\nlbiboyd.com, apbuildersusa.com, dugganla.com, rudindonner.com, etc.\n\nSig-extract --limit=100 running in background (PID 94284) on the 173\nempty-name pool. Wikidata bulk drain 10k batch 2 also kicked for\nasseeninmovies (PID 96412)."},{"hash":"f5688ed","date":"2026-05-12 09:19:41 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): signature-block LLM extraction → Jo Hempelmann shipped","body":"scrapers/extract-from-signatures.js — for each candidate with empty\ndisplay_name (148 such), pull most-recent George info@ message body,\nslice to signature region (after -- delimiter or last 14 non-empty\nlines), gemma3:12b in JSON-mode extracts:\n  full_name, firm_name, firm_city, firm_role,\n  linkedin_personal, linkedin_company, website, phone.\n\nThen materializes:\n  - UPDATE sod_email_candidates.display_name + last_signature\n  - INSERT sod_designers + sod_firms with role/city\n  - INSERT sod_designer_firm join with extracted title\n  - INSERT linkedin/website URLs into sod_links (kind='sig-extract')\n\nTwo important guardrails:\n  - Length sanity check on name (3-80 chars) and firm (3-120 chars).\n  - Hard refusal of 'Designer Wallcoverings*' as firm_name — that's\n    DW's OWN outgoing signature embedded in the contact's reply, NOT\n    their firm. LLM grabbed it from rpaschall@pinecrestinsurance.com,\n    kcfox007@gmail.com, angela@handwerk.company first run; guard\n    added, hallucinated 'Designer Wallcoverings and Fabrics' firm row\n    deleted.\n\nFirst-pass result on 25 highest-signal empty-name candidates:\n  jo@katieswallpaper.ca → Jo Hempelmann, Katie's Wallpaper Installation\n    Inc., Los Angeles, Wallpaper Installation Project Manager\n  export@bmwallpaper.com → Jenny Liu (name only — no firm in sig)\n  jcamacho427@gmail.com → Jerry Camacho (later flagged is_noise —\n    Steve's accountant, not a designer)\n  rpaschall / kcfox007 / angela@handwerk → hallucinated DW (guarded\n    after run)\n  17 of 25 had no message in info@ (personal-Gmail-only contacts)\n\nManual cleanup tied to this tick (27 candidates flipped is_noise):\n  Family: nataliaabrams (Steve's wife), bobfox729, billfox92117,\n  jiabramsla, dylan.r.fox, kcfox007 (all relatives).\n  Service providers / vendors: hrftax53 (accountant), jcamacho427\n  (accountant), tom@osbornepartners (financial advisor), michael@\n  montereyrentals (landlord), mike@5to1web (IT vendor), rpaschall@\n  pinecrestinsurance (insurance), angela@handwerk, email@rentmanager,\n  circulation1@labj*, jimdultz@comcast (vendor),\n  carole.fabrics@carolefabrics, helen.brier.b6@mail.toray (Toray),\n  kundservice@photowall (Photowall), noreply@zopim (transactional),\n  natalia/sabrina@studentdebtcrisis (wife's nonprofit), chounanian\n  (ex-DW VP), browntwn / mitz / greg_perry (personal).\n\nLedger after this tick:\n  real_designer_candidates  200  (was 225 — 25 dropped to noise)\n  noise_total               58   (was 31)\n  vendors_total             84\n  sod_designers (real)       4   (was 1 — Jo Hempelmann added)\n  sod_designers (reps)      40\n  sod_firms_total           82   (+1 Katie's Wallpaper, hallucinated DW removed)"},{"hash":"4ced542","date":"2026-05-12 08:58:00 -0700","author":"Steve Abrams","subject":"expand to 150 designers — added 62 active US firms with firm websites, IG, and source credits to AD/House Beautiful/Elle Decor/Veranda/Robb Report/Luxe/New England Home; designer pages now render 'Find the firm' + 'Sources & press' blocks","body":""},{"hash":"41d434d","date":"2026-05-12 08:48:59 -0700","author":"Steve Abrams","subject":"feat(starsofdesign): pull DW client contacts from George info@ Gmail","body":"scrapers/george-info-recon.js — walks 6 client-flavored Gmail searches\non info@designerwallcoverings.com via the now-portable George agent\n(commit a4d0448 fixed the Mac/Kamatera path issue). Each search target\nis a different DW-client signal:\n\n  shopify-order      from Shopify order-notification mailers; extracts\n                     customer billing email FROM THE BODY (not the\n                     From: shopifyemail.com header). signal=0.97\n  trade-signup       subject:trade|wholesale.                signal=0.93\n  sample-request     subject:sample|memo|swatch.            signal=0.85\n  quote-request      subject:quote|custom|pricing.          signal=0.82\n  project-inquiry    subject:project|spec|residence|hosp.   signal=0.78\n  designer-subject   subject:interior|residential|hosp.     signal=0.78\n\nEvery email is checked against scrapers/vendor-blocklist.json before\nlanding as is_designer — vendors get logged at status='is_vendor' (audit\ntrail) with score=0 so they can't be promoted later.\n\nTick results — 480 messages scanned across 6 queries:\n  clients (new is_designer)   212\n  vendors filtered             36\n  self (info@dw)               151\n  no extractable email         81\n\nPlus 3 new vendor domains caught + added to blocklist + flipped:\n  koroseal.com, innovationsusa.com, kneedlerfauchere.com,\n  oalusa.com, archiproducts.com, elitis.us, ccsend.com,\n  billtrust.com, battagliaincmarble.ccsend.com → flipped 16 candidates\n  to is_vendor.\n\nFinal ledger after George recon + cleanup:\n  candidates_total          343  (was 236, +107 net new clients)\n  real_designer_candidates  225\n  vendors_filtered           86\n  noise                      31\n  sod_firms                  81  (top-40 seed + 40 reps + 1)\n\nReal DW-client surfaces newly captured (top by domain):\n  dugganla.com         (Duggan Interiors LA)\n  apbuildersusa.com    (AP Builders)\n  rudindonner.com      (Rudin & Donner NYC)\n  dkmdesignstudio.com  (DKM Design Studio)\n  egg-and-dart.com     (San Diego interiors)\n  davidsonlondon.com   (Davidson London)\n  + 64 more.\n\nNext tick: signature-block parser to extract firm/role/city from each\ncandidate's most-recent message body, then promote to sod_designers."},{"hash":"7022cb6","date":"2026-05-12 08:48:37 -0700","author":"Steve Abrams","subject":"expand directory: 26 → 88 designers (added historic foundational + mid-century + contemporary diverse voices, era_sort sorted)","body":""},{"hash":"ecb46aa","date":"2026-05-12 08:38:41 -0700","author":"Steve Abrams","subject":"deploy.conf for kamatera deploy script (port 9928 health check)","body":""},{"hash":"4e7adb5","date":"2026-05-12 08:37:26 -0700","author":"Steve Abrams","subject":"fix(starsofdesign): vendors are NOT clients — apply blocklist, seed top-40 US firms","body":"Steve correction 2026-05-12:\n  paulmontgomery, desima, newwall, beverlyhillswallpaper are VENDORS\n  (manufacturers DW buys from), NOT designer clients. And\n  beverlyhillswallpaper.com is dead — never use.\n\nThree changes:\n\nscrapers/vendor-blocklist.json — codified the rule. Every domain with a\n~/.claude/skills/*-scraper-manager/ skill is by definition a vendor we\nbuy from. Plus the wider blocklist from Steve's prior corrections\n(Kravet, Schumacher, Scalamandre, Fabricut, Materialbank, Donghia,\nStroheim, Maharam, Pollack, Phillip Jeffries, Cole&Son, Romo, Osborne&\nLittle, Thibaut, Pierre Frey, Mind the Gap, etc.).\n\nscrapers/apply-vendor-blocklist.js — bulk-flips existing candidates that\ngot auto-promoted to status='is_designer' in tick 1+2. Removes any\nsod_designers/sod_firms rows whose primary_email is on the blocklist.\nIdempotent.\n\n  Result: 43 candidates → is_vendor, 1 → is_noise (beverlyhillswallpaper),\n  3 wrongly-created sod_designers rows deleted.\n\ndata/top-us-firms.json + scrapers/seed-top-firms.js — deterministic\nseed of 40 top US architecture (Gensler / HOK / Perkins&Will / SOM /\nKPF / NBBJ / HKS / Cannon / HDR / Stantec / Corgan / Page / Gresham\nSmith / Cooper Carry / Leo A Daly / Pickard Chilton / DS+R / Snohetta /\nCallisonRTKL / AECOM) + 20 top US interior design (Kelly Wearstler /\nStudio Sofield / Nate Berkus / Studio McGee / Shamshiri / Magnolia /\nChampalimaud / Yabu Pushelberg / Roman&Williams / Rockwell / Bunny\nWilliams / Victoria Hagan / Markham Roberts / Steven Gambrel / Robert\nStilin / Ken Fulk / Suzanne Kasler / Bilhuber / Miles Redd / Sheila\nBridges) firms. Inserts into sod_firms with public LinkedIn company\nslugs + firm-website URLs in sod_links. Logos stay made-with-ai\nplaceholders until each firm claims its profile.\n\nLedger after correction:\n  sod_firms_total            81  (+40 from seed)\n  sod_firms_50plus           18\n  sod_links_linkedin         40\n  sod_links_firmsite         40\n  candidates_is_vendor       48  (was 5)\n  candidates_is_designer    156  (was 200 — vendors flipped out)\n  candidates_total          236\n\nMemory codified: feedback_starsofdesign_clients_not_vendors.md.\n\nBLOCKER surfaced for Steve:\n  George Gmail agent at 127.0.0.1:9850 has hasCredentials=false.\n  Need OAuth consent flow run so George can read DW info@ inbox.\n  That inbox (Shopify order notifications, trade signups, sample\n  requests) is where DW's actual designer CLIENTS live — much higher\n  signal than Steve's personal Gmail (which is heavy on vendor sales\n  reps emailing him for buy-side relationships)."},{"hash":"51fa79b","date":"2026-05-12 08:36:12 -0700","author":"Steve Abrams","subject":"gucci hero anatomy — logo UL + hamburger UR + huge wordmark/tagline hero with star-field ground (per standing rule)","body":""},{"hash":"fed7183","date":"2026-05-12 08:19:29 -0700","author":"Steve Abrams","subject":"feat(starsofdesign/tick2): local-LLM triage on remaining candidates","body":"scrapers/triage-candidates.js — drives Ollama (gemma3:12b by default)\nin JSON-mode to label each sod_email_candidates row as one of:\nis_designer / is_firm / is_vendor / is_press / is_noise. Writes both\nthe status enum and a human-readable triage note (label, confidence,\nshort reason) into notes via CONCAT_WS so the original signal-score\nnote is preserved.\n\nWhy gemma3:12b not qwen3:14b — first pass at qwen3:14b returned empty\ncontent for all 39 rows because its <think> mode swallows the entire\nnum_predict=200 budget before producing JSON. Switched default to\ngemma3:12b which is ~0.5s/row and JSON-stable.\n\nDeterministic short-circuit for masked customer rows — the 25 digital_\nsample_orders rows wear synthetic customer{1..25}@example.com addresses\n(privacy-mask in dw_unified.digital_sample_orders); those skip the LLM\nentirely and resolve straight to is_noise.\n\nTick 2 results on 39 still-new candidates:\n  is_designer  2  (paulmontgomery.com, beverlyhillswallpaper.com)\n  is_firm      2  (desima.com, newwall.com)\n  is_vendor    5  (priti, printmurals, fahertybrand, toplinegraphics, profectionpainting)\n  is_press     0\n  is_noise    30  (25 masked-customer + studentdebtcrisis/filmla/trueaccord/etc.)\n  errors       0\n\nStatus snapshot post-tick:\n  candidates_total      236   (was 236)\n  status_new              0   (was 39 — fully drained)\n  status_is_designer    200\n  status_is_firm          1   (4 firm-labeled rows kept their LLM label after\n                                promote-candidates.js attempted name extraction)\n  status_is_vendor        5\n  status_is_noise        30\n\nKnown gap (for tick 3): promote-candidates.js can't materialize sod_designers\n/sod_firms rows when display_name is empty — guessNameAndFirm() returns\n{person:null, firm:null} and produces designer=null firm=null. Tick 3's\nsignature-block parser fixes this by extracting names from each\ncandidate's last-seen email signature. Tick 2 deliberately doesn't try to\nbackfill name-from-domain because that's tick 3's job and the cleaner\nfix."},{"hash":"1542658","date":"2026-05-12 08:02:34 -0700","author":"Steve Abrams","subject":"feat(starsofdesign/tick1): Gmail + Shopify recon → 236 candidates / 44 designers / 41 firms","body":"Two-source ingest into sod_email_candidates per Steve's spec.\n\nGmail side (scrapers/gmail-recon-ingest.js):\n- Sub-agent walked steveabramsdesigns@gmail.com via claude.ai Gmail MCP,\n  scoping queries to 'from:me newer_than:5y' + 8 design-flavored variants\n  going back to 2017. 279 reply threads scanned, 203 unique non-Steve\n  participants extracted to /tmp/sod-candidates.ndjson.\n- Drop-list filters noreply/notifications/SaaS-vendor noise (~25 domains).\n- Receiver script computes signal_score = reply_ratio*0.85 + domain_bonus\n  (design/studio/interiors/architect = +0.20).\n- 190/203 land at signal >= 0.8.\n\nShopify side (scrapers/shopify-recon.js) — added per Steve 'also use shoppy database':\n- dw_unified.trade_signups (7 rows): verified DW trade-program signups.\n  Land at signal=0.95, status='is_designer' (auto-promoted, skip triage).\n- dw_unified.digital_sample_orders (25 unique): paid sample customers.\n  Land at signal=0.70, status='new' for later triage.\n- designer_representatives.designer_reps (40 rows): manufacturer rep\n  agencies. Direct-insert into sod_designers + sod_firms (skip candidate\n  stage entirely — they're already curated) with role='Manufacturer Rep'\n  and a 'made-with-ai' headshot.\n\nAuto-promotion (existing scrapers/promote-candidates.js, --min=0.85):\n- 197/236 candidates flipped to status='is_designer' or 'is_firm'.\n- Most Gmail rows have empty display_name (MCP returns bare emails for\n  many threads); follow-up tick will do get_thread name extraction.\n\nIdempotent: all INSERTs use ON CONFLICT with GREATEST(signal_score) and\nLEAST/GREATEST first_seen/last_seen so re-runs only enrich.\n\nLedger after this tick:\n  sod_email_candidates    236  (197 promoted, 39 awaiting triage)\n  sod_designers            44\n  sod_firms                41\n  sod_designer_firm        41\n  sod_links                 0  (designer_reps have no URLs/emails on file)"},{"hash":"bde4448","date":"2026-05-12 07:37:19 -0700","author":"Steve Abrams","subject":"starsofdesign tick0: PG schema + AI placeholder + scaffold","body":""},{"hash":"70b7912","date":"2026-05-10 09:41:41 -0700","author":"Steve Abrams","subject":"add /sitemap.xml route + JSON-LD Person schema on designer detail","body":"- robots.txt already advertised /sitemap.xml; route was missing (404).\n  Now serves all 4 static pages + every designer slug with lastmod/priority.\n- Detail pages now emit schema.org Person JSON-LD (name, bio, styles via\n  knowsAbout, city via workLocation, DW pairings as subjectOf) so SERP\n  cards get rich-result eligibility."},{"hash":"aa14a02","date":"2026-05-10 03:50:34 -0700","author":"Steve Abrams","subject":"initial scaffold + GA gtag (G-YCT0F2Q692) + 27 designer profiles","body":""}]}