{"slug":"interiordesignershowroom","total":120,"limit":100,"offset":0,"since":null,"commits":[{"hash":"420a619","date":"2026-09-11 19:10:45 -0700","author":"Steve Abrams","subject":"Bump interiordesignershowroom to 0.5.1 after verified socket migration","body":""},{"hash":"a482499","date":"2026-09-11 08:26:08 -0700","author":"Steve Abrams","subject":"Use Unix socket for showroom Postgres fallback","body":""},{"hash":"6145bf4","date":"2026-09-10 06:56:04 -0700","author":"Steve Abrams","subject":"Disclose AdSense cookies in privacy page","body":""},{"hash":"b58941c","date":"2026-09-10 00:54:10 -0700","author":"Steve Abrams","subject":"Allow AdSense through public CSP","body":""},{"hash":"0358e6e","date":"2026-09-09 16:02:11 -0700","author":"Steve","subject":"TK-11341: add AdSense Auto-Ads loader + ads.txt (revert to remove ads)","body":""},{"hash":"323ecdb","date":"2026-09-05 10:01:19 -0700","author":"Steve Abrams","subject":"TK-11260: HOTSPOT_PROVIDER dispatcher + openai fallback, backfill 14 empty-hotspot rooms","body":"Gemini AI Studio prepay is still depleted (confirmed live 429\nRESOURCE_EXHAUSTED), so the 12 Flux-regenerated room scenes from TK-10402\nplus steelcase-office/mid-century-room stayed at hotspots=[] (rendered but\nnot shoppable). Mirrors the existing SCENE_PROVIDER pattern (lib/scene.js):\nlib/hotspots.js is now a thin dispatcher over lib/hotspot-providers/{gemini,openai}.js,\nsame {locateProducts, COST_PER_CALL} contract, so server.js and\nbackfill-room-scenes.js are untouched.\n\nRan scripts/backfill-hotspots.js (new, hotspots-only — ~10x cheaper than a\nfull scene regen) with HOTSPOT_PROVIDER=openai against the LOCAL dev DB:\n14 rooms updated, $0.07 total (gpt-5.2 vision, ~$0.005/room). 13/14 rooms\nnow carry at least one hotspot; \"room\" got a genuine 0-match (frontend\nedge-chip fallback covers it, same as Gemini's own omit-if-unsure behavior).\n\nLedgered as reversible (undo = UPDATE rooms SET hotspots='[]' for the 14\nslugs) at ~/.claude/yolo-queue/executed-reversible/ledger.jsonl.\n\nCo-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01VAXbNq4CYRmd9hRam2NU6P"},{"hash":"156ccf7","date":"2026-09-04 12:18:10 -0700","author":"Steve","subject":"TK-10402: undo script for Flux room-scene backfill","body":"12 NULL room scene_images regenerated via Replicate-Flux fallback (Gemini\nprepay depleted). This reverts scene_image->NULL + deletes the generated PNGs.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01EgcJfyrTaPp4vJYtptSXGG"},{"hash":"9c27c31","date":"2026-09-04 08:25:09 -0700","author":"Steve Abrams","subject":"deploy: guard prod-only runtime data from rsync --delete","body":"deploy.sh runs 'rsync -az --delete' with no data/ exclude, so a deploy\noverwrote the box's runtime file with the local copy and destroyed\neverything written since the last deploy. Added a surgical\nRSYNC_EXTRA_EXCLUDES for this project's runtime path(s) only, so genuine\nbuild artifacts under data/ keep syncing.\n\nVerified by itemized rsync --dry-run against prod: the runtime file is\nlisted for transfer WITHOUT the exclude and absent WITH it.\n\nFleet sweep, TK-11227. Same bug class as the July 2026 artmura/quadrille\nmis-target fix and the abramsagency lead-ledger fix (TK-10536).\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_013mq9RQtVZZEWvreDqqTgv2"},{"hash":"abbea82","date":"2026-09-03 14:14:36 -0700","author":"Steve Abrams","subject":"TK-10402: route guide heroes through the SCENE_PROVIDER dispatcher + add spend caps","body":"The documented Flux runbook was broken for the guides half: gen-guide-heroes.js\ncalled the Gemini endpoint directly and silently ignored SCENE_PROVIDER, so\n`SCENE_PROVIDER=replicate-flux node scripts/gen-guide-heroes.js` would have failed\nonce per guide against the depleted Gemini key (~109 failures, prod scope).\n\n- lib/scene-providers/{gemini,replicate-flux}.js: add generateEditorial({subject})\n  -> {buffer, cost}. Gemini's prompt text is byte-identical to the inline one it\n  replaces, so the default path is unchanged.\n- lib/scene.js: export generateEditorial through the dispatcher.\n- scripts/gen-guide-heroes.js: use scene.generateEditorial (provider-agnostic).\n- lib/run-guard.js (new): shared --dry-run / --limit / --max-cost parsing so a cap\n  behaves identically across both paid drivers.\n- both drivers: --dry-run ($0 target list + estimate), --max-cost hard cap (default\n  $12, pre-flight abort + per-image stop), and a consecutive-failure abort so an\n  unfunded credential can't emit the same error once per row across the batch.\n\nVerified $0: dry-runs on both drivers, cap abort, default provider still gemini\n($0.039), replicate-flux dispatch resolves, node --check on all touched files,\nserver.js + lib/roomgen unaffected. No provider call made; ticket stays gated on\nSteve's spend + funded credential.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_011X4xabTP7zwUuN7zeDPfbJ"},{"hash":"1a8e5c7","date":"2026-08-27 11:08:04 -0700","author":"Steve Abrams","subject":"TK-10402: AbortSignal 30s timeout + ok-check on Flux image download (inert path hardening)","body":"Prevents a hung Replicate CDN download from wedging the batch indefinitely;\nadds an explicit HTTP-status check. No behavior change on the funded path,\nno spend (fallback stays inert until a REPLICATE_API_TOKEN is routed).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"797a158","date":"2026-08-25 12:31:33 -0700","author":"Steve Abrams","subject":"chore: refactor review (clean, no changes), v0.5.0 (session close)","body":"Session added the SCENE_PROVIDER seam + Flux-Kontext fallback (50e7120) — a\nbackward-compatible feature, default gemini unchanged. javascript-pro refactor pass:\nno edits warranted. Steve chose the no-Gemini path; Flux fallback stays inert until a\nREPLICATE_API_TOKEN is funded (Steve-gated).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"50e7120","date":"2026-08-25 12:25:30 -0700","author":"Steve Abrams","subject":"TK-10402: pluggable scene-provider seam + Flux-Kontext fallback (inert until funded)","body":"Extracts the Gemini nano-banana impl behind a SCENE_PROVIDER dispatcher (lib/scene.js)\nso a depleted provider is a one-flag flip. Default=gemini is byte-identical ($0.039,\nsame output path/contract) — backfill-room-scenes.js + lib/roomgen untouched.\nAdds lib/scene-providers/replicate-flux.js: reference-capable (keeps 'real product in\nthe room'), throws a clear actionable error + spends nothing until a REPLICATE_API_TOKEN\nis funded. Gemini prepay top-up remains the recommended path.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"4e4edbb","date":"2026-08-10 12:41:37 -0700","author":"Steve Abrams","subject":"chore: v0.4.5 (session close) — backfill-room-scenes.js lint-clean; Option B pipeline ready, blocked on Gemini prepay","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"a89299b","date":"2026-08-10 11:30:17 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-10T11:29:10 (6 data files) — .playwright-mcp/page-2026-08-10T18-04-30-272Z.yml .playwright-mcp/page-2026-08-10T18-04-41-320Z.yml .playwright-mcp/page-2026-08-10T18-04-50-448Z.yml ids-guide-modern-bedroom.png ids-home.png","body":""},{"hash":"cb0b958","date":"2026-08-10 11:18:10 -0700","author":"Steve Abrams","subject":"TK-10402: backfill-room-scenes.js — regenerate scene_image for reconciled rooms via nano-banana (reuses lib/scene + lib/hotspots, idempotent, per-image cost)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"666bd0c","date":"2026-08-10 10:50:02 -0700","author":"Steve Abrams","subject":"chore: bump v0.4.4 (session close) — guide-image fallback (3407b7f) verified; no refactor (already committed for gated deploy)","body":""},{"hash":"3407b7f","date":"2026-08-10 08:59:39 -0700","author":"Steve Abrams","subject":"fix(TK-10390): guide image fallback — no more imageless guides after reconcile","body":"The 257-path reconcile NULLed 107 guides.hero_image, leaving guides imageless\n(JSON-LD Article.image dropped, og:image -> logo, home/index cards blank) — a\nreal SEO/social regression cre-agent's contrarian gate caught (M-00475).\n\nOption A (render-layer, $0): guides now fall back to a representative product\nimage (first non-suppressed product of the guide) — exactly like rooms fall back\nto product thumbs. Applied to all 3 surfaces: detail route (Article.image +\nog:image + visible hero via guideImg), home guide-strip + /guides index (via a\nfirst-product-image subquery). data-hide-on-error still guards the img.\n\nVerified: fallback subquery returns real product images 5/5 guides; absUrl passes\nexternal CDN URLs through unchanged (no double-prefix); simulated prod-NULL path\n-> product image; syntax OK, routes 200; guides WITH a real hero unchanged.\nDeploy gated -> pending-approval."},{"hash":"771434d","date":"2026-08-10 08:01:05 -0700","author":"Steve Abrams","subject":"chore: bump v0.4.3 (session close) — 404/CSP/image-degrade/reconcile all live-verified; code syntax-clean, no refactor (already deployed, would desync prod)","body":""},{"hash":"286edb3","date":"2026-08-10 07:53:21 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-10T07:52:37 (4 data files) — .playwright-mcp/page-2026-08-10T14-40-45-681Z.yml .playwright-mcp/page-2026-08-10T14-43-02-390Z.yml .playwright-mcp/page-2026-08-10T14-43-15-486Z.yml .playwright-mcp/page-2026-08-10T14-52-10-562Z.yml","body":""},{"hash":"c6f5c6b","date":"2026-08-10 06:20:22 -0700","author":"Steve Abrams","subject":"TK-10390: reconcile-dead-images.js — dry-run tool to NULL dead scene_image/hero_image paths","body":"Finds rooms.scene_image / guides.hero_image pointing to a missing local file and,\nwith --apply (GATED prod write, snapshot-first + single transaction), NULLs them so\npages fall back cleanly and the dead path leaves the OG/JSON-LD image tags too.\nDry-run by default, read-only. Interim cleanup; proper fix = regenerate the missing\nPNGs (claude-email's roomgen lane). Verified: syntax OK, local dry-run reports clean."},{"hash":"0acaddb","date":"2026-08-10 04:12:32 -0700","author":"steve@designerwallcoverings.com","subject":"add scripts/fix-dangling-scenes.js — NULL out dangling scene_image DB refs on prod (TK-10390)","body":""},{"hash":"9530ee0","date":"2026-08-09 17:29:21 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-09T17:28:38 (2 data files) — .playwright-mcp/page-2026-08-10T00-09-07-501Z.yml .playwright-mcp/page-2026-08-10T00-20-51-095Z.yml","body":""},{"hash":"63069ec","date":"2026-08-09 17:21:29 -0700","author":"Steve Abrams","subject":"fix: graceful-degrade broken guide-hero + room-scene images (interim)","body":"Interim mitigation for TK-10390 (150 auto-rooms + some guide heroes point to\n/img/rooms/*.png files that don't exist -> broken-image icons on /room/:slug,\n/guides, and home). Root-cause regeneration stays claude-email's auto-room lane.\n\n- imgfallback.js: data-hide-on-error now hides the whole figure.room-scene-wrap\n  when a .room-scene img fails (so the hotspot layer doesn't float over an empty\n  box); other opt-in imgs hide themselves as before.\n- sceneFigure (render.js) + 3 guide-hero <img> sites (home/guides/detail): added\n  data-hide-on-error.\n- No-op for images that load; complementary to regeneration (not conflicting).\n\nVerified in real Chrome: broke a scene src -> figure display:none, roomThumbs\nrail stays visible, valid images unaffected. Deploy GATED -> pending-approval."},{"hash":"23f8ed4","date":"2026-08-09 17:14:18 -0700","author":"Steve Abrams","subject":"fix(csp): allow static.cloudflareinsights.com in public script-src","body":"CF auto-injects its Web-Analytics beacon on the proxied zone; the strict\npublic script-src ('self' + googletagmanager) was blocking it -> a CSP\nconsole error on every page. Added the CF Insights origin (public only;\n/admin unchanged). Verified: public CSP now lists the origin, /admin still\n'unsafe-inline'-only, server boots clean."},{"hash":"cb035b8","date":"2026-08-09 09:08:48 -0700","author":"Steve Abrams","subject":"hardening: branded 404 page + remove orphan public/js/cart.js","body":"- 404 handler was a bare 'Not found / back to showroom' line; now a branded\n  Error-404 page with helpful destinations (Shop/Rooms/Build/Guides), inline\n  styles matching the footer pattern (no CSP/style-src impact), reusing layout\n  header+footer. Verified: /nope -> 404 status + new copy, home still 200.\n- public/js/cart.js was dead scaffolding from the initial generic-ecommerce\n  base — loaded by no script (this is an affiliate showroom; click-through is\n  /go, there is no cart/checkout). Removed; moodboard.js comment de-referenced.\n- /shop no-results state already handled (server.js grid-empty); sitemap\n  lastmod left as-is (guides/rooms already carry accurate lastmod; fabricating\n  static-page dates would be an SEO-negative, not an improvement).\n\nDeploy to Kamatera is GATED -> drafted to pending-approval."},{"hash":"e5b86aa","date":"2026-08-05 11:38:35 -0700","author":"Steve","subject":"Remove AdSense loader + ads.txt (luxury/commerce surface, not in AdSense account) — staged; deploy is Steve-gated","body":""},{"hash":"31b8301","date":"2026-08-04 16:42:28 -0700","author":"Steve Abrams","subject":"AdSense: loader in head + /ads.txt (ca-pub-5278231299883833) — approved ad site","body":""},{"hash":"160817d","date":"2026-08-03 12:59:17 -0700","author":"Steve Abrams","subject":"chore: bump v0.4.2 (session close) — GA4-under-CSP fix; quality gate clean (no linter, refactor no-op)","body":""},{"hash":"0e28b93","date":"2026-08-03 12:52:57 -0700","author":"Steve Abrams","subject":"GA4 under strict CSP: allow-list gtag.js origin + self-host inline bootstrap (/js/ga.js, deferred)","body":"The parallel GA4 injection (bc6f4d9) added an external googletagmanager.com script\n+ an INLINE gtag config to the layout head — both blocked by C11's script-src 'self'\n(dead analytics on next reload). Fix keeps the CSP strict: allow-list\nwww.googletagmanager.com for the library, move the inline bootstrap to a self-hosted\ndeferred /js/ga.js so 'self' admits it (no 'unsafe-inline' reintroduced). connect-src\nleft intentionally open (documented) so GA4 regional beacons keep working — enumerating\nthem is brittle. Cody-gated, verified live (CSP header, ga.js 200+deferred, 0 inline gtag).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"c988220","date":"2026-08-03 12:39:25 -0700","author":"Steve","subject":"deploy: add push-full-kamatera.sh (full-source sync, avoids per-file divergence 502s)","body":""},{"hash":"bc6f4d9","date":"2026-08-03 12:30:44 -0700","author":"Steve","subject":"GA4: inject gtag.js (G-DH9G5HL7YJ) into layout head — analytics for interiordesignershowroom.com","body":""},{"hash":"c686c5d","date":"2026-08-03 12:22:26 -0700","author":"Steve Abrams","subject":"fix(server): graceful listen error handler + honest HOST bind (was unhandled crash on EADDRINUSE + log lied about bind host)","body":""},{"hash":"27b1775","date":"2026-08-03 12:12:52 -0700","author":"Steve Abrams","subject":"security: full script-src 'self' CSP on public (delegated all img onerror incl client JS) + fix /admin path-guard prefix collision (Cody-hardened)","body":""},{"hash":"93f0ffc","date":"2026-08-03 11:59:13 -0700","author":"Steve Abrams","subject":"perf: content-hash asset cache-buster (?v=) + immutable css/js caching — deploy-safe, logged fallback, dev-guard (Cody-hardened)","body":""},{"hash":"3b62dda","date":"2026-08-03 11:53:53 -0700","author":"Steve Abrams","subject":"auto-save: 2026-08-03T11:53:46 (3 files) — lib/render.js server.js lib/assetv.js","body":""},{"hash":"6c5123a","date":"2026-08-03 11:47:33 -0700","author":"Steve Abrams","subject":"seo: honest Offer schema (data-driven itemCondition + conditional availability) + complete article OG tags (Cody-hardened)","body":""},{"hash":"f1385c9","date":"2026-08-03 11:38:46 -0700","author":"Steve Abrams","subject":"perf: correct static caching — content-hashed images immutable 1y, non-hashed 1d, css/js revalidate (deploy-safe, Cody-hardened)","body":""},{"hash":"cdb2c85","date":"2026-08-03 11:25:08 -0700","author":"Steve Abrams","subject":"seo+security: guides CollectionPage schema + Article url/dateModified + escape </script> in ALL JSON-LD (fleet-wide injection fix, Cody-hardened)","body":""},{"hash":"41049f5","date":"2026-08-03 11:23:39 -0700","author":"Steve Abrams","subject":"auto-save: 2026-08-03T11:23:33 (1 files) — server.js","body":""},{"hash":"1bd3992","date":"2026-08-03 11:14:28 -0700","author":"Steve Abrams","subject":"security: response headers + non-breaking CSP (frame-ancestors/object-src/base-uri) + patch /api/render error-message leak (Cody-hardened)","body":""},{"hash":"c34917c","date":"2026-08-03 11:13:46 -0700","author":"steve","subject":"snapshot before deploy: in-tree security headers (nosniff/frame/referrer/permissions, drop x-powered-by) + don't leak raw error to /api/render client","body":""},{"hash":"81d733e","date":"2026-08-03 11:08:43 -0700","author":"steve","subject":"refine: shared fmtStamp (storefront+admin, NaN/future-safe), sortable products table (Newest|Most clicks), post-migration schema assertion — v0.4.1","body":""},{"hash":"3f3869c","date":"2026-08-03 11:04:34 -0700","author":"Steve Abrams","subject":"a11y: working skip-to-content link (focus-moving, tabindex=-1) + prefers-reduced-motion support (Cody-hardened)","body":""},{"hash":"85a3efa","date":"2026-08-03 10:55:39 -0700","author":"Steve Abrams","subject":"seo+integrity: per-page OG images + saved rooms/guides/catalog fully honor suppression & exclude wall paints (Cody-hardened)","body":""},{"hash":"303f890","date":"2026-08-03 10:55:06 -0700","author":"Steve","subject":"chore: sync package-lock to v0.4.0","body":""},{"hash":"7c3f401","date":"2026-08-03 10:54:55 -0700","author":"Steve","subject":"chore: v0.4.0 (session close — date+time-added on store cards, admin analytics, deploy schema-migration safety)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"c8362bb","date":"2026-08-03 10:53:24 -0700","author":"Steve Abrams","subject":"auto-save: 2026-08-03T10:53:14 (2 files) — lib/rooms.js server.js","body":""},{"hash":"181743c","date":"2026-08-03 10:44:12 -0700","author":"Steve","subject":"deploy: schema-migration step + /shop smoke test (stop code-ahead-of-schema)","body":"Prevents a repeat of the 2026-08-03 regression where shipped code referenced a\nprod-missing column (products.suppressed) + tables (affiliate_settings,\nsuppress_rules) and /shop 500'd while the DB-blind /healthz smoke test stayed\ngreen.\n\n- db/schema.sql: explicit ADD COLUMN IF NOT EXISTS guards (inline, before\n  dependent indexes) so the file is a complete idempotent migration on a fresh\n  OR drifted DB, not just a fresh-create.\n- scripts/migrate.sh: applies schema.sql with ON_ERROR_STOP; loads DATABASE_URL\n  from the remote's own .env.\n- .deploy.conf: BUILD_CMD runs the migration on the remote after install/before\n  reload; HEALTH_URL now hits /shop (exercises the real catalog query, not the\n  DB-blind /healthz); INSTALL_CMD bakes in the sharp-safe (scripts-enabled) install.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"ffa8079","date":"2026-08-03 10:39:27 -0700","author":"Steve Abrams","subject":"chore: v0.3.0 — keyword/id suppression feature (session close)","body":"Author: session that shipped 72708fc (admin Suppress page). Version bump only;\nworking-tree edits to lib/rooms.js/routes/admin.js/server.js + new lib/ids.js belong\nto a concurrent session and are intentionally left uncommitted."},{"hash":"39571bf","date":"2026-08-03 10:38:36 -0700","author":"Steve Abrams","subject":"robustness: bigint-safe id guard across all client-value->id paths (/go, product_ids, wall_paint_id, admin+suppress routes) — no more 500 on malformed/overflow ids","body":""},{"hash":"aae117f","date":"2026-08-03 10:32:52 -0700","author":"Steve","subject":"harden: intOrNull() guard on client-supplied ids reaching bigint columns","body":"Non-numeric wall_paint_id (and similar) now coerces to null instead of\nNumber()->NaN reaching a bigint query and throwing a 500. Applied at\n/api/rooms and /api/render call sites. (Pre-existing working-tree change,\nsnapshotted before deploy so shipped code == committed code.)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"78c986b","date":"2026-08-03 10:28:50 -0700","author":"Steve","subject":"store: date + time added on product cards — New badge + 'Added …' line","body":"Every storefront product card now shows when it was added to the store: a NEW\nbadge over the image for items added in the last 14 days (exact date+time in the\nhover title), plus a muted, dated 'Added Mon D, YYYY, H:MM AM' line under the\nprice. New shared addedStamp() helper in render.js mirrors the admin when()\nformat; card-new + card-added styles added to site.css. Chosen treatment per\n2026-08-03 decision (overrides the usual 'customer cards omit created date').\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"72708fc","date":"2026-08-03 10:26:48 -0700","author":"Steve Abrams","subject":"admin: keyword/id suppression rules (Suppress page) + Vacuum re-apply + Refresh","body":"- New suppress_rules table (durable keyword/id hide rules, distinct from source-level\n  affiliate_settings and the raw suppressed flag).\n- /admin/suppress: enter keywords (and/or ids) -> immediately flip suppressed=TRUE on\n  every current match (title/brand/advertiser). Reuses the pervasive NOT suppressed gate\n  so hides propagate to /shop, /rooms, guides, and the sitemap.\n- Vacuum re-applies all rules (catches products ingested later matching an old keyword);\n  Refresh reloads/recounts; View shop -> /shop?room=decor&style=modern.\n- Per-rule live match/hidden counts, created date+time, Re-apply, Remove, Remove+unhide.\n- Verified end-to-end: add velvet (7 hidden), storefront drop confirmed, vacuum idempotent,\n  remove+unhide restores state; CSRF guard returns 403."},{"hash":"82f2795","date":"2026-08-03 10:22:20 -0700","author":"Steve","subject":"admin: per-product click analytics column (total, 7d, last-clicked date)","body":"Fold click aggregates into the products query via one grouped LEFT JOIN and\nrender a Clicks column per row — lifetime total, a 7-day sub-count, and the\nlast-clicked timestamp (dated with the standard when() helper). No-click\nproducts show a muted — instead of fake zeros.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"5b5102f","date":"2026-08-03 10:16:22 -0700","author":"Steve Abrams","subject":"seo: breadcrumbs (nav + BreadcrumbList) across facet/room/guide/looks pages, multi-JSON-LD layout (Cody-hardened)","body":""},{"hash":"ecc8fe0","date":"2026-08-03 09:59:41 -0700","author":"Steve Abrams","subject":"nav: promote kitchen/bathroom to rooms + upper-right room hamburger (collapsed on load, all room types)","body":""},{"hash":"6a15502","date":"2026-08-03 09:50:23 -0700","author":"Steve Abrams","subject":"seo: indexable facet landing pages — sitemap + self-canonical whitelist, ratio-gated, orphan rooms 404 (Cody-hardened)","body":""},{"hash":"3e86ee5","date":"2026-08-03 09:49:33 -0700","author":"Steve","subject":"IDS admin: implement EZ-Join DTD verdict (C+relabel), fix smart-quote regression, sync rooms.hotspots schema (v0.2.1)","body":"- EZ-Join verdict C-as-superset-of-A: joinTarget() resolves a per-network\n  marketplace SEARCH deep-link (brand pre-filled) where searchVerified, else the\n  plain landing (safe fallback). All networks land today (unverified); flip\n  searchVerified per network after a live test. Honest relabel 'EZ Join' ->\n  'Join via <Network>'.\n- Fix: refactor pass had introduced curly quotes as HTML attr delimiters on the\n  join button (class=\"ezjoin\"/href broke styling+link) — restored straight quotes.\n- db/rooms.sql: declare + idempotently ALTER-add the hotspots JSONB column that\n  createRoom() writes (schema file was drifted from the live DB).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"562d723","date":"2026-08-03 09:40:13 -0700","author":"Steve","subject":"chore: lint, refactor, v0.2.0 (session close)","body":"Lint: colon-safe Basic-auth parse. Refactor (behavior-preserving): move\nAFFILIATE_ENABLED const above buildWhere; NUL->KEY_SEP; chip param key->netKey;\ndedup joinedNets label. Verified: auth 401/200, /shop + affiliates + suggested\n200, toggle round-trip. Minor bump — session shipped the affiliate on/off admin.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"33bf504","date":"2026-08-03 09:23:47 -0700","author":"Steve","subject":"IDS admin: confirm CJ PID 101848244, drop bogus 1062321 (TK-10171)","body":".env CJ_WEBSITE_ID=101848244 matches the PID in the live tracking links, so CJ\nattribution is correct. 1062321 was identified as a FileMaker customer acct\n(client Claire, Shopify order #32749), not a CJ id — removed from the record.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"8fb23cd","date":"2026-08-03 09:17:20 -0700","author":"Steve","subject":"IDS admin: record CJ account identifiers on Affiliates tab (TK-10171)","body":"Adds a CJ Account reference bar to /admin/affiliates from data/cj-account.json:\nlogin steve@designerwallcoverings.com, CID 7896980, live-link PID 101848244\n(all evidence-confirmed). 1062321 shown as 'unverified' — it matches neither the\nCID nor the PID in the live click-[PID]-[AID] tracking links, so it is NOT wired\ninto anything pending confirmation under CJ Account > Websites.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"770915b","date":"2026-08-03 09:13:31 -0700","author":"Steve Abrams","subject":"admin: keyword line-hider with preview — catch a line across reseller-mislabeled brands (Honiture gap)","body":""},{"hash":"dcba584","date":"2026-08-03 09:10:11 -0700","author":"Steve Abrams","subject":"admin: brand-level line selector — hide/show a whole brand across every advertiser","body":""},{"hash":"99f7508","date":"2026-08-03 08:52:29 -0700","author":"Steve Abrams","subject":"auto-save: 2026-08-03T08:52:19 (7 files) — db/schema.sql lib/catalog.js lib/render.js lib/rooms.js public/css/site.css","body":""},{"hash":"95495fd","date":"2026-08-03 08:49:58 -0700","author":"Steve","subject":"IDS admin: 'Suggested lines to join' affiliate tab (TK-10171)","body":"Adds a second tab under /admin/affiliates: a curated gap-finder of on-brand\nhome/decor affiliate programs across CJ + other networks. Flags lines already\nin the catalog as Joined, gives an EZ Join deep-link into the right network's\nmarketplace for the rest, filterable by network. Roster is data-driven\n(data/suggested-affiliates.json), read live so it extends without a restart.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"e72a006","date":"2026-08-03 08:45:20 -0700","author":"Steve","subject":"moodboard drawer: Pieces tab defaults open (board visible on open); Project/Create stay collapsed; explicit collapse still persists","body":""},{"hash":"d44578c","date":"2026-08-03 08:35:52 -0700","author":"Steve Abrams","subject":"Switch Honiture affiliate OFF (kept, hidden) + drop its ingest keyword","body":""},{"hash":"81400f7","date":"2026-08-03 08:33:35 -0700","author":"Steve","subject":"moodboard drawer: in-drawer project switcher + collapsible Project/Pieces/Create tabs (collapsed on load, state persisted)","body":""},{"hash":"590fca7","date":"2026-08-03 08:28:14 -0700","author":"Steve","subject":"shop: collapse facet rail on load, 4-across grid + columns slider, mood-board project select","body":"- facet rail -> native <details>, all collapsed on load (auto-opens only the group holding an active filter)\n- grid controls: columns slider (2-6, default 4) driving #grid via --gcols; grid starts 4-across even pre-JS\n- sort: date/color/style/brand/title/price all present (Newest relabelled 'Date - Newest')\n- mood board -> named PROJECTS: #mbProjectSel switcher, create/switch, per-project item lists; legacy single board migrated to 'My Board'"},{"hash":"f14bc91","date":"2026-08-03 08:01:06 -0700","author":"steve","subject":"refactor: replace all SELECT * with explicit column lists (lib/cols.js) — clears the pre-deploy lint block so the standard deploy.sh path works for IDS (no more surgical-rsync workaround); result sets identical","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"28e228f","date":"2026-08-03 08:01:05 -0700","author":"steve","subject":"feat(moodboard): client-side spend guard on Create Room Setting — two-step confirm (\"Generate room ~$0.039? Tap again\") + per-session cap of 8 renders, so a public visitor can't silently rack up Gemini spend (server per-IP limit still applies)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"e5ef1c4","date":"2026-08-03 07:32:34 -0700","author":"steve","subject":"feat: shop-time Mood Board — collect pieces while shopping, assemble, one button turns them into a photoreal room setting","body":"- public/js/moodboard.js: self-contained client feature (mirrors cart.js). Injects a Mood Board button on every product card, floating FAB + slide-in drawer that assembles all selections, a star-Primary toggle, room-type picker, and a Create Room Setting button that POSTs the selected ids to the existing /api/render (Gemini 2.5 Flash Image, ~$0.039) and shows the rendered room + shoppable pieces inline\n- lib/render.js: load moodboard.js globally so selection works site-wide (shop, rooms, home, looks)\n- Primary piece is sent first so it anchors the room (scene refs the first 4 pieces)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"713ee0d","date":"2026-08-03 07:32:34 -0700","author":"steve","subject":"fix(render): match bigint product ids type-safe — /api/render was silently ignoring explicit product_ids (string p.id === number id never matched), rendering a generic room; also fixes /build explicit-piece selection","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"96f59a1","date":"2026-08-02 23:48:26 -0700","author":"Steve Abrams","subject":"ingest: partner-scoped CJ sweeps (partnerIds) + TWOPAGES program joined 2026-08-02 — keyword search can't reach niche joined advertisers; feed indexes 24-48h post-join, re-runs pick it up","body":""},{"hash":"b8892c3","date":"2026-08-02 20:23:58 -0700","author":"Steve Abrams","subject":"ingest: add robot/cordless vacuum + honiture brand sweep — Honiture CJ program joined 2026-08-02 (56 products, all tracked, $20-$260)","body":"Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>"},{"hash":"53a67e4","date":"2026-08-02 20:12:15 -0700","author":"Steve Abrams","subject":"fix .deploy.conf: IDS runs on 9876 (9861 belongs to staged.dw) — smoke test was probing the wrong app","body":""},{"hash":"8898a07","date":"2026-08-02 19:39:26 -0700","author":"Steve Abrams","subject":"docs: go-live runbook for auto-guides (gated)","body":""},{"hash":"213ca8b","date":"2026-08-02 19:38:29 -0700","author":"Steve Abrams","subject":"feat: auto guide generator — gen-guide.js builds a room (shared lib/roomgen) + authors an editorial guide around its exact pieces (local qwen3:14b, template fallback); room-type rotation for variety; 5 guides published locally","body":""},{"hash":"325e399","date":"2026-08-02 00:46:18 -0700","author":"Steve Abrams","subject":"add tracked-link coverage canary: samples live /go redirects, alerts on worsening CJ-tracking coverage (CNCP), daily 3:15am","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"64a5449","date":"2026-08-01 23:57:10 -0700","author":"Steve Abrams","subject":"feat: affiliate cart — collect pieces, checkout opens each product in its own tab (per-merchant). Server-rendered add-cart buttons + client cart.js (localStorage list, floating cart button, slide-in drawer, tab-fanout checkout with popup-block fallback)","body":""},{"hash":"47e1b8d","date":"2026-08-01 23:46:24 -0700","author":"Steve Abrams","subject":"room page: 3x-larger click-to-shop thumbnails (info + Buy on click); big thumbs are the shop surface on scene-less rooms so they no longer dead-end","body":"- roomThumbs: each thumb is now a <button> that expands product info (brand/title/price) + a tracked Buy button inline, instead of jumping straight out to the affiliate link\n- thumbs 56px -> 168px (3x); new /js/room-thumbs.js toggle (one-open, Esc/outside-click closes), mirrors hotspot-card grammar\n- server room route: render the big thumbs as the shoppable surface when a room has no scene_image (fixes glam-room dead-end); scene rooms unchanged (scene + side rail + grid)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"a9db00f","date":"2026-08-01 23:34:43 -0700","author":"Steve Abrams","subject":"feat(build): right panel shows the products IN the generated room (shoppable) + vibe-only render back-fills a full 6-piece room; middle keeps the glowing hotspot dots; Browse-all restores the catalog","body":""},{"hash":"9906389","date":"2026-08-01 23:24:02 -0700","author":"Steve Abrams","subject":"feat(build): infinite-scroll the products panel — offset pagination in searchProducts/API, scroll-to-load-more in build.js (48/page, stable id-tiebreak sort, no dup rows); left filters reset to page 0","body":""},{"hash":"bede8a7","date":"2026-08-01 23:19:33 -0700","author":"Steve Abrams","subject":"feat(room): thumbnail rail beside the scene — stacked right on desktop, below on mobile; image-only thumbs, item name on hover; each a tracked shop link","body":""},{"hash":"439a773","date":"2026-08-01 23:04:29 -0700","author":"Steve Abrams","subject":"ui(build): persistent 3-column Room Builder — left words, right products column, middle images/room-settings centered (desktop); products no longer hidden under hamburger; hint text updated","body":""},{"hash":"b7e8531","date":"2026-08-01 22:59:33 -0700","author":"Steve Abrams","subject":"feat: shoppable hotspots on the live Room Builder gallery too (/api/render returns vision-located hotspots; build.js overlays dots w/ hover-card + click-to-shop; saved rooms persist builder hotspots)","body":""},{"hash":"ffe693d","date":"2026-08-01 22:48:55 -0700","author":"Steve Abrams","subject":"feat: shoppable room-setting images (vision-located hotspots w/ IoU dedup) + auto room generator from CJ inventory (Gemini scene + hotspots, ~$0.04/room); rooms.hotspots jsonb; inline hotspot cards on /room/:slug","body":""},{"hash":"8df6b7b","date":"2026-08-01 22:40:07 -0700","author":"Steve Abrams","subject":"auto-save: 2026-08-01T22:40:01 (3 files) — lib/rooms.js lib/hotspots.js scripts/gen-room-setting.js","body":""},{"hash":"4a02b54","date":"2026-08-01 22:35:47 -0700","author":"Steve Abrams","subject":"refine: real Gemini guide heroes (replace picsum placeholders, 1200x630 jpg ~90kb) + unpublish junk test rooms from index","body":""},{"hash":"082c438","date":"2026-08-01 22:30:45 -0700","author":"Steve Abrams","subject":"refine: soften freshness chip to 'Price checked' + SEO structured-data (Organization/WebSite/SearchAction on home, ItemList+Product on shop/rooms, per-page OG image, sitemap adds looks/build/public-rooms + lastmod)","body":""},{"hash":"1ead1eb","date":"2026-08-01 22:16:24 -0700","author":"Steve Abrams","subject":"refine: price-freshness trust chip on cards (price_checked_at) + PNG/apple-touch favicon fallbacks","body":""},{"hash":"7f7de63","date":"2026-08-01 22:07:17 -0700","author":"Steve Abrams","subject":"harden /api/render + admin: persistent restart-proof render cost-guard (cf-connecting-ip + daily cap), scene_image allowlist, admin CSRF origin check","body":""},{"hash":"8d27aa6","date":"2026-08-01 22:02:20 -0700","author":"Steve Abrams","subject":"Frontend refinement: consistency + edge/empty states + mobile + a11y + loading states across storefront and Room Builder","body":"Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"},{"hash":"96155e2","date":"2026-08-01 21:58:08 -0700","author":"Steve Abrams","subject":"Data quality: dead-image guard (in_stock flag), image-based color enrichment for the 12-color filter, dedup","body":""},{"hash":"582513e","date":"2026-08-01 21:30:53 -0700","author":"Steve Abrams","subject":"Fix products panel + close button z-index (were behind sticky header); panel now overlays correctly","body":""},{"hash":"012c874","date":"2026-08-01 21:28:41 -0700","author":"Steve Abrams","subject":"Room Builder: fix hamburger click (was behind sticky header); show room's pieces as a shoppable grid below the generated render; single-color product column at all widths","body":""},{"hash":"03357d1","date":"2026-08-01 21:24:12 -0700","author":"Steve Abrams","subject":"Room Builder: right products = single image-forward column; whole card click selects into the top bar; larger selected-product thumbnails in the tray","body":""},{"hash":"c3798f4","date":"2026-08-01 21:19:18 -0700","author":"Steve Abrams","subject":"Add idempotent nightly live-catalog refresh (SSH-tunnel upsert into prod; swaps in tracked links as CJ advertisers are approved)","body":""},{"hash":"1012ec8","date":"2026-08-01 21:11:30 -0700","author":"Steve Abrams","subject":"Elevate site UI to steelcase.com-caliber editorial design","body":""},{"hash":"3448c99","date":"2026-08-01 21:09:34 -0700","author":"Steve Abrams","subject":"auto-save: 2026-08-01T21:09:29 (7 files) — lib/scene.js package-lock.json package.json server.js _ctest.js","body":""}]}