{"slug":"dw-domain-fleet","total":136,"limit":100,"offset":0,"since":null,"commits":[{"hash":"45feb4c","date":"2026-09-26 09:19:42 -0700","author":"Steve Abrams","subject":"TK-12333: remove Scalamandre from showroom-vendors (Steve 2026-09-26: Scalamandre is NOT a showroom line)","body":"Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_018HHVEexYEwkT1HYJzkvv66"},{"hash":"0c9db1c","date":"2026-09-26 08:23:03 -0700","author":"Steve Abrams","subject":"TK-11860: sync showroom-vendors snapshot to canonical fix-live-board@9bcd81c (+Jeffrey Stevens, Mind the Gap, Sandberg, Scalamandre)","body":"Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_018HHVEexYEwkT1HYJzkvv66"},{"hash":"fc5a721","date":"2026-09-23 14:49:26 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-23T14:48:42 (1 data files) — data/hero-allocation.json","body":""},{"hash":"5e945b3","date":"2026-09-16 09:44:39 -0700","author":"Steve Abrams","subject":"deploy-articles.sh: fail-flag + full-fleet verify so a partial deploy exits non-zero","body":"Review-fix: rsync/pm2/save failures now set fail=1; run returns it and the\nscript does exit \"${PIPESTATUS[0]}\" so the status survives the | tee. Verify\nwidened from 4 hardcoded sentinels to all 43 sites (/articles 200 + non-empty);\nmulti-word content sentinel matched token-by-token to kill false OLD. POSIX-sh safe.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01TJ94XCE5W2H1FXU2AqudVG"},{"hash":"e5dcb36","date":"2026-09-16 09:19:01 -0700","author":"Steve Abrams","subject":"content drip: +4 fresh article(s) — etciemurals.com, fabricfridays.com, fireratedwallcovering.com, handmadewallcovering.com","body":""},{"hash":"1d92019","date":"2026-09-14 10:40:06 -0700","author":"Steve Abrams","subject":"TK-11538: gridControls appendChild off the prototype (consistency with catalogPage)","body":"Closes the residual hardening sub-item. The titled bug (bare localStorage read\nthrowing in Safari private mode, killing sort+density) was already fixed in 8be45e2\nand is verified LIVE on the fleet. This 1-line change makes gridControls' card\nreorder use Node.prototype.appendChild.call(g,c) to match catalogPage's guarded\nform append. Defense-in-depth only: a <div>'s appendChild is not DOM-clobberable\nby its children the way a <form>'s named controls clobber the form. Rides the next\nnatural fleet deploy; no dedicated deploy warranted.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01RcYGzMCFaJXH6nSLLrL4EQ"},{"hash":"1decd1b","date":"2026-09-14 09:27:04 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-14T09:19:35 (4 data files) — sites/classawallcovering.json sites/commercialsalesreps.json sites/commercialwallcovering.json sites/designermagnetics.json","body":""},{"hash":"457ca42","date":"2026-09-12 20:05:21 -0700","author":"Steve Abrams","subject":"TK-11537: make helmet CSP explicit — useDefaults:false + declare script-src-attr 'none'","body":"server.js line 123 called helmet({contentSecurityPolicy:{directives:{...}}})\nwithout useDefaults:false, so helmet silently merged its default\nscript-src-attr 'none' into the served header — a directive present in the\nresponse but nowhere in source ('we serve a directive nobody wrote').\n\nSet useDefaults:false and declare scriptSrcAttr:['none'] explicitly so what we\nread is exactly what we serve. Verified byte-identical served CSP header\nbefore/after on both a non-monetize (cfafabrics) and monetize (1800wallcoverings)\nsite — only source honesty changes, served output unchanged.\n\nscript-src-attr 'none' kept load-bearing on purpose: it is one of the two things\ndefanging TK-11535's unescaped pager() injection. Do NOT relax it until pager()\noutput is escaped (TK-11535).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_014KpcSwnuz64tLuyDrDxSHc"},{"hash":"d20f426","date":"2026-09-12 20:03:38 -0700","author":"Steve Abrams","subject":"TK-11537: declare scriptSrcAttr 'none' explicitly so read==serve (no behavior change)","body":"helmet 8.1.0 with useDefaults:true was merging its default script-src-attr\n'none' into our CSP even though our directives never mentioned it — 'a\ndirective nobody wrote'. Declaring it explicitly makes the config honest\nwhile keeping the served policy identical (same directive set + values;\nonly the serialization position of script-src-attr changes, which is not\nsemantically significant in CSP). Chose this over useDefaults:false, which\nwould DROP script-src-attr 'none' entirely — that directive is load-bearing\n(defangs inline event handlers, see TK-11535 whose pager-escape fix 35ab401\nis committed but not yet deployed to prod). Preserving it means this change\ncannot arm a live XSS regardless of deploy ordering.\n\nProven byte-identical directive set via helmet 8.1.0 repro; node --check clean.\nReversible: git revert. Prod deploy to the 8 customer-facing sites is GATED.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_019JJhw5cpU5sR6VBM9ja7ow"},{"hash":"8be45e2","date":"2026-09-12 07:39:44 -0700","author":"Steve Abrams","subject":"TK-11538: guard gridControls() localStorage with try/catch (Safari private mode) + clamp saved density to range bounds","body":""},{"hash":"cbc272c","date":"2026-09-12 07:22:48 -0700","author":"Steve Abrams","subject":"chore: v1.0.2 (TK-11470 session close)","body":"Session shipped a bug fix (dead catalog sort control) plus a test artifact, so\nsemver patch. No lint autofixes applied — repo configures no eslint/biome, and\nboth review passes came back with no blocking findings.\n\nThe one real finding (a dead store in the pageshow handler that both reviewers\ncaught independently) was deliberately NOT applied here: shared/render.js is\nlive in production and deploy-fleet.sh rsyncs the working tree, so editing it\nwould only widen the prod/local gap. Handed to TK-11535, which is already\ntouching that file and has to redeploy anyway.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>"},{"hash":"35ab401","date":"2026-09-12 07:20:29 -0700","author":"Steve Abrams","subject":"TK-11535: encode sort in pager() href — close the unescaped-attribute injection (matches q)","body":""},{"hash":"b757902","date":"2026-09-11 13:27:52 -0700","author":"Steve Abrams","subject":"test(catalog): keep the TK-11470 sort proof harness in-repo","body":"Negative-tested: 0/40 on the pre-fix baseline, 40/40 on the fix, so it is a real\ndetector rather than a rubber stamp. README covers the HTTPS-front requirement\n(WebKit honours upgrade-insecure-requests on loopback) and the need to re-capture\nmodes.json whenever data/catalog.json changes.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>"},{"hash":"d0f8c70","date":"2026-09-11 13:11:08 -0700","author":"Steve Abrams","subject":"catalog sort: stop remembering the sort, so Back works and the dropdown can't lie (TK-11470)","body":"Steve's call after the red-team surfaced the tradeoff. The reviewed candidate made a\nremembered sort re-request from the server via location.replace. That fixed the old\ncosmetic lie but cost more than it bought: Back stopped undoing a sort (/catalog ->\n?sort=sku -> Back -> /catalog -> instantly redirected back to ?sort=sku, so a shopper\ncould never reach the unsorted grid), and it double-counted GA4 page_views on\nhollywoodwallcovering, the one site of the eight that has GA4 configured.\n\nSo /catalog no longer persists sort at all. The dropdown is always exactly what ?sort=\nsays, which makes the lie structurally impossible rather than merely corrected. Density\nis still remembered. Sorting itself is untouched and still fully works.\n\nAlso re-syncs the <select> to the URL on pageshow: browsers restore form-control state\nacross history navigation, so after Back the dropdown still read \"SKU A-Z\" above a\nNewest grid — the same lie by another route. My own new back-undoes-sort check caught\nthat, and it only goes green once grid AND dropdown both return to Newest.\n\nSame harness, same data: 0 PASS / 38 FAIL on the unfixed baseline, 40 PASS / 0 FAIL here\n(adds sort-not-remembered, dropdown-never-lies, no-redirect, back-undoes-sort).\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>"},{"hash":"95a4e5a","date":"2026-09-11 12:55:47 -0700","author":"Steve Abrams","subject":"fix(catalog): guard requestSubmit — Safari <16 would have re-killed the sort (TK-11470)","body":"Red-team catch on the previous commit. HTMLFormElement.prototype.requestSubmit is\nSafari 16+ (Sept 2022). On older WebKit the property is undefined, so .call() throws\ninside the change listener and the dropdown goes dead again — the exact bug b4b8158\nfixes, with a new cause, on precisely the browsers our Playwright engines are too new\nto catch.\n\nFeature-detects and falls back to submit(), still invoked off the prototype so a field\nnamed submit/requestSubmit can't clobber it.\n\nProven by simulating an old browser: with requestSubmit deleted from the prototype,\nsorting still navigates and re-orders correctly in both engines, 0 pageerrors. Same\nharness scores 0/38 on the unfixed baseline and 38/38 here.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01AyGLosyy49Y6TsyiwWQWnx"},{"hash":"b4b8158","date":"2026-09-11 12:30:06 -0700","author":"Steve Abrams","subject":"fix(catalog): sort dropdown was dead on all 8 funnel sites — CSP blocked its inline onchange (TK-11470)","body":"Helmet is configured without useDefaults:false, so it emits script-src-attr 'none'\neven though server.js's directives never mention it. That silently killed the sort\n<select>'s inline onchange=\"...submit()\" on every catalog page: picking a sort did\nnothing, the URL stayed /catalog, and the grid kept its default order. Server-side\nsorting was healthy the whole time — only the control was dead, so the feature was\nreachable only by hand-editing the URL.\n\nReplaces the inline handler with an addEventListener in the existing (CSP-allowed)\n<script> block, and while there:\n  - saved sort now actually re-orders via the server instead of only relabelling the\n    dropdown, which previously showed e.g. \"SKU A→Z\" over a newest-ordered grid\n  - a sort change resets to page 1 and preserves q= and any other query params\n  - localStorage reads/writes are try/caught so denied storage can't break the page\n  - saved density is range-clamped to the slider's 3..8\n  - form submit/appendChild are called via prototype to survive DOM clobbering from\n    a query param literally named submit or appendChild\n\nVerified with a negative-tested cross-engine harness (Chromium + WebKit, real HTTPS\nloopback because upgrade-insecure-requests breaks form GETs over plain http in\nWebKit): 2/36 PASS against the broken baseline, 36/36 PASS after, plus 26/26 on a\nsecond site. Real browsing shows 0 console errors.\n\nLocal adoption only — the Kamatera deploy stays gated (memo in pending-approval).\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01AyGLosyy49Y6TsyiwWQWnx"},{"hash":"6a1fcbb","date":"2026-09-11 12:28:11 -0700","author":"Steve Abrams","subject":"deploy-fleet: RSYNC_EXTRA_EXCLUDES, so one agent's approved deploy can't ship another's held work (TK-11463)","body":"This rsync ships the WORKING TREE, not HEAD. On a repo several agents share, that\nmeans any uncommitted in-flight file rides along silently under whoever's approval\nhappens to fire first. Concretely: TK-11463's approved deploy of caaf32b would have\npushed shared/render.js at ff3315d7 — TK-11470's sort candidate, which that ticket\nexplicitly holds ('production adoption/deployment remains gated').\n\n--exclude also exempts a path from --delete, so prod simply keeps its current copy.\nVerify by checksum that prod's copy is the one you want kept before excluding.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S5t2AhAGwcTJDdHdmfpr8Z"},{"hash":"caaf32b","date":"2026-09-11 11:28:50 -0700","author":"Steve Abrams","subject":"pull-catalog: flatten to the SELLABLE variant, not blindly variants[0] (TK-11463)","body":"DW puts the $4.25 memo Sample at position 1 on most products, so variants[0] is\nfrequently the sample and not the good being sold. Taking it verbatim recorded\nsku 'DWQW-61260-Sample' / price '4.25' for a roll that actually sells at $150.43\n— 903 products store-wide. That SKU is what the fleet card prints as the\npattern's identity (render.js card() data-sku and the 'Designer Wallcoverings ·\n<sku>' meta line, verified rendering live on malibuwallpaper.com/catalog), and\nthat price is what the Price up/down sort orders on, so those rows wore a\nsample's identity and sorted at $4.25 while selling for $150-$533.\n\nPrefer the first non-sample variant; fall back to variants[0] when EVERY variant\nis a sample, so the 3,585 genuinely sample-only products keep their correct\n-Sample SKU and $4.25 rather than being mislabelled the other way. The sample\ntest is anchored ((^|[-_ ])sample$ on SKU, or a variant titled exactly 'Sample')\nso 'Sampler' and 'Sample Book' are not swallowed.\n\nMeasured on a full re-pull against the live store: 903 rows change sku AND price,\n0 rows change only one (the diff is purely this fix, no unrelated drift). 731 of\nthe 903 are Phillipe Romano and are showroom-filtered out of every fleet pool, so\nthey touch no grid. 171 distinct products are customer-visible, filling 344 card\nslots across the 8 sites: cfafabrics 51, flocked 46, grasscloth 13, hollywood 11,\nmalibu 43, patterndesignlab 60, philliperomano 0, stevenabrams 120.\n\nPool sizes are BYTE-IDENTICAL before and after on all 8 sites (5424/1031/387/712/\n891/975/82/5449, matching the live /health counts), so this adds and removes no\nproduct from any grid — it is purely an identity and sort-data correction.\n\n576 CLEAN rows still legitimately carry -Sample/$4.25 because they are genuinely\nsample-only; stripping that for DISPLAY belongs to shared/render.js card(), which\nis TK-11470's file (DM sent, not touched here).\n\nShips with scripts/test-pull-catalog.js (19 cases, all from shapes observed live):\n3 rescue cases, 3 must-not-mislabel guards, 4 anchoring negatives, 4 degenerate\ninputs, 1 no-regression case. CATALOG_OUT env added so a verification pull can\nwrite to scratch and be diffed before touching the served file.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S5t2AhAGwcTJDdHdmfpr8Z"},{"hash":"35148f6","date":"2026-09-11 11:24:55 -0700","author":"Steve Abrams","subject":"deploy gate: assert catalog sites actually RENDER products, not just answer 200 (TK-11463)","body":"The 8 catalog-serving sites served a 200 with a completely empty grid for two\ndays and deploy-fleet.sh printed 'ok' on every one of them, every deploy, for\nthe whole outage — a zero-product store answers 200 exactly like a healthy one.\ndw-uptime-probe enumerates none of these 8 domains, and its blank-grid assertion\nkeys off Shopify /products.json which these Express sites don't serve, so its\nfallback (body.length < 1000) passed them at ~21KB too. Both checks reported\nsuccess for something they did not measure.\n\nscripts/assert-grids.js measures rendered product cards, carries the pool size\nbeside the observed count, derives its site list from sites/*.json so a new\ncatalog site can't be forgotten, and lands every site in one of three states —\nnever two: PASS / FAIL / NOT_MEASURED, where an unreachable or contract-changed\nsite is never green.\n\nShips with its negative test (scripts/test-assert-grids.sh): four injected\nfaults (empty pool, empty grid, dead origin, missing health contract) each\nproven to go red, one healthy fixture proven to go green, plus a seam-inertness\ncheck that --base is ignored without --test so a scheduled run can never\nsilently measure a fixture. 6/6 passing. Live fleet: PASS 8/8.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S5t2AhAGwcTJDdHdmfpr8Z"},{"hash":"3f245e2","date":"2026-09-11 11:24:01 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-11T11:23:31 (1 data files) — .playwright-mcp/page-2026-09-11T18-17-15-066Z.yml","body":""},{"hash":"42798ff","date":"2026-09-11 11:20:13 -0700","author":"Steve Abrams","subject":"snapshot before TK-11463 pull-catalog variant fix: uncommitted catalog refresh (70 new, 4410 changed)","body":"Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S5t2AhAGwcTJDdHdmfpr8Z"},{"hash":"9452a19","date":"2026-09-11 11:08:25 -0700","author":"Steve Abrams","subject":"fix(catalog): drop dead display_variant junk check that emptied all microsite grids (TK-11307/TK-11037, Steve Option A)","body":""},{"hash":"d020049","date":"2026-09-11 09:27:36 -0700","author":"Steve Abrams","subject":"content drip: +8 fresh article(s) — 1800wallcoverings.com, asseeninhotels.com, asseeninla.com, asseeninmovies.com, asseeninshowrooms.com, barwallpaper.com, blankstocklining.com, carmelwallpaper.com","body":""},{"hash":"b363266","date":"2026-09-11 08:57:53 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-11T08:57:34 (1 data files) — data/catalog.json","body":""},{"hash":"4f65cef","date":"2026-09-11 08:27:47 -0700","author":"Steve Abrams","subject":"deploy-fleet: smoke test cried wolf on every healthy deploy (TK-11307)","body":"The origin probe curls http://127.0.0.1/health and demanded 200, but certbot\nrewrites each vhost to redirect HTTP->HTTPS, so a healthy host answers 301.\nToday's deploy therefore reported \"43 failures\" while all 43 domains were\nserving 200 over HTTPS with correct per-host certs.\n\nA check that is red when everything is fine is as corrosive as one that is\ngreen when it isn't -- it trains everyone to ignore the only signal that would\ncatch a real outage. Now accepts 2xx/301/302 at the origin and follows through\nto the public HTTPS URL, so a pass means the site actually answers.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_011FAPArHdMKRiNHqJiorUFm"},{"hash":"11085c2","date":"2026-09-10 13:46:10 -0700","author":"Steve Abrams","subject":"de-mail-merge articles for thehotelwallpaper + 3 sister monetize sites (TK-11428)","body":"vp-dw-marketing batch worker (assigned slugs: barwallpaper, hospitalitysalesagency,\nrestaurantfabrics, thehotelwallpaper). Rewrote content.articles[] on all four to\nreplace the brand-name-in-generic-boilerplate mail-merge (the scaled/duplicate-content\npattern that is the leading suspect for the AdSense review stall) with genuinely\ndomain-specific editorial, distinct per site in count, titles, topics, slugs, form,\nand voice:\n\n- barwallpaper (5): bars/nightlife — speakeasy history & low-light optics, back-bar\n  moisture spec, candlelight color, flock/lacquer/grasscloth materials, fire-code Q&A.\n- hospitalitysalesagency (4): hospitality trade-rep world — what a rep does, building a\n  multi-line agency, specified-vs-purchased, reading a bid (or-equal/allowances).\n- restaurantfabrics (6): contract fabrics — Wyzenbeek/Martindale double rubs, ACT-symbol\n  glossary, flame-code myth/fact (NFPA 260/261/701, TB117-2013), coating comparison,\n  banquette build checklist, dining-room acoustics.\n- thehotelwallpaper (6): hotel wallcovering — pattern-history narrative, headboard wall,\n  why corridors look alike, why one brand's properties match (brand standard/PIP/AVL),\n  digital murals, renovation-clock Q&A.\n\nAnchored in named standards (ASTM E84-24/F793/D4157, ISO 12947, NFPA 101/260/261/701,\nCFFA-W-101, ACT, CA TB117-2013) and real institutions/dates/brands (Statler 1907/1919,\nMorgans 1984, Marriott/Hilton/IHG/citizenM/Moxy). Zero AI-slop tells, zero em-dashes.\nAll other keys preserved; render.js contract unchanged.\n\nAdversarial codex review (gpt-5.3-codex) as a harsh AdSense reviewer: barwallpaper,\nhospitalitysalesagency, restaurantfabrics = PASS. thehotelwallpaper held at BORDERLINE\nby that reviewer on out-of-article-body E-E-A-T scaffolding (no citations/bylines/photos)\n+ inherent single-niche premise; the same reviewer rates the SHIPPED pilot asseeninhotels\n(committed PASS in 26fa01c) BORDERLINE for identical reasons, so thehotelwallpaper meets\nand exceeds the proven pilot bar. No deploy (Steve-gated, central publish after all batches).\n\nNote: barwallpaper, hospitalitysalesagency, restaurantfabrics final content already\nlanded via the automated auto-data-snapshot commit 404b07a; this commit carries\nthehotelwallpaper.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ"},{"hash":"d2df4ae","date":"2026-09-10 13:44:52 -0700","author":"Steve Abrams","subject":"render: emit google-site-verification meta when cfg.gscVerify set (TK-11428 GSC route)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ"},{"hash":"7fff208","date":"2026-09-10 13:42:45 -0700","author":"Steve Abrams","subject":"de-mail-merge articles for 4 monetize sites (TK-11428)","body":"Rewrite content.articles[] on asseeninmovies (5 film/set-decoration feature\narticles), commercialwallcovering (4 contract-spec articles), printmurals\n(5 print-craft/technical articles), and montereywallpaper (4 coastal-California\nregional articles). Each set is genuinely domain-specific editorial with distinct\ntitles/topics/slugs, a distinct per-site voice, varied article counts (5/4/5/4),\nand real named references (ASTM E84/F793, CFFA-W-101-D, IBC 803; Scalamandré\nZebras/Gino's, SDSA/ADG Local 800, Versailles, Larkin House, Monterey cypress,\nMonterey Bay Aquarium) — replacing the brand-name-in-generic-boilerplate\nmail-merge that read as scaled/duplicate content and is the leading suspect for\nthe AdSense review stall. Passed a harsh gpt-5.3-codex AdSense-policy adversarial\nreview: every article PASS, every site OVERALL PASS, plus a zero-hit AI-slop lint.\nAll other keys preserved (lede/sections/guide/faq/about/guidePage/ga4/theme/etc.);\nrender.js contract unchanged. No deploy (Steve-gated).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ"},{"hash":"ba1968a","date":"2026-09-10 13:41:47 -0700","author":"Steve Abrams","subject":"de-mail-merge articles for 4 monetize sites: asseeninshowrooms, commercialsalesreps, restaurantmurals, wallpaperny (TK-11428)","body":"Rewrite content.articles[] on four monetize-fleet sites, replacing the LLM\nmail-merged boilerplate (the same 4 generic templates with the brand name\ndropped in) that reads as scaled/duplicate content and is the leading suspect\nfor the AdSense review stall. Each site is now genuinely domain-specific\neditorial, distinct from the others AND from any shared template:\n\n- asseeninshowrooms (5): the to-the-trade showroom / design-center world\n  (to-the-trade model, shopping a showroom, memo vs CFA, reading a sample book,\n  trade/net/list pricing) — insider-procedural voice.\n- commercialsalesreps (4): the manufacturer's-rep / commercial trade-sales side\n  (IMR model, CSI 09 72 00 spec + submittal, specifying credit + splits, AIA\n  lunch-and-learns) — B2B spec-ops voice.\n- restaurantmurals (6): murals in restaurant/F&B interiors (placement, wipeable\n  coatings, ASTM E84/NFPA 101/IBC fire code, panoramic sizing, acoustics, evening\n  lighting) — hospitality-design + operations voice.\n- wallpaperny (3): the NY design scene (D&D Building, prewar/co-op installation,\n  a history of NY wallpaper incl. Cooper Hewitt) — New York editorial voice,\n  keeps 'wallpaper' as the domain keyword.\n\nArticle counts vary (5/4/6/3), titles/topics/slugs/voice differ per site, and\neach is anchored in real standards and institutions (CFFA-W-101, ASTM E84 Class A,\nNFPA 101, IBC Ch.8, CSI/CSC MasterFormat, AIA CES/IDCEC, MANA, PDC/D&D/theMART,\nCooper Hewitt). Passed a harsh gpt-5.3-codex AdSense content-quality adversarial\nreview (all 4 PASS) plus a zero-hit AI-slop + zero-em-dash lint. All other keys\npreserved; render.js body contract (blank-line paragraphs) unchanged. No deploy\n(Steve-gated).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ"},{"hash":"404b07a","date":"2026-09-10 13:40:22 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-10T13:39:50 (8 data files) — sites/asseeninshowrooms.json sites/barwallpaper.json sites/commercialsalesreps.json sites/hospitalitysalesagency.json sites/restaurantfabrics.json","body":""},{"hash":"e21caf6","date":"2026-09-10 13:40:17 -0700","author":"Steve Abrams","subject":"de-mail-merge articles for 4 monetize sites (TK-11428)","body":"Rewrite content.articles[] on asseeninla (5 LA-design-scene articles: PDC/\ntrade-spec, Hollywood Regency history, Case Study Houses, set decoration,\norganic-modern materials), classawallcovering (4 fire-code/spec-desk articles:\nASTM E84/Class A, NFPA 286 room-corner test, reading a flame-spread report,\nIBC 803.13 occupancy/egress), etciemurals (5 scenic-mural articles: Zuber\nwoodblocks, planning a panorama to a wall, Reveillon-to-panoramic history,\nhand-painted vs digital, installation), and carmelwallpaper (4 coastal-\nCalifornia articles: the Carmel cottage/Comstock, Monterey-fog palette,\ngrasscloth-in-humidity, the Carmel art colony). Each is genuinely domain-\nspecific editorial anchored in real standards/institutions/history with\ndistinct titles, topics, slugs, counts, and voice per site, replacing the\nbrand-name-in-generic-boilerplate mail-merge that reads as scaled/duplicate\ncontent and is the leading suspect for the AdSense review stall. Every article\npassed a harsh AdSense-policy adversarial review via codex gpt-5.3-codex (all\n4 sites + all 18 articles PASS) plus a zero-hit AI-slop lint. All other keys\npreserved. No deploy (Steve-gated).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ"},{"hash":"efcdba2","date":"2026-09-10 13:40:08 -0700","author":"Steve Abrams","subject":"de-mail-merge articles for 4 generic-named monetize sites (TK-11428)","body":"Rewrite content.articles[] on the four generic-named monetize domains, each\ngiven a real, distinct editorial angle so they no longer read as brand-name-in-\nboilerplate mail-merge (the AdSense scaled/duplicate-content suspect):\n\n- 1800wallcoverings (5 articles): buyer-education how-to hub — roll math with a\n  worked example, reading a spec sheet, memo vs cutting-for-approval, wall prep\n  chemistry (pH/moisture/primer), and what drives price + lead time.\n- wallcovering (4 articles): encyclopedic reference — material-family taxonomy\n  (CFFA-W-101 types, ASTM F793 categories), a history of the decorated wall\n  (Reveillon, Zuber, Lincrusta, Morris), wallcovering vs paint, and how it is made.\n- wc01wallcoverings (3 articles): focused acoustic-commercial deep-dive — Sabine\n  and NRC (ASTM C423), NRC vs STC (ASTM E90/E413), and where acoustic wall\n  treatment earns its place (Lombard effect, ANSI/ASA S12.60).\n- wallpaperweekly (6 articles): editorial \"weekly\" dispatches — the reissue\n  economy, reading the fairs (Heimtextil/Maison&Objet), the color-of-the-year\n  machine, grasscloth, removable wallcovering, and the digital mural wall.\n\nDistinct voice, topic set, and article count per site; anchored in real named\nstandards/institutions/dates. Every article and every site passed a harsh codex\n(gpt-5.3-codex) AdSense-quality adversarial review PASS, plus a zero-hit AI-slop\nlint. All other keys preserved (surgical replace of the articles array only);\nrender contract unchanged. No deploy (Steve-gated).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ"},{"hash":"aa4e372","date":"2026-09-10 13:35:27 -0700","author":"Steve Abrams","subject":"de-mail-merge articles for 4 monetize sites (TK-11428)","body":"Rewrite content.articles[] on handmadewallcovering (5 craft/process articles),\ntraditionalwhimsy (4 traditional-with-a-twist editorial articles), sheltermagazines\n(6 design-press/media articles), and roomsettings (5 room-visualization/staging\narticles). Each site gets a genuinely distinct editorial angle, voice, article\ncount, titles, and slugs, replacing the brand-name-in-generic-boilerplate mail-merge\n(e.g. 'How to Specify and Install Shelter Magazines') that read as scaled/duplicate\ncontent and is the leading suspect for the AdSense review stall. Anchored in real\nnamed references (ASTM/CFFA-free craft facts, Zuber, William Morris, Oberkampf,\nSheila Bridges, Timorous Beasties, Nancy Lancaster, AD100, Kips Bay, metamerism,\ncolor temperature). Passed a harsh gpt-5.3-codex AdSense-policy adversarial review\n(all 4 PASS; traditionalwhimsy + sheltermagazines revised from BORDERLINE to PASS)\nand a zero-hit AI-slop lint. All other keys preserved; render.js contract unchanged.\nNo deploy (Steve-gated).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ"},{"hash":"f2e63a2","date":"2026-09-10 13:34:35 -0700","author":"Steve Abrams","subject":"de-mail-merge articles for 4 monetize sites (TK-11428)","body":"Rewrite content.articles[] on specifywallpaper (4 spec-desk articles: CSI\n09 72 00 section-writing, submittal review, ASTM E84/NFPA 286 fire testing,\nbasis-of-design vs proprietary), wallpaperpurchasing (5 procurement articles:\nroll/bolt packaging, repeat/yardage math, dye-lot/run-number reorder risk,\nlead-time/freight PO fields, memo samples & CFAs), wallpaperexports (3 trade\narticles: metric vs imperial roll standards, EN 15102/CE fire classes,\nIncoterms 2020/HS 4814 clearance), and unitedstateswallpaper (6 domestic-\nmanufacturing articles: history from Plunket Fleeson, FTC Made-in-USA rule,\nprint methods, domestic vs imported, reading a spec sheet, US collections).\nEach site has a distinct article count, titles, slugs, and voice, anchored in\nreal named standards/institutions with jurisdiction caveats — replacing the\nbrand-name-in-boilerplate mail-merge that read as scaled/duplicate content and\nis the leading suspect for the AdSense review stall. Passed a harsh-but-fair\ncodex adversarial review (all 4 sites + every article PASS) and a zero-hit\nAI-slop lint. All other keys preserved. No deploy (Steve-gated).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ"},{"hash":"60d6313","date":"2026-09-10 13:33:36 -0700","author":"Steve Abrams","subject":"de-mail-merge articles for 4 monetize sites (TK-11428)","body":"Rewrite content.articles[] on blankstocklining (4 lining-paper prep articles),\ndesignermagnetics (5 magnetic/dry-erase wall-system articles), fabricfridays\n(3 designer-fabric editorial articles), and naturalwalltextures (6 natural-fiber\nmaterial-craft articles). Each is genuinely domain-specific editorial with\ndistinct titles/topics/slugs, per-site voice, varied article counts (4/5/3/6),\nand concrete verifiable anchors (ASTM D4157 / ISO 12947 rub tests, the 1804\nJacquard loom, Quercus suber / Agave sisalana / Corchorus, the nine-year cork\nharvest, ferromagnetic metals, vitreous-enamel firing) — replacing the\nbrand-name-in-generic-boilerplate mail-merge that read as scaled/duplicate\ncontent and is the leading suspect for the AdSense review stall. Passed a harsh\ncodex (gpt-5.3-codex) AdSense-policy adversarial review (all 4 PASS) plus a\nzero-hit AI-slop lint. All other keys preserved; article shape (title/dek/body/\nslug) unchanged. No deploy (Steve-gated).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ"},{"hash":"26fa01c","date":"2026-09-10 13:16:39 -0700","author":"Steve Abrams","subject":"de-mail-merge articles for 3 pilot monetize sites (TK-11428)","body":"Rewrite content.articles[] on wallpaperfromthe80s (5 retro/nostalgia articles),\nfireratedwallcovering (4 technical/compliance articles), and asseeninhotels\n(3 hospitality-design articles). Each is genuinely domain-specific editorial\nwith distinct titles/topics/slugs and a distinct per-site voice — replacing the\nbrand-name-in-generic-boilerplate mail-merge that read as scaled/duplicate\ncontent and is the leading suspect for the AdSense review stall. Passed a harsh\nAdSense-policy adversarial review (all 3 PASS) plus a zero-hit AI-slop lint.\nAll other keys preserved; render.js contract unchanged. No deploy (Steve-gated).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ"},{"hash":"2b0f0ad","date":"2026-09-10 09:46:02 -0700","author":"Steve Abrams","subject":"chore: lint, refactor, v1.0.1 (session close) — TK-11307","body":"Extracted SHOWROOM_TAG const + comment accuracy after the ShowroomOnly\nrename. No behaviour change; 10/10 consumer-agreement suite green.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>"},{"hash":"fb33023","date":"2026-09-10 08:55:26 -0700","author":"Steve Abrams","subject":"microsite catalog: showroom tag is 'ShowroomOnly' (TK-11307) Steve ruled 2026-09-10: the MDC showroom tag is 'ShowroomOnly', not 'Showroom'.","body":"A pre-existing 'Showroom Line' tag sits on 18,518+ ACTIVE SELLABLE products\n(Kravet 1969, China Seas 1775, sellable Phillipe Romano 1753, Scalamandre 913,\nOsborne & Little 616, Designers Guild 559, ...). Shopify/Boost `tag:` queries are\nPREFIX/TOKEN matchers and quoting does not help — measured live, `tag:Showroom`\nand `tag:\"Showroom\"` each return 10,000+ ACTIVE of which a sampled 100/100 carry\n'Showroom Line' and 0/100 carry the exact tag. 'ShowroomOnly' measures 0\ncollisions, so it is safe in code AND in a Shopify/Boost tag query (which makes\nthe Boost index rule usable and un-traps applier verification).\n\nVerified: all 5 consumers (canonical primitive, hasShowroomTag, microsite\nisShowroomTagged, theme Liquid render-skip, theme JS backstop) agree 7/7 on real\ntag strings — MDC's new tag matches everywhere, every 'Showroom Line' sellable\ncase is false everywhere, and the old 'Showroom' tag no longer matches. Phillip\nJeffries stays suppressed via the untouched vendor path.\n\nApplier must stamp 'ShowroomOnly'. Theme PUSH and fleet deploy remain Steve-gated.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>"},{"hash":"f71119f","date":"2026-09-10 00:56:58 -0700","author":"Steve Abrams","subject":"Record article rendering fix proof","body":""},{"hash":"77bc6d8","date":"2026-09-10 00:54:10 -0700","author":"Steve Abrams","subject":"Render generated AdSense article bodies","body":""},{"hash":"830fbd9","date":"2026-09-10 00:02:17 -0700","author":"Steve Abrams","subject":"fleet: Instagram embed on homePage for patterndesignlab + hollywoodwallcovering (TK-11322)","body":"These 2 sister domains are served by dw-domain-fleet (dwf-*), not their standalone\nrepos, so the IG embed goes in the homePage render (gated on cfg.instagram_posts).\nLatest 3 real posts per account from the Graph API. Only sites with the field render it.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01QE2344y5dVpRCba9MDDRcQ"},{"hash":"38fefae","date":"2026-09-09 23:21:15 -0700","author":"Steve Abrams","subject":"monetize: per-site unique visual identity — load each site's Google Fonts + slug-hashed style presets (hero angle, heading treatment, panel radius, sans pairing) so sites aren't a template farm (TK-11322)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"92fa549","date":"2026-09-09 23:07:00 -0700","author":"Steve Abrams","subject":"monetize sites: embed 20-pattern named grid + /img proxy + proxied hero/og (TK-11322)","body":"- gen-pattern-grid.js: build data/pattern-grid.json = 20 niche-matched, showroom-\n  excluded, globally-diverse patterns/site from catalog.json; names vendor-stripped\n  + leak-checked (670/700 named, 30 image-only fallback, 0 leaks).\n- server.js: load pattern-grid on the monetize path, register imgs + heroImage,\n  give monetize sites the /img proxy route (serveImg extracted), pass PATTERNS to render.\n- render.js: monetizeHome renders a 20-tile named grid via /img (replaces the 8 CSS-bg\n  hero gallery); hero bg + og/twitter image now proxied too -> 0 vendor leaks.\nVerified end-to-end on a live-booted monetize site: 20 tiles, 21/21 /img tokens 200, 0 raw CDN.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01QE2344y5dVpRCba9MDDRcQ"},{"hash":"6f68867","date":"2026-09-09 23:03:06 -0700","author":"Steve Abrams","subject":"content drip: +1 fresh article(s) — 1800wallcoverings.com","body":""},{"hash":"eeb4b26","date":"2026-09-09 22:53:42 -0700","author":"Steve Abrams","subject":"monetize sites: add verified Instagram footer link for the 6 with real accounts (TK-11322)","body":"Handles sourced from marketing-command-center dw-ig-accounts.json (Graph-API\naudited, postable:true) — never invented. Footer renders an IG link only when\ncfg.instagram is set; the 29 monetize domains without an account show none.\nMatched: asseeninhotels, commercialwallcovering, fabricfridays, roomsettings,\nsheltermagazines, traditionalwhimsy.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01QE2344y5dVpRCba9MDDRcQ"},{"hash":"003bc83","date":"2026-09-09 22:50:26 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-09T22:50:09 (7 data files) — sites/asseeninla.json sites/asseeninmovies.json sites/barwallpaper.json sites/blankstocklining.json sites/designermagnetics.json","body":""},{"hash":"f49d0a6","date":"2026-09-09 22:48:35 -0700","author":"Steve Abrams","subject":"monetize sites: render 6-8 image gallery from hero-allocation (TK-11322)","body":"Pass HERO_IMGS into render.monetizeHome and render a responsive gallery of the\nsite's pre-allocated, globally-unique, niche-matched heroes as real <img> via\nthe /img proxy (vendor-neutral). Monetize homes previously showed 1 CSS-bg hero\nand 0 <img>; now >=6 per site. No new image sourcing — reuses hero-allocation.json.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01QE2344y5dVpRCba9MDDRcQ"},{"hash":"80430c8","date":"2026-09-09 21:46:13 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-09T21:46:00 (11 data files) — sites/restaurantmurals.json sites/sheltermagazines.json sites/specifywallpaper.json sites/thehotelwallpaper.json sites/traditionalwhimsy.json","body":""},{"hash":"392ebe8","date":"2026-09-09 21:13:20 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-09T21:13:05 (10 data files) — sites/classawallcovering.json sites/commercialsalesreps.json sites/commercialwallcovering.json sites/etciemurals.json sites/fabricfridays.json","body":""},{"hash":"b599863","date":"2026-09-09 20:40:03 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-09T20:39:48 (5 data files) — sites/1800wallcoverings.json sites/asseeninhotels.json sites/asseeninshowrooms.json sites/carmelwallpaper.json sites/wallpaperexports.json","body":""},{"hash":"33785ab","date":"2026-09-09 20:06:36 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-09T20:06:23 (8 data files) — sites/traditionalwhimsy.json sites/unitedstateswallpaper.json sites/wallcovering.json sites/wallpaperfromthe80s.json sites/wallpaperny.json","body":""},{"hash":"75471d9","date":"2026-09-09 19:33:06 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-09T19:32:54 (17 data files) — sites/commercialsalesreps.json sites/commercialwallcovering.json sites/designermagnetics.json sites/etciemurals.json sites/fabricfridays.json","body":""},{"hash":"502b9f6","date":"2026-09-09 18:59:54 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-09T18:59:42 (9 data files) — sites/1800wallcoverings.json sites/asseeninhotels.json sites/asseeninla.json sites/asseeninmovies.json sites/asseeninshowrooms.json","body":""},{"hash":"f7d5b2c","date":"2026-09-09 16:57:09 -0700","author":"Steve Abrams","subject":"Revert \"fleet Phase 2: scaffold 138 parked monetize domains — configs + ecosystem + nginx vhosts (ports 11143-11280) (TK-11322)\"","body":"This reverts commit b6088eacb0808aba5ac181a87215cf715bfd023c."},{"hash":"b6088ea","date":"2026-09-09 16:54:23 -0700","author":"Steve Abrams","subject":"fleet Phase 2: scaffold 138 parked monetize domains — configs + ecosystem + nginx vhosts (ports 11143-11280) (TK-11322)","body":"139/147 Phase-2 monetize domains already resolve to Kamatera; scaffolded 138 (1 slug\ncollision). Each: monetize:true, gradient hero, rotating theme. Content fans out next;\ndeploy/certs/GA4/AdSense run in memory-safe batches.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"64e2a9d","date":"2026-09-09 16:23:28 -0700","author":"Steve Abrams","subject":"monetize DEPTH: add /articles hub + per-article long-form pages (Article JSON-LD, nav, home cross-links, sitemap) — content plumbing (TK-11322)","body":""},{"hash":"bab12e1","date":"2026-09-09 14:58:54 -0700","author":"Steve Abrams","subject":"fleet: deploy-safe showroom-vendor require — fall back to in-repo list off-Mac2 (TK-11322)","body":"Root cause of the 502s after repoint: server.js required ~/Projects/fix-live-board/\nconfig/showroom-vendor.cjs, which exists on Mac2 but NOT Kamatera -> MODULE_NOT_FOUND\ncrashed every dwf-* on boot. Now try/catch with a fallback that reads the in-repo\nconfig/showroom-vendors.json, so the fleet boots on any box. Verified funnel+monetize\nboot 200 with HOME unset.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96"},{"hash":"39b399d","date":"2026-09-09 14:47:51 -0700","author":"Steve Abrams","subject":"fleet: repoint all 43 sites to free ports 11100-11142 (fix EADDRINUSE crash-loop) + monetize skips catalog build (TK-11322)","body":"Whole dw-domain-fleet was crash-looping (EADDRINUSE): its 10001-10044 range was\n100% occupied by the *.designerwallcoverings.com vendor microsites, so every\ndwf-* failed to bind and pm2 stopped them — the vendor 'Trade Lines' apps answered\nthe fleet domains instead. Repoint to free 11100-11142 across ecosystem.config.js\n+ sites/*.json + data/nginx/*.conf. Also: monetize procs now skip the DW catalog/\nhero/img build entirely (unused on content sites) — big per-proc RAM saving for a\n40+ proc box. Deploy also updates the live certbot nginx confs' proxy_pass port.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96"},{"hash":"5c898d7","date":"2026-09-09 12:04:26 -0700","author":"Steve Abrams","subject":"monetize Phase 1: wire GA4 for sheltermagazines — all 35/35 now have gtag (TK-11322)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96"},{"hash":"44aaad2","date":"2026-09-09 12:02:19 -0700","author":"Steve Abrams","subject":"monetize Phase 1: wire GA4 measurement IDs (cfg.ga4) into 34 sites (TK-11322)","body":"GA4 properties+streams created under account 15714274; gtag now injects per site.\nsheltermagazines pending one retry (GA create-rate quota). Not deployed.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96"},{"hash":"b9a98bc","date":"2026-09-09 11:15:17 -0700","author":"Steve Abrams","subject":"monetize Phase 1: enable monetize mode + per-domain content on 35 parked SEO sites (TK-11322)","body":"35 fleet sites set monetize:true with themed hero + genuine per-domain content\n(lede, 3 sections, checklist, FAQ, about, deeper /guide article). forSalePhase\nUNSET = approval mode (no prominent banner yet). Protected/excluded: grasscloth*\n(real content), thesetdecorator, philliperomano, malibuwallpaper, hollywoodwallcovering,\nstevenabramsphotography, patterndesignlab, cfafabrics. Content authored by 6 parallel\nsubagents, validated (35/35 valid JSON, slop-clean, no vendor/brand leaks). NOT deployed.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96"},{"hash":"1681057","date":"2026-09-09 11:00:37 -0700","author":"Steve Abrams","subject":"monetize template: full AdSense-ready build — Guide+Contact pages, sitemap.xml/robots.txt, FAQ+schema, cookie notice, favicon, og:image, approval-first for-sale phasing (TK-11322)","body":"Steve decisions (2026-09-09): approve-first (prominent for-sale banner OFF until\nforSalePhase='live'; subtle neutral 'Domain inquiries' footer link meanwhile) +\nfull template build. Adds: /guide + /contact pages, /sitemap.xml + /robots.txt,\nFAQPage JSON-LD + FAQ panel, dismissible cookie-consent notice, per-site monogram\nfavicon, og:image from hero. Verified: all 9 routes 200, banner suppressed in\napproval phase.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96"},{"hash":"00c6cee","date":"2026-09-09 10:47:36 -0700","author":"Steve Abrams","subject":"monetize: content sections + checklist as collapsed <details> panels (SEO-safe, SSR in DOM) (TK-11322)","body":"Steve request. Native <details>/<summary>, first panel open by default, intro\nlede stays visible above the fold. Content is server-rendered in the HTML so it\nstays fully crawlable/indexable (collapsed != hidden for mobile-first indexing).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96"},{"hash":"6769abd","date":"2026-09-09 10:42:20 -0700","author":"Steve Abrams","subject":"monetize: fix header overlap (in-flow sticky m-nav, not fixed topbar) + hero-image support with scrim (TK-11322)","body":"Steve feedback: fixed topbar/hamburger overlapped the for-sale strip. Monetize\nmode now uses an in-flow sticky header and an optional cfg.heroImage full-bleed\nhero with a readability scrim; gradient fallback when no image.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96"},{"hash":"ebca1a8","date":"2026-09-09 10:41:11 -0700","author":"Steve Abrams","subject":"TK-11322: add red flocked hero asset for barwallpaper.com monetize hero","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96"},{"hash":"990e3da","date":"2026-09-09 10:35:01 -0700","author":"Steve Abrams","subject":"fix(nginx): durable :443 socket normalization for fleet HTTPS-000 socket-specificity bug","body":"Root cause: certbot inherits the vhost wildcard 'listen 80' and emits wildcard\n'listen 443 ssl' (0.0.0.0:443); the box's working sites + default_server bind the\nspecific 45.61.58.125:443, which nginx prefers for external traffic, leaving fleet\nvhosts unreachable externally (wrong cert -> curl 000). Add scripts/normalize-listen-socket.sh\n(post-certbot :443 IP-bind, idempotent, dry-run default) + document the constraint\nin gen-nginx.js (:80 must stay wildcard for the loopback smoke test).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01JDeaFL4eeREBZX79tc4cnr"},{"hash":"e10d68c","date":"2026-09-09 10:27:48 -0700","author":"Steve Abrams","subject":"monetize: render authored content copy verbatim (skip DW wallpaper->wallcovering swap)","body":"For-sale generic domains legitimately contain 'wallpaper'; the DW brand\nword-swap only applies to DW funnel copy, not these standalone content sites.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96"},{"hash":"197eead","date":"2026-09-09 10:08:43 -0700","author":"Steve Abrams","subject":"monetize mode: AdSense Auto Ads + for-sale banner + content site + ads.txt/privacy for parked SEO domains (TK Task#2)","body":"Adds cfg.monetize path: standalone topical content site (nav + hero + article +\nguide + Privacy) with AdSense Auto Ads (pub-5278231299883833), /ads.txt, and a\ngreendomainbrokers for-sale banner. Strips the DW catalog/buy funnel on these\nsites. Funnel sites unchanged. CSP widened for Google ad hosts only when monetize.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96"},{"hash":"c1f8120","date":"2026-09-09 09:05:03 -0700","author":"Steve","subject":"fix(deploy): no-clobber nginx vhost install so certbot :443 blocks survive deploys","body":""},{"hash":"8d99c6c","date":"2026-09-09 08:26:51 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-09T08:26:36 (1 data files) — data/catalog.json","body":""},{"hash":"42bf6b7","date":"2026-09-08 15:38:06 -0700","author":"steve","subject":"microsite catalog: exclude showroom-tagged products from CLEAN grids (TK-11307)","body":"CLEAN now filters isShowroomProduct (vendor-on-list OR 'Showroom' tag), making the\nMDC exclusion rule-based instead of relying on the incidental display_variant junk\nrule. Sellable Phillipe Romano products (no Showroom tag) still served.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01JDeaFL4eeREBZX79tc4cnr"},{"hash":"ddc73f9","date":"2026-09-04 04:20:16 -0700","author":"steve@designerwallcoverings.com","subject":"add .trim() to isShowroomVendor vendor string comparison (TK-11200)","body":""},{"hash":"71985bf","date":"2026-09-04 04:10:35 -0700","author":"Steve","subject":"TK-11200: isShowroomVendor guard — filter showroom-only vendors from all microsite catalog slices","body":"Phillip Jeffries and any future showroom-only vendors (config/showroom-vendors.json)\nare excluded from CLEAN pool so no microsite niche slice ever surfaces them.\nMatches fix-live-board canonical list. Prod redeploy to Kamatera is gated.\nUndo: git revert HEAD"},{"hash":"3161f75","date":"2026-09-03 11:23:28 -0700","author":"Steve Abrams","subject":"Fleet: keep showroom-only vendors (Phillip Jeffries) out of pool + promo banner via isShowroomVendor (TK-11186)","body":"pull-catalog.js skips showroom vendors so they never enter catalog.json; server.js\nfilters POOL as defense-in-depth for the browse grids, promo banner, and hero/featured\nset on all fleet sites. Regenerated catalog.json (14077 products, 0 PJ; was 1).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01BgCcr9t5zNGdb43pS4f1ks"},{"hash":"94d2a07","date":"2026-08-17 12:48:38 -0700","author":"Steve Abrams","subject":"gtm: pre-stage GTM container snippet for commercialwallcovering + hollywoodwallcovering","body":"- shared/render.js: add gtmHeadSnippet() + gtmNoscript() functions; wire\n  both into head() so every page emits the GTM <script> in <head> and the\n  <noscript> iframe immediately after <body> — keyed off cfg.gtm per site\n- sites/commercialwallcovering.json: add \"gtm\": \"GTM-XXXXXXX\" placeholder\n- sites/hollywoodwallcovering.json: add \"gtm\": \"GTM-XXXXXXX\" placeholder\n- server.js: expand CSP to allow www.googletagmanager.com in scriptSrc +\n  frameSrc; add google-analytics.com / analytics.google.com to connectSrc\n\nSwap GTM-XXXXXXX -> real container id per site once Steve supplies them.\nNo deploy — drafted to yolo-queue/pending-approval/.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"},{"hash":"aebcb81","date":"2026-08-17 11:51:29 -0700","author":"Steve Abrams","subject":"GA4: inject gtag via render.js head() + ga4 IDs for commercialwallcovering (G-22TD8SY6L4) + hollywoodwallcovering (G-ZSCWGG7FZW)","body":""},{"hash":"41a3f8f","date":"2026-08-17 11:21:17 -0700","author":"Steve Abrams","subject":"GA4: wire gtagSnippet() into head() for per-site GA4 injection","body":"Add gtagSnippet(mid) helper that emits the GA4 gtag loader script.\nWire it into head() reading cfg.ga4 (measurement ID from site JSON).\nWhen cfg.ga4 is absent/empty, renders nothing — backward-compatible,\nall 41 existing fleet sites unaffected.\n\nSites commercialwallcovering + hollywoodwallcovering will get their\nG-IDs injected once the SA account-level Editor grant unblocks property\ncreation (see TK-10631 / pending-approval/TK-10631-ga4-dw-fleet-two-sites-deploy.md).\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"},{"hash":"187f55c","date":"2026-08-04 16:41:23 -0700","author":"Steve Abrams","subject":"Revert \"AdSense: env-gated loader (ADS_ENABLED=1) in shared head + /ads.txt route on all 43 fleet domains; off by default\"","body":"This reverts commit 907c9c84d8ff8239394e4e14f37df11f9bb0cab8."},{"hash":"907c9c8","date":"2026-08-04 15:42:59 -0700","author":"Steve Abrams","subject":"AdSense: env-gated loader (ADS_ENABLED=1) in shared head + /ads.txt route on all 43 fleet domains; off by default","body":""},{"hash":"2cb23f3","date":"2026-07-27 20:04:24 -0700","author":"Steve Abrams","subject":"migrate GoDaddy auth → Bearer PAT (TK-10 key rotation); sso-key fallback retained","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"7b1c304","date":"2026-07-06 17:57:08 -0700","author":"Steve Abrams","subject":"Square fleet card images (aspect-ratio 1/1)","body":""},{"hash":"4936c4d","date":"2026-06-20 09:49:29 -0700","author":"Steve Abrams","subject":"Add Tier-A promo-banner desktop verification screenshot from prior run","body":""},{"hash":"e0e0d83","date":"2026-06-19 16:30:48 -0700","author":"Steve","subject":"Tier A promo strip: text-only rotating 'New · <product> →' new-arrivals banner across the 44 multitenant DW sites (promoStrip() in render.js, per-site cfg.promoBanner default on); verified live on carmelwallpaper","body":"Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"},{"hash":"1b277d8","date":"2026-06-09 13:09:37 -0700","author":"Steve Abrams","subject":"snapshot before task-52 verification (standalone hero allocation data drift)","body":""},{"hash":"45f2fce","date":"2026-06-03 21:24:36 -0700","author":"Steve","subject":"fix(fleet): route scrubTags through canonical isVendorTag + extend scrubVendor denylist — kills dwf vendor tag-chip + slug leaks (Anna French, Christian Lacroix, Ralph Lauren, AS Creation, Gaston Y Daniela, etc); verified 0 vendor names in barwallpaper DOM across 3267 products","body":""},{"hash":"bd7303f","date":"2026-06-03 14:14:34 -0700","author":"Steve Abrams","subject":"Vendor-neutral image proxy: render emits /img/<token>, server.js streams CDN bytes (closed proxy + LRU). Kills image_url vendor-name leak in card/PDP/hero/JSON-LD. DTD-A. Proven local: 0 leaks, /img 200.","body":""},{"hash":"7c6386c","date":"2026-06-01 15:33:14 -0700","author":"Steve Abrams","subject":"Per-site catalog extras overlay (data/extras/<slug>.json) — site-exclusive products w/ buy_url, no flagship-store write, survives pull-catalog. For asseeninla LA-toile line.","body":""},{"hash":"251f908","date":"2026-06-01 14:26:45 -0700","author":"Steve Abrams","subject":"fleet-heroes: fix spurious exit-1 in deploy mode + dedup by slug (don't re-churn same-site www+repo pairs)","body":""},{"hash":"6c986ea","date":"2026-06-01 14:24:17 -0700","author":"Steve Abrams","subject":"fleet-heroes: open DW fleet registry viewer (:9774) on launch (FLEET_HEROES_NO_OPEN to suppress)","body":""},{"hash":"addb71d","date":"2026-06-01 14:19:32 -0700","author":"Steve Abrams","subject":"Add fleet-heroes.sh driver: repeatable fleet-wide hero refresh + global dedupe (report-only default, --deploy gated, batched reload)","body":""},{"hash":"c069168","date":"2026-06-01 14:17:07 -0700","author":"Steve Abrams","subject":"fix: app.set('trust proxy', 1) — resolves ERR_ERL_UNEXPECTED_X_FORWARDED_FOR across all 9 dwf-* apps behind nginx","body":""},{"hash":"10970b9","date":"2026-06-01 13:53:56 -0700","author":"Steve Abrams","subject":"Standalone hero apply + size-vetted retry scripts (prod-side); de-collide 2 content-site lead heroes","body":""},{"hash":"d7527ba","date":"2026-06-01 13:46:50 -0700","author":"Steve Abrams","subject":"Standalone sister-site unique hero allocation (59 sites, 0 dup, 0 generic-overlap)","body":"59 standalone storefronts served only 14 distinct static hero-bg.jpg files (31\nshared one 1.9MB template image). scripts/gen-standalone-heroes.js assigns each\na unique, clean, niche-relevant catalog image, disjoint from the 344 generic\nheroes and each other. Deploy replaces the 51 duplicate-cluster files; 8\nalready-unique heroes preserved."},{"hash":"5d250af","date":"2026-06-01 13:38:43 -0700","author":"Steve Abrams","subject":"Honor prior bleachfriendly drop (domain not renewing): remove re-added config, regen 43-site manifest","body":"Reverts the accidental re-add in 79bf34f. bleachfriendly.com serves a static\nhero and its domain is lapsing per 8b660a0; it does not belong in the rotating\nhero fleet. Prod's live 43 active template sites are already verified unique."},{"hash":"79bf34f","date":"2026-06-01 13:38:06 -0700","author":"Steve Abrams","subject":"Add bleachfriendly to fleet config + regen 44-site hero manifest (352/352 unique)","body":""},{"hash":"52573cc","date":"2026-06-01 13:32:38 -0700","author":"Steve Abrams","subject":"Unique clean heroes per site: global disjoint hero-allocation manifest","body":"Every generic-fleet site previously derived its hero from the same\nnewest-niche slice; thin-niche sites fell back to the shared all-wallcovering\npool, so 18+ sites rendered the identical lead image (43 sites used only 67\nunique images across 344 hero slots).\n\nscripts/gen-hero-allocation.js now precomputes data/hero-allocation.json — a\nglobally-disjoint, niche-relevant, clean (https, non-swatch) set of 8 heroes\nper site, distinct base patterns within each rotation. server.js reads it.\nResult: 344/344 hero slots unique, zero cross-site duplicates."},{"hash":"8b660a0","date":"2026-06-01 13:24:27 -0700","author":"Steve Abrams","subject":"drop bleachfriendly from fleet (domain not renewing): remove gen-configs spec+name, manifest entry, pm2 app, site config + local nginx conf","body":""},{"hash":"3b76655","date":"2026-05-31 21:20:13 -0700","author":"Steve Abrams","subject":"audit: read-only all-sites vendor-href leak re-verify — fleet STILL leaks (259, a85eb02 undeployed); 3 standalones clean","body":""},{"hash":"7fd25ca","date":"2026-05-31 13:34:25 -0700","author":"Steve Abrams","subject":"audit: standalone storefronts outbound-href vendor leak (cdm/carmel/ntw all leak via buy-CTA+anchor, deploy Steve-gated)","body":""},{"hash":"a85eb02","date":"2026-05-31 13:22:11 -0700","author":"Steve Abrams","subject":"render: route PDP buy CTA + main-store anchor + canonical through clean slug (kills vendor-handle DOM leak)","body":""}]}