{"slug":"starsofdesign","total":108,"limit":100,"offset":0,"since":null,"commits":[{"hash":"58c5d31","date":"2026-09-10 10:06:40 -0700","author":"Steve Abrams","subject":"Keep Stars of Design sitemaps available without database","body":""},{"hash":"32994ed","date":"2026-09-10 00:54:10 -0700","author":"Steve Abrams","subject":"Allow AdSense through public CSP","body":""},{"hash":"2297cd9","date":"2026-09-09 23:57:21 -0700","author":"Steve Abrams","subject":"Revert \"Add auto-updating All News Daily embedded Shorts section to /videos\"","body":"This reverts commit 8ffbfd96fa23bb3890e0ee80c823fc22b3ef5c63."},{"hash":"2dd03c4","date":"2026-09-09 23:57:21 -0700","author":"Steve Abrams","subject":"Revert \"Mac2 sync-and-push job for the /videos All News Daily section (keeps it auto-updated on prod)\"","body":"This reverts commit 88cd92f2805ec0f32cdc97fd8ae742cc85bc9696."},{"hash":"1072d8b","date":"2026-09-09 23:51:11 -0700","author":"Steve Abrams","subject":"promote-candidates: enumerate columns instead of SELECT * (satisfies pre-deploy PII-guard lint)","body":""},{"hash":"88cd92f","date":"2026-09-09 23:46:39 -0700","author":"Steve Abrams","subject":"Mac2 sync-and-push job for the /videos All News Daily section (keeps it auto-updated on prod)","body":""},{"hash":"8ffbfd9","date":"2026-09-09 23:41:47 -0700","author":"Steve Abrams","subject":"Add auto-updating All News Daily embedded Shorts section to /videos","body":"- scripts/sync-allnewsdaily-videos.mjs pulls the channel uploads via the\n  YouTube Data API (raw fetch, refresh-token flow) and writes\n  data/allnewsdaily-videos.json, filtered to our 'All News Daily —' Shorts\n- lib/data.js loadAllNewsDaily() (mtime-cache, missing-file -> [])\n- /videos route passes allNewsDaily to the template\n- videos.ejs renders a new top section of 9:16 embedded iframe players;\n  hides gracefully when empty. Existing 75-video design gallery unchanged.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"572966b","date":"2026-09-09 16:02:18 -0700","author":"Steve","subject":"TK-11341: add AdSense Auto-Ads loader + ads.txt (revert to remove ads)","body":""},{"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":""}]}