{"slug":"prestige-car-wash","total":54,"limit":100,"offset":0,"since":null,"commits":[{"hash":"f947bb1","date":"2026-07-27 11:29:11 -0700","author":"Steve Abrams","subject":"chore: v0.4.0 (session close) — 11 features shipped, place_id + apex cleanup + GA4 live","body":""},{"hash":"c1fd206","date":"2026-07-27 11:02:00 -0700","author":"Steve Abrams","subject":"Admin: persistent live-view <-> admin-view toggle","body":"Admin page sets a pcw.admin browser flag + shows a 'Live view' pill (-> /). Live customer\npages (via decorate) show a 'Admin view' pill (-> /admin) ONLY when that flag is set, so the\nlogged-in admin can always flip both ways while real customers never see it.\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"5ff3476","date":"2026-07-27 08:24:13 -0700","author":"Steve Abrams","subject":"Activate Google review CTA: set verified place_id (looked up via Places API)","body":"Looked up the REAL place_id via the Places API (GOOGLE_PLACES_API_KEY, $0.017): returned\nname 'Prestige Car Wash' + addr '7601 Reseda Blvd, Reseda, CA 91335' — verified match, not a\nguess. Set places.json place_id=ChIJ5Wj7RtKbwoAR-3obIxcKE1g, which activates the honest\n'Leave us a review on Google' CTA (writereview deep-link) in the trust block.\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"4937b3b","date":"2026-07-27 07:43:04 -0700","author":"Steve Abrams","subject":"Drop non-existent apex: prestige.agentabrams.com is the permanent home","body":"Steve 2026-07-27: prestigehandcarwash.com does not exist — keep the *.agentabrams build only.\nPurge stale apex references: places.json website + robots.txt Sitemap -> prestige.agentabrams.com,\nand rewrite the SITE_ORIGIN comment (no longer an interim 'flip after cutover' — it's permanent).\nAll served canonical/og/sitemap/JSON-LD already resolved to agentabrams; this cleans the data.\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"e694e62","date":"2026-07-27 07:27:46 -0700","author":"Steve Abrams","subject":"Analytics-lite: first-party, cookie-free conversion tracking + admin card","body":"Self-hosted aggregate counters (pageview / form_start / form_submit) -> honest view→book\nconversion, surfaced on the admin Overview. No cookies, no IP, no PII, no consent banner, no\nGA. sendBeacon pageview (all customer pages via decorate), funnel events on the contact form.\nContrarian FIX-FIRST: added rateLimit(60/min) to the public /api/track (was an unauthenticated\nunrated disk-write), atomic temp+rename write (no corruption), and the conversion shows as\n~X% (est.) so its approximation is visible, not buried. Bounded to last 120 day-buckets.\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"acd004a","date":"2026-07-26 21:57:56 -0700","author":"Steve Abrams","subject":"A11y + perf pass: focus-visible, reduced-motion, aria labels, font preconnect","body":"DTD unanimous 5/5 (A). pcw.css: visible :focus-visible outlines on all controls, .sr-only\nutility, prefers-reduced-motion block. Hamburger buttons: aria-label + real aria-expanded\ntoggle (all 4 page types). FAQ decorative +/- marker aria-hidden. decorate(): gstatic font\npreconnect (crossorigin, deduped) on every page. Contrarian GO; applied its fix — the JS\nform-success scrollTo now honors prefers-reduced-motion (CSS scroll-behavior didn't cover it).\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"2062a31","date":"2026-07-26 21:41:23 -0700","author":"Steve Abrams","subject":"Admin: lead worklist (click-to-call/email + follow-up-first order) + CSV export","body":"DTD unanimous 5/5 (A). Leads tab: click-to-call (tel:) + click-to-email (mailto:) on each\ncard, a 'Needs follow-up' default sort (oldest-cold first, then new, contacted, done), and a\nGET /api/admin/leads.csv export (behind requireAdmin, CSV/formula-injection neutralized) with\nan Export button. Contrarian GO; applied its two quality fixes: telDigits returns '' for\nnon-valid lengths (garbage phone renders as plain text, not a dead tel: link) and the worklist\ntiebreak surfaces the longest-waiting cold lead first. (Known minor, logged: leads localStorage\nsort-index shifts once post-deploy — self-corrects on next pick.)\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"7c66f9d","date":"2026-07-26 21:30:19 -0700","author":"Steve Abrams","subject":"Harden booking form: honeypot + validation + dupe-guard + resilient client","body":"DTD unanimous 5/5 (B). POST /api/contact: honeypot (non-demographic field name so autofill\ncan't false-drop a real lead; drops are logged for audit), human-readable validation\n(name/phone/email), and a 120s duplicate-submit guard. contact.html: double-submit lock,\nclient validation mirror, network-failure handling, consistent red/green result states.\nContrarian FIX-FIRST resolved: renamed honeypot company->b_confirm (autofill-safe),\nconsole.warn on drop (observability), fixed error-state background tint.\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"61afb84","date":"2026-07-26 21:19:32 -0700","author":"Steve Abrams","subject":"Path B: self-canonicalize to prestige.agentabrams.com (stop feeding GoDaddy) + ship guides","body":"Steve chose B. New SITE_ORIGIN constant drives all self-referential SEO (canonical, og:url,\nsitemap, article URLs, AutoWash.url) to prestige.agentabrams.com — the domain this app is\nactually served from — instead of the apex prestigehandcarwash.com, which serves a SEPARATE\nGoDaddy site. Resolves the contrarian NO-GO by its own stated A-grade bar. Also: og:type=article\non individual guide pages. Unblocks the held /guides deploy. Flip SITE_ORIGIN to the apex once\nits DNS is cut over to Kamatera. (publisher.logo intentionally omitted — no real logo asset.)\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"2cec353","date":"2026-07-26 21:02:04 -0700","author":"Steve Abrams","subject":"Guides: evergreen car-care articles (/guides + 3 posts) — BUILT, DEPLOY HELD","body":"DTD A (3/2). 3 honest server-rendered guides w/ BlogPosting+BreadcrumbList schema,\ninternal links to services/booking, sitemap + homepage-footer link. Head-injection\nrefactored into shared decorate(). Locally verified (200s, schema, 404, sitemap, no\nfabricated stats). DEPLOY HELD: contrarian NO-GO — site-wide canonical points at\nprestigehandcarwash.com which serves a SEPARATE GoDaddy site, so shipping SEO pages\nhands authority to GoDaddy. Domain-identity/DNS decision surfaced to Steve (gated).\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"d13f501","date":"2026-07-26 20:50:10 -0700","author":"Steve Abrams","subject":"Discovery: honest 'find us & review' trust block (real off-site profiles)","body":"DTD A (4/4 valid). Server-renders REAL Google Maps + Yelp + Instagram links from places()\ninto the homepage; no star number reproduced on-page (routes to live profile — no drift).\nContrarian FIX-FIRST: (1) review-solicitation CTA gated on a verified Google place_id and\nroutes to Google's writereview dialog — NOT Yelp (Yelp filters solicited reviews); currently\nplace_id is empty so the CTA is inert until set. (2) whole section stripped server-side if no\nreal profiles (no floating heading). rel=noopener (follow, for sameAs authority).\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"9d17487","date":"2026-07-26 20:41:42 -0700","author":"Steve Abrams","subject":"Services: per-service 'Book this' deep-links pre-select the service on /contact","body":"DTD A (3/5). Service cards link to /contact?service=<name>; contact form pre-selects\nthe matching real option (exact-match, never DOM-injected). Contrarian FIX-FIRST:\nescape option name/price (quote/angle-bracket corruption), suppress service pre-select\nunder ?intent=membership (no contradictory form state). Canonical verified: ?service=\nvariants already canonicalize to clean /contact (no duplicate-URL risk).\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"0760139","date":"2026-07-26 20:34:40 -0700","author":"Steve Abrams","subject":"Mobile: persistent Book + click-to-call action bar on customer pages","body":"DTD unanimous (5/5) A. Server-injected via pageHtml, CSS-only <=640px, no JS.\ntel: from real places().phone (single source, no fabrication). Contrarian FIX-FIRST:\nBook button is page-aware — scrolls to #f on /contact instead of self-linking to top.\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"cc20300","date":"2026-07-26 20:27:06 -0700","author":"Steve Abrams","subject":"FAQ: booking-objection accordion + FAQPage JSON-LD, single-source & server-rendered","body":"DTD panel picked A (3/2 over friction-reducers); dissent folded in — answers ARE\nthe booking objections. faqs() is one source for both the visible <details> accordion\n(server-rendered into #faqList, native <details>, zero JS) and the FAQPage schema, so\nvisible content matches structured data. Contrarian FIX-FIRST resolved: was client-only\nrender (Google manual-action risk) -> now server-rendered in crawler HTML.\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"39653bd","date":"2026-07-26 20:12:17 -0700","author":"Steve Abrams","subject":"chore: v0.3.1 (session close — LHF/SWOT/Slideshow)","body":""},{"hash":"10e7bf1","date":"2026-07-26 16:38:48 -0700","author":"steve@designerwallcoverings.com","subject":"Analytics: GA4 gtag injection on customer pages, gated on GA_MEASUREMENT_ID (inert until set, format-validated, admin untracked)","body":""},{"hash":"aa47ccf","date":"2026-07-26 16:15:25 -0700","author":"steve@designerwallcoverings.com","subject":"SEO: broaden OG/Twitter + per-page canonical to /services + /contact (shared pageHtml); robust 500 fallback + canonical dup-guard — contrarian-gated","body":""},{"hash":"1b0a067","date":"2026-07-26 16:14:33 -0700","author":"Steve Abrams","subject":"auto-save: 2026-07-26T16:14:26 (1 files) — server.js","body":""},{"hash":"f74fd9f","date":"2026-07-26 15:45:52 -0700","author":"steve@designerwallcoverings.com","subject":"Admin: flag unworked leads going cold (>48h) on cards + funnel; fix undefined-status default","body":""},{"hash":"53de6fd","date":"2026-07-26 15:39:49 -0700","author":"steve@designerwallcoverings.com","subject":"Admin Overview: lead-funnel summary (status counts, new-this-week, conversion %, unworked-leads nudge) — dtd:D","body":""},{"hash":"7c77409","date":"2026-07-26 15:06:01 -0700","author":"steve@designerwallcoverings.com","subject":"Admin: lead-status pipeline (new→contacted→booked/won/lost) — dtd:C, contrarian-gated (revert-on-fail + in-place update)","body":""},{"hash":"157f32a","date":"2026-07-26 14:37:04 -0700","author":"steve@designerwallcoverings.com","subject":"SEO: enrich AutoWash JSON-LD with hasOfferCatalog (real service prices, quote-only safe)","body":""},{"hash":"82b259c","date":"2026-07-26 14:35:18 -0700","author":"steve@designerwallcoverings.com","subject":"SEO infra: robots.txt + generated /sitemap.xml + homepage OG/Twitter tags — contrarian-gated (robust desc regex + dup-og guard)","body":""},{"hash":"cbe52eb","date":"2026-07-26 14:30:28 -0700","author":"steve@designerwallcoverings.com","subject":"SEO: server-inject AutoWash/LocalBusiness JSON-LD from places.json (single source, no drift) — dtd:B, contrarian-gated (cache dropped)","body":""},{"hash":"57d8ba0","date":"2026-07-26 08:52:49 -0700","author":"Steve Abrams","subject":"slideshow: sync sanitized deck (XSS-safe cover); render verified 6 slides","body":""},{"hash":"c94229a","date":"2026-07-26 08:47:24 -0700","author":"Steve Abrams","subject":"slideshow: /slideshow deck + Present button (deck falls back /portal→/ to parse the marketing deliverable)","body":""},{"hash":"2f4ad36","date":"2026-07-26 08:38:37 -0700","author":"steve@designerwallcoverings.com","subject":"Membership: 'launching soon' call-out + interest capture via existing lead form (no billing; live billing awaits Stripe)","body":""},{"hash":"3ccfc3c","date":"2026-07-26 08:36:43 -0700","author":"steve@designerwallcoverings.com","subject":"Booking: honest 'typical busy-ness today' wait indicator (GET /api/wait, LA-tz, qualitative not fabricated minutes) — dtd:A, contrarian-gated","body":""},{"hash":"cbf5a8b","date":"2026-07-26 08:20:03 -0700","author":"steve@designerwallcoverings.com","subject":"Growth ideas: backfill conservative SWOT + light costs for membership, mobile-route, ceramic (decision-grade board)","body":""},{"hash":"060a5b2","date":"2026-07-26 08:08:03 -0700","author":"steve@designerwallcoverings.com","subject":"Growth admin: per-idea Action Brief (printable/copyable), dtd:A, contrarian-gated (data-id anti-injection + go() closes modal)","body":""},{"hash":"2c10e98","date":"2026-07-26 07:55:32 -0700","author":"steve@designerwallcoverings.com","subject":"Growth admin: tab-aware sort control on all data grids (sort+density rule; dtd:A, contrarian-gated)","body":""},{"hash":"6edbd5d","date":"2026-07-26 07:41:37 -0700","author":"Steve Abrams","subject":"auto-save: 2026-07-26T07:41:30 (2 files) — public/admin/index.html public/assets/lhf.js","body":""},{"hash":"ed68222","date":"2026-07-26 07:34:57 -0700","author":"steve@designerwallcoverings.com","subject":"Growth admin: expandable SWOT · Costs · Payback panel on idea cards (dtd:A, contrarian-gated)","body":""},{"hash":"78ccc70","date":"2026-07-26 07:30:39 -0700","author":"Steve Abrams","subject":"lhf: contrarian fixes — panel note replaces per-card filler, numeric ROI","body":""},{"hash":"e8481b2","date":"2026-07-26 07:25:21 -0700","author":"Steve Abrams","subject":"admin: Low Hanging Fruit ranked panel (ROI÷time over /api/admin/suggestions, quick-wins flagged)","body":""},{"hash":"e283f28","date":"2026-07-26 07:19:39 -0700","author":"steve@designerwallcoverings.com","subject":"Prestige supercharger idea: pivot to owned route + payback model in SWOT","body":""},{"hash":"f8cc786","date":"2026-07-26 07:13:53 -0700","author":"steve@designerwallcoverings.com","subject":"Add growth idea: 6 Tesla Superchargers with costed SWOT (host vs owned)","body":""},{"hash":"02fe543","date":"2026-07-22 07:18:17 -0700","author":"Steve Abrams","subject":"chore: lint (harden promoUse onclick), refactor (mmss), v0.3.0 (session close)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"557b5cd","date":"2026-07-22 07:07:44 -0700","author":"Steve Abrams","subject":"Add HyperFrames promo videos to the growth-center backend","body":"New 🎬 Promos admin tab surfacing the four HyperFrames-generated brand films\n(full tour, story cut, 30s motion promo, backend demo) staged under\nmedia/promos/. Backed by data/promos.json + /api/admin/promos (normalized URLs,\nmm:ss, on-disk ready flag). Each promo streams on demand (preload=none) and can\nbe handed to Compose & Post as an attachable asset in one click. Large .mp4s\ngitignored (regenerable from the sibling video pipelines); posters + manifest\ntracked. Health/Overview now counts promos.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"d9afc41","date":"2026-07-21 12:58:16 -0700","author":"Steve Abrams","subject":"chore: v0.2.0 (Compose & Post feature — session close)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"723f0ac","date":"2026-07-20 11:52:10 -0700","author":"Steve Abrams","subject":"prestige Phase 1: server-side social-post queue (GET/POST/DELETE /api/admin/schedule) + inert publish-due.js worker + API-backed composer — deployed snapshot","body":""},{"hash":"5683565","date":"2026-07-20 11:41:21 -0700","author":"Steve Abrams","subject":"scope: real social auto-posting scheduler for prestige marketing tab","body":""},{"hash":"13127d7","date":"2026-07-20 11:20:57 -0700","author":"Steve Abrams","subject":"prestige admin: Hootsuite-style Marketing tab (socials composer left, collapsed asset library right) — deployed snapshot","body":""},{"hash":"62c7522","date":"2026-07-19 09:58:54 -0700","author":"Steve","subject":"Update Prestige business phone to (747) 265-6293 in places.json","body":""},{"hash":"e65a96d","date":"2026-07-19 07:29:46 -0700","author":"Steve Abrams","subject":"admin: Compose & Post tab — create socials + post from assets in one place","body":"Local scaffold only (live build owned by another session; NOT deployed).\n\nFeature — a new '✨ Compose & Post' command-center tab that turns the existing\nmedia library + best-times into a one-screen social workflow:\n- asset picker over /media (real photos + service videos, videos auto-paired\n  to their poster stills)\n- one-click caption templates (data/social-templates.json) with {service}/{blurb}\n  fill-in from the real services, + click-to-add hashtag sets\n- pick platforms (IG/FB/Google/TikTok/Nextdoor/Yelp) → best-time hint per platform\n  from best-times.json + one-click 'Open <network>' into each native composer\n  (auto-copies the caption), Copy caption, Download asset, Save draft\n- DRAFT-ONLY by rule: never auto-posts; drafts persist to reports/social-drafts.jsonl\n  and list back as a mini content calendar\n\nNew APIs: GET /api/admin/media, /api/admin/social/templates, /api/admin/social/drafts,\nPOST /api/admin/social/draft (all admin-gated, read-only media scan, append-only drafts).\n\nRefactor / fixes:\n- sortServices 'featured' NaN bug (undefined-featured → unstable sort) → boolean coerce\n- enc() now escapes & > \" (not just <) — hardens the lead-render surface\n- Overview 'Google Places' tag now reflects real credential presence, not a hardcoded 'mock'\n\nVerified locally on :9911: syntax OK, all 4 APIs return correct data (18 assets,\n6 platforms/5 templates, draft save+list), admin page serves with the tab, auth gate 401s.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"9002496","date":"2026-07-19 07:25:22 -0700","author":"Steve Abrams","subject":"auto-save: 2026-07-19T07:25:16 (1 files) — server.js","body":""},{"hash":"4e71985","date":"2026-07-05 17:08:14 -0700","author":"Steve","subject":"Render REAL business photos + videos (do NOT overwrite the curated prod places.json): format-agnostic photo_urls + video passthrough on home & admin Google tab; adopt curated places.json + track real media in git","body":""},{"hash":"c69cb23","date":"2026-07-05 17:03:45 -0700","author":"Steve","subject":"Show real Google photos: server-side photo proxy (/api/places/photo/:i, key kept server-side, cached) + photo gallery on home + photos & reviews in admin Google tab","body":""},{"hash":"c09753a","date":"2026-07-05 04:56:46 -0700","author":"Steve","subject":"Wire Google Places: real listing (7601 Reseda Blvd, 4.3★/47, hours, reviews, photos) live on both sites; key routed to secrets/.env; Credentials tab now 'set'","body":""},{"hash":"9dc4f07","date":"2026-07-04 16:07:26 -0700","author":"Steve","subject":"Fix service media paths to match generated svc-<id>.{png,mp4}; ship media in deploy; live at prestige.designerwallcoverings.com","body":""},{"hash":"36db139","date":"2026-07-04 13:46:04 -0700","author":"Steve","subject":"Contrarian FIX-FIRST pass: real hero copy (no terminal cmd), location/hours block from Places, honest contact copy, contact rate-limit + field caps + optional webhook notify, prod default-password warning; gitignore runtime leads","body":""},{"hash":"4f61ddf","date":"2026-07-04 13:39:10 -0700","author":"Steve","subject":"Fix CSP: allow inline event-handler attrs (script-src-attr) so admin tabs/filters/onerror fallbacks work; add favicon route. Verified 13/13 desktop+mobile","body":""},{"hash":"b94f670","date":"2026-07-04 13:36:06 -0700","author":"Steve","subject":"Add scripts: db schema, gen-media (Nano Banana + SeeDance, dry-run gated), places-read, ig-pull, competitors-scan, best-times; fix gen-media --only off-by-one","body":""},{"hash":"4a6ce21","date":"2026-07-04 13:32:45 -0700","author":"Steve","subject":"PCW scaffold: marketing site + growth admin (10 buckets), seeded from SFV market research; sort+density+left-filters, admin date+time chips, masked credentials, gated Google draft-writes","body":""}]}