Creative ideas + design notes
Commits with substantial prose (≥120 chars) — the rationale behind each move.
45feb4c · 2026-09-26 · TK-12333: remove Scalamandre from showroom-vendors (Steve 2026-09-26: Scalamandre is NOT a showroom line)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HHVEexYEwkT1HYJzkvv66
0c9db1c · 2026-09-26 · TK-11860: sync showroom-vendors snapshot to canonical fix-live-board@9bcd81c (+Jeffrey Stevens, Mind the Gap, Sandberg, Scalamandre)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HHVEexYEwkT1HYJzkvv66
5e945b3 · 2026-09-16 · deploy-articles.sh: fail-flag + full-fleet verify so a partial deploy exits non-zero
Review-fix: rsync/pm2/save failures now set fail=1; run returns it and the
script does exit "${PIPESTATUS[0]}" so the status survives the | tee. Verify
widened from 4 hardcoded sentinels to all 43 sites (/articles 200 + non-empty);
multi-word content sentinel matched token-by-token to kill false OLD. POSIX-sh safe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJ94XCE5W2H1FXU2AqudVG
1d92019 · 2026-09-14 · TK-11538: gridControls appendChild off the prototype (consistency with catalogPage)
Closes the residual hardening sub-item. The titled bug (bare localStorage read
throwing in Safari private mode, killing sort+density) was already fixed in 8be45e2
and is verified LIVE on the fleet. This 1-line change makes gridControls' card
reorder use Node.prototype.appendChild.call(g,c) to match catalogPage's guarded
form append. Defense-in-depth only: a <div>'s appendChild is not DOM-clobberable
by its children the way a <form>'s named controls clobber the form. Rides the next
natural fleet deploy; no dedicated deploy warranted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RcYGzMCFaJXH6nSLLrL4EQ
457ca42 · 2026-09-12 · TK-11537: make helmet CSP explicit — useDefaults:false + declare script-src-attr 'none'
server.js line 123 called helmet({contentSecurityPolicy:{directives:{...}}})
without useDefaults:false, so helmet silently merged its default
script-src-attr 'none' into the served header — a directive present in the
response but nowhere in source ('we serve a directive nobody wrote').
Set useDefaults:false and declare scriptSrcAttr:['none'] explicitly so what we
read is exactly what we serve. Verified byte-identical served CSP header
before/after on both a non-monetize (cfafabrics) and monetize (1800wallcoverings)
site — only source honesty changes, served output unchanged.
script-src-attr 'none' kept load-bearing on purpose: it is one of the two things
defanging TK-11535's unescaped pager() injection. Do NOT relax it until pager()
output is escaped (TK-11535).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014KpcSwnuz64tLuyDrDxSHc
d20f426 · 2026-09-12 · TK-11537: declare scriptSrcAttr 'none' explicitly so read==serve (no behavior change)
helmet 8.1.0 with useDefaults:true was merging its default script-src-attr
'none' into our CSP even though our directives never mentioned it — 'a
directive nobody wrote'. Declaring it explicitly makes the config honest
while keeping the served policy identical (same directive set + values;
only the serialization position of script-src-attr changes, which is not
semantically significant in CSP). Chose this over useDefaults:false, which
would DROP script-src-attr 'none' entirely — that directive is load-bearing
(defangs inline event handlers, see TK-11535 whose pager-escape fix 35ab401
is committed but not yet deployed to prod). Preserving it means this change
cannot arm a live XSS regardless of deploy ordering.
Proven byte-identical directive set via helmet 8.1.0 repro; node --check clean.
Reversible: git revert. Prod deploy to the 8 customer-facing sites is GATED.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019JJhw5cpU5sR6VBM9ja7ow
cbc272c · 2026-09-12 · chore: v1.0.2 (TK-11470 session close)
Session shipped a bug fix (dead catalog sort control) plus a test artifact, so
semver patch. No lint autofixes applied — repo configures no eslint/biome, and
both review passes came back with no blocking findings.
The one real finding (a dead store in the pageshow handler that both reviewers
caught independently) was deliberately NOT applied here: shared/render.js is
live in production and deploy-fleet.sh rsyncs the working tree, so editing it
would only widen the prod/local gap. Handed to TK-11535, which is already
touching that file and has to redeploy anyway.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b757902 · 2026-09-11 · test(catalog): keep the TK-11470 sort proof harness in-repo
Negative-tested: 0/40 on the pre-fix baseline, 40/40 on the fix, so it is a real
detector rather than a rubber stamp. README covers the HTTPS-front requirement
(WebKit honours upgrade-insecure-requests on loopback) and the need to re-capture
modes.json whenever data/catalog.json changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
d0f8c70 · 2026-09-11 · catalog sort: stop remembering the sort, so Back works and the dropdown can't lie (TK-11470)
Steve's call after the red-team surfaced the tradeoff. The reviewed candidate made a
remembered sort re-request from the server via location.replace. That fixed the old
cosmetic lie but cost more than it bought: Back stopped undoing a sort (/catalog ->
?sort=sku -> Back -> /catalog -> instantly redirected back to ?sort=sku, so a shopper
could never reach the unsorted grid), and it double-counted GA4 page_views on
hollywoodwallcovering, the one site of the eight that has GA4 configured.
So /catalog no longer persists sort at all. The dropdown is always exactly what ?sort=
says, which makes the lie structurally impossible rather than merely corrected. Density
is still remembered. Sorting itself is untouched and still fully works.
Also re-syncs the <select> to the URL on pageshow: browsers restore form-control state
across history navigation, so after Back the dropdown still read "SKU A-Z" above a
Newest grid — the same lie by another route. My own new back-undoes-sort check caught
that, and it only goes green once grid AND dropdown both return to Newest.
Same harness, same data: 0 PASS / 38 FAIL on the unfixed baseline, 40 PASS / 0 FAIL here
(adds sort-not-remembered, dropdown-never-lies, no-redirect, back-undoes-sort).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
95a4e5a · 2026-09-11 · fix(catalog): guard requestSubmit — Safari <16 would have re-killed the sort (TK-11470)
Red-team catch on the previous commit. HTMLFormElement.prototype.requestSubmit is
Safari 16+ (Sept 2022). On older WebKit the property is undefined, so .call() throws
inside the change listener and the dropdown goes dead again — the exact bug b4b8158
fixes, with a new cause, on precisely the browsers our Playwright engines are too new
to catch.
Feature-detects and falls back to submit(), still invoked off the prototype so a field
named submit/requestSubmit can't clobber it.
Proven by simulating an old browser: with requestSubmit deleted from the prototype,
sorting still navigates and re-orders correctly in both engines, 0 pageerrors. Same
harness scores 0/38 on the unfixed baseline and 38/38 here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AyGLosyy49Y6TsyiwWQWnx
b4b8158 · 2026-09-11 · fix(catalog): sort dropdown was dead on all 8 funnel sites — CSP blocked its inline onchange (TK-11470)
Helmet is configured without useDefaults:false, so it emits script-src-attr 'none'
even though server.js's directives never mention it. That silently killed the sort
<select>'s inline onchange="...submit()" on every catalog page: picking a sort did
nothing, the URL stayed /catalog, and the grid kept its default order. Server-side
sorting was healthy the whole time — only the control was dead, so the feature was
reachable only by hand-editing the URL.
Replaces the inline handler with an addEventListener in the existing (CSP-allowed)
<script> block, and while there:
- saved sort now actually re-orders via the server instead of only relabelling the
dropdown, which previously showed e.g. "SKU A→Z" over a newest-ordered grid
- a sort change resets to page 1 and preserves q= and any other query params
- localStorage reads/writes are try/caught so denied storage can't break the page
- saved density is range-clamped to the slider's 3..8
- form submit/appendChild are called via prototype to survive DOM clobbering from
a query param literally named submit or appendChild
Verified with a negative-tested cross-engine harness (Chromium + WebKit, real HTTPS
loopback because upgrade-insecure-requests breaks form GETs over plain http in
WebKit): 2/36 PASS against the broken baseline, 36/36 PASS after, plus 26/26 on a
second site. Real browsing shows 0 console errors.
Local adoption only — the Kamatera deploy stays gated (memo in pending-approval).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AyGLosyy49Y6TsyiwWQWnx
6a1fcbb · 2026-09-11 · deploy-fleet: RSYNC_EXTRA_EXCLUDES, so one agent's approved deploy can't ship another's held work (TK-11463)
This rsync ships the WORKING TREE, not HEAD. On a repo several agents share, that
means any uncommitted in-flight file rides along silently under whoever's approval
happens to fire first. Concretely: TK-11463's approved deploy of caaf32b would have
pushed shared/render.js at ff3315d7 — TK-11470's sort candidate, which that ticket
explicitly holds ('production adoption/deployment remains gated').
--exclude also exempts a path from --delete, so prod simply keeps its current copy.
Verify by checksum that prod's copy is the one you want kept before excluding.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5t2AhAGwcTJDdHdmfpr8Z
caaf32b · 2026-09-11 · pull-catalog: flatten to the SELLABLE variant, not blindly variants[0] (TK-11463)
DW puts the $4.25 memo Sample at position 1 on most products, so variants[0] is
frequently the sample and not the good being sold. Taking it verbatim recorded
sku 'DWQW-61260-Sample' / price '4.25' for a roll that actually sells at $150.43
— 903 products store-wide. That SKU is what the fleet card prints as the
pattern's identity (render.js card() data-sku and the 'Designer Wallcoverings ·
<sku>' meta line, verified rendering live on malibuwallpaper.com/catalog), and
that price is what the Price up/down sort orders on, so those rows wore a
sample's identity and sorted at $4.25 while selling for $150-$533.
Prefer the first non-sample variant; fall back to variants[0] when EVERY variant
is a sample, so the 3,585 genuinely sample-only products keep their correct
-Sample SKU and $4.25 rather than being mislabelled the other way. The sample
test is anchored ((^|[-_ ])sample$ on SKU, or a variant titled exactly 'Sample')
so 'Sampler' and 'Sample Book' are not swallowed.
Measured on a full re-pull against the live store: 903 rows change sku AND price,
0 rows change only one (the diff is purely this fix, no unrelated drift). 731 of
the 903 are Phillipe Romano and are showroom-filtered out of every fleet pool, so
they touch no grid. 171 distinct products are customer-visible, filling 344 card
slots across the 8 sites: cfafabrics 51, flocked 46, grasscloth 13, hollywood 11,
malibu 43, patterndesignlab 60, philliperomano 0, stevenabrams 120.
Pool sizes are BYTE-IDENTICAL before and after on all 8 sites (5424/1031/387/712/
891/975/82/5449, matching the live /health counts), so this adds and removes no
product from any grid — it is purely an identity and sort-data correction.
576 CLEAN rows still legitimately carry -Sample/$4.25 because they are genuinely
sample-only; stripping that for DISPLAY belongs to shared/render.js card(), which
is TK-11470's file (DM sent, not touched here).
Ships with scripts/test-pull-catalog.js (19 cases, all from shapes observed live):
3 rescue cases, 3 must-not-mislabel guards, 4 anchoring negatives, 4 degenerate
inputs, 1 no-regression case. CATALOG_OUT env added so a verification pull can
write to scratch and be diffed before touching the served file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5t2AhAGwcTJDdHdmfpr8Z
35148f6 · 2026-09-11 · deploy gate: assert catalog sites actually RENDER products, not just answer 200 (TK-11463)
The 8 catalog-serving sites served a 200 with a completely empty grid for two
days and deploy-fleet.sh printed 'ok' on every one of them, every deploy, for
the whole outage — a zero-product store answers 200 exactly like a healthy one.
dw-uptime-probe enumerates none of these 8 domains, and its blank-grid assertion
keys off Shopify /products.json which these Express sites don't serve, so its
fallback (body.length < 1000) passed them at ~21KB too. Both checks reported
success for something they did not measure.
scripts/assert-grids.js measures rendered product cards, carries the pool size
beside the observed count, derives its site list from sites/*.json so a new
catalog site can't be forgotten, and lands every site in one of three states —
never two: PASS / FAIL / NOT_MEASURED, where an unreachable or contract-changed
site is never green.
Ships with its negative test (scripts/test-assert-grids.sh): four injected
faults (empty pool, empty grid, dead origin, missing health contract) each
proven to go red, one healthy fixture proven to go green, plus a seam-inertness
check that --base is ignored without --test so a scheduled run can never
silently measure a fixture. 6/6 passing. Live fleet: PASS 8/8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5t2AhAGwcTJDdHdmfpr8Z
42798ff · 2026-09-11 · snapshot before TK-11463 pull-catalog variant fix: uncommitted catalog refresh (70 new, 4410 changed)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5t2AhAGwcTJDdHdmfpr8Z
4f65cef · 2026-09-11 · deploy-fleet: smoke test cried wolf on every healthy deploy (TK-11307)
The origin probe curls http://127.0.0.1/health and demanded 200, but certbot
rewrites each vhost to redirect HTTP->HTTPS, so a healthy host answers 301.
Today's deploy therefore reported "43 failures" while all 43 domains were
serving 200 over HTTPS with correct per-host certs.
A check that is red when everything is fine is as corrosive as one that is
green when it isn't -- it trains everyone to ignore the only signal that would
catch a real outage. Now accepts 2xx/301/302 at the origin and follows through
to the public HTTPS URL, so a pass means the site actually answers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FAPArHdMKRiNHqJiorUFm
11085c2 · 2026-09-10 · de-mail-merge articles for thehotelwallpaper + 3 sister monetize sites (TK-11428)
vp-dw-marketing batch worker (assigned slugs: barwallpaper, hospitalitysalesagency,
restaurantfabrics, thehotelwallpaper). Rewrote content.articles[] on all four to
replace the brand-name-in-generic-boilerplate mail-merge (the scaled/duplicate-content
pattern that is the leading suspect for the AdSense review stall) with genuinely
domain-specific editorial, distinct per site in count, titles, topics, slugs, form,
and voice:
- barwallpaper (5): bars/nightlife — speakeasy history & low-light optics, back-bar
moisture spec, candlelight color, flock/lacquer/grasscloth materials, fire-code Q&A.
- hospitalitysalesagency (4): hospitality trade-rep world — what a rep does, building a
multi-line agency, specified-vs-purchased, reading a bid (or-equal/allowances).
- restaurantfabrics (6): contract fabrics — Wyzenbeek/Martindale double rubs, ACT-symbol
glossary, flame-code myth/fact (NFPA 260/261/701, TB117-2013), coating comparison,
banquette build checklist, dining-room acoustics.
- thehotelwallpaper (6): hotel wallcovering — pattern-history narrative, headboard wall,
why corridors look alike, why one brand's properties match (brand standard/PIP/AVL),
digital murals, renovation-clock Q&A.
Anchored in named standards (ASTM E84-24/F793/D4157, ISO 12947, NFPA 101/260/261/701,
CFFA-W-101, ACT, CA TB117-2013) and real institutions/dates/brands (Statler 1907/1919,
Morgans 1984, Marriott/Hilton/IHG/citizenM/Moxy). Zero AI-slop tells, zero em-dashes.
All other keys preserved; render.js contract unchanged.
Adversarial codex review (gpt-5.3-codex) as a harsh AdSense reviewer: barwallpaper,
hospitalitysalesagency, restaurantfabrics = PASS. thehotelwallpaper held at BORDERLINE
by that reviewer on out-of-article-body E-E-A-T scaffolding (no citations/bylines/photos)
+ inherent single-niche premise; the same reviewer rates the SHIPPED pilot asseeninhotels
(committed PASS in 26fa01c) BORDERLINE for identical reasons, so thehotelwallpaper meets
and exceeds the proven pilot bar. No deploy (Steve-gated, central publish after all batches).
Note: barwallpaper, hospitalitysalesagency, restaurantfabrics final content already
landed via the automated auto-data-snapshot commit 404b07a; this commit carries
thehotelwallpaper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ
d2df4ae · 2026-09-10 · render: emit google-site-verification meta when cfg.gscVerify set (TK-11428 GSC route)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ
7fff208 · 2026-09-10 · de-mail-merge articles for 4 monetize sites (TK-11428)
Rewrite content.articles[] on asseeninmovies (5 film/set-decoration feature
articles), commercialwallcovering (4 contract-spec articles), printmurals
(5 print-craft/technical articles), and montereywallpaper (4 coastal-California
regional articles). Each set is genuinely domain-specific editorial with distinct
titles/topics/slugs, a distinct per-site voice, varied article counts (5/4/5/4),
and real named references (ASTM E84/F793, CFFA-W-101-D, IBC 803; Scalamandré
Zebras/Gino's, SDSA/ADG Local 800, Versailles, Larkin House, Monterey cypress,
Monterey Bay Aquarium) — replacing the brand-name-in-generic-boilerplate
mail-merge that read as scaled/duplicate content and is the leading suspect for
the AdSense review stall. Passed a harsh gpt-5.3-codex AdSense-policy adversarial
review: every article PASS, every site OVERALL PASS, plus a zero-hit AI-slop lint.
All other keys preserved (lede/sections/guide/faq/about/guidePage/ga4/theme/etc.);
render.js contract unchanged. No deploy (Steve-gated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ
ba1968a · 2026-09-10 · de-mail-merge articles for 4 monetize sites: asseeninshowrooms, commercialsalesreps, restaurantmurals, wallpaperny (TK-11428)
Rewrite content.articles[] on four monetize-fleet sites, replacing the LLM
mail-merged boilerplate (the same 4 generic templates with the brand name
dropped in) that reads as scaled/duplicate content and is the leading suspect
for the AdSense review stall. Each site is now genuinely domain-specific
editorial, distinct from the others AND from any shared template:
- asseeninshowrooms (5): the to-the-trade showroom / design-center world
(to-the-trade model, shopping a showroom, memo vs CFA, reading a sample book,
trade/net/list pricing) — insider-procedural voice.
- commercialsalesreps (4): the manufacturer's-rep / commercial trade-sales side
(IMR model, CSI 09 72 00 spec + submittal, specifying credit + splits, AIA
lunch-and-learns) — B2B spec-ops voice.
- restaurantmurals (6): murals in restaurant/F&B interiors (placement, wipeable
coatings, ASTM E84/NFPA 101/IBC fire code, panoramic sizing, acoustics, evening
lighting) — hospitality-design + operations voice.
- wallpaperny (3): the NY design scene (D&D Building, prewar/co-op installation,
a history of NY wallpaper incl. Cooper Hewitt) — New York editorial voice,
keeps 'wallpaper' as the domain keyword.
Article counts vary (5/4/6/3), titles/topics/slugs/voice differ per site, and
each is anchored in real standards and institutions (CFFA-W-101, ASTM E84 Class A,
NFPA 101, IBC Ch.8, CSI/CSC MasterFormat, AIA CES/IDCEC, MANA, PDC/D&D/theMART,
Cooper Hewitt). Passed a harsh gpt-5.3-codex AdSense content-quality adversarial
review (all 4 PASS) plus a zero-hit AI-slop + zero-em-dash lint. All other keys
preserved; render.js body contract (blank-line paragraphs) unchanged. No deploy
(Steve-gated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ
e21caf6 · 2026-09-10 · de-mail-merge articles for 4 monetize sites (TK-11428)
Rewrite content.articles[] on asseeninla (5 LA-design-scene articles: PDC/
trade-spec, Hollywood Regency history, Case Study Houses, set decoration,
organic-modern materials), classawallcovering (4 fire-code/spec-desk articles:
ASTM E84/Class A, NFPA 286 room-corner test, reading a flame-spread report,
IBC 803.13 occupancy/egress), etciemurals (5 scenic-mural articles: Zuber
woodblocks, planning a panorama to a wall, Reveillon-to-panoramic history,
hand-painted vs digital, installation), and carmelwallpaper (4 coastal-
California articles: the Carmel cottage/Comstock, Monterey-fog palette,
grasscloth-in-humidity, the Carmel art colony). Each is genuinely domain-
specific editorial anchored in real standards/institutions/history with
distinct titles, topics, slugs, counts, and voice per site, replacing the
brand-name-in-generic-boilerplate mail-merge that reads as scaled/duplicate
content and is the leading suspect for the AdSense review stall. Every article
passed a harsh AdSense-policy adversarial review via codex gpt-5.3-codex (all
4 sites + all 18 articles PASS) plus a zero-hit AI-slop lint. All other keys
preserved. No deploy (Steve-gated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ
efcdba2 · 2026-09-10 · de-mail-merge articles for 4 generic-named monetize sites (TK-11428)
Rewrite content.articles[] on the four generic-named monetize domains, each
given a real, distinct editorial angle so they no longer read as brand-name-in-
boilerplate mail-merge (the AdSense scaled/duplicate-content suspect):
- 1800wallcoverings (5 articles): buyer-education how-to hub — roll math with a
worked example, reading a spec sheet, memo vs cutting-for-approval, wall prep
chemistry (pH/moisture/primer), and what drives price + lead time.
- wallcovering (4 articles): encyclopedic reference — material-family taxonomy
(CFFA-W-101 types, ASTM F793 categories), a history of the decorated wall
(Reveillon, Zuber, Lincrusta, Morris), wallcovering vs paint, and how it is made.
- wc01wallcoverings (3 articles): focused acoustic-commercial deep-dive — Sabine
and NRC (ASTM C423), NRC vs STC (ASTM E90/E413), and where acoustic wall
treatment earns its place (Lombard effect, ANSI/ASA S12.60).
- wallpaperweekly (6 articles): editorial "weekly" dispatches — the reissue
economy, reading the fairs (Heimtextil/Maison&Objet), the color-of-the-year
machine, grasscloth, removable wallcovering, and the digital mural wall.
Distinct voice, topic set, and article count per site; anchored in real named
standards/institutions/dates. Every article and every site passed a harsh codex
(gpt-5.3-codex) AdSense-quality adversarial review PASS, plus a zero-hit AI-slop
lint. All other keys preserved (surgical replace of the articles array only);
render contract unchanged. No deploy (Steve-gated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ
aa4e372 · 2026-09-10 · de-mail-merge articles for 4 monetize sites (TK-11428)
Rewrite content.articles[] on handmadewallcovering (5 craft/process articles),
traditionalwhimsy (4 traditional-with-a-twist editorial articles), sheltermagazines
(6 design-press/media articles), and roomsettings (5 room-visualization/staging
articles). Each site gets a genuinely distinct editorial angle, voice, article
count, titles, and slugs, replacing the brand-name-in-generic-boilerplate mail-merge
(e.g. 'How to Specify and Install Shelter Magazines') that read as scaled/duplicate
content and is the leading suspect for the AdSense review stall. Anchored in real
named references (ASTM/CFFA-free craft facts, Zuber, William Morris, Oberkampf,
Sheila Bridges, Timorous Beasties, Nancy Lancaster, AD100, Kips Bay, metamerism,
color temperature). Passed a harsh gpt-5.3-codex AdSense-policy adversarial review
(all 4 PASS; traditionalwhimsy + sheltermagazines revised from BORDERLINE to PASS)
and a zero-hit AI-slop lint. All other keys preserved; render.js contract unchanged.
No deploy (Steve-gated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ
f2e63a2 · 2026-09-10 · de-mail-merge articles for 4 monetize sites (TK-11428)
Rewrite content.articles[] on specifywallpaper (4 spec-desk articles: CSI
09 72 00 section-writing, submittal review, ASTM E84/NFPA 286 fire testing,
basis-of-design vs proprietary), wallpaperpurchasing (5 procurement articles:
roll/bolt packaging, repeat/yardage math, dye-lot/run-number reorder risk,
lead-time/freight PO fields, memo samples & CFAs), wallpaperexports (3 trade
articles: metric vs imperial roll standards, EN 15102/CE fire classes,
Incoterms 2020/HS 4814 clearance), and unitedstateswallpaper (6 domestic-
manufacturing articles: history from Plunket Fleeson, FTC Made-in-USA rule,
print methods, domestic vs imported, reading a spec sheet, US collections).
Each site has a distinct article count, titles, slugs, and voice, anchored in
real named standards/institutions with jurisdiction caveats — replacing the
brand-name-in-boilerplate mail-merge that read as scaled/duplicate content and
is the leading suspect for the AdSense review stall. Passed a harsh-but-fair
codex adversarial review (all 4 sites + every article PASS) and a zero-hit
AI-slop lint. All other keys preserved. No deploy (Steve-gated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ
60d6313 · 2026-09-10 · de-mail-merge articles for 4 monetize sites (TK-11428)
Rewrite content.articles[] on blankstocklining (4 lining-paper prep articles),
designermagnetics (5 magnetic/dry-erase wall-system articles), fabricfridays
(3 designer-fabric editorial articles), and naturalwalltextures (6 natural-fiber
material-craft articles). Each is genuinely domain-specific editorial with
distinct titles/topics/slugs, per-site voice, varied article counts (4/5/3/6),
and concrete verifiable anchors (ASTM D4157 / ISO 12947 rub tests, the 1804
Jacquard loom, Quercus suber / Agave sisalana / Corchorus, the nine-year cork
harvest, ferromagnetic metals, vitreous-enamel firing) — replacing the
brand-name-in-generic-boilerplate mail-merge that read as scaled/duplicate
content and is the leading suspect for the AdSense review stall. Passed a harsh
codex (gpt-5.3-codex) AdSense-policy adversarial review (all 4 PASS) plus a
zero-hit AI-slop lint. All other keys preserved; article shape (title/dek/body/
slug) unchanged. No deploy (Steve-gated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ
26fa01c · 2026-09-10 · de-mail-merge articles for 3 pilot monetize sites (TK-11428)
Rewrite content.articles[] on wallpaperfromthe80s (5 retro/nostalgia articles),
fireratedwallcovering (4 technical/compliance articles), and asseeninhotels
(3 hospitality-design articles). Each is genuinely domain-specific editorial
with distinct titles/topics/slugs and a distinct per-site voice — replacing the
brand-name-in-generic-boilerplate mail-merge that read as scaled/duplicate
content and is the leading suspect for the AdSense review stall. Passed a harsh
AdSense-policy adversarial review (all 3 PASS) plus a zero-hit AI-slop lint.
All other keys preserved; render.js contract unchanged. No deploy (Steve-gated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqMpGAFR7JLU42gQLSBoyQ
2b0f0ad · 2026-09-10 · chore: lint, refactor, v1.0.1 (session close) — TK-11307
Extracted SHOWROOM_TAG const + comment accuracy after the ShowroomOnly
rename. No behaviour change; 10/10 consumer-agreement suite green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fb33023 · 2026-09-10 · microsite catalog: showroom tag is 'ShowroomOnly' (TK-11307) Steve ruled 2026-09-10: the MDC showroom tag is 'ShowroomOnly', not 'Showroom'.
A pre-existing 'Showroom Line' tag sits on 18,518+ ACTIVE SELLABLE products
(Kravet 1969, China Seas 1775, sellable Phillipe Romano 1753, Scalamandre 913,
Osborne & Little 616, Designers Guild 559, ...). Shopify/Boost `tag:` queries are
PREFIX/TOKEN matchers and quoting does not help — measured live, `tag:Showroom`
and `tag:"Showroom"` each return 10,000+ ACTIVE of which a sampled 100/100 carry
'Showroom Line' and 0/100 carry the exact tag. 'ShowroomOnly' measures 0
collisions, so it is safe in code AND in a Shopify/Boost tag query (which makes
the Boost index rule usable and un-traps applier verification).
Verified: all 5 consumers (canonical primitive, hasShowroomTag, microsite
isShowroomTagged, theme Liquid render-skip, theme JS backstop) agree 7/7 on real
tag strings — MDC's new tag matches everywhere, every 'Showroom Line' sellable
case is false everywhere, and the old 'Showroom' tag no longer matches. Phillip
Jeffries stays suppressed via the untouched vendor path.
Applier must stamp 'ShowroomOnly'. Theme PUSH and fleet deploy remain Steve-gated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
830fbd9 · 2026-09-10 · fleet: Instagram embed on homePage for patterndesignlab + hollywoodwallcovering (TK-11322)
These 2 sister domains are served by dw-domain-fleet (dwf-*), not their standalone
repos, so the IG embed goes in the homePage render (gated on cfg.instagram_posts).
Latest 3 real posts per account from the Graph API. Only sites with the field render it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QE2344y5dVpRCba9MDDRcQ
92fa549 · 2026-09-09 · monetize sites: embed 20-pattern named grid + /img proxy + proxied hero/og (TK-11322)
- gen-pattern-grid.js: build data/pattern-grid.json = 20 niche-matched, showroom-
excluded, globally-diverse patterns/site from catalog.json; names vendor-stripped
+ leak-checked (670/700 named, 30 image-only fallback, 0 leaks).
- server.js: load pattern-grid on the monetize path, register imgs + heroImage,
give monetize sites the /img proxy route (serveImg extracted), pass PATTERNS to render.
- render.js: monetizeHome renders a 20-tile named grid via /img (replaces the 8 CSS-bg
hero gallery); hero bg + og/twitter image now proxied too -> 0 vendor leaks.
Verified end-to-end on a live-booted monetize site: 20 tiles, 21/21 /img tokens 200, 0 raw CDN.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QE2344y5dVpRCba9MDDRcQ
eeb4b26 · 2026-09-09 · monetize sites: add verified Instagram footer link for the 6 with real accounts (TK-11322)
Handles sourced from marketing-command-center dw-ig-accounts.json (Graph-API
audited, postable:true) — never invented. Footer renders an IG link only when
cfg.instagram is set; the 29 monetize domains without an account show none.
Matched: asseeninhotels, commercialwallcovering, fabricfridays, roomsettings,
sheltermagazines, traditionalwhimsy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QE2344y5dVpRCba9MDDRcQ
f49d0a6 · 2026-09-09 · monetize sites: render 6-8 image gallery from hero-allocation (TK-11322)
Pass HERO_IMGS into render.monetizeHome and render a responsive gallery of the
site's pre-allocated, globally-unique, niche-matched heroes as real <img> via
the /img proxy (vendor-neutral). Monetize homes previously showed 1 CSS-bg hero
and 0 <img>; now >=6 per site. No new image sourcing — reuses hero-allocation.json.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QE2344y5dVpRCba9MDDRcQ
b6088ea · 2026-09-09 · fleet Phase 2: scaffold 138 parked monetize domains — configs + ecosystem + nginx vhosts (ports 11143-11280) (TK-11322)
139/147 Phase-2 monetize domains already resolve to Kamatera; scaffolded 138 (1 slug
collision). Each: monetize:true, gradient hero, rotating theme. Content fans out next;
deploy/certs/GA4/AdSense run in memory-safe batches.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bab12e1 · 2026-09-09 · fleet: deploy-safe showroom-vendor require — fall back to in-repo list off-Mac2 (TK-11322)
Root cause of the 502s after repoint: server.js required ~/Projects/fix-live-board/
config/showroom-vendor.cjs, which exists on Mac2 but NOT Kamatera -> MODULE_NOT_FOUND
crashed every dwf-* on boot. Now try/catch with a fallback that reads the in-repo
config/showroom-vendors.json, so the fleet boots on any box. Verified funnel+monetize
boot 200 with HOME unset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96
39b399d · 2026-09-09 · fleet: repoint all 43 sites to free ports 11100-11142 (fix EADDRINUSE crash-loop) + monetize skips catalog build (TK-11322)
Whole dw-domain-fleet was crash-looping (EADDRINUSE): its 10001-10044 range was
100% occupied by the *.designerwallcoverings.com vendor microsites, so every
dwf-* failed to bind and pm2 stopped them — the vendor 'Trade Lines' apps answered
the fleet domains instead. Repoint to free 11100-11142 across ecosystem.config.js
+ sites/*.json + data/nginx/*.conf. Also: monetize procs now skip the DW catalog/
hero/img build entirely (unused on content sites) — big per-proc RAM saving for a
40+ proc box. Deploy also updates the live certbot nginx confs' proxy_pass port.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96
5c898d7 · 2026-09-09 · monetize Phase 1: wire GA4 for sheltermagazines — all 35/35 now have gtag (TK-11322)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96
44aaad2 · 2026-09-09 · monetize Phase 1: wire GA4 measurement IDs (cfg.ga4) into 34 sites (TK-11322)
GA4 properties+streams created under account 15714274; gtag now injects per site.
sheltermagazines pending one retry (GA create-rate quota). Not deployed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96
b9a98bc · 2026-09-09 · monetize Phase 1: enable monetize mode + per-domain content on 35 parked SEO sites (TK-11322)
35 fleet sites set monetize:true with themed hero + genuine per-domain content
(lede, 3 sections, checklist, FAQ, about, deeper /guide article). forSalePhase
UNSET = approval mode (no prominent banner yet). Protected/excluded: grasscloth*
(real content), thesetdecorator, philliperomano, malibuwallpaper, hollywoodwallcovering,
stevenabramsphotography, patterndesignlab, cfafabrics. Content authored by 6 parallel
subagents, validated (35/35 valid JSON, slop-clean, no vendor/brand leaks). NOT deployed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96
1681057 · 2026-09-09 · 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)
Steve decisions (2026-09-09): approve-first (prominent for-sale banner OFF until
forSalePhase='live'; subtle neutral 'Domain inquiries' footer link meanwhile) +
full template build. Adds: /guide + /contact pages, /sitemap.xml + /robots.txt,
FAQPage JSON-LD + FAQ panel, dismissible cookie-consent notice, per-site monogram
favicon, og:image from hero. Verified: all 9 routes 200, banner suppressed in
approval phase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96
00c6cee · 2026-09-09 · monetize: content sections + checklist as collapsed <details> panels (SEO-safe, SSR in DOM) (TK-11322)
Steve request. Native <details>/<summary>, first panel open by default, intro
lede stays visible above the fold. Content is server-rendered in the HTML so it
stays fully crawlable/indexable (collapsed != hidden for mobile-first indexing).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96
6769abd · 2026-09-09 · monetize: fix header overlap (in-flow sticky m-nav, not fixed topbar) + hero-image support with scrim (TK-11322)
Steve feedback: fixed topbar/hamburger overlapped the for-sale strip. Monetize
mode now uses an in-flow sticky header and an optional cfg.heroImage full-bleed
hero with a readability scrim; gradient fallback when no image.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96
ebca1a8 · 2026-09-09 · TK-11322: add red flocked hero asset for barwallpaper.com monetize hero
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96
990e3da · 2026-09-09 · fix(nginx): durable :443 socket normalization for fleet HTTPS-000 socket-specificity bug
Root cause: certbot inherits the vhost wildcard 'listen 80' and emits wildcard
'listen 443 ssl' (0.0.0.0:443); the box's working sites + default_server bind the
specific 45.61.58.125:443, which nginx prefers for external traffic, leaving fleet
vhosts unreachable externally (wrong cert -> curl 000). Add scripts/normalize-listen-socket.sh
(post-certbot :443 IP-bind, idempotent, dry-run default) + document the constraint
in gen-nginx.js (:80 must stay wildcard for the loopback smoke test).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDeaFL4eeREBZX79tc4cnr
e10d68c · 2026-09-09 · monetize: render authored content copy verbatim (skip DW wallpaper->wallcovering swap)
For-sale generic domains legitimately contain 'wallpaper'; the DW brand
word-swap only applies to DW funnel copy, not these standalone content sites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96
197eead · 2026-09-09 · monetize mode: AdSense Auto Ads + for-sale banner + content site + ads.txt/privacy for parked SEO domains (TK Task#2)
Adds cfg.monetize path: standalone topical content site (nav + hero + article +
guide + Privacy) with AdSense Auto Ads (pub-5278231299883833), /ads.txt, and a
greendomainbrokers for-sale banner. Strips the DW catalog/buy funnel on these
sites. Funnel sites unchanged. CSP widened for Google ad hosts only when monetize.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S81Y5KkGKrUHg2mwWQtD96
42bf6b7 · 2026-09-08 · microsite catalog: exclude showroom-tagged products from CLEAN grids (TK-11307)
CLEAN now filters isShowroomProduct (vendor-on-list OR 'Showroom' tag), making the
MDC exclusion rule-based instead of relying on the incidental display_variant junk
rule. Sellable Phillipe Romano products (no Showroom tag) still served.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDeaFL4eeREBZX79tc4cnr
71985bf · 2026-09-04 · TK-11200: isShowroomVendor guard — filter showroom-only vendors from all microsite catalog slices
Phillip Jeffries and any future showroom-only vendors (config/showroom-vendors.json)
are excluded from CLEAN pool so no microsite niche slice ever surfaces them.
Matches fix-live-board canonical list. Prod redeploy to Kamatera is gated.
Undo: git revert HEAD
3161f75 · 2026-09-03 · Fleet: keep showroom-only vendors (Phillip Jeffries) out of pool + promo banner via isShowroomVendor (TK-11186)
pull-catalog.js skips showroom vendors so they never enter catalog.json; server.js
filters POOL as defense-in-depth for the browse grids, promo banner, and hero/featured
set on all fleet sites. Regenerated catalog.json (14077 products, 0 PJ; was 1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgCcr9t5zNGdb43pS4f1ks
94d2a07 · 2026-08-17 · gtm: pre-stage GTM container snippet for commercialwallcovering + hollywoodwallcovering
- shared/render.js: add gtmHeadSnippet() + gtmNoscript() functions; wire
both into head() so every page emits the GTM <script> in <head> and the
<noscript> iframe immediately after <body> — keyed off cfg.gtm per site
- sites/commercialwallcovering.json: add "gtm": "GTM-XXXXXXX" placeholder
- sites/hollywoodwallcovering.json: add "gtm": "GTM-XXXXXXX" placeholder
- server.js: expand CSP to allow www.googletagmanager.com in scriptSrc +
frameSrc; add google-analytics.com / analytics.google.com to connectSrc
Swap GTM-XXXXXXX -> real container id per site once Steve supplies them.
No deploy — drafted to yolo-queue/pending-approval/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
41a3f8f · 2026-08-17 · GA4: wire gtagSnippet() into head() for per-site GA4 injection
Add gtagSnippet(mid) helper that emits the GA4 gtag loader script.
Wire it into head() reading cfg.ga4 (measurement ID from site JSON).
When cfg.ga4 is absent/empty, renders nothing — backward-compatible,
all 41 existing fleet sites unaffected.
Sites commercialwallcovering + hollywoodwallcovering will get their
G-IDs injected once the SA account-level Editor grant unblocks property
creation (see TK-10631 / pending-approval/TK-10631-ga4-dw-fleet-two-sites-deploy.md).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>