{"slug":"rentv-2026","total":1254,"limit":100,"offset":0,"since":null,"commits":[{"hash":"f19f6ee4","date":"2026-08-25 10:51:22 -0700","author":"Steve","subject":"initial scaffold + creds-safe fetch guard (gitify per standing rule)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"437aa93c","date":"2026-08-15 04:17:28 -0700","author":"steve@designerwallcoverings.com","subject":"TK-10569: port /api/advertise + /api/advertise-inquiries from rentv to rentv-2026","body":"Lead-capture endpoint was missing from feature/2026-overhaul-adslots branch.\nMirrors original rentv implementation; uses rentv-2026's existing writeThrottle\n(10/hr per IP) instead of the manual counter pattern."},{"hash":"5a59077e","date":"2026-08-15 04:10:39 -0700","author":"Steve Abrams","subject":"rentv: per-IP write-form rate limits on /api/subscribe + /api/ad/reserve (TK-10559)","body":"TK-10559 approved 2026-08-14 by rentv-approver triage session.\n\n/api/subscribe: writeThrottle(10 req/hr per IP) — prevents JSONL list flooding;\nexisting subSet.size cap still applies for dedup.\n\n/api/ad/reserve: adReserveThrottle(5 req/hr per IP) — prioritized (not idempotent;\neach inquiry appends to ad-reservations.jsonl and marks slot reserved).\n\nBoth throttle with Retry-After header. No external dep (plain Map + Date.now).\n\nCo-Authored-By: 4am-fix-loop <noreply@anthropic.com>"},{"hash":"f2bf7551","date":"2026-08-15 04:09:11 -0700","author":"Steve Abrams","subject":"rentv: P1+P2+P3 public-launch hardening (TK-10564/10575, Steve-approved 2026-08-14)","body":"P1 (critical): fix INTERNAL_STATIC %2f bypass — test decodeURIComponent(req.path) so\n/admin%2f correctly 302→/login like /admin does. Closes live UI-shell info-disclosure.\n\nP2: trust proxy (req.protocol=https, req.ip=real-client behind nginx TLS terminator);\nbrute-force throttle on POST /api/login (10 bad attempts/15 min → 429 Retry-After);\nrobots.txt Disallow: /api/ to block bulk API/CSV scraping.\n\nP3: app.disable('x-powered-by'); baseline headers middleware (X-Content-Type-Options,\nX-Frame-Options, Referrer-Policy, HSTS on secure connections).\n\nCo-Authored-By: 4am-fix-loop <noreply@anthropic.com>"},{"hash":"d5a4141e","date":"2026-08-15 04:06:21 -0700","author":"Steve Abrams","subject":"pr-intelligence: add CAN-SPAM List-Unsubscribe header + address footer to buildMime (TK-10578)","body":"Before: buildMime emitted only From/To/Subject/MIME headers — no List-Unsubscribe\nheader, no physical address footer injection. Sufficient for 1:1 outreach but\nnon-compliant for bulk/programmatic sends.\n\nAfter: buildMime injects List-Unsubscribe + List-Unsubscribe-Post (RFC 8058 one-click)\nheaders and physical address + opt-out footer into text/HTML bodies when\nPR_MAILING_ADDRESS / PR_UNSUBSCRIBE_URL env vars are set. Also adds PR_FROM_NAME for\n\"Display Name <addr>\" From header formatting. Backwards-compatible: env vars default\nto '' so 1:1 outreach path is unchanged if not configured.\n\nCo-Authored-By: 4am-fix-loop <noreply@anthropic.com>"},{"hash":"a415d241","date":"2026-08-10 16:26:42 -0700","author":"Steve Abrams","subject":"chore: v0.24.1 (session close — ad-slots correctness fixes + coverage)","body":""},{"hash":"fc7ec801","date":"2026-08-10 11:39:18 -0700","author":"Steve Abrams","subject":"fix(adslots): 5 ad-system.cjs correctness fixes + regression guards (TK-10364)","body":"Graph-engineering hardening of the marquee ad-slots module, each defect\nreproduced against real data and contrarian-gated before fixing:\n\n- summarize() now counts 'reserved' AND an 'other' catch-all, so\n  sold+available+reserved+other === total for ANY status in ad-state.json\n  (an admin board reconciling the header was silently short by soft-held\n  and any legacy/hand-edited status).\n- originals uid strips the pre-existing 'orig-' prefix (data ids are already\n  orig-brand/orig-deals/…) — was emitting 'orig-orig-brand', so admin\n  overrides keyed by the logical id silently missed. Contained to the ad\n  module; originals.json (read by the main site) is left untouched.\n- vimeo inventory concat is Array.isArray-guarded — vimeo-library.json ships\n  episodes/clips as numeric COUNTS, not arrays; hardened + forward-compatible.\n- admin POST /api/ad/admin/video rejects a non-numeric price_monthly instead\n  of persisting NaN (NaN passed the != null overlay test and served a\n  misleading price:null over the heuristic).\n\ntest/adslots/bugfixes.test.mjs adds regression guards for each. adslots\nsuite 154 pass, full suite 224 pass, 0 fail. No deploy, no prod push.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"f709ec78","date":"2026-08-10 11:27:19 -0700","author":"Steve Abrams","subject":"test(adslots): +124 coverage (buildInventory sources/dedup, reserve soft-hold lifecycle, exact price ladder, admin gating) + replace a pricing tautology with live-value asserts","body":"Graph-engineering pass under TK-10364: 6 new zero-dep test files raise the ad-system.cjs adslots suite 23->147 (0 fail). No production code changed. Contrarian(Cody)-gated: fixed a false-confidence tautology in pricing (assert.ok(200<250) -> live suggested_price asserts), pinned the intentional admin-can-unsell vs public-reserve-cannot asymmetry, and clarified the quoted_monthly=0 known-bug guard.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"423cfa3d","date":"2026-08-08 08:37:30 -0700","author":"Steve Abrams","subject":"test(adslots): cover ad-system admin write paths (video/placement/site-template)","body":"Completes src/ad-system.cjs coverage — admin state mutations (uid/id 400 guards,\nstatus-enum validation, Number coercion, persistence to ad-state.json), reservations\nread (reversed + empty-safe), and the site-template pick (enum guard + persistence +\nread-back + the 'going live is separately gated' note). No production code changed.\nSuite 84->93 green.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"9af4fe93","date":"2026-08-08 08:36:50 -0700","author":"Steve Abrams","subject":"chore: sync package-lock version to 0.24.0 (trailing the v0.24.0 bump)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"adcaafca","date":"2026-08-08 07:40:29 -0700","author":"Steve Abrams","subject":"test(adslots): rate-card data contract + rate-card/slot endpoint coverage","body":"Covers the half of src/ad-system.cjs cycle-1 left untested. Real-data contract on\ndata/ad-slots.json (unique ids, required id/name/type, numeric price, valid status\nenum, created_at present) so a malformed hand-edit is caught in CI, not by an\nadvertiser. Plus fake-Express coverage of /api/ad/rate-card creative merge and\n/api/ad/slot/:id known-id merge + unknown-id 404. No production code changed.\nSuite 77->84 green.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"e15f5398","date":"2026-08-08 07:10:56 -0700","author":"Steve Abrams","subject":"test(adslots): cover ad-system + fix filtered inventory summary mismatch","body":"/api/ad/inventory summarized a fresh unfiltered buildInventory() instead of the\nfiltered list it returns, so summary.total/by_source/sold/available disagreed with\ncount/items on any ?source= / ?status= request. Summarize the returned list\ninstead (also removes a redundant second inventory scan).\n\nAdds test/adslots/inventory.test.mjs: 7 tests via a zero-dep fake-Express harness\nover temp fixtures — suggestPrice per-source asks + plays bumps + $50 rounding,\nsummarize source/status counts, reserve 400/200 validation, and a regression guard\nthat fails against the old summarize(buildInventory()) call. Suite 70->77 green.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"4295511a","date":"2026-08-07 15:32:50 -0700","author":"Steve","subject":"chore: v0.24.0 (session close — View-all toggle wired + preview synced)","body":""},{"hash":"d71f0c89","date":"2026-08-07 15:26:48 -0700","author":"Steve Abrams","subject":"fix(login): resolve /api/login against location.origin so Basic-Auth creds-in-URL access can't taint the fetch base","body":"Page reached via https://user:pass@host/login inherits creds into the document\nbase URL; relative fetch('/api/login') then resolves to a credentialed URL and\nthe Fetch spec throws 'Request cannot be constructed from a URL that includes\ncredentials'. location.origin never carries userinfo. Matches the surgical\nsingle-file hotfix already deployed to prod (/root/public-projects/rentv).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"ee2f9802","date":"2026-08-07 15:22:58 -0700","author":"Steve","subject":"fix: inject template-toggle.js (View-all compare) site-wide — was orphaned, only /toggle.js (front/backend switcher) was wired","body":""},{"hash":"ed7346a7","date":"2026-08-07 14:21:20 -0700","author":"Steve","subject":"chore: gitignore node_modules symlink (was leaking as untracked) + package-lock","body":""},{"hash":"a6b6120d","date":"2026-08-07 14:19:58 -0700","author":"Steve (RENTV 2026)","subject":"5x sweep 2: 'Make this the site' works in preview mode (PUBLIC/OPEN), stays adminOnly on prod","body":""},{"hash":"fb82cb6c","date":"2026-08-07 14:18:28 -0700","author":"Steve (RENTV 2026)","subject":"5x sweep 1: add favicon.ico + favicon.svg (fixes /favicon.ico 404 site-wide)","body":""},{"hash":"3a9785bd","date":"2026-08-07 14:10:26 -0700","author":"Steve (RENTV 2026)","subject":"selector: bake rank + why-it-wins + competitor into each full-screen preview modal","body":""},{"hash":"cfd849be","date":"2026-08-07 14:07:56 -0700","author":"Steve (RENTV 2026)","subject":"selector: add Classic (original) as 6th option + rank all 6 + why-it-wins + CRE competitor landscape","body":"- Classic = the current live site, now a first-class selectable card (iframe→/, chosen accepts 'classic').\n- All 6 ranked (#1 Cinematic … #6 Classic) with a 'Why it wins' pitch + the CRE competitor each out-designs (Bisnow/The REview/The Real Deal/CoStar/Commercial Observer/current).\n- New 'The field' section: 6 real CRE news-wire/conference rivals (The Real Deal, Bisnow, Commercial Observer, GlobeSt, CoStar, Connect CRE) with links.\n- Cards sorted by rank; #1 gets a ★ Recommended tag.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"a28939c1","date":"2026-08-07 14:07:07 -0700","author":"Steve","subject":"chore: v0.23.0 (session close — admin View all template-compare toggle)","body":""},{"hash":"72de4de2","date":"2026-08-07 14:00:48 -0700","author":"Steve","subject":"TK-10350: render-proof screenshots for the 5 template variants (A/C/D/E)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"93172711","date":"2026-08-07 14:00:28 -0700","author":"Steve","subject":"TK-10350: verify 5 distinct template variants + wire template D video cards into ad inventory (all 123 videos sponsorable)","body":"- Verified /2026/{a,b,c,d,e}.html all render (HTTP 200, no JS errors — only\n  cosmetic favicon + prod-only news-thumbnail 404s) and are genuinely distinct\n  UI/UX directions: A Video Wall (dark, video-forward), B Editorial (serif\n  news river), C Cinematic (pure-black, red accent), D Magazine (navy serif\n  front page), E Dashboard (monospace CRE terminal).\n- Confirmed all 123 vimeo-library videos are wired as sponsorable ad inventory\n  via src/ad-system.cjs (/api/ad/inventory: total 154, vimeo 123, 0 missing,\n  every item drillable via view_url/embed).\n- FIX: template D had sponsoredFlag() helper + CSS defined but never called —\n  its /api/videos-driven cards showed no per-video sponsorship layer that\n  A/B/C/E have. Added loadSponsorMap() + sponsorForVideo() to merge\n  /api/ad/inventory sponsorship state onto D's review cards + Video-of-Week\n  hero. Verified headless: D now renders SPONSORED flags (Kidder Mathews, JLL)\n  + Sponsor-this offers.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"1add61c9","date":"2026-08-07 13:56:14 -0700","author":"Steve (RENTV 2026)","subject":"docs: surgical deploy handoff for claude-rentv-max (toggle + templates + ad-slots)","body":""},{"hash":"cf5c27a7","date":"2026-08-07 13:54:34 -0700","author":"Steve Abrams","subject":"TK-10105: fix heygen 'Not logged in' auth wiring in buildloop","body":"The heygen CLI authenticates from EITHER ~/.heygen/credentials OR $HEYGEN_API_KEY.\nUnder launchd/sandbox the credentials file can be unreachable and the env var is\nempty, so any heygen call in a cycle dies with 'Not logged in' / 'no API key found'.\nExport the valid key (routed by secrets-manager into the heygen skill .env) before\ninvoking claude so auth/list/whoami checks work. Renders stay gated (no spend tool;\nprompt forces HeyGen renders to a pending-approval memo).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"3e325c5f","date":"2026-08-07 13:53:47 -0700","author":"Steve Abrams","subject":"template-toggle: add admin 'View all — compare' overlay (all 6 templates live side-by-side)","body":""},{"hash":"e6e748c4","date":"2026-08-07 13:44:40 -0700","author":"Steve (RENTV 2026)","subject":"rentv 2026: admin front-end template toggle","body":"Floating switcher (public/template-toggle.js) lets a logged-in admin/user flip the\nlive front end between the original site + the 5 concepts (A-E) from the front end.\nGating: live front page shows it only for an authed admin/user session (/api/whoami);\nconcept pages (/2026/*, /advertise) always show it (demoable); ?tt=1 forces it on.\nMarks the chosen site with a ★ (reads site-template.json). Injected into index +\nall 5 templates + advertise.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"23094df1","date":"2026-08-07 13:24:28 -0700","author":"Steve (RENTV 2026)","subject":"rentv: re-gate site behind un/pw (reverse 2026-08-06 public launch)","body":"Steve 2026-08-07: rentv.agentabrams.com should not be publicly live; ask for\nun/pw to enter. Reverses the TK-10284 public tier: anonymous visitors are now\nchallenged at the app layer — browser navigations 302 to /login (login.html),\nassets/APIs get 401 + WWW-Authenticate (browser un/pw popup). Reuses the existing\nROLE_CREDS (admin + user tiers) — no new secrets. PUBLIC=1 env restores open mode\nwithout a code edit. Verified locally: anon 302/401, valid creds 200, session +\nbad-creds paths correct.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"089da29b","date":"2026-08-07 11:30:01 -0700","author":"Steve (RENTV 2026)","subject":"RENTV 2026: element-numbering overlay (all templates) + demo sold-sponsorship creatives","body":"- public/2026/elements.js: shared drop-in that numbers every significant element\n  (stable DOM-order N + badge + searchable index panel), toggled by button / 'e' key / ?num=1.\n  Injected into all 5 templates + selector + advertise + ad-admin so any element is\n  referenceable as 'Template C · el N' in future feedback.\n- public/ad-creatives/{jll-leaderboard,cbre-masthead,mm-rectangle}.svg: demo banner\n  creatives so the sold-slot creative-serving state is visible on the live templates\n  (data/ad-state.json seeds 3 sold banners + 3 sold video sponsorships; runtime, gitignored).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"827eb166","date":"2026-08-07 11:23:45 -0700","author":"Steve (RENTV 2026)","subject":"RENTV 2026: 5-template selector + ad-slots-for-sale system (all videos as sponsorable inventory)","body":"- 5 distinct video-forward homepage templates (public/2026/{a..e}.html): Video Wall,\n  Editorial Lead, Cinematic, Magazine, Dashboard — each self-contained, live data,\n  sort+density on every grid, ad slots served in-context.\n- Selector modal (public/2026/index.html): live iframe previews, full-screen switcher\n  with device toggle, 'Make this the site' -> data/site-template.json (admin-gated, local pick only).\n- Ad-slots system (src/ad-system.cjs, one-line mount in server.js): banner/newsletter/video-surface\n  rate card (data/ad-slots.json) + ALL 154 RENTV videos pulled into sponsorable inventory live\n  (youtube+vimeo+originals+news-wire), monthly pricing, availability, reserve-inquiry flow (NO charge),\n  admin state, reservations queue.\n- Rebuilt /advertise into a slots-for-sale marketplace; /ad-admin management surface\n  (created date+time chips, toggleable columns); /ad-slot.js on-page banner serving.\n\nLocal build in isolated worktree; prod deploy stays claude-rentv/Steve gated.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"5b0ae439","date":"2026-08-07 10:42:33 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T10:41:58 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"7061d199","date":"2026-08-07 10:09:47 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T10:09:04 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"10285af7","date":"2026-08-07 09:38:05 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T09:37:33 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"3e204fc8","date":"2026-08-07 09:21:56 -0700","author":"Steve Abrams","subject":"chore: v0.21.5 → v0.22.0 (session close — All Star Group section + 5-theme picker)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"4bfeb75c","date":"2026-08-07 09:07:05 -0700","author":"Steve Abrams","subject":"allstar: default theme → Capital (Blackstone dark+gold) for first load","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"0521e012","date":"2026-08-07 09:05:45 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T09:05:06 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"0a9bf943","date":"2026-08-07 08:55:48 -0700","author":"Steve Abrams","subject":"allstar: 5 firm-inspired themes with distinct UI per theme (toggle + persist)","body":"Each theme is a different layout system, not a recolor — routed the remaining\nhardcoded colors/gradients/fonts through CSS vars, then per-theme structural\noverrides (hero arrangement, nav treatment, card shape, radius, light/dark):\n - Editorial (RENTV/Real Deal, red serif, ruled), Institutional (CBRE/M&M navy\n   full-bleed band, boxed nav), Capital (Blackstone dark luxe, centered serif,\n   gold hairlines), Brokerage (Cushman/JLL emerald, pill nav, soft cards),\n   Summit (modern expo indigo→violet gradient, pill nav).\nMasthead theme switcher, localStorage-persisted, pre-paint FOUC guard.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"6b98f166","date":"2026-08-07 08:33:23 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T08:32:43 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"da2aa3f6","date":"2026-08-07 08:33:05 -0700","author":"Steve Abrams","subject":"Add All Star Group section to RENTV backend — RENTV-style rebuild, 2026","body":"Rebuilds allstargroup.com the way RENTV works: a self-contained backend page\n(public/allstar.html) served behind adminOnly at /allstar, with the site's\nreal nav rebuilt as tabs (Home / Events / Income Property Lending), refreshed\nfor 2026 (flagship CRELC — April 29 2026, Westin Long Beach).\n\n- Brings in real video: two YouTube embeds of Arnie Garfinkel on the events\n  (oEmbed-verified embeddable).\n- Highlights Arnie Garfinkel + his full experience (IPL since 1995, Pacific\n  Thrift & Loan, UCCI, Foothill Capital, CAMB/NAMB/CMBA speaker).\n- Wired all three RENTV touchpoints: adminOnly route, INTERNAL_PAGE (footer),\n  and INTERNAL_STATIC (security gate so /allstar.html can't leak publicly).\n- Added an All Star Group card to the RENTV Backend hub.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"f3f859ab","date":"2026-08-07 08:01:00 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T08:00:20 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"557f42ea","date":"2026-08-07 07:29:54 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T07:29:27 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"4cf0c4ad","date":"2026-08-07 07:19:49 -0700","author":"Steve Abrams","subject":"consulting portal: sync refreshed CRCP growth page + daynight readability fix from consulting-rentv-com@55f002c (TK-10299/TK-10319)","body":"Landed by claude-mac2 with Steve's explicit in-session authorization (\"HELP DO IT\", 2026-08-07).\nSingle-file scoped commit; triggers the auto-deploy hook to make prod durable (was on an interim rsync)."},{"hash":"8e9b0dd5","date":"2026-08-07 06:58:55 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T06:58:19 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"5908c9c8","date":"2026-08-07 06:27:48 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T06:27:13 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"7200a545","date":"2026-08-07 05:56:55 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T05:56:28 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"8c92dd65","date":"2026-08-07 05:26:08 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T05:25:40 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"1a4a5cda","date":"2026-08-07 04:55:12 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T04:54:46 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"3039ae02","date":"2026-08-07 04:24:15 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T04:23:46 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"51ae0ace","date":"2026-08-07 03:53:27 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T03:53:01 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"7c16d0b4","date":"2026-08-07 03:22:41 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T03:22:15 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"a1de2be7","date":"2026-08-07 02:51:54 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T02:51:26 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"1df4a642","date":"2026-08-07 02:20:58 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T02:20:18 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"f12c8ab2","date":"2026-08-07 01:49:36 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T01:49:10 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"1af72918","date":"2026-08-07 01:18:51 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T01:18:25 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"96d1ede2","date":"2026-08-07 00:47:58 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T00:47:33 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"ac9af4fd","date":"2026-08-07 00:17:05 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-07T00:16:37 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"eb01e95d","date":"2026-08-06 23:46:18 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T23:45:50 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"55fdc7ec","date":"2026-08-06 23:15:29 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T23:15:02 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"735b79ac","date":"2026-08-06 22:44:43 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T22:44:16 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"026b9613","date":"2026-08-06 22:13:54 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T22:13:22 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"a7dacdd9","date":"2026-08-06 21:43:04 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T21:42:38 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"30954cd4","date":"2026-08-06 21:12:20 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T21:11:53 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"66c560a5","date":"2026-08-06 20:41:33 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T20:41:05 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"8e4a3989","date":"2026-08-06 20:10:44 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T20:10:15 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"4865af4c","date":"2026-08-06 19:39:48 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T19:39:21 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"d2db9fcc","date":"2026-08-06 19:09:00 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T19:08:34 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"db9cc1f3","date":"2026-08-06 18:38:08 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T18:37:41 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"59a60b88","date":"2026-08-06 18:27:19 -0700","author":"Steve","subject":"fix(archive): dedup parse-article's STALE parser fork → import the hardened deal-parse (single source of truth). parse-article.mjs kept its own OLD copies of parseAmount/txnOf/typeOf/classify/parseSize/summarize — the news-archive pipeline (pull-archive→parseArticle) was silently running WITHOUT every deal-parse fix. Proven live-divergent: $153/sf→153 (now null), $5 billionaire→$5B (now 5), 104k sf→lost (now 104000), summarize '$1. 625' (now '$1.625'). Deleted the 6 duplicates, import+re-export from deal-parse. +5 tests (parseHeadline/parseDate/bodyText/parseArticle + a dedup-lock), 70/0.","body":""},{"hash":"01984a25","date":"2026-08-06 18:07:19 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T18:06:44 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"f2f651fd","date":"2026-08-06 17:36:24 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T17:35:56 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"47c3761d","date":"2026-08-06 17:35:54 -0700","author":"Steve","subject":"polish(services): trade chips — specialty trades lead, General buckets trail muted","body":"Cody #3: General Building (~91% of contractors) was swamping the trade-filter row and\ndefeating its purpose. Now the specialty trades (Electrical/Plumbing/HVAC/Roofing/etc.)\nlead; General Building + General Engineering trail with a muted dashed style. Client-only, $0."},{"hash":"47cb4fdb","date":"2026-08-06 17:06:40 -0700","author":"Steve","subject":"feat(services): contractor trade filter (CSLB class) for Construction","body":"Deal teams can filter 25.7k LA contractors by trade — General Building, Electrical (C-10),\nHVAC (C-20), Plumbing (C-36), Roofing, Concrete, Structural Steel, Fire Protection, Elevator,\netc. — via a regex match on the CSLB class inside license_type. New /api/services/trades facet\n(counts DISTINCT firms so the chip count matches the deduped result), trade param on\n/api/services + CSV export, client trade-filter chips on Construction (deep-linkable).\n\nCody gate (FIX FIRST) resolved: (1) general-building regex proven to NOT leak B-2 Residential\nRemodeling — 0 matches against 184 real B-2 rows, documented; (2) facet/filter count gap fixed\nby counting distinct firms (chip 1955 == result 1955). $0, existing data."},{"hash":"9911142f","date":"2026-08-06 17:05:25 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T17:04:50 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"04680821","date":"2026-08-06 17:02:29 -0700","author":"Steve","subject":"harden(brief): stale-lock TTL — a hard-killed run (SIGKILL/OOM) that leaves the brief-gen lock behind can't silently disable brief refresh forever; a run >15min old (≫ any real run) is stolen with a WARN, else a live concurrent run holds it → skip. Closes the one lock caveat Cody raised on the concurrent-run fix.","body":""},{"hash":"3e79a22d","date":"2026-08-06 17:00:31 -0700","author":"Steve","subject":"harden(agents review): spend-safety + robustness pass on this session's code (2 read-only reviewers, findings verified before applying). gen-deal-brief: (1) ledger write wrapped so a post-TTS ENOSPC surfaces LOUD instead of silently under-counting the cap; (2) --force now bypasses dedupe ONLY, cap needs explicit --force-overcap; (3) lockfile prevents two overlapping runs double-spending; (4) DRY_RUN=0 no longer treated as dry. parseAmount: (?![a-z]) kills the '$5 billionaire'→$5B fabrication (0/30 corpus regressions; now feeds paid TTS). brief.html: scheme-guard t.href (no javascript: URLs). +1 test, 65/0.","body":""},{"hash":"9c83657e","date":"2026-08-06 16:52:26 -0700","author":"Steve","subject":"fix(firm): profile KPIs drill deeper (every-datum-hrefs rule) — 'Largest deal' → that deal's page, 'Top asset class' → /sector?type=. KPIs with no clean deeper target (Total volume/Deals/Markets) left unlinked rather than forced. Verified targets valid (/sector accepts ?type=), profile serves 200.","body":""},{"hash":"1a6901b5","date":"2026-08-06 16:38:11 -0700","author":"Steve","subject":"feat(brief): hard monthly spend cap (BRIEF_MONTHLY_CAP, default 40 ≈ $6/mo) — a dollar backstop beyond the top-deal-change dedupe. Even if the top deal flaps, spend is bounded; ledger in rsync-excluded public/audio (prod-owned, survives deploys). Verified: cap=0 skips before any TTS call (no mp3, no spend).","body":""},{"hash":"35d978b2","date":"2026-08-06 16:34:27 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T16:33:58 (8 data files) — .deploy.conf data/deals-registry.json data/deals.json data/markets.json data/news.json","body":""},{"hash":"ab40d2bf","date":"2026-08-06 16:31:28 -0700","author":"Steve","subject":"feat(brief): auto-refresh the Boomer 'Deal of the Day' — pull-deals chains generateBrief() after writing deals.json (dedupe-guarded → spends ~$0.15 ONLY when the top deal changes; NON-FATAL so a TTS error never breaks the crawl). Keeps /brief current on prod via the existing pull-deals cron — no crontab edit. gen-deal-brief refactored to export generateBrief() + run standalone only when invoked directly. 64/0.","body":""},{"hash":"6574a4ff","date":"2026-08-06 16:30:54 -0700","author":"Steve","subject":"feat(services): Property Management now registry-backed (162 DRE-licensed PM firms)","body":"SERVICE_CATS.property → role 'Property Manager' (was featured-only); serviceSpecialty surfaces\nthe local-model descriptor; ca_dre_pm → 'CA DRE' source label. Real firms + featured majors, with\nverified DRE 'Licensed' status + official DRE public-record links."},{"hash":"fd1945d5","date":"2026-08-06 16:16:25 -0700","author":"Steve","subject":"nav: surface /firm (💼 Active Dealmakers) in the site-wide Explore drawer (rentv-nav.js) — the live Broker/Firm feature was only in its own page's drawer, undiscoverable elsewhere. One-line additive edit next to the deal-data links. Discoverability for a shipped feature.","body":""},{"hash":"48878dbe","date":"2026-08-06 16:09:24 -0700","author":"Steve","subject":"feat(brief): Boomer 'Deal of the Day' audio brief (REPOINT feature #3, Steve-approved) — gen-deal-brief.mjs composes a factual script from the top deal (reuses acquirer) + ElevenLabs TTS in Boomer's voice (czUPGx5phxE6qpQ46nRS), with a top-deal-change spend guard (~$0.15/day not per-cron) + DRY_RUN; brief.html gains a graceful audio player. ALSO fixes a real summarize() bug the brief surfaced: the sentence splitter broke decimals ('$1.625'→'$1. 625') on every deal card — now splits only on a terminator+space+capital. +1 test, 64/0. Generated 1 brief ($0.147, local).","body":""},{"hash":"21845887","date":"2026-08-06 16:03:37 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T16:03:07 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"8e0541c9","date":"2026-08-06 16:01:19 -0700","author":"Steve","subject":"feat(services): CSV export + per-view SEO/JSON-LD + provider detail pages","body":"Three sales/discoverability features:\n\n1. CSV export — /api/services/export.csv streams the current category/metro/filter as a\n   deduped one-row-per-firm prospect list (biggest-first, featured included), admin\n   '⤓ Export CSV' button; Content-Disposition attachment. ~1,088 lender rows/click.\n2. SEO — /services now server-injects per-metro/category <title>, meta description,\n   canonical, OG tags, and JSON-LD (CollectionPage + BreadcrumbList + ItemList of\n   featured firms) via a sendPage(transform) hook; /services + 6 category URLs added\n   to sitemap.xml. Crawlable for 'LA commercial title companies' etc.\n3. Provider detail pages — /services/:id + /api/services/provider/:id: full profile\n   with the firm's complete branch-license roster (the value-add over the card),\n   humanized specialties, official public-record links, 'Pairs well with' deal-team\n   cross-links, and admin Solicit+note. Card firm names now link to the detail page\n   (honors the all-data-points-href rule). Handles both registry (numeric) and\n   featured (f-*) ids.\n\nVerified: export headers+attachment, per-cat <title>/JSON-LD types, detail page renders\n37-branch roster + pairs, card names deep-link."},{"hash":"6190cac5","date":"2026-08-06 15:51:51 -0700","author":"Steve","subject":"feat(deals): parseAcquirer recall 19→23/30 — handle development announcements (dev verbs: starts work on / breaks ground / underway → GO Industrial, Meta Housing) + passive voice ('Acquired by <Firm>' / 'sold to <Firm>' → WareSpace, Blackstone). Still zero asset-as-firm false positives (asset/place-fronted guard unchanged). +5 test cases, 63/0.","body":""},{"hash":"e393d66a","date":"2026-08-06 15:42:52 -0700","author":"Steve","subject":"feat(services): DTD-verdict guardrails on the Featured layer (keep-with-guardrails)","body":"Panel voted 8/8 to KEEP the curated Featured layer, conditional on hardening the\npay-to-play firewall. Implements the four attached conditions:\n\n1. Disclosure banner above Featured cards: '★ Featured = unpaid editorial selection —\n   no firm has paid for placement …' (public-facing, on every category with featured).\n2. Published selection methodology in the banner (prominence + verifiable standing +\n   metro coverage), sourced from services-featured.json selection_criteria.\n3. Pay-to-play tripwire: seed 'policy' field + code — a paid slot MUST set paid:true,\n   which renders 'Sponsored' (not '★ Featured'), visually distinct, with rel=sponsored\n   on its link; editorial Featured stays rel=nofollow. Never silently mixed.\n4. last_verified date (shown in banner) + verify_cadence_days:90 in the seed for a\n   re-check cadence so a defunct firm is never shown as Featured.\n\nVerified: facets returns featured_meta; Title view renders the banner with the date +\n'no firm has paid' line; featured rows carry paid:false / source_label 'Featured'."},{"hash":"b63cb339","date":"2026-08-06 15:32:40 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T15:32:11 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"8d3f54a3","date":"2026-08-06 15:32:29 -0700","author":"Steve","subject":"feat(firm): dedicated firm PROFILE view on /firm?name= — header + KPI strip (total volume, deals, markets, top asset class, largest deal) + that firm's deals as full cards, with a back link. This completes the 'Broker/Firm profile page' vision (REPOINT #1); index leaderboard unchanged. Client-side, reuses build() data, verified computation + both modes 200.","body":""},{"hash":"04e22ebe","date":"2026-08-06 15:27:17 -0700","author":"Steve","subject":"consulting portal: revert stray theme-aware --dim/--panel indirection (forced-black bg already handles readability)","body":""},{"hash":"8d29e427","date":"2026-08-06 15:24:41 -0700","author":"Steve","subject":"feat(firm): /firm 'Active Dealmakers' page — deal flow grouped by acquiring firm (ranked by volume/count, expandable to each firm's deals, deep-linkable ?name=), + acquirer link on deal cards → /firm (href-deeper rule). REPOINT feature #1 visible layer, built on parseAcquirer/acquirer. Pure client-side off /api/deals (zero server.js edits — express.static serves it at /firm); null-acquirer deals honestly excluded + disclosed, never bucketed as a phantom firm.","body":""},{"hash":"9aa5412f","date":"2026-08-06 15:11:04 -0700","author":"Steve","subject":"feat(deals): parseAcquirer() extracts the leading firm from a deal title + persist as 'acquirer' — the data substrate for the Broker/Firm profile pages (REPOINT feature #1, Steve-approved). HIGH precision: 19/30 clean firms on the live corpus, 0 asset-as-firm false positives (asset-fronted/place-fronted/passive → honest-null). +2 tests, 63/0. Field populates on next pull-deals cron run.","body":""},{"hash":"e8a2e401","date":"2026-08-06 15:08:25 -0700","author":"Steve","subject":"chore(services): localhost-only ?reset=outreach to clear the outreach basket","body":"Dev/preview convenience — clears the rentv.growthLI.v1 localStorage bucket, but only\nwhen hostname is 127.0.0.1/localhost, so a shared or bookmarked link can never wipe a\nreal outreach list on the live site. No-op on the production domain."},{"hash":"44ff063a","date":"2026-08-06 15:01:46 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T15:00:51 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"0007cc49","date":"2026-08-06 14:30:26 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T14:29:57 (8 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"ef48e64f","date":"2026-08-06 14:23:53 -0700","author":"Steve","subject":"fix(deals): summarize() collapses whitespace runs — the sentence class absorbed leading spaces so join yielded 'end.  Start' (2-3 spaces) on 30% of the LIVE corpus's customer-facing deal-card summaries. \\s+→' ' normalizes (also flattens tab/newline from fetched bodies). Updated 2 tests that pinned the artifact + added a source-multispace test. 61/0","body":""},{"hash":"4c3bc491","date":"2026-08-06 14:16:22 -0700","author":"Steve","subject":"feat(services): per-firm pitch note rides into the /social letter","body":"Admin card gets a dashed 'Note for the pitch' field. On Solicit (single or bulk) the\nnote is stored on the outreach item and — in social.html's letterFor — opens the\ngenerated sponsorship letter (capitalized, auto-punctuated) so each pitch is\npersonalized (e.g. 'Met at ICSC Las Vegas — keen on industrial.'). Note is editable:\nre-soliciting a firm updates its stored note; the note also shows in the outreach\nrail sub-line. Verified E2E via Playwright: /services note → localStorage → /social\nletterFor output contains the note."},{"hash":"980b5351","date":"2026-08-06 14:04:05 -0700","author":"Steve","subject":"chore: remove dead looksPlace helper + vN patch bump (session close) — parseAmount \\s* + sanitizeLocation state-name map hardening","body":""},{"hash":"d0acfe5e","date":"2026-08-06 13:59:27 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-06T13:58:45 (7 data files) — data/deals-registry.json data/deals.json data/markets.json data/news.json data/search-index-meta.json","body":""},{"hash":"971e9e4b","date":"2026-08-06 13:57:29 -0700","author":"Steve","subject":"harden(deals): parseAmount \\s* binds magnitude across a typographic double space ($52  million→$52M, was $52); document range + billionaire as zero-occurrence follow-ons. Pure superset — real corpus uses ≤1 space, no existing input changes. +1 test, 60/0","body":""},{"hash":"206b9954","date":"2026-08-06 13:39:08 -0700","author":"Steve","subject":"Gate E (TK-10076): /services degraded-state banner on realestate outage (was silent 'No providers found') + zero-row services canary (4x/day → CNCP)","body":""}]}